diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:44:51 +0000 |
commit | 9e3c08db40b8916968b9f30096c7be3f00ce9647 (patch) | |
tree | a68f146d7fa01f0134297619fbe7e33db084e0aa /devtools/client/styleeditor/test | |
parent | Initial commit. (diff) | |
download | thunderbird-upstream.tar.xz thunderbird-upstream.zip |
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'devtools/client/styleeditor/test')
122 files changed, 10678 insertions, 0 deletions
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 @@ +<!doctype html> +<html> +<head> + <title>testcase for autocomplete testing</title> + <link rel="stylesheet" type="text/css" href="resources_inpage1.css"/> + <style type="text/css"> + div { + font-size: 4em; + } + + div > span { + text-decoration: underline; + } + + div + button { + border: 2px dotted red; + } + </style> +</head> +<body> + <div>parent <span>child</span></div><button>sibling</button> +</body> +</html> diff --git a/devtools/client/styleeditor/test/browser.ini b/devtools/client/styleeditor/test/browser.ini new file mode 100644 index 0000000000..089f1b26e0 --- /dev/null +++ b/devtools/client/styleeditor/test/browser.ini @@ -0,0 +1,141 @@ +[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 + # add nosniff header to test against Bug 1330383 + sourcemap-css/sourcemaps.css.map^headers^ + 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 + # add nosniff header to test against Bug 1330383 + sourcemap-sass/sourcemaps.scss^headers^ + 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] +[browser_styleeditor_autocomplete-disabled.js] +[browser_styleeditor_autocomplete.js] +[browser_styleeditor_bom.js] +[browser_styleeditor_bug_1247083_inline_stylesheet_numbering.js] +[browser_styleeditor_bug_1405342_serviceworker_iframes.js] +skip-if = !debug && (os == "win") || (os == "linux" && os_version == "18.04") #bug 1424914 +[browser_styleeditor_bug_740541_iframes.js] +[browser_styleeditor_bug_851132_middle_click.js] +[browser_styleeditor_bug_870339.js] +[browser_styleeditor_copyurl.js] +[browser_styleeditor_enabled.js] +[browser_styleeditor_fetch-from-netmonitor.js] +skip-if = http3 # Bug 1829298 +[browser_styleeditor_filesave.js] +skip-if = http3 # Bug 1829298 +[browser_styleeditor_filter.js] +[browser_styleeditor_fission_switch_target.js] +[browser_styleeditor_highlight-selector.js] +[browser_styleeditor_import.js] +[browser_styleeditor_import_rule.js] +[browser_styleeditor_init.js] +[browser_styleeditor_inline_friendly_names.js] +[browser_styleeditor_loading.js] +skip-if = http3 # Bug 1829298 +[browser_styleeditor_loading_with_containers.js] +[browser_styleeditor_media_sidebar_links.js] +[browser_styleeditor_media_sidebar_sourcemaps.js] +[browser_styleeditor_missing_stylesheet.js] +[browser_styleeditor_navigate.js] +[browser_styleeditor_new.js] +[browser_styleeditor_nostyle.js] +[browser_styleeditor_opentab.js] +[browser_styleeditor_pretty.js] +[browser_styleeditor_private_perwindowpb.js] +[browser_styleeditor_reload.js] +[browser_styleeditor_resize_performance.js] +[browser_styleeditor_scroll.js] +[browser_styleeditor_selectstylesheet.js] +[browser_styleeditor_sidebars.js] +[browser_styleeditor_sourcemap_chrome.js] +[browser_styleeditor_sourcemap_large.js] +[browser_styleeditor_sourcemap_watching.js] +skip-if = http3 # Bug 1829298 +[browser_styleeditor_sourcemaps.js] +[browser_styleeditor_sourcemaps_inline.js] +[browser_styleeditor_sv_keynav.js] +[browser_styleeditor_sv_resize.js] +[browser_styleeditor_sync.js] +[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] +[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..6a7c7cc4b8 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_at_rules_sidebar.js @@ -0,0 +1,312 @@ +/* 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, 36]; +const NEW_RULE = ` + @media (max-width: 750px) { + div { + color: blue; + } + + @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, 5, "5 @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", + }); +} + +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(); + let text = editor.sourceEditor.getText(); + text += NEW_RULE; + + const listChange = ui.once("at-rules-list-changed"); + editor.sourceEditor.setText(text); + await listChange; + + const sidebar = editor.details.querySelector(".stylesheet-sidebar"); + const entries = [...sidebar.querySelectorAll(".at-rule-label")]; + is(entries.length, 6, "six @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], + conditionText: LABELS[5], + matches: false, + line: LINE_NOS[5], + }); +} + +/** + * 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( + [ + "<!DOCTYPE html>", + "<html>", + " <head>", + " <title>Bug 1301854</title>", + ' <link rel="stylesheet" type="text/css" href="' + BOM_CSS + '">', + " </head>", + " <body>", + " </body>", + "</html>", + ].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( + [ + "<!DOCTYPE html>", + "<html>", + " <head>", + " <title>Style editor numbering test page</title>", + + // first inline stylesheet + " <style>", + " #p-blue {", + " color: blue;", + " }", + " </style>", + // first external stylesheet + ' <link rel="stylesheet" type="text/css" href="' + SIMPLE + '">', + // second external stylesheet + ' <link rel="stylesheet" type="text/css" href="' + LONG + '">', + // second inline stylesheet + " <style>", + " #p-green {", + " color: green;", + " }", + " #p-red {", + " color: red;", + " }", + " </style>", + + " </head>", + " <body>", + " </body>", + "</html>", + ].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( + [ + "<!DOCTYPE html>", + "<html>", + "<head>", + "<title>Bug 740541</title>", + ], + stylesheets.map(function (sheet) { + return ( + '<link rel="stylesheet" type="text/css" href="' + sheet + '">' + ); + }), + ["</head>", "<body>"], + framedDocuments.map(function (doc) { + return '<iframe src="' + doc + '"></iframe>'; + }), + ["</body>", "</html>"] + ) + .join("\n") + ) + ); + } + + const DOCUMENT_WITH_INLINE_STYLE = + "data:text/html;charset=UTF-8," + + encodeURIComponent( + [ + "<!DOCTYPE html>", + "<html>", + " <head>", + " <title>Bug 740541</title>", + ' <style type="text/css">', + " .something {", + " }", + " </style>", + " </head>", + " <body>", + " </body>", + " </html>", + ].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( + [ + "<!DOCTYPE html>", + "<html>", + " <head>", + " <title>Bug 870339</title>", + ' <link rel="stylesheet" type="text/css" href="' + SIMPLE + '">', + " </head>", + " <body>", + " </body>", + "</html>", + ].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..40324b2ea6 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_enabled.js @@ -0,0 +1,129 @@ +/* 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"); + ok(stylesheetToggle.getBoundingClientRect().width > 0); + ok(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"); + 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..e49d3cf8a9 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_filesave.js @@ -0,0 +1,91 @@ +/* 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 = FileUtils.getFile("ProfD", [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( + ` + <!DOCTYPE html> + <html> + <head> + <title>Test filter</title> + <link rel="stylesheet" type="text/css" href="${TEST_BASE_HTTPS}simple.css"> + ${Array.from({ length: INITIAL_INLINE_STYLE_SHEETS_COUNT }) + .map((_, i) => `<style>/* inline ${i} */</style>`) + .join("\n")} + <link rel="stylesheet" type="text/css" href="${TEST_BASE_HTTPS}pretty.css"> + </head> + <body> + </body> + </html> + ` + ); + +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), + [ + "<inline style sheet #1>", + "<inline style sheet #10>", + "<inline style sheet #11>", + "<inline style sheet #12>", + "<inline style sheet #13>", + "<inline style sheet #14>", + "<inline style sheet #15>", + "<inline style sheet #16>", + "<inline style sheet #17>", + "<inline style sheet #18>", + "<inline style sheet #19>", + "<inline style sheet #100>", + ], + `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, + "<inline style sheet #1>", + "focus is on first inline stylesheet" + ); + + EventUtils.synthesizeKey("VK_DOWN", {}, panelWindow); + is( + panelWindow.document.activeElement.childNodes[0].value, + "<inline style sheet #10>", + "focus is on inline stylesheet #10" + ); + + EventUtils.synthesizeKey("VK_DOWN", {}, panelWindow); + is( + panelWindow.document.activeElement.childNodes[0].value, + "<inline style sheet #11>", + "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("<inline style sheet #101>"), + "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<Element> + */ +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<String> + */ +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..9150e88729 --- /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 === 6); + ok(true, `Six 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..8936d86ce6 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_highlight-selector.js @@ -0,0 +1,199 @@ +/* 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= + <style>h2{color:cyan}</style> + <h2>highlighter test</h2>`; +const TOP_LEVEL_URL = `https://example.com/document-builder.sjs?html= + <style>h1{color:red}</style> + <h1>highlighter test</h1> + <iframe src='${REMOTE_IFRAME_URL}'></iframe>`; + +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"); + + 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<string>} 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..456985f384 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_import.js @@ -0,0 +1,54 @@ +/* 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 = FileUtils.getFile("ProfD", [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..35a23fe016 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_inline_friendly_names.js @@ -0,0 +1,101 @@ +/* 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 = FileUtils.getFile("ProfD", [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..40d70cf924 --- /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 = await 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..ecf788e412 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_loading_with_containers.js @@ -0,0 +1,70 @@ +/* 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."); + checkSheet(ui.editors[0], EXPECTED_SHEETS[0]); + 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 }; +} + +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_media_sidebar_links.js b/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_links.js new file mode 100644 index 0000000000..1fb9a13195 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_media_sidebar_links.js @@ -0,0 +1,158 @@ +/* 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 () { + 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"); + let conditions = sidebar.querySelectorAll(".at-rule-condition"); + + const onMediaChange = once(ui, "at-rules-list-changed"); + const onRDMOpened = once(ui, "responsive-mode-opened"); + + 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); + + info("Waiting for the @media list to update"); + await onMediaChange; + + // 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." + ); + conditions = sidebar.querySelectorAll(".at-rule-condition"); + ok( + !conditions[itemIndex].classList.contains("media-condition-unmatched"), + "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..27df58235c --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_new.js @@ -0,0 +1,97 @@ +/* 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; + + 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); + }); +} + +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 ruleCount = editor.summary.querySelector( + ".stylesheet-rule-count" + ).textContent; + is(parseInt(ruleCount, 10), 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..102daf9d97 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_pretty.js @@ -0,0 +1,82 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that minified sheets are automatically prettified but other are left +// untouched. + +const TESTCASE_URI = TEST_BASE_HTTP + "minified.html"; + +/* + body { + background:white; + } + div { + font-size:4em; + color:red + } + span { + color:green; + } +*/ +const PRETTIFIED_SOURCE = + "" + + "body {\r?\n" + + "\tbackground:white;\r?\n" + + "}\r?\n" + + "div {\r?\n" + + "\tfont-size:4em;\r?\n" + + "\tcolor:red\r?\n" + + "}\r?\n" + + "span {\r?\n" + + "\tcolor:green;\r?\n" + + "}\r?\n"; + +/* + body { background: red; } + div { + font-size: 5em; + color: red + } +*/ +const ORIGINAL_SOURCE = + "" + + "body { background: red; }\r?\n" + + "div {\r?\n" + + "font-size: 5em;\r?\n" + + "color: red\r?\n" + + "}"; + +const EXPAND_TAB = "devtools.editor.expandtab"; + +add_task(async function () { + const oldExpandTabPref = SpecialPowers.getBoolPref(EXPAND_TAB); + // The 'EXPAND_TAB' preference has to be set to false because + // the constant 'PRETTIFIED_SOURCE' uses tabs for indentation. + SpecialPowers.setBoolPref(EXPAND_TAB, false); + + const { ui } = await openStyleEditorForURL(TESTCASE_URI); + is(ui.editors.length, 2, "Two sheets present."); + + info("Testing minified style sheet."); + let editor = await ui.editors[0].getSourceEditor(); + + const prettifiedSourceRE = new RegExp(PRETTIFIED_SOURCE); + ok( + prettifiedSourceRE.test(editor.sourceEditor.getText()), + "minified source has been prettified automatically" + ); + + info("Selecting second, non-minified style sheet."); + await ui.selectStyleSheet(ui.editors[1].styleSheet); + + editor = ui.editors[1]; + + const originalSourceRE = new RegExp(ORIGINAL_SOURCE); + ok( + originalSourceRE.test(editor.sourceEditor.getText()), + "non-minified source has been left untouched" + ); + + SpecialPowers.setBoolPref(EXPAND_TAB, oldExpandTabPref); +}); 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_resize_performance.js b/devtools/client/styleeditor/test/browser_styleeditor_resize_performance.js new file mode 100644 index 0000000000..cd978fd624 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_resize_performance.js @@ -0,0 +1,62 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +/** + * This is a performance test designed to check we are not redrawing the UI too many times + * after resizing the window, when the styleeditor displays mediaqueries which are source + * mapped. + * See Bug 1453044 for more details. + */ + +const TESTCASE_URI = TEST_BASE_HTTP + "many-media-rules-sourcemaps/index.html"; + +// Maximum delay allowed between two at-rules-list-changed events. +const EVENTS_DELAY = 2000; + +// The window resize will still trigger several resize events which will lead to several +// UI updates. Arbitrary maximum number of events allowed to be fired for a single resize. +// This used to be > 100 events for this test case. +const MAX_EVENTS = 10; + +add_task(async function () { + const { toolbox, ui } = await openStyleEditorForURL(TESTCASE_URI); + + const win = toolbox.win.parent; + const originalWidth = win.outerWidth; + const originalHeight = win.outerHeight; + + // Ensure the window is above 500px wide for @media (min-width: 500px) + if (originalWidth < 500) { + info("Window is too small for the test, resize it to > 800px width"); + const onMediaListChanged = waitForManyEvents(ui, EVENTS_DELAY); + await resizeWindow(800, ui, win); + info("Wait for at-rules-list-changed events to settle"); + await onMediaListChanged; + } + + info( + "Resize the window to stop matching media queries, and trigger the UI updates" + ); + const onMediaListChanged = waitForManyEvents(ui, win, EVENTS_DELAY); + await resizeWindow(400, ui, win); + const eventsCount = await onMediaListChanged; + + ok( + eventsCount < MAX_EVENTS, + `Too many events fired (expected less than ${MAX_EVENTS}, got ${eventsCount})` + ); + + win.resizeTo(originalWidth, originalHeight); +}); + +/** + * Resize the window to the provided width. + */ +async function resizeWindow(width, ui, win) { + const onResize = once(win, "resize"); + win.resizeTo(width, win.outerHeight); + info("Wait for window resize event"); + await onResize; +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_scroll.js b/devtools/client/styleeditor/test/browser_styleeditor_scroll.js new file mode 100644 index 0000000000..d9ae030068 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_scroll.js @@ -0,0 +1,96 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that editor scrolls to correct line if it's selected with +// * selectStyleSheet (specified line) +// * click on the sidebar item (line before the editor was unselected) +// See bug 1148086. + +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( + [ + "<!DOCTYPE html>", + "<html>", + " <head>", + " <title>Editor scroll test page</title>", + ' <link rel="stylesheet" type="text/css" href="' + SIMPLE + '">', + ' <link rel="stylesheet" type="text/css" href="' + LONG + '">', + " </head>", + " <body>Editor scroll test page</body>", + "</html>", + ].join("\n") + ); +const LINE_TO_SELECT = 201; + +add_task(async function () { + const { ui } = await openStyleEditorForURL(DOCUMENT_WITH_LONG_SHEET); + + is(ui.editors.length, 2, "Two editors present."); + + const simpleEditor = ui.editors[0]; + const longEditor = ui.editors[1]; + + info(`Selecting doc_long.css and scrolling to line ${LINE_TO_SELECT}`); + + // We need to wait for editor-selected if we want to check the scroll + // position as scrolling occurs after selectStyleSheet resolves but before the + // event is emitted. + let selectEventPromise = waitForEditorToBeSelected(longEditor, ui); + await ui.selectStyleSheet(longEditor.styleSheet, LINE_TO_SELECT); + await selectEventPromise; + + info("Checking that the correct line is visible after initial load"); + + const { from, to } = longEditor.sourceEditor.getViewport(); + info(`Lines ${from}-${to} are visible (expected ${LINE_TO_SELECT}).`); + + ok(from <= LINE_TO_SELECT, "The editor scrolled too much."); + ok(to >= LINE_TO_SELECT, "The editor scrolled too little."); + + const initialScrollTop = longEditor.sourceEditor.getScrollInfo().top; + info(`Storing scrollTop = ${initialScrollTop} for later comparison.`); + + info("Selecting the first editor (simple.css)"); + await ui.selectStyleSheet(simpleEditor.styleSheet); + + info("Selecting doc_long.css again."); + selectEventPromise = waitForEditorToBeSelected(longEditor, ui); + + // Can't use ui.selectStyleSheet here as it will scroll the editor back to top + // and we want to check that the previous scroll position is restored. + const summary = await ui.getEditorSummary(longEditor); + summary.click(); + + info("Waiting for doc_long.css to be selected."); + await selectEventPromise; + + const scrollTop = longEditor.sourceEditor.getScrollInfo().top; + is( + scrollTop, + initialScrollTop, + "Scroll top was restored after the sheet was selected again." + ); +}); + +/** + * A helper that waits "editor-selected" event for given editor. + * + * @param {StyleSheetEditor} editor + * The editor to wait for. + * @param {StyleEditorUI} ui + * The StyleEditorUI the editor belongs to. + */ +var waitForEditorToBeSelected = async function (editor, ui) { + info(`Waiting for ${editor.friendlyName} to be selected.`); + let selected = await ui.once("editor-selected"); + while (selected != editor) { + info(`Ignored editor-selected for editor ${editor.friendlyName}.`); + selected = await ui.once("editor-selected"); + } + + info(`Got editor-selected for ${editor.friendlyName}.`); +}; diff --git a/devtools/client/styleeditor/test/browser_styleeditor_selectstylesheet.js b/devtools/client/styleeditor/test/browser_styleeditor_selectstylesheet.js new file mode 100644 index 0000000000..b455ec3c7f --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_selectstylesheet.js @@ -0,0 +1,25 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that StyleEditorUI.selectStyleSheet selects the correct sheet, line and +// column. + +const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html"; + +const LINE_NO = 5; +const COL_NO = 0; + +add_task(async function () { + const { ui } = await openStyleEditorForURL(TESTCASE_URI); + const editor = ui.editors[1]; + + info("Selecting style sheet #1."); + await ui.selectStyleSheet(editor.styleSheet, LINE_NO); + + is(ui.selectedEditor, ui.editors[1], "Second editor is selected."); + const { line, ch } = ui.selectedEditor.sourceEditor.getCursor(); + + is(line, LINE_NO, "correct line selected"); + is(ch, COL_NO, "correct column selected"); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_sidebars.js b/devtools/client/styleeditor/test/browser_styleeditor_sidebars.js new file mode 100644 index 0000000000..d0cdb4acfe --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_sidebars.js @@ -0,0 +1,67 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TESTCASE_URI = TEST_BASE_HTTPS + "media-rules.html"; + +const PREF_SHOW_AT_RULES_SIDEBAR = "devtools.styleeditor.showAtRulesSidebar"; +const PREF_SIDEBAR_WIDTH = "devtools.styleeditor.atRulesSidebarWidth"; +const PREF_NAV_WIDTH = "devtools.styleeditor.navSidebarWidth"; + +// Initial widths for the navigation and media sidebars, which will be set via +// the corresponding preferences. +// The widths should remain between the current min-width and max-width for the +// styleeditor sidebars (currently 100px and 400px). +const NAV_WIDTH = 210; +const MEDIA_WIDTH = 250; + +// Test that sidebar in the styleeditor can be resized. +add_task(async function () { + await pushPref(PREF_SHOW_AT_RULES_SIDEBAR, true); + await pushPref(PREF_NAV_WIDTH, NAV_WIDTH); + await pushPref(PREF_SIDEBAR_WIDTH, MEDIA_WIDTH); + + const { panel, ui } = await openStyleEditorForURL(TESTCASE_URI); + const doc = panel.panelWindow.document; + + info("Open editor for inline sheet with @media rules to have both splitters"); + const inlineMediaEditor = ui.editors[3]; + inlineMediaEditor.summary.querySelector(".stylesheet-name").click(); + await inlineMediaEditor.getSourceEditor(); + + info("Check the initial widths of side panels match the preferences values"); + const navSidebar = doc.querySelector(".splitview-controller"); + is(navSidebar.clientWidth, NAV_WIDTH); + + const mediaSidebar = doc.querySelector( + ".splitview-active .stylesheet-sidebar" + ); + is(mediaSidebar.clientWidth, MEDIA_WIDTH); + + info( + "Resize the navigation splitter and check the navigation sidebar is updated" + ); + const navSplitter = doc.querySelector(".devtools-side-splitter"); + dragElement(navSplitter, { startX: 1, startY: 10, deltaX: 50, deltaY: 0 }); + is(navSidebar.clientWidth, NAV_WIDTH + 50); + + info("Resize the media splitter and check the media sidebar is updated"); + const mediaSplitter = doc.querySelector( + ".splitview-active .devtools-side-splitter" + ); + dragElement(mediaSplitter, { startX: 1, startY: 10, deltaX: -50, deltaY: 0 }); + is(mediaSidebar.clientWidth, MEDIA_WIDTH + 50); +}); + +/* Helpers */ + +function dragElement(el, { startX, startY, deltaX, deltaY }) { + const win = el.ownerGlobal; + const endX = startX + deltaX; + const endY = startY + deltaY; + + EventUtils.synthesizeMouse(el, startX, startY, { type: "mousedown" }, win); + EventUtils.synthesizeMouse(el, endX, endY, { type: "mousemove" }, win); + EventUtils.synthesizeMouse(el, endX, endY, { type: "mouseup" }, win); +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_sourcemap_chrome.js b/devtools/client/styleeditor/test/browser_styleeditor_sourcemap_chrome.js new file mode 100644 index 0000000000..997171e30d --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_sourcemap_chrome.js @@ -0,0 +1,47 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +const TEST_URI = URL_ROOT_SSL + "doc_sourcemap_chrome.html"; +const CHROME_TEST_URI = CHROME_URL_ROOT + "doc_sourcemap_chrome.html"; +const GENERATED_NAME = "sourcemaps_chrome.css"; +const ORIGINAL_NAME = "sourcemaps.scss"; + +/** + * Test that a sourcemap served by a chrome URL for a http document will not be resolved. + */ +add_task(async function () { + const { ui } = await openStyleEditorForURL(TEST_URI); + ok( + findStylesheetByName(ui, GENERATED_NAME), + "Sourcemap not resolved: generated source is listed" + ); + ok( + !findStylesheetByName(ui, ORIGINAL_NAME), + "Sourcemap not resolved: original source is not listed" + ); +}); + +/** + * Test that a sourcemap served by a chrome URL for a chrome document is resolved. + */ +add_task(async function () { + const { ui } = await openStyleEditorForURL(CHROME_TEST_URI); + ok( + findStylesheetByName(ui, ORIGINAL_NAME), + "Sourcemap resolved: original source is listed" + ); + ok( + !findStylesheetByName(ui, GENERATED_NAME), + "Sourcemap resolved: generated source is not listed" + ); +}); + +function findStylesheetByName(ui, name) { + return ui.editors.some( + editor => + editor.summary + .querySelector(".stylesheet-name > label") + .getAttribute("value") === name + ); +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_sourcemap_large.js b/devtools/client/styleeditor/test/browser_styleeditor_sourcemap_large.js new file mode 100644 index 0000000000..b03cef01c1 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_sourcemap_large.js @@ -0,0 +1,34 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Covers the case from Bug 1128747, where loading a sourcemapped +// file prevents the correct editor from being selected on load, +// and causes a second iframe to be appended when the user clicks +// editor in the list. + +const TESTCASE_URI = TEST_BASE_HTTPS + "sourcemaps-large.html"; + +add_task(async function () { + const { ui } = await openStyleEditorForURL(TESTCASE_URI); + + await openEditor(ui.editors[0]); + const iframes = ui.selectedEditor.details.querySelectorAll("iframe"); + + is(iframes.length, 1, "There is only one editor iframe"); + ok( + ui.selectedEditor.summary.classList.contains("splitview-active"), + "The editor is selected" + ); +}); + +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_sourcemap_watching.js b/devtools/client/styleeditor/test/browser_styleeditor_sourcemap_watching.js new file mode 100644 index 0000000000..472e34b7fc --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_sourcemap_watching.js @@ -0,0 +1,155 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* eslint-disable mozilla/no-arbitrary-setTimeout */ + +"use strict"; + +const TESTCASE_URI_HTML = TEST_BASE_HTTP + "sourcemaps-watching.html"; +const TESTCASE_URI_CSS = TEST_BASE_HTTP + "sourcemap-css/sourcemaps.css"; +const TESTCASE_URI_REG_CSS = TEST_BASE_HTTP + "simple.css"; +const TESTCASE_URI_SCSS = TEST_BASE_HTTP + "sourcemap-sass/sourcemaps.scss"; +const TESTCASE_URI_MAP = TEST_BASE_HTTP + "sourcemap-css/sourcemaps.css.map"; +const TESTCASE_SCSS_NAME = "sourcemaps.scss"; + +const TRANSITIONS_PREF = "devtools.styleeditor.transitions"; + +const CSS_TEXT = "* { color: blue }"; + +add_task(async function () { + await new Promise(resolve => { + SpecialPowers.pushPrefEnv({ set: [[TRANSITIONS_PREF, false]] }, resolve); + }); + + // copy all our files over so we don't screw them up for other tests + const HTMLFile = await copy(TESTCASE_URI_HTML, ["sourcemaps.html"]); + const CSSFile = await copy(TESTCASE_URI_CSS, [ + "sourcemap-css", + "sourcemaps.css", + ]); + await copy(TESTCASE_URI_SCSS, ["sourcemap-sass", "sourcemaps.scss"]); + await copy(TESTCASE_URI_MAP, ["sourcemap-css", "sourcemaps.css.map"]); + await copy(TESTCASE_URI_REG_CSS, ["simple.css"]); + + const uri = Services.io.newFileURI(HTMLFile); + const testcaseURI = uri.resolve(""); + + const { ui } = await openStyleEditorForURL(testcaseURI); + + let editor = ui.editors[1]; + if (getStylesheetNameFor(editor) != TESTCASE_SCSS_NAME) { + editor = ui.editors[2]; + } + + is(getStylesheetNameFor(editor), TESTCASE_SCSS_NAME, "found scss editor"); + + const link = getLinkFor(editor); + link.click(); + + await editor.getSourceEditor(); + + let color = await getComputedStyleProperty({ + selector: "div", + name: "color", + }); + is(color, "rgb(255, 0, 102)", "div is red before saving file"); + + const styleApplied = editor.once("style-applied"); + + await pauseForTimeChange(); + + // Edit and save Sass in the editor. This will start off a file-watching + // process waiting for the CSS file to change. + await editSCSS(editor); + + // We can't run Sass or another compiler, so we fake it by just + // directly changing the CSS file. + await editCSSFile(CSSFile); + + info("wrote to CSS file, waiting for style-applied event"); + + await styleApplied; + + color = await getComputedStyleProperty({ selector: "div", name: "color" }); + is(color, "rgb(0, 0, 255)", "div is blue after saving file"); + + // Ensure that the editor didn't revert. Bug 1346662. + is(editor.sourceEditor.getText(), CSS_TEXT, "edits remain applied"); +}); + +function editSCSS(editor) { + return new Promise(resolve => { + editor.sourceEditor.setText(CSS_TEXT); + + editor.saveToFile(null, function (file) { + ok(file, "Scss file should be saved"); + resolve(); + }); + }); +} + +function editCSSFile(CSSFile) { + return write(CSS_TEXT, CSSFile); +} + +function pauseForTimeChange() { + return new Promise(resolve => { + // We have to wait for the system time to turn over > 1000 ms so that + // our file's last change time will show a change. This reflects what + // would happen in real life with a user manually saving the file. + setTimeout(resolve, 2000); + }); +} + +/* Helpers */ + +function getLinkFor(editor) { + return editor.summary.querySelector(".stylesheet-name"); +} + +function getStylesheetNameFor(editor) { + return editor.summary + .querySelector(".stylesheet-name > label") + .getAttribute("value"); +} + +function copy(srcChromeURL, destFilePath) { + const destFile = FileUtils.getFile("ProfD", destFilePath); + return write(read(srcChromeURL), destFile); +} + +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) { + return new Promise(resolve => { + const istream = getInputStream(data); + const ostream = FileUtils.openSafeFileOutputStream(file); + + NetUtil.asyncCopy(istream, ostream, function (status) { + if (!Components.isSuccessCode(status)) { + info("Coudln't write to " + file.path); + return; + } + resolve(file); + }); + }); +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_sourcemaps.js b/devtools/client/styleeditor/test/browser_styleeditor_sourcemaps.js new file mode 100644 index 0000000000..452988eb68 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_sourcemaps.js @@ -0,0 +1,152 @@ +/* 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 + "sourcemaps.html"; +const PREF = "devtools.source-map.client-service.enabled"; + +const contents = { + "sourcemaps.scss": [ + "", + "$paulrougetpink: #f06;", + "", + "div {", + " color: $paulrougetpink;", + "}", + "", + "span {", + " background-color: #EEE;", + "}", + ].join("\n"), + "contained.scss": [ + "$pink: #f06;", + "", + "#header {", + " color: $pink;", + "}", + ].join("\n"), + "sourcemaps.css": [ + "div {", + " color: #ff0066; }", + "", + "span {", + " background-color: #EEE; }", + "", + "/*# sourceMappingURL=sourcemaps.css.map */", + ].join("\n"), + "contained.css": [ + "#header {", + " color: #f06; }", + "", + "/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJma" + + "WxlIjoiIiwic291cmNlcyI6WyJzYXNzL2NvbnRhaW5lZC5zY3NzIl0sIm5hbWVzIjpbXSwi" + + "bWFwcGluZ3MiOiJBQUVBO0VBQ0UsT0FISyIsInNvdXJjZXNDb250ZW50IjpbIiRwaW5rOiA" + + "jZjA2O1xuXG4jaGVhZGVyIHtcbiAgY29sb3I6ICRwaW5rO1xufSJdfQ==*/", + ].join("\n"), + "test-stylus.styl": [ + "paulrougetpink = #f06;", + "", + "div", + " color: paulrougetpink", + "", + "span", + " background-color: #EEE", + "", + ].join("\n"), + "test-stylus.css": [ + "div {", + " color: #f06;", + "}", + "span {", + " background-color: #eee;", + "}", + "/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb" + + "3VyY2VzIjpbInRlc3Qtc3R5bHVzLnN0eWwiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFB" + + "RUE7RUFDRSxPQUFPLEtBQVA7O0FBRUY7RUFDRSxrQkFBa0IsS0FBbEIiLCJmaWxlIjoidGV" + + "zdC1zdHlsdXMuY3NzIiwic291cmNlc0NvbnRlbnQiOlsicGF1bHJvdWdldHBpbmsgPSAjZj" + + "A2O1xuXG5kaXZcbiAgY29sb3I6IHBhdWxyb3VnZXRwaW5rXG5cbnNwYW5cbiAgYmFja2dyb" + + "3VuZC1jb2xvcjogI0VFRVxuIl19 */", + ].join("\n"), +}; + +const cssNames = ["sourcemaps.css", "contained.css", "test-stylus.css"]; +const origNames = ["sourcemaps.scss", "contained.scss", "test-stylus.styl"]; + +waitForExplicitFinish(); + +add_task(async function () { + const { ui } = await openStyleEditorForURL(TESTCASE_URI); + + is( + ui.editors.length, + 4, + "correct number of editors with source maps enabled" + ); + + // Test first plain css editor + testFirstEditor(ui.editors[0]); + + // Test Scss editors + await testEditor(ui.editors[1], origNames); + await testEditor(ui.editors[2], origNames); + await testEditor(ui.editors[3], origNames); + + // Test disabling original sources + await togglePref(ui); + + is(ui.editors.length, 4, "correct number of editors after pref toggled"); + + // Test CSS editors + await testEditor(ui.editors[1], cssNames); + await testEditor(ui.editors[2], cssNames); + await testEditor(ui.editors[3], cssNames); + + Services.prefs.clearUserPref(PREF); +}); + +function testFirstEditor(editor) { + const name = getStylesheetNameFor(editor); + is(name, "simple.css", "First style sheet display name is correct"); +} + +function testEditor(editor, possibleNames) { + const name = getStylesheetNameFor(editor); + ok(possibleNames.includes(name), name + " editor name is correct"); + + return openEditor(editor).then(() => { + const expectedText = contents[name]; + + const text = editor.sourceEditor.getText(); + + is(text, expectedText, name + " editor contains expected text"); + }); +} + +/* Helpers */ + +function togglePref(UI) { + const editorsPromise = UI.once("stylesheets-refreshed"); + const selectedPromise = UI.once("editor-selected"); + + Services.prefs.setBoolPref(PREF, false); + + return Promise.all([editorsPromise, selectedPromise]); +} + +function openEditor(editor) { + getLinkFor(editor).click(); + + return editor.getSourceEditor(); +} + +function getLinkFor(editor) { + return editor.summary.querySelector(".stylesheet-name"); +} + +function getStylesheetNameFor(editor) { + return editor.summary + .querySelector(".stylesheet-name > label") + .getAttribute("value"); +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_sourcemaps_inline.js b/devtools/client/styleeditor/test/browser_styleeditor_sourcemaps_inline.js new file mode 100644 index 0000000000..933a128a74 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_sourcemaps_inline.js @@ -0,0 +1,89 @@ +/* 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 + "sourcemaps-inline.html"; +const PREF = "devtools.source-map.client-service.enabled"; + +const sassContent = `body { + background-color: black; + & > h1 { + color: white; + } +} +`; + +const cssContent = + `body { + background-color: black; +} +body > h1 { + color: white; +} +` + + "/*# sourceMappingURL=data:application/json;base64,ewoidmVyc2lvbiI6IDMsCiJtY" + + "XBwaW5ncyI6ICJBQUFBLElBQUs7RUFDSCxnQkFBZ0IsRUFBRSxLQUFLO0VBQ3ZCLFNBQU87SUFD" + + "TCxLQUFLLEVBQUUsS0FBSyIsCiJzb3VyY2VzIjogWyJ0ZXN0LnNjc3MiXSwKInNvdXJjZXNDb25" + + "0ZW50IjogWyJib2R5IHtcbiAgYmFja2dyb3VuZC1jb2xvcjogYmxhY2s7XG4gICYgPiBoMSB7XG" + + "4gICAgY29sb3I6IHdoaXRlO1xuICB9XG59XG4iXSwKIm5hbWVzIjogW10sCiJmaWxlIjogInRlc" + + "3QuY3NzIgp9Cg== */"; + +add_task(async function () { + const { ui } = await openStyleEditorForURL(TESTCASE_URI); + + is( + ui.editors.length, + 1, + "correct number of editors with source maps enabled" + ); + + await testEditor(ui.editors[0], "test.scss", sassContent); + + // Test disabling original sources + await togglePref(ui); + + is(ui.editors.length, 1, "correct number of editors after pref toggled"); + + // Test CSS editors + await testEditor(ui.editors[0], "<inline style sheet #1>", cssContent); + + Services.prefs.clearUserPref(PREF); +}); + +async function testEditor(editor, expectedName, expectedText) { + const name = getStylesheetNameFor(editor); + is(expectedName, name, name + " editor name is correct"); + + await openEditor(editor); + const text = editor.sourceEditor.getText(); + is(text, expectedText, name + " editor contains expected text"); +} + +/* Helpers */ + +function togglePref(UI) { + const editorsPromise = UI.once("stylesheets-refreshed"); + const selectedPromise = UI.once("editor-selected"); + + Services.prefs.setBoolPref(PREF, false); + + return Promise.all([editorsPromise, selectedPromise]); +} + +function openEditor(editor) { + getLinkFor(editor).click(); + + return editor.getSourceEditor(); +} + +function getLinkFor(editor) { + return editor.summary.querySelector(".stylesheet-name"); +} + +function getStylesheetNameFor(editor) { + return editor.summary + .querySelector(".stylesheet-name > label") + .getAttribute("value"); +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_sv_keynav.js b/devtools/client/styleeditor/test/browser_styleeditor_sv_keynav.js new file mode 100644 index 0000000000..5967eb40b5 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_sv_keynav.js @@ -0,0 +1,85 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that the style sheet list can be navigated with keyboard. + +const TESTCASE_URI = TEST_BASE_HTTP + "four.html"; + +add_task(async function () { + const { panel, ui } = await openStyleEditorForURL(TESTCASE_URI); + + info("Waiting for source editor to load."); + await ui.editors[0].getSourceEditor(); + + const onEditorSelected = new Promise(resolve => { + const off = ui.on("editor-selected", editor => { + if (editor == ui.editors[2]) { + resolve(); + off(); + } + }); + }); + + info("Testing keyboard navigation on the sheet list."); + testKeyboardNavigation(ui.editors[0], panel); + + info("Waiting for editor #2 to be selected due to keyboard navigation."); + await onEditorSelected; + ok(ui.editors[2].sourceEditor.hasFocus(), "Editor #2 has focus."); +}); + +function getStylesheetNameLinkFor(editor) { + return editor.summary.querySelector(".stylesheet-name"); +} + +function testKeyboardNavigation(editor, panel) { + const panelWindow = panel.panelWindow; + const ui = panel.UI; + waitForFocus(function () { + const summary = editor.summary; + EventUtils.synthesizeMouseAtCenter(summary, {}, panelWindow); + + let item = getStylesheetNameLinkFor(ui.editors[0]); + is( + panelWindow.document.activeElement, + item, + "editor 0 item is the active element" + ); + + EventUtils.synthesizeKey("VK_DOWN", {}, panelWindow); + item = getStylesheetNameLinkFor(ui.editors[1]); + is( + panelWindow.document.activeElement, + item, + "editor 1 item is the active element" + ); + + EventUtils.synthesizeKey("VK_HOME", {}, panelWindow); + item = getStylesheetNameLinkFor(ui.editors[0]); + is( + panelWindow.document.activeElement, + item, + "fist editor item is the active element" + ); + + EventUtils.synthesizeKey("VK_END", {}, panelWindow); + item = getStylesheetNameLinkFor(ui.editors[3]); + is( + panelWindow.document.activeElement, + item, + "last editor item is the active element" + ); + + EventUtils.synthesizeKey("VK_UP", {}, panelWindow); + item = getStylesheetNameLinkFor(ui.editors[2]); + is( + panelWindow.document.activeElement, + item, + "editor 2 item is the active element" + ); + + EventUtils.synthesizeKey("VK_RETURN", {}, panelWindow); + // this will attach and give focus editor 2 + }, panelWindow); +} diff --git a/devtools/client/styleeditor/test/browser_styleeditor_sv_resize.js b/devtools/client/styleeditor/test/browser_styleeditor_sv_resize.js new file mode 100644 index 0000000000..76098145bc --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_sv_resize.js @@ -0,0 +1,53 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test that resizing the source editor container doesn't move the caret. + +const TESTCASE_URI = TEST_BASE_HTTP + "simple.html"; + +const { Toolbox } = require("resource://devtools/client/framework/toolbox.js"); + +add_task(async function () { + const { toolbox, ui } = await openStyleEditorForURL(TESTCASE_URI); + + is(ui.editors.length, 2, "There are 2 style sheets initially"); + + info("Changing toolbox host to a window."); + await toolbox.switchHost(Toolbox.HostType.WINDOW); + + const editor = await ui.editors[0].getSourceEditor(); + const originalSourceEditor = editor.sourceEditor; + + const hostWindow = toolbox.win.parent; + const originalWidth = hostWindow.outerWidth; + const originalHeight = hostWindow.outerHeight; + + // to check the caret is preserved + originalSourceEditor.setCursor(originalSourceEditor.getPosition(4)); + + info("Resizing window."); + hostWindow.resizeTo(120, 480); + + const sourceEditor = ui.editors[0].sourceEditor; + is( + sourceEditor, + originalSourceEditor, + "the editor still references the same Editor instance" + ); + + is( + sourceEditor.getOffset(sourceEditor.getCursor()), + 4, + "the caret position has been preserved" + ); + + info("Restoring window to original size."); + hostWindow.resizeTo(originalWidth, originalHeight); +}); + +registerCleanupFunction(() => { + // Restore the host type for other tests. + Services.prefs.clearUserPref("devtools.toolbox.host"); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_sync.js b/devtools/client/styleeditor/test/browser_styleeditor_sync.js new file mode 100644 index 0000000000..6b97e1cae0 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_sync.js @@ -0,0 +1,73 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that changes in the style inspector are synchronized into the +// style editor. + +const TESTCASE_URI = TEST_BASE_HTTP + "sync.html"; + +const expectedText = ` + body { + border-width: 15px; + /*! color: red; */ + } + + #testid { + /*! font-size: 4em; */ + } + `; + +async function closeAndReopenToolbox() { + await gDevTools.closeToolboxForTab(gBrowser.selectedTab); + const { ui: newui } = await openStyleEditor(); + return newui; +} + +add_task(async function () { + await addTab(TESTCASE_URI); + const { inspector, view } = await openRuleView(); + await selectNode("#testid", inspector); + let ruleEditor = getRuleViewRuleEditor(view, 1); + + // Disable the "font-size" property. + let propEditor = ruleEditor.rule.textProps[0].editor; + let onModification = view.once("ruleview-changed"); + propEditor.enable.click(); + await onModification; + + // Disable the "color" property. Note that this property is in a + // rule that also contains a non-inherited property -- so this test + // is also testing that property editing works properly in this + // situation. + ruleEditor = getRuleViewRuleEditor(view, 3); + propEditor = ruleEditor.rule.textProps[1].editor; + onModification = view.once("ruleview-changed"); + propEditor.enable.click(); + await onModification; + + let { ui } = await openStyleEditor(); + + let editor = await ui.editors[0].getSourceEditor(); + let text = editor.sourceEditor.getText(); + is(text, expectedText, "style inspector changes are synced"); + + // Close and reopen the toolbox, to see that the edited text remains + // available. + ui = await closeAndReopenToolbox(); + editor = await ui.editors[0].getSourceEditor(); + text = editor.sourceEditor.getText(); + is(text, expectedText, "changes remain after close and reopen"); + + // For the time being, the actor does not update the style's owning + // node's textContent. See bug 1205380. + const textContent = await SpecialPowers.spawn( + gBrowser.selectedBrowser, + [], + async function () { + return content.document.querySelector("style").textContent; + } + ); + + isnot(textContent, expectedText, "changes not written back to style node"); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_syncAddProperty.js b/devtools/client/styleeditor/test/browser_styleeditor_syncAddProperty.js new file mode 100644 index 0000000000..51546eaba6 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_syncAddProperty.js @@ -0,0 +1,52 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that adding a new rule is synced to the style editor. + +const TESTCASE_URI = TEST_BASE_HTTP + "sync.html"; +const TESTCASE_URI_WITH_CSP = TEST_BASE_HTTP + "sync_with_csp.html"; + +const expectedText = ` + body { + border-width: 15px; + color: red; + } + + #testid { + font-size: 4em; + /*! background-color: yellow; */ + } + `; + +add_task(async function () { + const URIs = [TESTCASE_URI, TESTCASE_URI_WITH_CSP]; + + for (const URI of URIs) { + await addTab(URI); + const { inspector, view } = await openRuleView(); + await selectNode("#testid", inspector); + + info("Focusing a new property name in the rule-view on " + URI); + const ruleEditor = getRuleViewRuleEditor(view, 1); + const editor = await focusEditableField(view, ruleEditor.closeBrace); + is( + inplaceEditor(ruleEditor.newPropSpan), + editor, + "The new property editor has focus" + ); + + const input = editor.input; + input.value = "/* background-color: yellow; */"; + + info("Pressing return to commit and focus the new value field"); + const onModifications = view.once("ruleview-changed"); + EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow); + await onModifications; + + const { ui } = await openStyleEditor(); + const sourceEditor = await ui.editors[0].getSourceEditor(); + const text = sourceEditor.sourceEditor.getText(); + is(text, expectedText, "selector edits are synced"); + } +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_syncAddRule.js b/devtools/client/styleeditor/test/browser_styleeditor_syncAddRule.js new file mode 100644 index 0000000000..e46ae57ed3 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_syncAddRule.js @@ -0,0 +1,30 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that adding a new rule is synced to the style editor. + +const TESTCASE_URI = TEST_BASE_HTTP + "sync.html"; + +const expectedText = ` +#testid { +}`; + +add_task(async function () { + await addTab(TESTCASE_URI); + const { inspector, view } = await openRuleView(); + await selectNode("#testid", inspector); + + const onRuleViewChanged = once(view, "ruleview-changed"); + view.addRuleButton.click(); + await onRuleViewChanged; + + const { ui } = await openStyleEditor(); + + info("Selecting the second editor"); + await ui.selectStyleSheet(ui.editors[1].styleSheet); + + const editor = ui.editors[1]; + const text = editor.sourceEditor.getText(); + is(text, expectedText, "selector edits are synced"); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_syncAlreadyOpen.js b/devtools/client/styleeditor/test/browser_styleeditor_syncAlreadyOpen.js new file mode 100644 index 0000000000..f91441e7ea --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_syncAlreadyOpen.js @@ -0,0 +1,50 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that changes in the style inspector are synchronized into the +// style editor. + +const TESTCASE_URI = TEST_BASE_HTTP + "sync.html"; + +const expectedText = ` + body { + border-width: 15px; + color: red; + } + + #testid { + /*! font-size: 4em; */ + } + `; + +add_task(async function () { + await addTab(TESTCASE_URI); + + const { inspector, view, toolbox } = await openRuleView(); + + // In this test, make sure the style editor is open before making + // changes in the inspector. + const { ui } = await openStyleEditor(); + const editor = await ui.editors[0].getSourceEditor(); + + const onEditorChange = new Promise(resolve => { + editor.sourceEditor.on("change", resolve); + }); + + await toolbox.getPanel("inspector"); + await selectNode("#testid", inspector); + const ruleEditor = getRuleViewRuleEditor(view, 1); + + // Disable the "font-size" property. + const propEditor = ruleEditor.rule.textProps[0].editor; + const onModification = view.once("ruleview-changed"); + propEditor.enable.click(); + await onModification; + + await openStyleEditor(); + await onEditorChange; + + const text = editor.sourceEditor.getText(); + is(text, expectedText, "style inspector changes are synced"); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_syncEditSelector.js b/devtools/client/styleeditor/test/browser_styleeditor_syncEditSelector.js new file mode 100644 index 0000000000..c82000aada --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_syncEditSelector.js @@ -0,0 +1,38 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that changes in the style inspector are synchronized into the +// style editor. + +const TESTCASE_URI = TEST_BASE_HTTP + "sync.html"; + +const expectedText = ` + body { + border-width: 15px; + color: red; + } + + #testid, span { + font-size: 4em; + } + `; + +add_task(async function () { + await addTab(TESTCASE_URI); + const { inspector, view } = await openRuleView(); + await selectNode("#testid", inspector); + const ruleEditor = getRuleViewRuleEditor(view, 1); + + let editor = await focusEditableField(view, ruleEditor.selectorText); + editor.input.value = "#testid, span"; + const onRuleViewChanged = once(view, "ruleview-changed"); + EventUtils.synthesizeKey("KEY_Enter"); + await onRuleViewChanged; + + const { ui } = await openStyleEditor(); + + editor = await ui.editors[0].getSourceEditor(); + const text = editor.sourceEditor.getText(); + is(text, expectedText, "selector edits are synced"); +}); diff --git a/devtools/client/styleeditor/test/browser_styleeditor_syncIntoRuleView.js b/devtools/client/styleeditor/test/browser_styleeditor_syncIntoRuleView.js new file mode 100644 index 0000000000..210e9b2bce --- /dev/null +++ b/devtools/client/styleeditor/test/browser_styleeditor_syncIntoRuleView.js @@ -0,0 +1,41 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +// Test that changes in the style editor are synchronized into the +// style inspector. + +const TEST_URI = ` + <style type='text/css'> + </style> + <div id='testid' class='testclass'>Styled Node</div> +`; + +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(); + const onRuleViewRefreshed = view.once("ruleview-refreshed"); + 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 onRuleViewRefreshed; + + 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..a099551db6 --- /dev/null +++ b/devtools/client/styleeditor/test/browser_toolbox_styleeditor.js @@ -0,0 +1,100 @@ +/* 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,<!DOCTYPE html> + <head> + <meta charset=utf8> + <link rel="stylesheet" type="text/css" href="${TEST_BASE_HTTP}simple.css"> + <head> + <body>Test browser toolbox</body>`; + +/* 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; + } + 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 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Bug 1405342</title> +</head> +<body> + <iframe src="https://test2.example.com/browser/devtools/client/styleeditor/test/iframe_with_service_worker.html"><iframe> +</body> +</html> diff --git a/devtools/client/styleeditor/test/doc_empty.html b/devtools/client/styleeditor/test/doc_empty.html new file mode 100644 index 0000000000..825c61e6b9 --- /dev/null +++ b/devtools/client/styleeditor/test/doc_empty.html @@ -0,0 +1,3 @@ +<!doctype html> +<html> +</html> diff --git a/devtools/client/styleeditor/test/doc_fetch_from_netmonitor.html b/devtools/client/styleeditor/test/doc_fetch_from_netmonitor.html new file mode 100644 index 0000000000..ff943978a0 --- /dev/null +++ b/devtools/client/styleeditor/test/doc_fetch_from_netmonitor.html @@ -0,0 +1,13 @@ +<!doctype html> +<html> +<head> + <title>Fetch from netmonitor testcase</title> + <link rel="stylesheet" charset="UTF-8" type="text/css" media="screen" href="doc_short_string.css"/> + <link rel="stylesheet" charset="UTF-8" type="text/css" media="screen" href="doc_long_string.css"/> + <!-- This last CSS is generated by a SJS server to avoid adding a 300,000 lines stylesheet to the codebase. --> + <link rel="stylesheet" charset="UTF-8" type="text/css" media="screen" href="sjs_huge-css-server.sjs"/> +</head> +<body> + <div>Fetch from netmonitor</div> +</body> +</html> diff --git a/devtools/client/styleeditor/test/doc_long.css b/devtools/client/styleeditor/test/doc_long.css new file mode 100644 index 0000000000..801a6e276f --- /dev/null +++ b/devtools/client/styleeditor/test/doc_long.css @@ -0,0 +1,402 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +div { + z-index: 1; +} + +div { + z-index: 2; +} + +div { + z-index: 3; +} + +div { + z-index: 4; +} + +div { + z-index: 5; +} + +div { + z-index: 6; +} + +div { + z-index: 7; +} + +div { + z-index: 8; +} + +div { + z-index: 9; +} + +div { + z-index: 10; +} + +div { + z-index: 11; +} + +div { + z-index: 12; +} + +div { + z-index: 13; +} + +div { + z-index: 14; +} + +div { + z-index: 15; +} + +div { + z-index: 16; +} + +div { + z-index: 17; +} + +div { + z-index: 18; +} + +div { + z-index: 19; +} + +div { + z-index: 20; +} + +div { + z-index: 21; +} + +div { + z-index: 22; +} + +div { + z-index: 23; +} + +div { + z-index: 24; +} + +div { + z-index: 25; +} + +div { + z-index: 26; +} + +div { + z-index: 27; +} + +div { + z-index: 28; +} + +div { + z-index: 29; +} + +div { + z-index: 30; +} + +div { + z-index: 31; +} + +div { + z-index: 32; +} + +div { + z-index: 33; +} + +div { + z-index: 34; +} + +div { + z-index: 35; +} + +div { + z-index: 36; +} + +div { + z-index: 37; +} + +div { + z-index: 38; +} + +div { + z-index: 39; +} + +div { + z-index: 40; +} + +div { + z-index: 41; +} + +div { + z-index: 42; +} + +div { + z-index: 43; +} + +div { + z-index: 44; +} + +div { + z-index: 45; +} + +div { + z-index: 46; +} + +div { + z-index: 47; +} + +div { + z-index: 48; +} + +div { + z-index: 49; +} + +div { + z-index: 50; +} + +div { + z-index: 51; +} + +div { + z-index: 52; +} + +div { + z-index: 53; +} + +div { + z-index: 54; +} + +div { + z-index: 55; +} + +div { + z-index: 56; +} + +div { + z-index: 57; +} + +div { + z-index: 58; +} + +div { + z-index: 59; +} + +div { + z-index: 60; +} + +div { + z-index: 61; +} + +div { + z-index: 62; +} + +div { + z-index: 63; +} + +div { + z-index: 64; +} + +div { + z-index: 65; +} + +div { + z-index: 66; +} + +div { + z-index: 67; +} + +div { + z-index: 68; +} + +div { + z-index: 69; +} + +div { + z-index: 70; +} + +div { + z-index: 71; +} + +div { + z-index: 72; +} + +div { + z-index: 73; +} + +div { + z-index: 74; +} + +div { + z-index: 75; +} + +div { + z-index: 76; +} + +div { + z-index: 77; +} + +div { + z-index: 78; +} + +div { + z-index: 79; +} + +div { + z-index: 80; +} + +div { + z-index: 81; +} + +div { + z-index: 82; +} + +div { + z-index: 83; +} + +div { + z-index: 84; +} + +div { + z-index: 85; +} + +div { + z-index: 86; +} + +div { + z-index: 87; +} + +div { + z-index: 88; +} + +div { + z-index: 89; +} + +div { + z-index: 90; +} + +div { + z-index: 91; +} + +div { + z-index: 92; +} + +div { + z-index: 93; +} + +div { + z-index: 94; +} + +div { + z-index: 95; +} + +div { + z-index: 96; +} + +div { + z-index: 97; +} + +div { + z-index: 98; +} + +div { + z-index: 99; +} + +div { + z-index: 100; +} diff --git a/devtools/client/styleeditor/test/doc_long_string.css b/devtools/client/styleeditor/test/doc_long_string.css new file mode 100644 index 0000000000..39a856314b --- /dev/null +++ b/devtools/client/styleeditor/test/doc_long_string.css @@ -0,0 +1,43 @@ +/* CSS file longer than the server's long string limit of 10000 bytes */ + +/* +Copyright (c) 2011, Yahoo! Inc. All rights reserved. +Code licensed under the BSD License: +http://developer.yahoo.com/yui/license.html +version: 2.9.0 +*/ +.yui-calcontainer{position:relative;float:left;_overflow:hidden}.yui-calcontainer iframe{position:absolute;border:0;margin:0;padding:0;z-index:0;width:100%;height:100%;left:0;top:0}.yui-calcontainer iframe.fixedsize{width:50em;height:50em;top:-1px;left:-1px}.yui-calcontainer.multi .groupcal{z-index:1;float:left;position:relative}.yui-calcontainer .title{position:relative;z-index:1}.yui-calcontainer .close-icon{position:absolute;z-index:1;text-indent:-10000em;overflow:hidden}.yui-calendar{position:relative}.yui-calendar .calnavleft{position:absolute;z-index:1;text-indent:-10000em;overflow:hidden}.yui-calendar .calnavright{position:absolute;z-index:1;text-indent:-10000em;overflow:hidden}.yui-calendar .calheader{position:relative;width:100%;text-align:center}.yui-calcontainer .yui-cal-nav-mask{position:absolute;z-index:2;margin:0;padding:0;width:100%;height:100%;_width:0;_height:0;left:0;top:0;display:none}.yui-calcontainer .yui-cal-nav{position:absolute;z-index:3;top:0;display:none}.yui-calcontainer .yui-cal-nav .yui-cal-nav-btn{display:-moz-inline-box;display:inline-block}.yui-calcontainer .yui-cal-nav .yui-cal-nav-btn button{display:block;*display:inline-block;*overflow:visible;border:0;background-color:transparent;cursor:pointer}.yui-calendar .calbody a:hover{background:inherit}p#clear{clear:left;padding-top:10px}.yui-skin-sam .yui-calcontainer{background-color:#f2f2f2;border:1px solid #808080;padding:10px}.yui-skin-sam .yui-calcontainer.multi{padding:0 5px 0 5px}.yui-skin-sam .yui-calcontainer.multi .groupcal{background-color:transparent;border:0;padding:10px 5px 10px 5px;margin:0}.yui-skin-sam .yui-calcontainer .title{background:url(../js/yui/assets/skins/sam/sprite.png) repeat-x 0 0;border-bottom:1px solid #ccc;font:100% sans-serif;color:#000;font-weight:bold;height:auto;padding:.4em;margin:0 -10px 10px -10px;top:0;left:0;text-align:left}.yui-skin-sam .yui-calcontainer.multi .title{margin:0 -5px 0 -5px}.yui-skin-sam .yui-calcontainer.withtitle{padding-top:0}.yui-skin-sam .yui-calcontainer .calclose{background:url(../js/yui/assets/skins/sam/sprite.png) no-repeat 0 -300px;width:25px;height:15px;top:.4em;right:.4em;cursor:pointer}.yui-skin-sam .yui-calendar{border-spacing:0;border-collapse:collapse;font:100% sans-serif;text-align:center;margin:0}.yui-skin-sam .yui-calendar .calhead{background:transparent;border:0;vertical-align:middle;padding:0}.yui-skin-sam .yui-calendar .calheader{background:transparent;font-weight:bold;padding:0 0 .6em 0;text-align:center}.yui-skin-sam .yui-calendar .calheader img{border:0}.yui-skin-sam .yui-calendar .calnavleft{background:url(../js/yui/assets/skins/sam/sprite.png) no-repeat 0 -450px;width:25px;height:15px;top:0;bottom:0;left:-10px;margin-left:.4em;cursor:pointer}.yui-skin-sam .yui-calendar .calnavright{background:url(../js/yui/assets/skins/sam/sprite.png) no-repeat 0 -500px;width:25px;height:15px;top:0;bottom:0;right:-10px;margin-right:.4em;cursor:pointer}.yui-skin-sam .yui-calendar .calweekdayrow{height:2em}.yui-skin-sam .yui-calendar .calweekdayrow th{padding:0;border:0}.yui-skin-sam .yui-calendar .calweekdaycell{color:#000;font-weight:bold;text-align:center;width:2em}.yui-skin-sam .yui-calendar .calfoot{background-color:#f2f2f2}.yui-skin-sam .yui-calendar .calrowhead,.yui-skin-sam .yui-calendar .calrowfoot{color:#a6a6a6;font-size:85%;font-style:normal;font-weight:normal;border:0}.yui-skin-sam .yui-calendar .calrowhead{text-align:right;padding:0 2px 0 0}.yui-skin-sam .yui-calendar .calrowfoot{text-align:left;padding:0 0 0 2px}.yui-skin-sam .yui-calendar td.calcell{border:1px solid #ccc;background:#fff;padding:1px;height:1.6em;line-height:1.6em;text-align:center;white-space:nowrap}.yui-skin-sam .yui-calendar td.calcell a{color:#06c;display:block;height:100%;text-decoration:none}.yui-skin-sam .yui-calendar td.calcell.today{background-color:#000}.yui-skin-sam .yui-calendar td.calcell.today a{background-color:#fff}.yui-skin-sam .yui-calendar td.calcell.oom{background-color:#ccc;color:#a6a6a6;cursor:default}.yui-skin-sam .yui-calendar td.calcell.oom a{color:#a6a6a6}.yui-skin-sam .yui-calendar td.calcell.selected{background-color:#fff;color:#000}.yui-skin-sam .yui-calendar td.calcell.selected a{background-color:#b3d4ff;color:#000}.yui-skin-sam .yui-calendar td.calcell.calcellhover{background-color:#426fd9;color:#fff;cursor:pointer}.yui-skin-sam .yui-calendar td.calcell.calcellhover a{background-color:#426fd9;color:#fff}.yui-skin-sam .yui-calendar td.calcell.previous{color:#e0e0e0}.yui-skin-sam .yui-calendar td.calcell.restricted{text-decoration:line-through}.yui-skin-sam .yui-calendar td.calcell.highlight1{background-color:#cf9}.yui-skin-sam .yui-calendar td.calcell.highlight2{background-color:#9cf}.yui-skin-sam .yui-calendar td.calcell.highlight3{background-color:#fcc}.yui-skin-sam .yui-calendar td.calcell.highlight4{background-color:#cf9}.yui-skin-sam .yui-calendar a.calnav{border:1px solid #f2f2f2;padding:0 4px;text-decoration:none;color:#000;zoom:1}.yui-skin-sam .yui-calendar a.calnav:hover{background:url(../js/yui/assets/skins/sam/sprite.png) repeat-x 0 0;border-color:#a0a0a0;cursor:pointer}.yui-skin-sam .yui-calcontainer .yui-cal-nav-mask{background-color:#000;opacity:.25;filter:alpha(opacity=25)}.yui-skin-sam .yui-calcontainer .yui-cal-nav{font-family:arial,helvetica,clean,sans-serif;font-size:93%;border:1px solid #808080;left:50%;margin-left:-7em;width:14em;padding:0;top:2.5em;background-color:#f2f2f2}.yui-skin-sam .yui-calcontainer.withtitle .yui-cal-nav{top:4.5em}.yui-skin-sam .yui-calcontainer.multi .yui-cal-nav{width:16em;margin-left:-8em}.yui-skin-sam .yui-calcontainer .yui-cal-nav-y,.yui-skin-sam .yui-calcontainer .yui-cal-nav-m,.yui-skin-sam .yui-calcontainer .yui-cal-nav-b{padding:5px 10px 5px 10px}.yui-skin-sam .yui-calcontainer .yui-cal-nav-b{text-align:center}.yui-skin-sam .yui-calcontainer .yui-cal-nav-e{margin-top:5px;padding:5px;background-color:#edf5ff;border-top:1px solid black;display:none}.yui-skin-sam .yui-calcontainer .yui-cal-nav label{display:block;font-weight:bold} +.yui-skin-sam .yui-calcontainer .yui-cal-nav-mc{width:100%;_width:auto}.yui-skin-sam .yui-calcontainer .yui-cal-nav-y input.yui-invalid{background-color:#ffee69;border:1px solid #000}.yui-skin-sam .yui-calcontainer .yui-cal-nav-yc{width:4em}.yui-skin-sam .yui-calcontainer .yui-cal-nav .yui-cal-nav-btn{border:1px solid #808080;background:url(../js/yui/assets/skins/sam/sprite.png) repeat-x 0 0;background-color:#ccc;margin:auto .15em}.yui-skin-sam .yui-calcontainer .yui-cal-nav .yui-cal-nav-btn button{padding:0 8px;font-size:93%;line-height:2;*line-height:1.7;min-height:2em;*min-height:auto;color:#000}.yui-skin-sam .yui-calcontainer .yui-cal-nav .yui-cal-nav-btn.yui-default{border:1px solid #304369;background-color:#426fd9;background:url(../js/yui/assets/skins/sam/sprite.png) repeat-x 0 -1400px}.yui-skin-sam .yui-calcontainer .yui-cal-nav .yui-cal-nav-btn.yui-default button{color:#fff} + +/* +Copyright (c) 2011, Yahoo! Inc. All rights reserved. +Code licensed under the BSD License: +http://developer.yahoo.com/yui/license.html +version: 2.9.0 +*/ +.yui-overlay,.yui-panel-container{visibility:hidden;position:absolute;z-index:2}.yui-panel{position:relative}.yui-panel-container form{margin:0}.mask{z-index:1;display:none;position:absolute;top:0;left:0;right:0;bottom:0}.mask.block-scrollbars{overflow:auto}.masked select,.drag select,.hide-select select{_visibility:hidden}.yui-panel-container select{_visibility:inherit}.hide-scrollbars,.hide-scrollbars *{overflow:hidden}.hide-scrollbars select{display:none}.show-scrollbars{overflow:auto}.yui-panel-container.show-scrollbars,.yui-tt.show-scrollbars{overflow:visible}.yui-panel-container.show-scrollbars .underlay,.yui-tt.show-scrollbars .yui-tt-shadow{overflow:auto}.yui-panel-container.shadow .underlay.yui-force-redraw{padding-bottom:1px}.yui-effect-fade .underlay,.yui-effect-fade .yui-tt-shadow{display:none}.yui-tt-shadow{position:absolute}.yui-override-padding{padding:0!important}.yui-panel-container .container-close{overflow:hidden;text-indent:-10000em;text-decoration:none}.yui-overlay.yui-force-redraw,.yui-panel-container.yui-force-redraw{margin-bottom:1px}.yui-skin-sam .mask{background-color:#000;opacity:.25;filter:alpha(opacity=25)}.yui-skin-sam .yui-panel-container{padding:0 1px;*padding:2px}.yui-skin-sam .yui-panel{position:relative;left:0;top:0;border-style:solid;border-width:1px 0;border-color:#808080;z-index:1;*border-width:1px;*zoom:1;_zoom:normal}.yui-skin-sam .yui-panel .hd,.yui-skin-sam .yui-panel .bd,.yui-skin-sam .yui-panel .ft{border-style:solid;border-width:0 1px;border-color:#808080;margin:0 -1px;*margin:0;*border:0}.yui-skin-sam .yui-panel .hd{border-bottom:solid 1px #ccc}.yui-skin-sam .yui-panel .bd,.yui-skin-sam .yui-panel .ft{background-color:#f2f2f2}.yui-skin-sam .yui-panel .hd{padding:0 10px;font-size:93%;line-height:2;*line-height:1.9;font-weight:bold;color:#000;background:url(../js/yui/assets/skins/sam/sprite.png) repeat-x 0 -200px}.yui-skin-sam .yui-panel .bd{padding:10px}.yui-skin-sam .yui-panel .ft{border-top:solid 1px #808080;padding:5px 10px;font-size:77%}.yui-skin-sam .container-close{position:absolute;top:5px;right:6px;width:25px;height:15px;background:url(../js/yui/assets/skins/sam/sprite.png) no-repeat 0 -300px;cursor:pointer}.yui-skin-sam .yui-panel-container .underlay{right:-1px;left:-1px}.yui-skin-sam .yui-panel-container.matte{padding:9px 10px;background-color:#fff}.yui-skin-sam .yui-panel-container.shadow{_padding:2px 4px 0 2px}.yui-skin-sam .yui-panel-container.shadow .underlay{position:absolute;top:2px;left:-3px;right:-3px;bottom:-3px;*top:4px;*left:-1px;*right:-1px;*bottom:-1px;_top:0;_left:0;_right:0;_bottom:0;_margin-top:3px;_margin-left:-1px;background-color:#000;opacity:.12;filter:alpha(opacity=12)}.yui-skin-sam .yui-dialog .ft{border-top:0;padding:0 10px 10px 10px;font-size:100%}.yui-skin-sam .yui-dialog .ft .button-group{display:block;text-align:right}.yui-skin-sam .yui-dialog .ft button.default{font-weight:bold}.yui-skin-sam .yui-dialog .ft span.default{border-color:#304369;background-position:0 -1400px}.yui-skin-sam .yui-dialog .ft span.default .first-child{border-color:#304369}.yui-skin-sam .yui-dialog .ft span.default button{color:#fff}.yui-skin-sam .yui-dialog .ft span.yui-button-disabled{background-position:0 -1500px;border-color:#ccc}.yui-skin-sam .yui-dialog .ft span.yui-button-disabled .first-child{border-color:#ccc}.yui-skin-sam .yui-dialog .ft span.yui-button-disabled button{color:#a6a6a6}.yui-skin-sam .yui-simple-dialog .bd .yui-icon{background:url(../js/yui/assets/skins/sam/sprite.png) no-repeat 0 0;width:16px;height:16px;margin-right:10px;float:left}.yui-skin-sam .yui-simple-dialog .bd span.blckicon{background-position:0 -1100px}.yui-skin-sam .yui-simple-dialog .bd span.alrticon{background-position:0 -1050px}.yui-skin-sam .yui-simple-dialog .bd span.hlpicon{background-position:0 -1150px}.yui-skin-sam .yui-simple-dialog .bd span.infoicon{background-position:0 -1200px}.yui-skin-sam .yui-simple-dialog .bd span.warnicon{background-position:0 -1900px}.yui-skin-sam .yui-simple-dialog .bd span.tipicon{background-position:0 -1250px}.yui-skin-sam .yui-tt .bd{position:relative;top:0;left:0;z-index:1;color:#000;padding:2px 5px;border-color:#d4c237 #A6982b #a6982b #A6982B;border-width:1px;border-style:solid;background-color:#ffee69}.yui-skin-sam .yui-tt.show-scrollbars .bd{overflow:auto}.yui-skin-sam .yui-tt-shadow{top:2px;right:-3px;left:-3px;bottom:-3px;background-color:#000}.yui-skin-sam .yui-tt-shadow-visible{opacity:.12;filter:alpha(opacity=12)} + +/* +Copyright (c) 2011, Yahoo! Inc. All rights reserved. +Code licensed under the BSD License: +http://developer.yahoo.com/yui/license.html +version: 2.9.0 +*/ +.yui-skin-sam .yui-dt-mask{position:absolute;z-index:9500}.yui-dt-tmp{position:absolute;left:-9000px}.yui-dt-scrollable .yui-dt-bd{overflow:auto}.yui-dt-scrollable .yui-dt-hd{overflow:hidden;position:relative}.yui-dt-scrollable .yui-dt-bd thead tr,.yui-dt-scrollable .yui-dt-bd thead th{position:absolute;left:-1500px}.yui-dt-scrollable tbody{-moz-outline:0}.yui-skin-sam thead .yui-dt-sortable{cursor:pointer}.yui-skin-sam thead .yui-dt-draggable{cursor:move}.yui-dt-coltarget{position:absolute;z-index:999}.yui-dt-hd{zoom:1}th.yui-dt-resizeable .yui-dt-resizerliner{position:relative}.yui-dt-resizer{position:absolute;right:0;bottom:0;height:100%;cursor:e-resize;cursor:col-resize;background-color:#CCC;opacity:0;filter:alpha(opacity=0)}.yui-dt-resizerproxy{visibility:hidden;position:absolute;z-index:9000;background-color:#CCC;opacity:0;filter:alpha(opacity=0)}th.yui-dt-hidden .yui-dt-liner,td.yui-dt-hidden .yui-dt-liner,th.yui-dt-hidden .yui-dt-resizer{display:none}.yui-dt-editor,.yui-dt-editor-shim{position:absolute;z-index:9000}.yui-skin-sam .yui-dt table{margin:0;padding:0;font-family:arial;font-size:inherit;border-collapse:separate;*border-collapse:collapse;border-spacing:0;border:1px solid #7f7f7f}.yui-skin-sam .yui-dt thead{border-spacing:0}.yui-skin-sam .yui-dt caption{color:#000;font-size:85%;font-weight:normal;font-style:italic;line-height:1;padding:1em 0;text-align:center}.yui-skin-sam .yui-dt th{background:#d8d8da url(../js/yui/assets/skins/sam/sprite.png) repeat-x 0 0}.yui-skin-sam .yui-dt th,.yui-skin-sam .yui-dt th a{font-weight:normal;text-decoration:none;color:#000;vertical-align:bottom}.yui-skin-sam .yui-dt th{margin:0;padding:0;border:0;border-right:1px solid #cbcbcb}.yui-skin-sam .yui-dt tr.yui-dt-first td{border-top:1px solid #7f7f7f}.yui-skin-sam .yui-dt th .yui-dt-liner{white-space:nowrap}.yui-skin-sam .yui-dt-liner{margin:0;padding:0;padding:4px 10px 4px 10px}.yui-skin-sam .yui-dt-coltarget{width:5px;background-color:red}.yui-skin-sam .yui-dt td{margin:0;padding:0;border:0;border-right:1px solid #cbcbcb;text-align:left}.yui-skin-sam .yui-dt-list td{border-right:0}.yui-skin-sam .yui-dt-resizer{width:6px}.yui-skin-sam .yui-dt-mask{background-color:#000;opacity:.25;filter:alpha(opacity=25)}.yui-skin-sam .yui-dt-message{background-color:#FFF}.yui-skin-sam .yui-dt-scrollable table{border:0}.yui-skin-sam .yui-dt-scrollable .yui-dt-hd{border-left:1px solid #7f7f7f;border-top:1px solid #7f7f7f;border-right:1px solid #7f7f7f}.yui-skin-sam .yui-dt-scrollable .yui-dt-bd{border-left:1px solid #7f7f7f;border-bottom:1px solid #7f7f7f;border-right:1px solid #7f7f7f;background-color:#FFF}.yui-skin-sam .yui-dt-scrollable .yui-dt-data tr.yui-dt-last td{border-bottom:1px solid #7f7f7f}.yui-skin-sam th.yui-dt-asc,.yui-skin-sam th.yui-dt-desc{background:url(../js/yui/assets/skins/sam/sprite.png) repeat-x 0 -100px}.yui-skin-sam th.yui-dt-sortable .yui-dt-label{margin-right:10px}.yui-skin-sam th.yui-dt-asc .yui-dt-liner{background:url(../js/yui/assets/skins/sam/dt-arrow-up.png) no-repeat right}.yui-skin-sam th.yui-dt-desc .yui-dt-liner{background:url(../js/yui/assets/skins/sam/dt-arrow-dn.png) no-repeat right}tbody .yui-dt-editable{cursor:pointer}.yui-dt-editor{text-align:left;background-color:#f2f2f2;border:1px solid #808080;padding:6px}.yui-dt-editor label{padding-left:4px;padding-right:6px}.yui-dt-editor .yui-dt-button{padding-top:6px;text-align:right}.yui-dt-editor .yui-dt-button button{background:url(../js/yui/assets/skins/sam/sprite.png) repeat-x 0 0;border:1px solid #999;width:4em;height:1.8em;margin-left:6px}.yui-dt-editor .yui-dt-button button.yui-dt-default{background:url(../js/yui/assets/skins/sam/sprite.png) repeat-x 0 -1400px;background-color:#5584e0;border:1px solid #304369;color:#FFF}.yui-dt-editor .yui-dt-button button:hover{background:url(../js/yui/assets/skins/sam/sprite.png) repeat-x 0 -1300px;color:#000}.yui-dt-editor .yui-dt-button button:active{background:url(../js/yui/assets/skins/sam/sprite.png) repeat-x 0 -1700px;color:#000}.yui-skin-sam tr.yui-dt-even{background-color:#FFF}.yui-skin-sam tr.yui-dt-odd{background-color:#edf5ff}.yui-skin-sam tr.yui-dt-even td.yui-dt-asc,.yui-skin-sam tr.yui-dt-even td.yui-dt-desc{background-color:#edf5ff}.yui-skin-sam tr.yui-dt-odd td.yui-dt-asc,.yui-skin-sam tr.yui-dt-odd td.yui-dt-desc{background-color:#dbeaff}.yui-skin-sam .yui-dt-list tr.yui-dt-even{background-color:#FFF}.yui-skin-sam .yui-dt-list tr.yui-dt-odd{background-color:#FFF}.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-asc,.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-desc{background-color:#edf5ff}.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-asc,.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-desc{background-color:#edf5ff}.yui-skin-sam th.yui-dt-highlighted,.yui-skin-sam th.yui-dt-highlighted a{background-color:#b2d2ff}.yui-skin-sam tr.yui-dt-highlighted,.yui-skin-sam tr.yui-dt-highlighted td.yui-dt-asc,.yui-skin-sam tr.yui-dt-highlighted td.yui-dt-desc,.yui-skin-sam tr.yui-dt-even td.yui-dt-highlighted,.yui-skin-sam tr.yui-dt-odd td.yui-dt-highlighted{cursor:pointer;background-color:#b2d2ff}.yui-skin-sam .yui-dt-list th.yui-dt-highlighted,.yui-skin-sam .yui-dt-list th.yui-dt-highlighted a{background-color:#b2d2ff}.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted,.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-asc,.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-desc,.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-highlighted,.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-highlighted{cursor:pointer;background-color:#b2d2ff}.yui-skin-sam th.yui-dt-selected,.yui-skin-sam th.yui-dt-selected a{background-color:#446cd7}.yui-skin-sam tr.yui-dt-selected td,.yui-skin-sam tr.yui-dt-selected td.yui-dt-asc,.yui-skin-sam tr.yui-dt-selected td.yui-dt-desc{background-color:#426fd9;color:#FFF}.yui-skin-sam tr.yui-dt-even td.yui-dt-selected,.yui-skin-sam tr.yui-dt-odd td.yui-dt-selected{background-color:#446cd7;color:#FFF}.yui-skin-sam .yui-dt-list th.yui-dt-selected,.yui-skin-sam .yui-dt-list th.yui-dt-selected a{background-color:#446cd7} +.yui-skin-sam .yui-dt-list tr.yui-dt-selected td,.yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-asc,.yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-desc{background-color:#426fd9;color:#FFF}.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-selected,.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-selected{background-color:#446cd7;color:#FFF}.yui-skin-sam .yui-dt-paginator{display:block;margin:6px 0;white-space:nowrap}.yui-skin-sam .yui-dt-paginator .yui-dt-first,.yui-skin-sam .yui-dt-paginator .yui-dt-last,.yui-skin-sam .yui-dt-paginator .yui-dt-selected{padding:2px 6px}.yui-skin-sam .yui-dt-paginator a.yui-dt-first,.yui-skin-sam .yui-dt-paginator a.yui-dt-last{text-decoration:none}.yui-skin-sam .yui-dt-paginator .yui-dt-previous,.yui-skin-sam .yui-dt-paginator .yui-dt-next{display:none}.yui-skin-sam a.yui-dt-page{border:1px solid #cbcbcb;padding:2px 6px;text-decoration:none;background-color:#fff}.yui-skin-sam .yui-dt-selected{border:1px solid #fff;background-color:#fff} + +/* +Copyright (c) 2011, Yahoo! Inc. All rights reserved. +Code licensed under the BSD License: +http://developer.yahoo.com/yui/license.html +version: 2.9.0 +*/ +.yui-button{display:-moz-inline-box;display:inline-block;vertical-align:text-bottom;}.yui-button .first-child{display:block;*display:inline-block;}.yui-button button,.yui-button a{display:block;*display:inline-block;border:none;margin:0;}.yui-button button{background-color:transparent;*overflow:visible;cursor:pointer;}.yui-button a{text-decoration:none;}.yui-skin-sam .yui-button{border-width:1px 0;border-style:solid;border-color:#808080;background:url(../js/yui/assets/skins/sam/sprite.png) repeat-x 0 0;margin:auto .25em;}.yui-skin-sam .yui-button .first-child{border-width:0 1px;border-style:solid;border-color:#808080;margin:0 -1px;_margin:0;}.yui-skin-sam .yui-button button,.yui-skin-sam .yui-button a,.yui-skin-sam .yui-button a:visited{padding:0 10px;font-size:93%;line-height:2;*line-height:1.7;min-height:2em;*min-height:auto;color:#000;}.yui-skin-sam .yui-button a{*line-height:1.875;*padding-bottom:1px;}.yui-skin-sam .yui-split-button button,.yui-skin-sam .yui-menu-button button{padding-right:20px;background-position:right center;background-repeat:no-repeat;}.yui-skin-sam .yui-menu-button button{background-image:url(../js/yui/assets/skins/sam/menu-button-arrow.png);}.yui-skin-sam .yui-split-button button{background-image:url(../js/yui/assets/skins/sam/split-button-arrow.png);}.yui-skin-sam .yui-button-focus{border-color:#7D98B8;background-position:0 -1300px;}.yui-skin-sam .yui-button-focus .first-child{border-color:#7D98B8;}.yui-skin-sam .yui-split-button-focus button{background-image:url(../js/yui/assets/skins/sam/split-button-arrow-focus.png);}.yui-skin-sam .yui-button-hover{border-color:#7D98B8;background-position:0 -1300px;}.yui-skin-sam .yui-button-hover .first-child{border-color:#7D98B8;}.yui-skin-sam .yui-split-button-hover button{background-image:url(../js/yui/assets/skins/sam/split-button-arrow-hover.png);}.yui-skin-sam .yui-button-active{border-color:#7D98B8;background-position:0 -1700px;}.yui-skin-sam .yui-button-active .first-child{border-color:#7D98B8;}.yui-skin-sam .yui-split-button-activeoption{border-color:#808080;background-position:0 0;}.yui-skin-sam .yui-split-button-activeoption .first-child{border-color:#808080;}.yui-skin-sam .yui-split-button-activeoption button{background-image:url(../js/yui/assets/skins/sam/split-button-arrow-active.png);}.yui-skin-sam .yui-radio-button-checked,.yui-skin-sam .yui-checkbox-button-checked{border-color:#304369;background-position:0 -1400px;}.yui-skin-sam .yui-radio-button-checked .first-child,.yui-skin-sam .yui-checkbox-button-checked .first-child{border-color:#304369;}.yui-skin-sam .yui-radio-button-checked button,.yui-skin-sam .yui-checkbox-button-checked button{color:#fff;}.yui-skin-sam .yui-button-disabled{border-color:#ccc;background-position:0 -1500px;}.yui-skin-sam .yui-button-disabled .first-child{border-color:#ccc;}.yui-skin-sam .yui-button-disabled button,.yui-skin-sam .yui-button-disabled a,.yui-skin-sam .yui-button-disabled a:visited{color:#A6A6A6;cursor:default;}.yui-skin-sam .yui-menu-button-disabled button{background-image:url(../js/yui/assets/skins/sam/menu-button-arrow-disabled.png);}.yui-skin-sam .yui-split-button-disabled button{background-image:url(../js/yui/assets/skins/sam/split-button-arrow-disabled.png);} + +/* +Copyright (c) 2011, Yahoo! Inc. All rights reserved. +Code licensed under the BSD License: +http://developer.yahoo.com/yui/license.html +version: 2.9.0 +*/ +.yui-skin-sam .yui-pg-container{display:block;margin:6px 0;white-space:nowrap}.yui-skin-sam .yui-pg-first,.yui-skin-sam .yui-pg-previous,.yui-skin-sam .yui-pg-next,.yui-skin-sam .yui-pg-last,.yui-skin-sam .yui-pg-current,.yui-skin-sam .yui-pg-pages,.yui-skin-sam .yui-pg-page{display:inline-block;font-family:arial,helvetica,clean,sans-serif;padding:3px 6px;zoom:1}.yui-skin-sam .yui-pg-pages{padding:0}.yui-skin-sam .yui-pg-current{padding:3px 0}.yui-skin-sam a.yui-pg-first:link,.yui-skin-sam a.yui-pg-first:visited,.yui-skin-sam a.yui-pg-first:active,.yui-skin-sam a.yui-pg-first:hover,.yui-skin-sam a.yui-pg-previous:link,.yui-skin-sam a.yui-pg-previous:visited,.yui-skin-sam a.yui-pg-previous:active,.yui-skin-sam a.yui-pg-previous:hover,.yui-skin-sam a.yui-pg-next:link,.yui-skin-sam a.yui-pg-next:visited,.yui-skin-sam a.yui-pg-next:active,.yui-skin-sam a.yui-pg-next:hover,.yui-skin-sam a.yui-pg-last:link,.yui-skin-sam a.yui-pg-last:visited,.yui-skin-sam a.yui-pg-last:active,.yui-skin-sam a.yui-pg-last:hover,.yui-skin-sam a.yui-pg-page:link,.yui-skin-sam a.yui-pg-page:visited,.yui-skin-sam a.yui-pg-page:active,.yui-skin-sam a.yui-pg-page:hover{color:#06c;text-decoration:underline;outline:0}.yui-skin-sam span.yui-pg-first,.yui-skin-sam span.yui-pg-previous,.yui-skin-sam span.yui-pg-next,.yui-skin-sam span.yui-pg-last{color:#a6a6a6}.yui-skin-sam .yui-pg-page{background-color:#fff;border:1px solid #cbcbcb;padding:2px 6px;text-decoration:none}.yui-skin-sam .yui-pg-current-page{background-color:transparent;border:0;font-weight:bold;padding:3px 6px}.yui-skin-sam .yui-pg-page{margin-left:1px;margin-right:1px}.yui-skin-sam .yui-pg-first,.yui-skin-sam .yui-pg-previous{padding-left:0}.yui-skin-sam .yui-pg-next,.yui-skin-sam .yui-pg-last{padding-right:0}.yui-skin-sam .yui-pg-current,.yui-skin-sam .yui-pg-rpp-options{margin-left:1em;margin-right:1em} diff --git a/devtools/client/styleeditor/test/doc_short_string.css b/devtools/client/styleeditor/test/doc_short_string.css new file mode 100644 index 0000000000..585dccea1a --- /dev/null +++ b/devtools/client/styleeditor/test/doc_short_string.css @@ -0,0 +1,15 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* ☺ */ + +body { + background: white; +} + +div { + font-size: 4em; +} + +div > span { + text-decoration: underline; +} diff --git a/devtools/client/styleeditor/test/doc_sourcemap_chrome.html b/devtools/client/styleeditor/test/doc_sourcemap_chrome.html new file mode 100644 index 0000000000..5052897708 --- /dev/null +++ b/devtools/client/styleeditor/test/doc_sourcemap_chrome.html @@ -0,0 +1,11 @@ +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <title>Testcase for sourcemap URLs pointing to unsupported protocols</title> + <link rel="stylesheet" type="text/css" href="sourcemap-css/sourcemaps_chrome.css"/> +</head> +<body> + <div>Protocol test</div> +</body> +</html> diff --git a/devtools/client/styleeditor/test/doc_xulpage.xhtml b/devtools/client/styleeditor/test/doc_xulpage.xhtml new file mode 100644 index 0000000000..41385afa4a --- /dev/null +++ b/devtools/client/styleeditor/test/doc_xulpage.xhtml @@ -0,0 +1,7 @@ +<?xml version="1.0"?> +<?xml-stylesheet href="simple.css" type="text/css"?> +<!DOCTYPE window> +<window xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <label value="Simple XUL document" /> +</window> diff --git a/devtools/client/styleeditor/test/four.html b/devtools/client/styleeditor/test/four.html new file mode 100644 index 0000000000..c0d51d691c --- /dev/null +++ b/devtools/client/styleeditor/test/four.html @@ -0,0 +1,25 @@ +<!doctype html> +<html> +<head> + <title>four stylesheets</title> + <link rel="stylesheet" type="text/css" media="scren" href="simple.css"/> + <style type="text/css"> + div { + font-size: 2em; + } + </style> + <style type="text/css"> + span { + font-size: 3em; + } + </style> + <style type="text/css"> + p { + font-size: 4em; + } + </style> +</head> +<body> + <div>four <span>stylesheets</span></div> +</body> +</html> diff --git a/devtools/client/styleeditor/test/head.js b/devtools/client/styleeditor/test/head.js new file mode 100644 index 0000000000..f685eca9bb --- /dev/null +++ b/devtools/client/styleeditor/test/head.js @@ -0,0 +1,201 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/* All top-level definitions here are exports. */ +/* eslint no-unused-vars: [2, {"vars": "local"}] */ + +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/devtools/client/inspector/shared/test/head.js", + this +); + +const TEST_BASE = + "chrome://mochitests/content/browser/devtools/client/styleeditor/test/"; +const TEST_BASE_HTTP = + "http://example.com/browser/devtools/client/styleeditor/test/"; +const TEST_BASE_HTTPS = + "https://example.com/browser/devtools/client/styleeditor/test/"; +const TEST_HOST = "mochi.test:8888"; + +/** + * Add a new test tab in the browser and load the given url. + * @param {String} url The url to be loaded in the new tab + * @param {Window} win The window to add the tab to (default: current window). + * @return a promise that resolves to the tab object when the url is loaded + */ +var addTab = function (url, win) { + info("Adding a new tab with URL: '" + url + "'"); + + return new Promise(resolve => { + const targetWindow = win || window; + const targetBrowser = targetWindow.gBrowser; + + const tab = (targetBrowser.selectedTab = BrowserTestUtils.addTab( + targetBrowser, + url + )); + BrowserTestUtils.browserLoaded(targetBrowser.selectedBrowser).then( + function () { + info("URL '" + url + "' loading complete"); + resolve(tab); + } + ); + }); +}; + +var navigateToAndWaitForStyleSheets = async function (url, ui, editorCount) { + const onClear = ui.once("stylesheets-clear"); + await navigateTo(url); + await onClear; + await waitUntil(() => ui.editors.length === editorCount); +}; + +var reloadPageAndWaitForStyleSheets = async function (ui, editorCount) { + info("Reloading the page."); + + const onClear = ui.once("stylesheets-clear"); + let count = 0; + const onAllEditorAdded = new Promise(res => { + const off = ui.on("editor-added", editor => { + count++; + info(`Received ${editor.friendlyName} (${count}/${editorCount})`); + if (count == editorCount) { + res(); + off(); + } + }); + }); + + await reloadBrowser(); + await onClear; + + await onAllEditorAdded; + info("All expected editors added"); +}; + +/** + * Open the style editor for the current tab. + */ +var openStyleEditor = async function (tab) { + if (!tab) { + tab = gBrowser.selectedTab; + } + const toolbox = await gDevTools.showToolboxForTab(tab, { + toolId: "styleeditor", + }); + const panel = toolbox.getPanel("styleeditor"); + const ui = panel.UI; + + return { toolbox, panel, ui }; +}; + +/** + * Creates a new tab in specified window navigates it to the given URL and + * opens style editor in it. + */ +var openStyleEditorForURL = async function (url, win) { + const tab = await addTab(url, win); + const result = await openStyleEditor(tab); + result.tab = tab; + return result; +}; + +/** + * Send an async message to the frame script and get back the requested + * computed style property. + * + * @param {String} selector + * The selector used to obtain the element. + * @param {String} pseudo + * pseudo id to query, or null. + * @param {String} name + * name of the property. + */ +var getComputedStyleProperty = async function (args) { + return SpecialPowers.spawn( + gBrowser.selectedBrowser, + [args], + function ({ selector, pseudo, name }) { + const element = content.document.querySelector(selector); + const style = content.getComputedStyle(element, pseudo); + return style.getPropertyValue(name); + } + ); +}; + +/** + * Wait for "at-rules-list-changed" events to settle on StyleEditorUI. + * Returns a promise that resolves the number of events caught while waiting. + * + * @param {StyleEditorUI} ui + * Current StyleEditorUI on which at-rules-list-changed events should be fired. + * @param {Number} delay + */ +function waitForManyEvents(ui, delay) { + return new Promise(resolve => { + let timer; + let count = 0; + const onEvent = () => { + count++; + clearTimeout(timer); + + // Wait for some time to catch subsequent events. + timer = setTimeout(() => { + // Remove the listener and resolve. + ui.off("at-rules-list-changed", onEvent); + resolve(count); + }, delay); + }; + ui.on("at-rules-list-changed", onEvent); + }); +} + +/** + * Creates a new style sheet in the Style Editor + + * @param {StyleEditorUI} ui + * Current StyleEditorUI on which to simulate pressing the + button. + * @param {Window} panelWindow + * The panelWindow property of the current Style Editor panel. + */ +function createNewStyleSheet(ui, panelWindow) { + info("Creating a new stylesheet now"); + + return new Promise(resolve => { + ui.once("editor-added", editor => { + editor.getSourceEditor().then(resolve); + }); + + waitForFocus(function () { + // create a new style sheet + const newButton = panelWindow.document.querySelector( + ".style-editor-newButton" + ); + ok(newButton, "'new' button exists"); + + EventUtils.synthesizeMouseAtCenter(newButton, {}, panelWindow); + }, panelWindow); + }); +} + +/** + * Returns the panel root element (StyleEditorUI._root) + * + * @param {StyleEditorPanel} panel + * @returns {Element} + */ +function getRootElement(panel) { + return panel.panelWindow.document.getElementById("style-editor-chrome"); +} + +/** + * Returns the panel context menu element + * + * @param {StyleEditorPanel} panel + * @returns {Element} + */ +function getContextMenuElement(panel) { + return panel.panelWindow.document.getElementById("sidebar-context"); +} diff --git a/devtools/client/styleeditor/test/iframe_service_worker.js b/devtools/client/styleeditor/test/iframe_service_worker.js new file mode 100644 index 0000000000..56c32c204a --- /dev/null +++ b/devtools/client/styleeditor/test/iframe_service_worker.js @@ -0,0 +1,12 @@ +"use strict"; + +self.onfetch = function (event) { + if (event.request.url.includes("sheet.css")) { + return event.respondWith(new Response("* { color: green; }")); + } + return null; +}; + +self.onactivate = function (event) { + event.waitUntil(self.clients.claim()); +}; diff --git a/devtools/client/styleeditor/test/iframe_with_service_worker.html b/devtools/client/styleeditor/test/iframe_with_service_worker.html new file mode 100644 index 0000000000..690515775e --- /dev/null +++ b/devtools/client/styleeditor/test/iframe_with_service_worker.html @@ -0,0 +1,33 @@ +<!DOCTYPE html> +<meta charset="utf-8"> + +Iframe loading a stylesheet via a service worker +<script> +"use strict"; + +function waitForActive(swr) { + const sw = swr.installing || swr.waiting || swr.active; + return new Promise(resolve => { + if (sw.state === "activated") { + resolve(swr); + return; + } + sw.addEventListener("statechange", function onStateChange(evt) { + if (sw.state === "activated") { + sw.removeEventListener("statechange", onStateChange); + resolve(swr); + } + }); + }); +} + +navigator.serviceWorker.register("iframe_service_worker.js", {scope: "."}) + .then(registration => waitForActive(registration)) + .then(() => { + const link = document.createElement("link"); + link.setAttribute("rel", "stylesheet"); + link.setAttribute("type", "text/css"); + link.setAttribute("href", "sheet.css"); + document.documentElement.appendChild(link); + }); +</script> diff --git a/devtools/client/styleeditor/test/import.css b/devtools/client/styleeditor/test/import.css new file mode 100644 index 0000000000..485688c0e6 --- /dev/null +++ b/devtools/client/styleeditor/test/import.css @@ -0,0 +1,8 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +@import url(import2.css); + +body { + margin: 0; +} diff --git a/devtools/client/styleeditor/test/import.html b/devtools/client/styleeditor/test/import.html new file mode 100644 index 0000000000..bc92baeba0 --- /dev/null +++ b/devtools/client/styleeditor/test/import.html @@ -0,0 +1,11 @@ +<!doctype html> +<html> +<head> + <title>import testcase</title> + <link rel="stylesheet" charset="UTF-8" type="text/css" media="screen" href="simple.css"/> + <link rel="stylesheet" charset="UTF-8" type="text/css" media="screen" href="import.css"/> +</head> +<body> + <div>import <span>testcase</span></div> +</body> +</html> diff --git a/devtools/client/styleeditor/test/import2.css b/devtools/client/styleeditor/test/import2.css new file mode 100644 index 0000000000..6037d4b10c --- /dev/null +++ b/devtools/client/styleeditor/test/import2.css @@ -0,0 +1,8 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +@import url(import.css); + +p { + padding: 5px; +} diff --git a/devtools/client/styleeditor/test/inline-1.html b/devtools/client/styleeditor/test/inline-1.html new file mode 100644 index 0000000000..76478893b2 --- /dev/null +++ b/devtools/client/styleeditor/test/inline-1.html @@ -0,0 +1,19 @@ +<!doctype html> +<html> + <head> + <title>Inline test page #1</title> + <style type="text/css"> + .second { + font-size:2em; + } + </style> + <style type="text/css"> + .first { + font-size:3em; + } + </style> + </head> + <body class="first"> + Inline test page #1 + </body> +</html> diff --git a/devtools/client/styleeditor/test/inline-2.html b/devtools/client/styleeditor/test/inline-2.html new file mode 100644 index 0000000000..e25285c31e --- /dev/null +++ b/devtools/client/styleeditor/test/inline-2.html @@ -0,0 +1,19 @@ +<!doctype html> +<html> + <head> + <title>Inline test page #2</title> + <style type="text/css"> + .second { + font-size:2em; + } + </style> + <style type="text/css"> + .first { + font-size:3em; + } + </style> + </head> + <body class="second"> + Inline test page #2 + </body> +</html> diff --git a/devtools/client/styleeditor/test/longload.html b/devtools/client/styleeditor/test/longload.html new file mode 100644 index 0000000000..30a3802798 --- /dev/null +++ b/devtools/client/styleeditor/test/longload.html @@ -0,0 +1,29 @@ +<!doctype html> +<html> +<head> + <title>Long load</title> + <link rel="stylesheet" charset="UTF-8" type="text/css" media="screen" href="simple.css"/> + <style type="text/css"> + body { + background: white; + } + + div { + font-size: 4em; + } + + div > span { + text-decoration: underline; + } + </style> +</head> +<body> + Time passes: + <script type="application/javascript"> + "use strict"; + for (let i = 0; i < 5000; i++) { + document.write("<br>..."); + } + </script> +</body> +</html> diff --git a/devtools/client/styleeditor/test/longname.html b/devtools/client/styleeditor/test/longname.html new file mode 100644 index 0000000000..111e67a2b9 --- /dev/null +++ b/devtools/client/styleeditor/test/longname.html @@ -0,0 +1,12 @@ +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <title>long name testcase</title> + <link rel="stylesheet" charset="UTF-8" type="text/css" media="screen" href="simple.css"/> + <link rel="stylesheet" charset="UTF-8" type="text/css" media="screen" href="veryveryverylongnamethatcanbreakthestyleeditor.css"/> +</head> +<body> + <div>long name testcase</div> +</body> +</html> diff --git a/devtools/client/styleeditor/test/many-media-rules-sourcemaps/index.html b/devtools/client/styleeditor/test/many-media-rules-sourcemaps/index.html new file mode 100644 index 0000000000..9734231b99 --- /dev/null +++ b/devtools/client/styleeditor/test/many-media-rules-sourcemaps/index.html @@ -0,0 +1,11 @@ +<!DOCTYPE html> +<html> +<head> + <title></title> + <link rel="stylesheet" type="text/css" href="sourcemap/sourcemap-css/sourcemaps.css"/> +</head> + +<body> + +</body> +</html> diff --git a/devtools/client/styleeditor/test/many-media-rules-sourcemaps/sourcemap/sourcemap-css/sourcemaps.css b/devtools/client/styleeditor/test/many-media-rules-sourcemaps/sourcemap/sourcemap-css/sourcemaps.css new file mode 100644 index 0000000000..edb7860289 --- /dev/null +++ b/devtools/client/styleeditor/test/many-media-rules-sourcemaps/sourcemap/sourcemap-css/sourcemaps.css @@ -0,0 +1,201 @@ +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: green; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +@media (min-width: 500px) { + div { + color: red; } } + +/*# sourceMappingURL=sourcemaps.css.map */ diff --git a/devtools/client/styleeditor/test/many-media-rules-sourcemaps/sourcemap/sourcemap-css/sourcemaps.css.map b/devtools/client/styleeditor/test/many-media-rules-sourcemaps/sourcemap/sourcemap-css/sourcemaps.css.map new file mode 100644 index 0000000000..f792f65c4e --- /dev/null +++ b/devtools/client/styleeditor/test/many-media-rules-sourcemaps/sourcemap/sourcemap-css/sourcemaps.css.map @@ -0,0 +1,10 @@ +{ + "version": 3, + "file": "sourcemaps.css", + "sources": [ + "../sourcemap-sass/sourcemaps.scss", + "../sourcemap-sass/_partial.scss" + ], + "names": [], + "mappings": "ACAA,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;AACnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,KAAK,GAAI;;ADtBnD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI;;AACjD,MAAM,EAAE,SAAS,EAAE,KAAK;EAAK,AAAA,GAAG,CAAC;IAAE,KAAK,EAAE,GAAG,GAAI" +}
\ No newline at end of file diff --git a/devtools/client/styleeditor/test/many-media-rules-sourcemaps/sourcemap/sourcemap-sass/_partial.scss b/devtools/client/styleeditor/test/many-media-rules-sourcemaps/sourcemap/sourcemap-sass/_partial.scss new file mode 100644 index 0000000000..7291ae831b --- /dev/null +++ b/devtools/client/styleeditor/test/many-media-rules-sourcemaps/sourcemap/sourcemap-sass/_partial.scss @@ -0,0 +1,25 @@ +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } +@media (min-width: 500px) { div { color: green; } } diff --git a/devtools/client/styleeditor/test/many-media-rules-sourcemaps/sourcemap/sourcemap-sass/sourcemaps.scss b/devtools/client/styleeditor/test/many-media-rules-sourcemaps/sourcemap/sourcemap-sass/sourcemaps.scss new file mode 100644 index 0000000000..a8c25f0d85 --- /dev/null +++ b/devtools/client/styleeditor/test/many-media-rules-sourcemaps/sourcemap/sourcemap-sass/sourcemaps.scss @@ -0,0 +1,27 @@ +@import 'partial'; + +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } +@media (min-width: 500px) { div { color: red; } } diff --git a/devtools/client/styleeditor/test/media-rules-sourcemaps.html b/devtools/client/styleeditor/test/media-rules-sourcemaps.html new file mode 100644 index 0000000000..ba18c35503 --- /dev/null +++ b/devtools/client/styleeditor/test/media-rules-sourcemaps.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <link rel="stylesheet" type="text/css" href="sourcemap-css/media-rules.css" +</head> +<body> + <div> + Testing style editor media sidebar with source maps + </div> +</body> +</html> diff --git a/devtools/client/styleeditor/test/media-rules.css b/devtools/client/styleeditor/test/media-rules.css new file mode 100644 index 0000000000..1adce84c4b --- /dev/null +++ b/devtools/client/styleeditor/test/media-rules.css @@ -0,0 +1,29 @@ +@media not all { + div { + color: blue; + } +} + +@media all { + div { + color: red; + } +} + +div { + width: 20px; + height: 20px; + background-color: ghostwhite; +} + +@media (max-width: 550px) { + div { + color: green; + } +} + +@media (min-height: 300px) and (max-height: 320px) { + div { + color: orange; + } +} diff --git a/devtools/client/styleeditor/test/media-rules.html b/devtools/client/styleeditor/test/media-rules.html new file mode 100644 index 0000000000..f170e1efad --- /dev/null +++ b/devtools/client/styleeditor/test/media-rules.html @@ -0,0 +1,37 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="UTF-8"> + <link rel="stylesheet" href="simple.css"/> + <link rel="stylesheet" href="media-rules.css"/> + <!-- This stylesheet is needed to ensure we cover the fix for Bug 1779043 --> + <style> + div { + color: salmon; + } + </style> + <style> + @media screen { + div { + outline: 1px solid tomato; + } + + @supports (display: flex) { + @media (1px < height < 10000px) { + div { + display: flex; + } + } + } + } + + @layer myLayer {} + @container (min-width: 1px) {} + </style> +</head> +<body> + <div> + Testing style editor media sidebar + </div> +</body> +</html> diff --git a/devtools/client/styleeditor/test/media-small.css b/devtools/client/styleeditor/test/media-small.css new file mode 100644 index 0000000000..ea21db4447 --- /dev/null +++ b/devtools/client/styleeditor/test/media-small.css @@ -0,0 +1,4 @@ +/* this stylesheet applies when min-width<400px */ +body { + background: red; +} diff --git a/devtools/client/styleeditor/test/media.html b/devtools/client/styleeditor/test/media.html new file mode 100644 index 0000000000..81222edb8e --- /dev/null +++ b/devtools/client/styleeditor/test/media.html @@ -0,0 +1,10 @@ +<!doctype html> +<html> +<head> + <link rel="stylesheet" type="text/css" href="simple.css" media="screen,print"/> + <link rel="stylesheet" type="text/css" href="media-small.css" media="screen and (min-width: 200px)"/> +</head> +<body> + <div>test for media labels</div> +</body> +</html> diff --git a/devtools/client/styleeditor/test/minified.html b/devtools/client/styleeditor/test/minified.html new file mode 100644 index 0000000000..ab8c67d256 --- /dev/null +++ b/devtools/client/styleeditor/test/minified.html @@ -0,0 +1,15 @@ +<!doctype html> +<html> +<head> + <title>minified testcase</title> + <link rel="stylesheet" href="pretty.css"/> + <style type="text/css">body { background: red; } +div { +font-size: 5em; +color: red +}</style> +</head> +<body> + <div>minified <span>testcase</span></div> +</body> +</html> diff --git a/devtools/client/styleeditor/test/missing.html b/devtools/client/styleeditor/test/missing.html new file mode 100644 index 0000000000..ce4ec08be8 --- /dev/null +++ b/devtools/client/styleeditor/test/missing.html @@ -0,0 +1,11 @@ +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <title>missing stylesheet testcase</title> + <link rel="stylesheet" charset="utf-8" type="text/css" media="screen" href="missing-stylesheet.css"/> + <link rel="stylesheet" charset="utf-8" type="text/css" media="screen" href="simple.css"/> +</head> +<body> +</body> +</html> diff --git a/devtools/client/styleeditor/test/nostyle.html b/devtools/client/styleeditor/test/nostyle.html new file mode 100644 index 0000000000..951945a72b --- /dev/null +++ b/devtools/client/styleeditor/test/nostyle.html @@ -0,0 +1,5 @@ +<html> + <div> + Page with no stylesheets + </div> +</html> diff --git a/devtools/client/styleeditor/test/pretty.css b/devtools/client/styleeditor/test/pretty.css new file mode 100644 index 0000000000..e597afa4bc --- /dev/null +++ b/devtools/client/styleeditor/test/pretty.css @@ -0,0 +1,2 @@ + +body{background:white;}div{font-size:4em;color:red}span{color:green;} diff --git a/devtools/client/styleeditor/test/resources_inpage.jsi b/devtools/client/styleeditor/test/resources_inpage.jsi new file mode 100644 index 0000000000..8b7895af52 --- /dev/null +++ b/devtools/client/styleeditor/test/resources_inpage.jsi @@ -0,0 +1,12 @@ + +// This script is used from within browser_styleeditor_cmd_edit.html + +window.addEventListener('load', function() { + var pid = document.getElementById('pid'); + var h3 = document.createElement('h3'); + h3.id = 'h3id'; + h3.classList.add('h3class'); + h3.appendChild(document.createTextNode('h3')); + h3.setAttribute('data-a1', 'h3'); + pid.parentNode.appendChild(h3); +}); diff --git a/devtools/client/styleeditor/test/resources_inpage1.css b/devtools/client/styleeditor/test/resources_inpage1.css new file mode 100644 index 0000000000..644deaaea7 --- /dev/null +++ b/devtools/client/styleeditor/test/resources_inpage1.css @@ -0,0 +1,11 @@ +@charset "utf-8"; + +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#pid { border-top: 2px dotted #F00; } +#divid { border-top: 2px dotted #00F; } +#h4id { border-top: 2px dotted #0F0; } +#h3id { border-top: 2px dotted #FF0; } diff --git a/devtools/client/styleeditor/test/resources_inpage2.css b/devtools/client/styleeditor/test/resources_inpage2.css new file mode 100644 index 0000000000..e4fa48e530 --- /dev/null +++ b/devtools/client/styleeditor/test/resources_inpage2.css @@ -0,0 +1,11 @@ +@charset "utf-8"; + +/* + * Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +*[data-a1=p] { border-left: 4px solid #F00; } +*[data-a1=div] { border-left: 4px solid #00F; } +*[data-a1=h4] { border-left: 4px solid #0F0; } +*[data-a1=h3] { border-left: 4px solid #FF0; } diff --git a/devtools/client/styleeditor/test/selector-highlighter.html b/devtools/client/styleeditor/test/selector-highlighter.html new file mode 100644 index 0000000000..e3b2f6f69c --- /dev/null +++ b/devtools/client/styleeditor/test/selector-highlighter.html @@ -0,0 +1 @@ +<style>div{color:red}</style><div>highlighter test</div> diff --git a/devtools/client/styleeditor/test/simple.css b/devtools/client/styleeditor/test/simple.css new file mode 100644 index 0000000000..4d737f305e --- /dev/null +++ b/devtools/client/styleeditor/test/simple.css @@ -0,0 +1,7 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* ☺ */ + +body { + margin: 0; +} diff --git a/devtools/client/styleeditor/test/simple.css.gz b/devtools/client/styleeditor/test/simple.css.gz Binary files differnew file mode 100644 index 0000000000..ee3b9efbc1 --- /dev/null +++ b/devtools/client/styleeditor/test/simple.css.gz diff --git a/devtools/client/styleeditor/test/simple.css.gz^headers^ b/devtools/client/styleeditor/test/simple.css.gz^headers^ new file mode 100644 index 0000000000..092020ab00 --- /dev/null +++ b/devtools/client/styleeditor/test/simple.css.gz^headers^ @@ -0,0 +1,4 @@ +Vary: Accept-Encoding +Content-Encoding: gzip +Content-Type: text/css + diff --git a/devtools/client/styleeditor/test/simple.gz.html b/devtools/client/styleeditor/test/simple.gz.html new file mode 100644 index 0000000000..d63362b8e0 --- /dev/null +++ b/devtools/client/styleeditor/test/simple.gz.html @@ -0,0 +1,23 @@ +<!doctype html> +<html> +<head> + <title>simple testcase</title> + <link rel="stylesheet" type="text/css" media="scren" href="simple.css.gz"/> + <style type="text/css"> + body { + background: white; + } + + div { + font-size: 4em; + } + + div > span { + text-decoration: underline; + } + </style> +</head> +<body> + <div>simple <span>testcase</span></div> +</body> +</html> diff --git a/devtools/client/styleeditor/test/simple.html b/devtools/client/styleeditor/test/simple.html new file mode 100644 index 0000000000..8f25cdf61e --- /dev/null +++ b/devtools/client/styleeditor/test/simple.html @@ -0,0 +1,24 @@ +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <title>simple testcase</title> + <link rel="stylesheet" charset="UTF-8" type="text/css" media="screen" href="simple.css"/> + <style type="text/css"> + body { + background: white; + } + + div { + font-size: 4em; + } + + div > span { + text-decoration: underline; + } + </style> +</head> +<body> + <div>simple <span>testcase</span></div> +</body> +</html> diff --git a/devtools/client/styleeditor/test/sjs_huge-css-server.sjs b/devtools/client/styleeditor/test/sjs_huge-css-server.sjs new file mode 100644 index 0000000000..480fb1ca91 --- /dev/null +++ b/devtools/client/styleeditor/test/sjs_huge-css-server.sjs @@ -0,0 +1,19 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +"use strict"; + +function handleRequest(request, response) { + response.setStatusLine(request.httpVersion, 200, "Och Aye"); + + response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); + response.setHeader("Pragma", "no-cache"); + response.setHeader("Expires", "0"); + response.setHeader("Content-Type", "text/plain; charset=utf-8", false); + + // Taken from devtools/shared/webconsole/network-monitor + const NETMONITOR_LIMIT = 1048576; + + // 2 * NETMONITOR_LIMIT reaches the exact limit for the netmonitor + // 3 * NETMONITOR_LIMIT makes sure we go past it. + response.write("x".repeat(3 * NETMONITOR_LIMIT)); +} diff --git a/devtools/client/styleeditor/test/sourcemap-css/contained.css b/devtools/client/styleeditor/test/sourcemap-css/contained.css new file mode 100644 index 0000000000..79572f6065 --- /dev/null +++ b/devtools/client/styleeditor/test/sourcemap-css/contained.css @@ -0,0 +1,4 @@ +#header { + color: #f06; } + +/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiIiwic291cmNlcyI6WyJzYXNzL2NvbnRhaW5lZC5zY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBO0VBQ0UsT0FISyIsInNvdXJjZXNDb250ZW50IjpbIiRwaW5rOiAjZjA2O1xuXG4jaGVhZGVyIHtcbiAgY29sb3I6ICRwaW5rO1xufSJdfQ==*/
\ No newline at end of file diff --git a/devtools/client/styleeditor/test/sourcemap-css/media-rules.css b/devtools/client/styleeditor/test/sourcemap-css/media-rules.css new file mode 100644 index 0000000000..d4283200fd --- /dev/null +++ b/devtools/client/styleeditor/test/sourcemap-css/media-rules.css @@ -0,0 +1,8 @@ +@media screen and (max-width: 320px) { + div { + width: 100px; } } +@media screen and (min-width: 1200px) { + div { + width: 400px; } } + +/*# sourceMappingURL=media-rules.css.map */ diff --git a/devtools/client/styleeditor/test/sourcemap-css/media-rules.css.map b/devtools/client/styleeditor/test/sourcemap-css/media-rules.css.map new file mode 100644 index 0000000000..76cd48fe23 --- /dev/null +++ b/devtools/client/styleeditor/test/sourcemap-css/media-rules.css.map @@ -0,0 +1,6 @@ +{ +"version": 3, +"mappings": "AAIE,oCAA4C;EAD9C,GAAI;IAEA,KAAK,EAAE,KAAK;AAEd,qCAA4C;EAJ9C,GAAI;IAKA,KAAK,EAAE,KAAK", +"sources": ["../sourcemap-sass/media-rules.scss"], +"file": "media-rules.css" +} diff --git a/devtools/client/styleeditor/test/sourcemap-css/sourcemaps.css b/devtools/client/styleeditor/test/sourcemap-css/sourcemaps.css new file mode 100644 index 0000000000..7246a9082a --- /dev/null +++ b/devtools/client/styleeditor/test/sourcemap-css/sourcemaps.css @@ -0,0 +1,7 @@ +div { + color: #ff0066; } + +span { + background-color: #EEE; } + +/*# sourceMappingURL=sourcemaps.css.map */
\ No newline at end of file diff --git a/devtools/client/styleeditor/test/sourcemap-css/sourcemaps.css.map b/devtools/client/styleeditor/test/sourcemap-css/sourcemaps.css.map new file mode 100644 index 0000000000..2e8f2911ce --- /dev/null +++ b/devtools/client/styleeditor/test/sourcemap-css/sourcemaps.css.map @@ -0,0 +1,6 @@ +{ +"version": 3, +"mappings": "AAGA,GAAI;EACF,KAAK,EAHU,OAAI;;AAMrB,IAAK;EACH,gBAAgB,EAAE,IAAI", +"sources": ["../sourcemap-sass/sourcemaps.scss"], +"file": "sourcemaps.css" +} diff --git a/devtools/client/styleeditor/test/sourcemap-css/sourcemaps.css.map^headers^ b/devtools/client/styleeditor/test/sourcemap-css/sourcemaps.css.map^headers^ new file mode 100644 index 0000000000..866f3e2fb0 --- /dev/null +++ b/devtools/client/styleeditor/test/sourcemap-css/sourcemaps.css.map^headers^ @@ -0,0 +1,2 @@ +X-Content-Type-Options: nosniff +Content-Type: text/plain diff --git a/devtools/client/styleeditor/test/sourcemap-css/sourcemaps_chrome.css b/devtools/client/styleeditor/test/sourcemap-css/sourcemaps_chrome.css new file mode 100644 index 0000000000..f6713bf1ea --- /dev/null +++ b/devtools/client/styleeditor/test/sourcemap-css/sourcemaps_chrome.css @@ -0,0 +1,7 @@ +div { + color: #ff0066; } + +span { + background-color: #EEE; } + +/*# sourceMappingURL=chrome://mochitests/content/browser/devtools/client/styleeditor/test/sourcemap-css/sourcemaps.css.map */ diff --git a/devtools/client/styleeditor/test/sourcemap-css/test-bootstrap-scss.css b/devtools/client/styleeditor/test/sourcemap-css/test-bootstrap-scss.css new file mode 100644 index 0000000000..e943c6ef4e --- /dev/null +++ b/devtools/client/styleeditor/test/sourcemap-css/test-bootstrap-scss.css @@ -0,0 +1,4513 @@ +/*! normalize.css v3.0.1 | MIT License | git.io/normalize */ +html { + font-family: sans-serif; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100%; } + +body { + margin: 0; } + +article, aside, details, figcaption, figure, footer, header, hgroup, main, nav, section, summary { + display: block; } + +audio, canvas, progress, video { + display: inline-block; + vertical-align: baseline; } + +audio:not([controls]) { + display: none; + height: 0; } + +[hidden], template { + display: none; } + +a { + background: transparent; } + +a:active, a:hover { + outline: 0; } + +abbr[title] { + border-bottom: 1px dotted; } + +b, strong { + font-weight: bold; } + +dfn { + font-style: italic; } + +h1 { + font-size: 2em; + margin: 0.67em 0; } + +mark { + background: #ff0; + color: #000; } + +small { + font-size: 80%; } + +sub, sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; } + +sup { + top: -0.5em; } + +sub { + bottom: -0.25em; } + +img { + border: 0; } + +svg:not(:root) { + overflow: hidden; } + +figure { + margin: 1em 40px; } + +hr { + box-sizing: content-box; + height: 0; } + +pre { + overflow: auto; } + +code, kbd, pre, samp { + font-family: monospace, monospace; + font-size: 1em; } + +button, input, optgroup, select, textarea { + color: inherit; + font: inherit; + margin: 0; } + +button { + overflow: visible; } + +button, select { + text-transform: none; } + +button, html input[type="button"], input[type="reset"], input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; } + +button[disabled], html input[disabled] { + cursor: default; } + +button::-moz-focus-inner, input::-moz-focus-inner { + border: 0; + padding: 0; } + +input { + line-height: normal; } + +input[type="checkbox"], input[type="radio"] { + box-sizing: border-box; + padding: 0; } + +input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-outer-spin-button { + height: auto; } + +input[type="search"] { + -webkit-appearance: textfield; + box-sizing: content-box; } + +input[type="search"]::-webkit-search-cancel-button, input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; } + +fieldset { + border: 1px solid #c0c0c0; + margin: 0 2px; + padding: 0.35em 0.625em 0.75em; } + +legend { + border: 0; + padding: 0; } + +textarea { + overflow: auto; } + +optgroup { + font-weight: bold; } + +table { + border-collapse: collapse; + border-spacing: 0; } + +td, th { + padding: 0; } + +@media print { + * { + text-shadow: none !important; + color: #000 !important; + background: transparent !important; + box-shadow: none !important; } + a, a:visited { + text-decoration: underline; } + a[href]:after { + content: " (" attr(href) ")"; } + abbr[title]:after { + content: " (" attr(title) ")"; } + a[href^="javascript:"]:after, a[href^="#"]:after { + content: ""; } + pre, blockquote { + border: 1px solid #999; + page-break-inside: avoid; } + thead { + display: table-header-group; } + tr, img { + page-break-inside: avoid; } + img { + max-width: 100% !important; } + p, h2, h3 { + orphans: 3; + widows: 3; } + h2, h3 { + page-break-after: avoid; } + select { + background: #fff !important; } + .navbar { + display: none; } + .table td, .table th { + background-color: #fff !important; } + .btn > .caret, .dropup > .btn > .caret { + border-top-color: #000 !important; } + .label { + border: 1px solid #000; } + .table { + border-collapse: collapse !important; } + .table-bordered th, .table-bordered td { + border: 1px solid #ddd !important; } } + +@font-face { + font-family: 'Glyphicons Halflings'; + src: url('../bower_components/bootstrap-sass-official/vendor/assets/fonts/bootstrap/glyphicons-halflings-regular.eot'); + src: url('../bower_components/bootstrap-sass-official/vendor/assets/fonts/bootstrap/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../bower_components/bootstrap-sass-official/vendor/assets/fonts/bootstrap/glyphicons-halflings-regular.woff') format('woff'), url('../bower_components/bootstrap-sass-official/vendor/assets/fonts/bootstrap/glyphicons-halflings-regular.ttf') format('truetype'), url('../bower_components/bootstrap-sass-official/vendor/assets/fonts/bootstrap/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); } + +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; } + +.glyphicon-asterisk:before { + content: "\2a"; } + +.glyphicon-plus:before { + content: "\2b"; } + +.glyphicon-euro:before { + content: "\20ac"; } + +.glyphicon-minus:before { + content: "\2212"; } + +.glyphicon-cloud:before { + content: "\2601"; } + +.glyphicon-envelope:before { + content: "\2709"; } + +.glyphicon-pencil:before { + content: "\270f"; } + +.glyphicon-glass:before { + content: "\e001"; } + +.glyphicon-music:before { + content: "\e002"; } + +.glyphicon-search:before { + content: "\e003"; } + +.glyphicon-heart:before { + content: "\e005"; } + +.glyphicon-star:before { + content: "\e006"; } + +.glyphicon-star-empty:before { + content: "\e007"; } + +.glyphicon-user:before { + content: "\e008"; } + +.glyphicon-film:before { + content: "\e009"; } + +.glyphicon-th-large:before { + content: "\e010"; } + +.glyphicon-th:before { + content: "\e011"; } + +.glyphicon-th-list:before { + content: "\e012"; } + +.glyphicon-ok:before { + content: "\e013"; } + +.glyphicon-remove:before { + content: "\e014"; } + +.glyphicon-zoom-in:before { + content: "\e015"; } + +.glyphicon-zoom-out:before { + content: "\e016"; } + +.glyphicon-off:before { + content: "\e017"; } + +.glyphicon-signal:before { + content: "\e018"; } + +.glyphicon-cog:before { + content: "\e019"; } + +.glyphicon-trash:before { + content: "\e020"; } + +.glyphicon-home:before { + content: "\e021"; } + +.glyphicon-file:before { + content: "\e022"; } + +.glyphicon-time:before { + content: "\e023"; } + +.glyphicon-road:before { + content: "\e024"; } + +.glyphicon-download-alt:before { + content: "\e025"; } + +.glyphicon-download:before { + content: "\e026"; } + +.glyphicon-upload:before { + content: "\e027"; } + +.glyphicon-inbox:before { + content: "\e028"; } + +.glyphicon-play-circle:before { + content: "\e029"; } + +.glyphicon-repeat:before { + content: "\e030"; } + +.glyphicon-refresh:before { + content: "\e031"; } + +.glyphicon-list-alt:before { + content: "\e032"; } + +.glyphicon-lock:before { + content: "\e033"; } + +.glyphicon-flag:before { + content: "\e034"; } + +.glyphicon-headphones:before { + content: "\e035"; } + +.glyphicon-volume-off:before { + content: "\e036"; } + +.glyphicon-volume-down:before { + content: "\e037"; } + +.glyphicon-volume-up:before { + content: "\e038"; } + +.glyphicon-qrcode:before { + content: "\e039"; } + +.glyphicon-barcode:before { + content: "\e040"; } + +.glyphicon-tag:before { + content: "\e041"; } + +.glyphicon-tags:before { + content: "\e042"; } + +.glyphicon-book:before { + content: "\e043"; } + +.glyphicon-bookmark:before { + content: "\e044"; } + +.glyphicon-print:before { + content: "\e045"; } + +.glyphicon-camera:before { + content: "\e046"; } + +.glyphicon-font:before { + content: "\e047"; } + +.glyphicon-bold:before { + content: "\e048"; } + +.glyphicon-italic:before { + content: "\e049"; } + +.glyphicon-text-height:before { + content: "\e050"; } + +.glyphicon-text-width:before { + content: "\e051"; } + +.glyphicon-align-left:before { + content: "\e052"; } + +.glyphicon-align-center:before { + content: "\e053"; } + +.glyphicon-align-right:before { + content: "\e054"; } + +.glyphicon-align-justify:before { + content: "\e055"; } + +.glyphicon-list:before { + content: "\e056"; } + +.glyphicon-indent-left:before { + content: "\e057"; } + +.glyphicon-indent-right:before { + content: "\e058"; } + +.glyphicon-facetime-video:before { + content: "\e059"; } + +.glyphicon-picture:before { + content: "\e060"; } + +.glyphicon-map-marker:before { + content: "\e062"; } + +.glyphicon-adjust:before { + content: "\e063"; } + +.glyphicon-tint:before { + content: "\e064"; } + +.glyphicon-edit:before { + content: "\e065"; } + +.glyphicon-share:before { + content: "\e066"; } + +.glyphicon-check:before { + content: "\e067"; } + +.glyphicon-move:before { + content: "\e068"; } + +.glyphicon-step-backward:before { + content: "\e069"; } + +.glyphicon-fast-backward:before { + content: "\e070"; } + +.glyphicon-backward:before { + content: "\e071"; } + +.glyphicon-play:before { + content: "\e072"; } + +.glyphicon-pause:before { + content: "\e073"; } + +.glyphicon-stop:before { + content: "\e074"; } + +.glyphicon-forward:before { + content: "\e075"; } + +.glyphicon-fast-forward:before { + content: "\e076"; } + +.glyphicon-step-forward:before { + content: "\e077"; } + +.glyphicon-eject:before { + content: "\e078"; } + +.glyphicon-chevron-left:before { + content: "\e079"; } + +.glyphicon-chevron-right:before { + content: "\e080"; } + +.glyphicon-plus-sign:before { + content: "\e081"; } + +.glyphicon-minus-sign:before { + content: "\e082"; } + +.glyphicon-remove-sign:before { + content: "\e083"; } + +.glyphicon-ok-sign:before { + content: "\e084"; } + +.glyphicon-question-sign:before { + content: "\e085"; } + +.glyphicon-info-sign:before { + content: "\e086"; } + +.glyphicon-screenshot:before { + content: "\e087"; } + +.glyphicon-remove-circle:before { + content: "\e088"; } + +.glyphicon-ok-circle:before { + content: "\e089"; } + +.glyphicon-ban-circle:before { + content: "\e090"; } + +.glyphicon-arrow-left:before { + content: "\e091"; } + +.glyphicon-arrow-right:before { + content: "\e092"; } + +.glyphicon-arrow-up:before { + content: "\e093"; } + +.glyphicon-arrow-down:before { + content: "\e094"; } + +.glyphicon-share-alt:before { + content: "\e095"; } + +.glyphicon-resize-full:before { + content: "\e096"; } + +.glyphicon-resize-small:before { + content: "\e097"; } + +.glyphicon-exclamation-sign:before { + content: "\e101"; } + +.glyphicon-gift:before { + content: "\e102"; } + +.glyphicon-leaf:before { + content: "\e103"; } + +.glyphicon-fire:before { + content: "\e104"; } + +.glyphicon-eye-open:before { + content: "\e105"; } + +.glyphicon-eye-close:before { + content: "\e106"; } + +.glyphicon-warning-sign:before { + content: "\e107"; } + +.glyphicon-plane:before { + content: "\e108"; } + +.glyphicon-calendar:before { + content: "\e109"; } + +.glyphicon-random:before { + content: "\e110"; } + +.glyphicon-comment:before { + content: "\e111"; } + +.glyphicon-magnet:before { + content: "\e112"; } + +.glyphicon-chevron-up:before { + content: "\e113"; } + +.glyphicon-chevron-down:before { + content: "\e114"; } + +.glyphicon-retweet:before { + content: "\e115"; } + +.glyphicon-shopping-cart:before { + content: "\e116"; } + +.glyphicon-folder-close:before { + content: "\e117"; } + +.glyphicon-folder-open:before { + content: "\e118"; } + +.glyphicon-resize-vertical:before { + content: "\e119"; } + +.glyphicon-resize-horizontal:before { + content: "\e120"; } + +.glyphicon-hdd:before { + content: "\e121"; } + +.glyphicon-bullhorn:before { + content: "\e122"; } + +.glyphicon-bell:before { + content: "\e123"; } + +.glyphicon-certificate:before { + content: "\e124"; } + +.glyphicon-thumbs-up:before { + content: "\e125"; } + +.glyphicon-thumbs-down:before { + content: "\e126"; } + +.glyphicon-hand-right:before { + content: "\e127"; } + +.glyphicon-hand-left:before { + content: "\e128"; } + +.glyphicon-hand-up:before { + content: "\e129"; } + +.glyphicon-hand-down:before { + content: "\e130"; } + +.glyphicon-circle-arrow-right:before { + content: "\e131"; } + +.glyphicon-circle-arrow-left:before { + content: "\e132"; } + +.glyphicon-circle-arrow-up:before { + content: "\e133"; } + +.glyphicon-circle-arrow-down:before { + content: "\e134"; } + +.glyphicon-globe:before { + content: "\e135"; } + +.glyphicon-wrench:before { + content: "\e136"; } + +.glyphicon-tasks:before { + content: "\e137"; } + +.glyphicon-filter:before { + content: "\e138"; } + +.glyphicon-briefcase:before { + content: "\e139"; } + +.glyphicon-fullscreen:before { + content: "\e140"; } + +.glyphicon-dashboard:before { + content: "\e141"; } + +.glyphicon-paperclip:before { + content: "\e142"; } + +.glyphicon-heart-empty:before { + content: "\e143"; } + +.glyphicon-link:before { + content: "\e144"; } + +.glyphicon-phone:before { + content: "\e145"; } + +.glyphicon-pushpin:before { + content: "\e146"; } + +.glyphicon-usd:before { + content: "\e148"; } + +.glyphicon-gbp:before { + content: "\e149"; } + +.glyphicon-sort:before { + content: "\e150"; } + +.glyphicon-sort-by-alphabet:before { + content: "\e151"; } + +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; } + +.glyphicon-sort-by-order:before { + content: "\e153"; } + +.glyphicon-sort-by-order-alt:before { + content: "\e154"; } + +.glyphicon-sort-by-attributes:before { + content: "\e155"; } + +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; } + +.glyphicon-unchecked:before { + content: "\e157"; } + +.glyphicon-expand:before { + content: "\e158"; } + +.glyphicon-collapse-down:before { + content: "\e159"; } + +.glyphicon-collapse-up:before { + content: "\e160"; } + +.glyphicon-log-in:before { + content: "\e161"; } + +.glyphicon-flash:before { + content: "\e162"; } + +.glyphicon-log-out:before { + content: "\e163"; } + +.glyphicon-new-window:before { + content: "\e164"; } + +.glyphicon-record:before { + content: "\e165"; } + +.glyphicon-save:before { + content: "\e166"; } + +.glyphicon-open:before { + content: "\e167"; } + +.glyphicon-saved:before { + content: "\e168"; } + +.glyphicon-import:before { + content: "\e169"; } + +.glyphicon-export:before { + content: "\e170"; } + +.glyphicon-send:before { + content: "\e171"; } + +.glyphicon-floppy-disk:before { + content: "\e172"; } + +.glyphicon-floppy-saved:before { + content: "\e173"; } + +.glyphicon-floppy-remove:before { + content: "\e174"; } + +.glyphicon-floppy-save:before { + content: "\e175"; } + +.glyphicon-floppy-open:before { + content: "\e176"; } + +.glyphicon-credit-card:before { + content: "\e177"; } + +.glyphicon-transfer:before { + content: "\e178"; } + +.glyphicon-cutlery:before { + content: "\e179"; } + +.glyphicon-header:before { + content: "\e180"; } + +.glyphicon-compressed:before { + content: "\e181"; } + +.glyphicon-earphone:before { + content: "\e182"; } + +.glyphicon-phone-alt:before { + content: "\e183"; } + +.glyphicon-tower:before { + content: "\e184"; } + +.glyphicon-stats:before { + content: "\e185"; } + +.glyphicon-sd-video:before { + content: "\e186"; } + +.glyphicon-hd-video:before { + content: "\e187"; } + +.glyphicon-subtitles:before { + content: "\e188"; } + +.glyphicon-sound-stereo:before { + content: "\e189"; } + +.glyphicon-sound-dolby:before { + content: "\e190"; } + +.glyphicon-sound-5-1:before { + content: "\e191"; } + +.glyphicon-sound-6-1:before { + content: "\e192"; } + +.glyphicon-sound-7-1:before { + content: "\e193"; } + +.glyphicon-copyright-mark:before { + content: "\e194"; } + +.glyphicon-registration-mark:before { + content: "\e195"; } + +.glyphicon-cloud-download:before { + content: "\e197"; } + +.glyphicon-cloud-upload:before { + content: "\e198"; } + +.glyphicon-tree-conifer:before { + content: "\e199"; } + +.glyphicon-tree-deciduous:before { + content: "\e200"; } + +* { + box-sizing: border-box; } + +*:before, *:after { + box-sizing: border-box; } + +html { + font-size: 62.5%; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); } + +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857; + color: #333333; + background-color: #fff; } + +input, button, select, textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; } + +a { + color: #428bca; + text-decoration: none; } + a:hover, a:focus { + color: #2a6596; + text-decoration: underline; } + a:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; } + +figure { + margin: 0; } + +img { + vertical-align: middle; } + +.img-responsive { + display: block; + max-width: 100%; + height: auto; } + +.img-rounded { + border-radius: 6px; } + +.img-thumbnail { + padding: 4px; + line-height: 1.42857; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + transition: all 0.2s ease-in-out; + display: inline-block; + max-width: 100%; + height: auto; } + +.img-circle { + border-radius: 50%; } + +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eeeeee; } + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; } + +.sr-only-focusable:active, .sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; } + +h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; } + h1 small, h1 .small, h2 small, h2 .small, h3 small, h3 .small, h4 small, h4 .small, h5 small, h5 .small, h6 small, h6 .small, .h1 small, .h1 .small, .h2 small, .h2 .small, .h3 small, .h3 .small, .h4 small, .h4 .small, .h5 small, .h5 .small, .h6 small, .h6 .small { + font-weight: normal; + line-height: 1; + color: #999999; } + +h1, .h1, h2, .h2, h3, .h3 { + margin-top: 20px; + margin-bottom: 10px; } + h1 small, h1 .small, .h1 small, .h1 .small, h2 small, h2 .small, .h2 small, .h2 .small, h3 small, h3 .small, .h3 small, .h3 .small { + font-size: 65%; } + +h4, .h4, h5, .h5, h6, .h6 { + margin-top: 10px; + margin-bottom: 10px; } + h4 small, h4 .small, .h4 small, .h4 .small, h5 small, h5 .small, .h5 small, .h5 .small, h6 small, h6 .small, .h6 small, .h6 .small { + font-size: 75%; } + +h1, .h1 { + font-size: 36px; } + +h2, .h2 { + font-size: 30px; } + +h3, .h3 { + font-size: 24px; } + +h4, .h4 { + font-size: 18px; } + +h5, .h5 { + font-size: 14px; } + +h6, .h6 { + font-size: 12px; } + +p { + margin: 0 0 10px; } + +.lead { + margin-bottom: 20px; + font-size: 16px; + font-weight: 200; + line-height: 1.4; } + @media (min-width: 768px) { + .lead { + font-size: 21px; } } + +small, .small { + font-size: 85%; } + +cite { + font-style: normal; } + +mark, .mark { + background-color: #fcf8e3; + padding: 0.2em; } + +.text-left { + text-align: left; } + +.text-right { + text-align: right; } + +.text-center { + text-align: center; } + +.text-justify { + text-align: justify; } + +.text-muted { + color: #999999; } + +.text-primary { + color: #428bca; } + +a.text-primary:hover { + color: #3073a9; } + +.text-success { + color: #3c763d; } + +a.text-success:hover { + color: #2b542b; } + +.text-info { + color: #31708f; } + +a.text-info:hover { + color: #245369; } + +.text-warning { + color: #8a6d3b; } + +a.text-warning:hover { + color: #66502c; } + +.text-danger { + color: #a94442; } + +a.text-danger:hover { + color: #843534; } + +.bg-primary { + color: #fff; } + +.bg-primary { + background-color: #428bca; } + +a.bg-primary:hover { + background-color: #3073a9; } + +.bg-success { + background-color: #dff0d8; } + +a.bg-success:hover { + background-color: #c1e2b3; } + +.bg-info { + background-color: #d9edf7; } + +a.bg-info:hover { + background-color: #afdaee; } + +.bg-warning { + background-color: #fcf8e3; } + +a.bg-warning:hover { + background-color: #f7ecb5; } + +.bg-danger { + background-color: #f2dede; } + +a.bg-danger:hover { + background-color: #e4b9b9; } + +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eeeeee; } + +ul, ol { + margin-top: 0; + margin-bottom: 10px; } + ul ul, ul ol, ol ul, ol ol { + margin-bottom: 0; } + +.list-unstyled, .list-inline { + padding-left: 0; + list-style: none; } + +.list-inline { + margin-left: -5px; } + .list-inline > li { + display: inline-block; + padding-left: 5px; + padding-right: 5px; } + +dl { + margin-top: 0; + margin-bottom: 20px; } + +dt, dd { + line-height: 1.42857; } + +dt { + font-weight: bold; } + +dd { + margin-left: 0; } + +.dl-horizontal dd:before, .dl-horizontal dd:after { + content: " "; + display: table; } +.dl-horizontal dd:after { + clear: both; } +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + clear: left; + text-align: right; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } + .dl-horizontal dd { + margin-left: 180px; } } + +abbr[title], abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #999999; } + +.initialism { + font-size: 90%; + text-transform: uppercase; } + +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #eeeeee; } + blockquote p:last-child, blockquote ul:last-child, blockquote ol:last-child { + margin-bottom: 0; } + blockquote footer, blockquote small, blockquote .small { + display: block; + font-size: 80%; + line-height: 1.42857; + color: #999999; } + blockquote footer:before, blockquote small:before, blockquote .small:before { + content: '\2014 \00A0'; } + +.blockquote-reverse, blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + border-right: 5px solid #eeeeee; + border-left: 0; + text-align: right; } + .blockquote-reverse footer:before, .blockquote-reverse small:before, .blockquote-reverse .small:before, blockquote.pull-right footer:before, blockquote.pull-right small:before, blockquote.pull-right .small:before { + content: ''; } + .blockquote-reverse footer:after, .blockquote-reverse small:after, .blockquote-reverse .small:after, blockquote.pull-right footer:after, blockquote.pull-right small:after, blockquote.pull-right .small:after { + content: '\00A0 \2014'; } + +blockquote:before, blockquote:after { + content: ""; } + +address { + margin-bottom: 20px; + font-style: normal; + line-height: 1.42857; } + +code, kbd, pre, samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; } + +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; } + +kbd { + padding: 2px 4px; + font-size: 90%; + color: #fff; + background-color: #333; + border-radius: 3px; + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25); } + +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857; + word-break: break-all; + word-wrap: break-word; + color: #333333; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; } + pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; } + +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; } + +.container { + margin-right: auto; + margin-left: auto; + padding-left: 15px; + padding-right: 15px; } + .container:before, .container:after { + content: " "; + display: table; } + .container:after { + clear: both; } + @media (min-width: 768px) { + .container { + width: 750px; } } + @media (min-width: 992px) { + .container { + width: 970px; } } + @media (min-width: 1200px) { + .container { + width: 1170px; } } + +.container-fluid { + margin-right: auto; + margin-left: auto; + padding-left: 15px; + padding-right: 15px; } + .container-fluid:before, .container-fluid:after { + content: " "; + display: table; } + .container-fluid:after { + clear: both; } + +.row { + margin-left: -15px; + margin-right: -15px; } + .row:before, .row:after { + content: " "; + display: table; } + .row:after { + clear: both; } + +.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { + position: relative; + min-height: 1px; + padding-left: 15px; + padding-right: 15px; } + +.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { + float: left; } + +.col-xs-1 { + width: 8.33333%; } + +.col-xs-2 { + width: 16.66667%; } + +.col-xs-3 { + width: 25%; } + +.col-xs-4 { + width: 33.33333%; } + +.col-xs-5 { + width: 41.66667%; } + +.col-xs-6 { + width: 50%; } + +.col-xs-7 { + width: 58.33333%; } + +.col-xs-8 { + width: 66.66667%; } + +.col-xs-9 { + width: 75%; } + +.col-xs-10 { + width: 83.33333%; } + +.col-xs-11 { + width: 91.66667%; } + +.col-xs-12 { + width: 100%; } + +.col-xs-pull-0 { + right: auto; } + +.col-xs-pull-1 { + right: 8.33333%; } + +.col-xs-pull-2 { + right: 16.66667%; } + +.col-xs-pull-3 { + right: 25%; } + +.col-xs-pull-4 { + right: 33.33333%; } + +.col-xs-pull-5 { + right: 41.66667%; } + +.col-xs-pull-6 { + right: 50%; } + +.col-xs-pull-7 { + right: 58.33333%; } + +.col-xs-pull-8 { + right: 66.66667%; } + +.col-xs-pull-9 { + right: 75%; } + +.col-xs-pull-10 { + right: 83.33333%; } + +.col-xs-pull-11 { + right: 91.66667%; } + +.col-xs-pull-12 { + right: 100%; } + +.col-xs-push-0 { + left: auto; } + +.col-xs-push-1 { + left: 8.33333%; } + +.col-xs-push-2 { + left: 16.66667%; } + +.col-xs-push-3 { + left: 25%; } + +.col-xs-push-4 { + left: 33.33333%; } + +.col-xs-push-5 { + left: 41.66667%; } + +.col-xs-push-6 { + left: 50%; } + +.col-xs-push-7 { + left: 58.33333%; } + +.col-xs-push-8 { + left: 66.66667%; } + +.col-xs-push-9 { + left: 75%; } + +.col-xs-push-10 { + left: 83.33333%; } + +.col-xs-push-11 { + left: 91.66667%; } + +.col-xs-push-12 { + left: 100%; } + +.col-xs-offset-0 { + margin-left: 0%; } + +.col-xs-offset-1 { + margin-left: 8.33333%; } + +.col-xs-offset-2 { + margin-left: 16.66667%; } + +.col-xs-offset-3 { + margin-left: 25%; } + +.col-xs-offset-4 { + margin-left: 33.33333%; } + +.col-xs-offset-5 { + margin-left: 41.66667%; } + +.col-xs-offset-6 { + margin-left: 50%; } + +.col-xs-offset-7 { + margin-left: 58.33333%; } + +.col-xs-offset-8 { + margin-left: 66.66667%; } + +.col-xs-offset-9 { + margin-left: 75%; } + +.col-xs-offset-10 { + margin-left: 83.33333%; } + +.col-xs-offset-11 { + margin-left: 91.66667%; } + +.col-xs-offset-12 { + margin-left: 100%; } + +@media (min-width: 768px) { + .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + float: left; } + .col-sm-1 { + width: 8.33333%; } + .col-sm-2 { + width: 16.66667%; } + .col-sm-3 { + width: 25%; } + .col-sm-4 { + width: 33.33333%; } + .col-sm-5 { + width: 41.66667%; } + .col-sm-6 { + width: 50%; } + .col-sm-7 { + width: 58.33333%; } + .col-sm-8 { + width: 66.66667%; } + .col-sm-9 { + width: 75%; } + .col-sm-10 { + width: 83.33333%; } + .col-sm-11 { + width: 91.66667%; } + .col-sm-12 { + width: 100%; } + .col-sm-pull-0 { + right: auto; } + .col-sm-pull-1 { + right: 8.33333%; } + .col-sm-pull-2 { + right: 16.66667%; } + .col-sm-pull-3 { + right: 25%; } + .col-sm-pull-4 { + right: 33.33333%; } + .col-sm-pull-5 { + right: 41.66667%; } + .col-sm-pull-6 { + right: 50%; } + .col-sm-pull-7 { + right: 58.33333%; } + .col-sm-pull-8 { + right: 66.66667%; } + .col-sm-pull-9 { + right: 75%; } + .col-sm-pull-10 { + right: 83.33333%; } + .col-sm-pull-11 { + right: 91.66667%; } + .col-sm-pull-12 { + right: 100%; } + .col-sm-push-0 { + left: auto; } + .col-sm-push-1 { + left: 8.33333%; } + .col-sm-push-2 { + left: 16.66667%; } + .col-sm-push-3 { + left: 25%; } + .col-sm-push-4 { + left: 33.33333%; } + .col-sm-push-5 { + left: 41.66667%; } + .col-sm-push-6 { + left: 50%; } + .col-sm-push-7 { + left: 58.33333%; } + .col-sm-push-8 { + left: 66.66667%; } + .col-sm-push-9 { + left: 75%; } + .col-sm-push-10 { + left: 83.33333%; } + .col-sm-push-11 { + left: 91.66667%; } + .col-sm-push-12 { + left: 100%; } + .col-sm-offset-0 { + margin-left: 0%; } + .col-sm-offset-1 { + margin-left: 8.33333%; } + .col-sm-offset-2 { + margin-left: 16.66667%; } + .col-sm-offset-3 { + margin-left: 25%; } + .col-sm-offset-4 { + margin-left: 33.33333%; } + .col-sm-offset-5 { + margin-left: 41.66667%; } + .col-sm-offset-6 { + margin-left: 50%; } + .col-sm-offset-7 { + margin-left: 58.33333%; } + .col-sm-offset-8 { + margin-left: 66.66667%; } + .col-sm-offset-9 { + margin-left: 75%; } + .col-sm-offset-10 { + margin-left: 83.33333%; } + .col-sm-offset-11 { + margin-left: 91.66667%; } + .col-sm-offset-12 { + margin-left: 100%; } } + +@media (min-width: 992px) { + .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { + float: left; } + .col-md-1 { + width: 8.33333%; } + .col-md-2 { + width: 16.66667%; } + .col-md-3 { + width: 25%; } + .col-md-4 { + width: 33.33333%; } + .col-md-5 { + width: 41.66667%; } + .col-md-6 { + width: 50%; } + .col-md-7 { + width: 58.33333%; } + .col-md-8 { + width: 66.66667%; } + .col-md-9 { + width: 75%; } + .col-md-10 { + width: 83.33333%; } + .col-md-11 { + width: 91.66667%; } + .col-md-12 { + width: 100%; } + .col-md-pull-0 { + right: auto; } + .col-md-pull-1 { + right: 8.33333%; } + .col-md-pull-2 { + right: 16.66667%; } + .col-md-pull-3 { + right: 25%; } + .col-md-pull-4 { + right: 33.33333%; } + .col-md-pull-5 { + right: 41.66667%; } + .col-md-pull-6 { + right: 50%; } + .col-md-pull-7 { + right: 58.33333%; } + .col-md-pull-8 { + right: 66.66667%; } + .col-md-pull-9 { + right: 75%; } + .col-md-pull-10 { + right: 83.33333%; } + .col-md-pull-11 { + right: 91.66667%; } + .col-md-pull-12 { + right: 100%; } + .col-md-push-0 { + left: auto; } + .col-md-push-1 { + left: 8.33333%; } + .col-md-push-2 { + left: 16.66667%; } + .col-md-push-3 { + left: 25%; } + .col-md-push-4 { + left: 33.33333%; } + .col-md-push-5 { + left: 41.66667%; } + .col-md-push-6 { + left: 50%; } + .col-md-push-7 { + left: 58.33333%; } + .col-md-push-8 { + left: 66.66667%; } + .col-md-push-9 { + left: 75%; } + .col-md-push-10 { + left: 83.33333%; } + .col-md-push-11 { + left: 91.66667%; } + .col-md-push-12 { + left: 100%; } + .col-md-offset-0 { + margin-left: 0%; } + .col-md-offset-1 { + margin-left: 8.33333%; } + .col-md-offset-2 { + margin-left: 16.66667%; } + .col-md-offset-3 { + margin-left: 25%; } + .col-md-offset-4 { + margin-left: 33.33333%; } + .col-md-offset-5 { + margin-left: 41.66667%; } + .col-md-offset-6 { + margin-left: 50%; } + .col-md-offset-7 { + margin-left: 58.33333%; } + .col-md-offset-8 { + margin-left: 66.66667%; } + .col-md-offset-9 { + margin-left: 75%; } + .col-md-offset-10 { + margin-left: 83.33333%; } + .col-md-offset-11 { + margin-left: 91.66667%; } + .col-md-offset-12 { + margin-left: 100%; } } + +@media (min-width: 1200px) { + .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { + float: left; } + .col-lg-1 { + width: 8.33333%; } + .col-lg-2 { + width: 16.66667%; } + .col-lg-3 { + width: 25%; } + .col-lg-4 { + width: 33.33333%; } + .col-lg-5 { + width: 41.66667%; } + .col-lg-6 { + width: 50%; } + .col-lg-7 { + width: 58.33333%; } + .col-lg-8 { + width: 66.66667%; } + .col-lg-9 { + width: 75%; } + .col-lg-10 { + width: 83.33333%; } + .col-lg-11 { + width: 91.66667%; } + .col-lg-12 { + width: 100%; } + .col-lg-pull-0 { + right: auto; } + .col-lg-pull-1 { + right: 8.33333%; } + .col-lg-pull-2 { + right: 16.66667%; } + .col-lg-pull-3 { + right: 25%; } + .col-lg-pull-4 { + right: 33.33333%; } + .col-lg-pull-5 { + right: 41.66667%; } + .col-lg-pull-6 { + right: 50%; } + .col-lg-pull-7 { + right: 58.33333%; } + .col-lg-pull-8 { + right: 66.66667%; } + .col-lg-pull-9 { + right: 75%; } + .col-lg-pull-10 { + right: 83.33333%; } + .col-lg-pull-11 { + right: 91.66667%; } + .col-lg-pull-12 { + right: 100%; } + .col-lg-push-0 { + left: auto; } + .col-lg-push-1 { + left: 8.33333%; } + .col-lg-push-2 { + left: 16.66667%; } + .col-lg-push-3 { + left: 25%; } + .col-lg-push-4 { + left: 33.33333%; } + .col-lg-push-5 { + left: 41.66667%; } + .col-lg-push-6 { + left: 50%; } + .col-lg-push-7 { + left: 58.33333%; } + .col-lg-push-8 { + left: 66.66667%; } + .col-lg-push-9 { + left: 75%; } + .col-lg-push-10 { + left: 83.33333%; } + .col-lg-push-11 { + left: 91.66667%; } + .col-lg-push-12 { + left: 100%; } + .col-lg-offset-0 { + margin-left: 0%; } + .col-lg-offset-1 { + margin-left: 8.33333%; } + .col-lg-offset-2 { + margin-left: 16.66667%; } + .col-lg-offset-3 { + margin-left: 25%; } + .col-lg-offset-4 { + margin-left: 33.33333%; } + .col-lg-offset-5 { + margin-left: 41.66667%; } + .col-lg-offset-6 { + margin-left: 50%; } + .col-lg-offset-7 { + margin-left: 58.33333%; } + .col-lg-offset-8 { + margin-left: 66.66667%; } + .col-lg-offset-9 { + margin-left: 75%; } + .col-lg-offset-10 { + margin-left: 83.33333%; } + .col-lg-offset-11 { + margin-left: 91.66667%; } + .col-lg-offset-12 { + margin-left: 100%; } } + +table { + max-width: 100%; + background-color: transparent; } + +th { + text-align: left; } + +.table { + width: 100%; + margin-bottom: 20px; } + .table > thead > tr > th, .table > thead > tr > td, .table > tbody > tr > th, .table > tbody > tr > td, .table > tfoot > tr > th, .table > tfoot > tr > td { + padding: 8px; + line-height: 1.42857; + vertical-align: top; + border-top: 1px solid #ddd; } + .table > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #ddd; } + .table > caption + thead > tr:first-child > th, .table > caption + thead > tr:first-child > td, .table > colgroup + thead > tr:first-child > th, .table > colgroup + thead > tr:first-child > td, .table > thead:first-child > tr:first-child > th, .table > thead:first-child > tr:first-child > td { + border-top: 0; } + .table > tbody + tbody { + border-top: 2px solid #ddd; } + .table .table { + background-color: #fff; } + +.table-condensed > thead > tr > th, .table-condensed > thead > tr > td, .table-condensed > tbody > tr > th, .table-condensed > tbody > tr > td, .table-condensed > tfoot > tr > th, .table-condensed > tfoot > tr > td { + padding: 5px; } + +.table-bordered { + border: 1px solid #ddd; } + .table-bordered > thead > tr > th, .table-bordered > thead > tr > td, .table-bordered > tbody > tr > th, .table-bordered > tbody > tr > td, .table-bordered > tfoot > tr > th, .table-bordered > tfoot > tr > td { + border: 1px solid #ddd; } + .table-bordered > thead > tr > th, .table-bordered > thead > tr > td { + border-bottom-width: 2px; } + +.table-striped > tbody > tr:nth-child(odd) > td, .table-striped > tbody > tr:nth-child(odd) > th { + background-color: #f9f9f9; } + +.table-hover > tbody > tr:hover > td, .table-hover > tbody > tr:hover > th { + background-color: #f5f5f5; } + +table col[class*="col-"] { + position: static; + float: none; + display: table-column; } + +table td[class*="col-"], table th[class*="col-"] { + position: static; + float: none; + display: table-cell; } + +.table > thead > tr > td.active, .table > thead > tr > th.active, .table > thead > tr.active > td, .table > thead > tr.active > th, .table > tbody > tr > td.active, .table > tbody > tr > th.active, .table > tbody > tr.active > td, .table > tbody > tr.active > th, .table > tfoot > tr > td.active, .table > tfoot > tr > th.active, .table > tfoot > tr.active > td, .table > tfoot > tr.active > th { + background-color: #f5f5f5; } + +.table-hover > tbody > tr > td.active:hover, .table-hover > tbody > tr > th.active:hover, .table-hover > tbody > tr.active:hover > td, .table-hover > tbody > tr:hover > .active, .table-hover > tbody > tr.active:hover > th { + background-color: #e8e8e8; } + +.table > thead > tr > td.success, .table > thead > tr > th.success, .table > thead > tr.success > td, .table > thead > tr.success > th, .table > tbody > tr > td.success, .table > tbody > tr > th.success, .table > tbody > tr.success > td, .table > tbody > tr.success > th, .table > tfoot > tr > td.success, .table > tfoot > tr > th.success, .table > tfoot > tr.success > td, .table > tfoot > tr.success > th { + background-color: #dff0d8; } + +.table-hover > tbody > tr > td.success:hover, .table-hover > tbody > tr > th.success:hover, .table-hover > tbody > tr.success:hover > td, .table-hover > tbody > tr:hover > .success, .table-hover > tbody > tr.success:hover > th { + background-color: #d0e9c6; } + +.table > thead > tr > td.info, .table > thead > tr > th.info, .table > thead > tr.info > td, .table > thead > tr.info > th, .table > tbody > tr > td.info, .table > tbody > tr > th.info, .table > tbody > tr.info > td, .table > tbody > tr.info > th, .table > tfoot > tr > td.info, .table > tfoot > tr > th.info, .table > tfoot > tr.info > td, .table > tfoot > tr.info > th { + background-color: #d9edf7; } + +.table-hover > tbody > tr > td.info:hover, .table-hover > tbody > tr > th.info:hover, .table-hover > tbody > tr.info:hover > td, .table-hover > tbody > tr:hover > .info, .table-hover > tbody > tr.info:hover > th { + background-color: #c4e4f3; } + +.table > thead > tr > td.warning, .table > thead > tr > th.warning, .table > thead > tr.warning > td, .table > thead > tr.warning > th, .table > tbody > tr > td.warning, .table > tbody > tr > th.warning, .table > tbody > tr.warning > td, .table > tbody > tr.warning > th, .table > tfoot > tr > td.warning, .table > tfoot > tr > th.warning, .table > tfoot > tr.warning > td, .table > tfoot > tr.warning > th { + background-color: #fcf8e3; } + +.table-hover > tbody > tr > td.warning:hover, .table-hover > tbody > tr > th.warning:hover, .table-hover > tbody > tr.warning:hover > td, .table-hover > tbody > tr:hover > .warning, .table-hover > tbody > tr.warning:hover > th { + background-color: #faf2cc; } + +.table > thead > tr > td.danger, .table > thead > tr > th.danger, .table > thead > tr.danger > td, .table > thead > tr.danger > th, .table > tbody > tr > td.danger, .table > tbody > tr > th.danger, .table > tbody > tr.danger > td, .table > tbody > tr.danger > th, .table > tfoot > tr > td.danger, .table > tfoot > tr > th.danger, .table > tfoot > tr.danger > td, .table > tfoot > tr.danger > th { + background-color: #f2dede; } + +.table-hover > tbody > tr > td.danger:hover, .table-hover > tbody > tr > th.danger:hover, .table-hover > tbody > tr.danger:hover > td, .table-hover > tbody > tr:hover > .danger, .table-hover > tbody > tr.danger:hover > th { + background-color: #ebcccc; } + +@media screen and (max-width: 767px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-y: hidden; + overflow-x: scroll; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #ddd; + -webkit-overflow-scrolling: touch; } + .table-responsive > .table { + margin-bottom: 0; } + .table-responsive > .table > thead > tr > th, .table-responsive > .table > thead > tr > td, .table-responsive > .table > tbody > tr > th, .table-responsive > .table > tbody > tr > td, .table-responsive > .table > tfoot > tr > th, .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; } + .table-responsive > .table-bordered { + border: 0; } + .table-responsive > .table-bordered > thead > tr > th:first-child, .table-responsive > .table-bordered > thead > tr > td:first-child, .table-responsive > .table-bordered > tbody > tr > th:first-child, .table-responsive > .table-bordered > tbody > tr > td:first-child, .table-responsive > .table-bordered > tfoot > tr > th:first-child, .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; } + .table-responsive > .table-bordered > thead > tr > th:last-child, .table-responsive > .table-bordered > thead > tr > td:last-child, .table-responsive > .table-bordered > tbody > tr > th:last-child, .table-responsive > .table-bordered > tbody > tr > td:last-child, .table-responsive > .table-bordered > tfoot > tr > th:last-child, .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; } + .table-responsive > .table-bordered > tbody > tr:last-child > th, .table-responsive > .table-bordered > tbody > tr:last-child > td, .table-responsive > .table-bordered > tfoot > tr:last-child > th, .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; } } + +fieldset { + padding: 0; + margin: 0; + border: 0; + min-width: 0; } + +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333333; + border: 0; + border-bottom: 1px solid #e5e5e5; } + +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: bold; } + +input[type="search"] { + box-sizing: border-box; } + +input[type="radio"], input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + line-height: normal; } + +input[type="file"] { + display: block; } + +input[type="range"] { + display: block; + width: 100%; } + +select[multiple], select[size] { + height: auto; } + +input[type="file"]:focus, input[type="radio"]:focus, input[type="checkbox"]:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; } + +output { + display: block; + padding-top: 7px; + font-size: 14px; + line-height: 1.42857; + color: #555555; } + +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857; + color: #555555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; } + .form-control:focus { + border-color: #66afe9; + outline: 0; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6); } + .form-control::placeholder { + color: #999999; + opacity: 1; } + .form-control:-ms-input-placeholder { + color: #999999; } + .form-control::-webkit-input-placeholder { + color: #999999; } + .form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control { + cursor: not-allowed; + background-color: #eeeeee; + opacity: 1; } + +textarea.form-control { + height: auto; } + +input[type="search"] { + -webkit-appearance: none; } + +input[type="date"], input[type="time"], input[type="datetime-local"], input[type="month"] { + line-height: 34px; + line-height: 1.42857 \0; } + input[type="date"].input-sm, .input-group-sm > input[type="date"].form-control, .input-group-sm > input[type="date"].input-group-addon, .input-group-sm > .input-group-btn > input[type="date"].btn, input[type="time"].input-sm, .input-group-sm > input[type="time"].form-control, .input-group-sm > input[type="time"].input-group-addon, .input-group-sm > .input-group-btn > input[type="time"].btn, input[type="datetime-local"].input-sm, .input-group-sm > input[type="datetime-local"].form-control, .input-group-sm > input[type="datetime-local"].input-group-addon, .input-group-sm > .input-group-btn > input[type="datetime-local"].btn, input[type="month"].input-sm, .input-group-sm > input[type="month"].form-control, .input-group-sm > input[type="month"].input-group-addon, .input-group-sm > .input-group-btn > input[type="month"].btn { + line-height: 30px; } + input[type="date"].input-lg, .input-group-lg > input[type="date"].form-control, .input-group-lg > input[type="date"].input-group-addon, .input-group-lg > .input-group-btn > input[type="date"].btn, input[type="time"].input-lg, .input-group-lg > input[type="time"].form-control, .input-group-lg > input[type="time"].input-group-addon, .input-group-lg > .input-group-btn > input[type="time"].btn, input[type="datetime-local"].input-lg, .input-group-lg > input[type="datetime-local"].form-control, .input-group-lg > input[type="datetime-local"].input-group-addon, .input-group-lg > .input-group-btn > input[type="datetime-local"].btn, input[type="month"].input-lg, .input-group-lg > input[type="month"].form-control, .input-group-lg > input[type="month"].input-group-addon, .input-group-lg > .input-group-btn > input[type="month"].btn { + line-height: 46px; } + +.form-group { + margin-bottom: 15px; } + +.radio, .checkbox { + display: block; + min-height: 20px; + margin-top: 10px; + margin-bottom: 10px; } + .radio label, .checkbox label { + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; } + +.radio input[type="radio"], .radio-inline input[type="radio"], .checkbox input[type="checkbox"], .checkbox-inline input[type="checkbox"] { + float: left; + margin-left: -20px; } + +.radio + .radio, .checkbox + .checkbox { + margin-top: -5px; } + +.radio-inline, .checkbox-inline { + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + vertical-align: middle; + font-weight: normal; + cursor: pointer; } + +.radio-inline + .radio-inline, .checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; } + +input[type="radio"][disabled], fieldset[disabled] input[type="radio"], input[type="checkbox"][disabled], fieldset[disabled] input[type="checkbox"], .radio[disabled], fieldset[disabled] .radio, .radio-inline[disabled], fieldset[disabled] .radio-inline, .checkbox[disabled], fieldset[disabled] .checkbox, .checkbox-inline[disabled], fieldset[disabled] .checkbox-inline { + cursor: not-allowed; } + +.input-sm, .input-group-sm > .form-control, .input-group-sm > .input-group-addon, .input-group-sm > .input-group-btn > .btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; } + +select.input-sm, .input-group-sm > select.form-control, .input-group-sm > select.input-group-addon, .input-group-sm > .input-group-btn > select.btn { + height: 30px; + line-height: 30px; } + +textarea.input-sm, .input-group-sm > textarea.form-control, .input-group-sm > textarea.input-group-addon, .input-group-sm > .input-group-btn > textarea.btn, select[multiple].input-sm, .input-group-sm > select[multiple].form-control, .input-group-sm > select[multiple].input-group-addon, .input-group-sm > .input-group-btn > select[multiple].btn { + height: auto; } + +.input-lg, .input-group-lg > .form-control, .input-group-lg > .input-group-addon, .input-group-lg > .input-group-btn > .btn { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; } + +select.input-lg, .input-group-lg > select.form-control, .input-group-lg > select.input-group-addon, .input-group-lg > .input-group-btn > select.btn { + height: 46px; + line-height: 46px; } + +textarea.input-lg, .input-group-lg > textarea.form-control, .input-group-lg > textarea.input-group-addon, .input-group-lg > .input-group-btn > textarea.btn, select[multiple].input-lg, .input-group-lg > select[multiple].form-control, .input-group-lg > select[multiple].input-group-addon, .input-group-lg > .input-group-btn > select[multiple].btn { + height: auto; } + +.has-feedback { + position: relative; } + .has-feedback .form-control { + padding-right: 42.5px; } + +.form-control-feedback { + position: absolute; + top: 25px; + right: 0; + z-index: 2; + display: block; + width: 34px; + height: 34px; + line-height: 34px; + text-align: center; } + +.input-lg + .form-control-feedback, .input-lg + .input-group-lg > .form-control, .input-group-lg > .input-lg + .form-control, .input-lg + .input-group-lg > .input-group-addon, .input-group-lg > .input-lg + .input-group-addon, .input-lg + .input-group-lg > .input-group-btn > .btn, .input-group-lg > .input-group-btn > .input-lg + .btn { + width: 46px; + height: 46px; + line-height: 46px; } + +.input-sm + .form-control-feedback, .input-sm + .input-group-sm > .form-control, .input-group-sm > .input-sm + .form-control, .input-sm + .input-group-sm > .input-group-addon, .input-group-sm > .input-sm + .input-group-addon, .input-sm + .input-group-sm > .input-group-btn > .btn, .input-group-sm > .input-group-btn > .input-sm + .btn { + width: 30px; + height: 30px; + line-height: 30px; } + +.has-success .help-block, .has-success .control-label, .has-success .radio, .has-success .checkbox, .has-success .radio-inline, .has-success .checkbox-inline { + color: #3c763d; } +.has-success .form-control { + border-color: #3c763d; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); } + .has-success .form-control:focus { + border-color: #2b542b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168; } +.has-success .input-group-addon { + color: #3c763d; + border-color: #3c763d; + background-color: #dff0d8; } +.has-success .form-control-feedback { + color: #3c763d; } + +.has-warning .help-block, .has-warning .control-label, .has-warning .radio, .has-warning .checkbox, .has-warning .radio-inline, .has-warning .checkbox-inline { + color: #8a6d3b; } +.has-warning .form-control { + border-color: #8a6d3b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); } + .has-warning .form-control:focus { + border-color: #66502c; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c09f6b; } +.has-warning .input-group-addon { + color: #8a6d3b; + border-color: #8a6d3b; + background-color: #fcf8e3; } +.has-warning .form-control-feedback { + color: #8a6d3b; } + +.has-error .help-block, .has-error .control-label, .has-error .radio, .has-error .checkbox, .has-error .radio-inline, .has-error .checkbox-inline { + color: #a94442; } +.has-error .form-control { + border-color: #a94442; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); } + .has-error .form-control:focus { + border-color: #843534; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483; } +.has-error .input-group-addon { + color: #a94442; + border-color: #a94442; + background-color: #f2dede; } +.has-error .form-control-feedback { + color: #a94442; } + +.form-control-static { + margin-bottom: 0; } + +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373; } + +@media (min-width: 768px) { + .form-inline .form-group, .form-inline .navbar-form { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; } + .form-inline .form-control, .form-inline .navbar-form { + display: inline-block; + width: auto; + vertical-align: middle; } + .form-inline .input-group, .form-inline .navbar-form { + display: inline-table; + vertical-align: middle; } + .form-inline .input-group .input-group-addon, .form-inline .input-group .navbar-form, .form-inline .input-group .input-group-btn, .form-inline .input-group .navbar-form, .form-inline .input-group .form-control, .form-inline .input-group .navbar-form { + width: auto; } + .form-inline .input-group > .form-control, .form-inline .input-group > .navbar-form { + width: 100%; } + .form-inline .control-label, .form-inline .navbar-form { + margin-bottom: 0; + vertical-align: middle; } + .form-inline .radio, .form-inline .navbar-form, .form-inline .checkbox, .form-inline .navbar-form { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + padding-left: 0; + vertical-align: middle; } + .form-inline .radio input[type="radio"], .form-inline .radio .navbar-form, .form-inline .checkbox input[type="checkbox"], .form-inline .checkbox .navbar-form { + float: none; + margin-left: 0; } + .form-inline .has-feedback .form-control-feedback, .form-inline .has-feedback .navbar-form { + top: 0; } } + +.form-horizontal .radio, .form-horizontal .checkbox, .form-horizontal .radio-inline, .form-horizontal .checkbox-inline { + margin-top: 0; + margin-bottom: 0; + padding-top: 7px; } +.form-horizontal .radio, .form-horizontal .checkbox { + min-height: 27px; } +.form-horizontal .form-group { + margin-left: -15px; + margin-right: -15px; } + .form-horizontal .form-group:before, .form-horizontal .form-group:after { + content: " "; + display: table; } + .form-horizontal .form-group:after { + clear: both; } +.form-horizontal .form-control-static { + padding-top: 7px; + padding-bottom: 7px; } +@media (min-width: 768px) { + .form-horizontal .control-label { + text-align: right; + margin-bottom: 0; + padding-top: 7px; } } +.form-horizontal .has-feedback .form-control-feedback { + top: 0; + right: 15px; } + +.btn { + display: inline-block; + margin-bottom: 0; + font-weight: normal; + text-align: center; + vertical-align: middle; + cursor: pointer; + background-image: none; + border: 1px solid transparent; + white-space: nowrap; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857; + border-radius: 4px; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; } + .btn:focus, .btn:active:focus, .btn.active:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; } + .btn:hover, .btn:focus { + color: #333; + text-decoration: none; } + .btn:active, .btn.active { + outline: 0; + background-image: none; + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); } + .btn.disabled, .btn[disabled], fieldset[disabled] .btn { + cursor: not-allowed; + pointer-events: none; + opacity: 0.65; + filter: alpha(opacity=65); + box-shadow: none; } + +.btn-default { + color: #333; + background-color: #fff; + border-color: #ccc; } + .btn-default:hover, .btn-default:focus, .btn-default:active, .btn-default.active, .open > .btn-default.dropdown-toggle { + color: #333; + background-color: #e6e6e6; + border-color: #adadad; } + .btn-default:active, .btn-default.active, .open > .btn-default.dropdown-toggle { + background-image: none; } + .btn-default.disabled, .btn-default.disabled:hover, .btn-default.disabled:focus, .btn-default.disabled:active, .btn-default.disabled.active, .btn-default[disabled], .btn-default[disabled]:hover, .btn-default[disabled]:focus, .btn-default[disabled]:active, .btn-default[disabled].active, fieldset[disabled] .btn-default, fieldset[disabled] .btn-default:hover, fieldset[disabled] .btn-default:focus, fieldset[disabled] .btn-default:active, fieldset[disabled] .btn-default.active { + background-color: #fff; + border-color: #ccc; } + .btn-default .badge { + color: #fff; + background-color: #333; } + +.btn-primary { + color: #fff; + background-color: #428bca; + border-color: #3580bd; } + .btn-primary:hover, .btn-primary:focus, .btn-primary:active, .btn-primary.active, .open > .btn-primary.dropdown-toggle { + color: #fff; + background-color: #3073a9; + border-color: #28608e; } + .btn-primary:active, .btn-primary.active, .open > .btn-primary.dropdown-toggle { + background-image: none; } + .btn-primary.disabled, .btn-primary.disabled:hover, .btn-primary.disabled:focus, .btn-primary.disabled:active, .btn-primary.disabled.active, .btn-primary[disabled], .btn-primary[disabled]:hover, .btn-primary[disabled]:focus, .btn-primary[disabled]:active, .btn-primary[disabled].active, fieldset[disabled] .btn-primary, fieldset[disabled] .btn-primary:hover, fieldset[disabled] .btn-primary:focus, fieldset[disabled] .btn-primary:active, fieldset[disabled] .btn-primary.active { + background-color: #428bca; + border-color: #3580bd; } + .btn-primary .badge { + color: #428bca; + background-color: #fff; } + +.btn-success { + color: #fff; + background-color: #5cb85c; + border-color: #4eae4c; } + .btn-success:hover, .btn-success:focus, .btn-success:active, .btn-success.active, .open > .btn-success.dropdown-toggle { + color: #fff; + background-color: #469d44; + border-color: #3b8439; } + .btn-success:active, .btn-success.active, .open > .btn-success.dropdown-toggle { + background-image: none; } + .btn-success.disabled, .btn-success.disabled:hover, .btn-success.disabled:focus, .btn-success.disabled:active, .btn-success.disabled.active, .btn-success[disabled], .btn-success[disabled]:hover, .btn-success[disabled]:focus, .btn-success[disabled]:active, .btn-success[disabled].active, fieldset[disabled] .btn-success, fieldset[disabled] .btn-success:hover, fieldset[disabled] .btn-success:focus, fieldset[disabled] .btn-success:active, fieldset[disabled] .btn-success.active { + background-color: #5cb85c; + border-color: #4eae4c; } + .btn-success .badge { + color: #5cb85c; + background-color: #fff; } + +.btn-info { + color: #fff; + background-color: #5bc0de; + border-color: #46bada; } + .btn-info:hover, .btn-info:focus, .btn-info:active, .btn-info.active, .open > .btn-info.dropdown-toggle { + color: #fff; + background-color: #31b2d5; + border-color: #269cbc; } + .btn-info:active, .btn-info.active, .open > .btn-info.dropdown-toggle { + background-image: none; } + .btn-info.disabled, .btn-info.disabled:hover, .btn-info.disabled:focus, .btn-info.disabled:active, .btn-info.disabled.active, .btn-info[disabled], .btn-info[disabled]:hover, .btn-info[disabled]:focus, .btn-info[disabled]:active, .btn-info[disabled].active, fieldset[disabled] .btn-info, fieldset[disabled] .btn-info:hover, fieldset[disabled] .btn-info:focus, fieldset[disabled] .btn-info:active, fieldset[disabled] .btn-info.active { + background-color: #5bc0de; + border-color: #46bada; } + .btn-info .badge { + color: #5bc0de; + background-color: #fff; } + +.btn-warning { + color: #fff; + background-color: #f0ad4e; + border-color: #eea236; } + .btn-warning:hover, .btn-warning:focus, .btn-warning:active, .btn-warning.active, .open > .btn-warning.dropdown-toggle { + color: #fff; + background-color: #ec971f; + border-color: #d58112; } + .btn-warning:active, .btn-warning.active, .open > .btn-warning.dropdown-toggle { + background-image: none; } + .btn-warning.disabled, .btn-warning.disabled:hover, .btn-warning.disabled:focus, .btn-warning.disabled:active, .btn-warning.disabled.active, .btn-warning[disabled], .btn-warning[disabled]:hover, .btn-warning[disabled]:focus, .btn-warning[disabled]:active, .btn-warning[disabled].active, fieldset[disabled] .btn-warning, fieldset[disabled] .btn-warning:hover, fieldset[disabled] .btn-warning:focus, fieldset[disabled] .btn-warning:active, fieldset[disabled] .btn-warning.active { + background-color: #f0ad4e; + border-color: #eea236; } + .btn-warning .badge { + color: #f0ad4e; + background-color: #fff; } + +.btn-danger { + color: #fff; + background-color: #d9534f; + border-color: #d43d3a; } + .btn-danger:hover, .btn-danger:focus, .btn-danger:active, .btn-danger.active, .open > .btn-danger.dropdown-toggle { + color: #fff; + background-color: #c92e2c; + border-color: #ac2525; } + .btn-danger:active, .btn-danger.active, .open > .btn-danger.dropdown-toggle { + background-image: none; } + .btn-danger.disabled, .btn-danger.disabled:hover, .btn-danger.disabled:focus, .btn-danger.disabled:active, .btn-danger.disabled.active, .btn-danger[disabled], .btn-danger[disabled]:hover, .btn-danger[disabled]:focus, .btn-danger[disabled]:active, .btn-danger[disabled].active, fieldset[disabled] .btn-danger, fieldset[disabled] .btn-danger:hover, fieldset[disabled] .btn-danger:focus, fieldset[disabled] .btn-danger:active, fieldset[disabled] .btn-danger.active { + background-color: #d9534f; + border-color: #d43d3a; } + .btn-danger .badge { + color: #d9534f; + background-color: #fff; } + +.btn-link { + color: #428bca; + font-weight: normal; + cursor: pointer; + border-radius: 0; } + .btn-link, .btn-link:active, .btn-link[disabled], fieldset[disabled] .btn-link { + background-color: transparent; + box-shadow: none; } + .btn-link, .btn-link:hover, .btn-link:focus, .btn-link:active { + border-color: transparent; } + .btn-link:hover, .btn-link:focus { + color: #2a6596; + text-decoration: underline; + background-color: transparent; } + .btn-link[disabled]:hover, .btn-link[disabled]:focus, fieldset[disabled] .btn-link:hover, fieldset[disabled] .btn-link:focus { + color: #999999; + text-decoration: none; } + +.btn-lg, .btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.33; + border-radius: 6px; } + +.btn-sm, .btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; } + +.btn-xs, .btn-group-xs > .btn { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; } + +.btn-block { + display: block; + width: 100%; + padding-left: 0; + padding-right: 0; } + +.btn-block + .btn-block { + margin-top: 5px; } + +input[type="submit"].btn-block, input[type="reset"].btn-block, input[type="button"].btn-block { + width: 100%; } + +.fade { + opacity: 0; + transition: opacity 0.15s linear; } + .fade.in { + opacity: 1; } + +.collapse { + display: none; } + .collapse.in { + display: block; } + +tr.collapse.in { + display: table-row; } + +tbody.collapse.in { + display: table-row-group; } + +.collapsing { + position: relative; + height: 0; + overflow: hidden; + transition: height 0.35s ease; } + +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px solid; + border-right: 4px solid transparent; + border-left: 4px solid transparent; } + +.dropdown { + position: relative; } + +.dropdown-toggle:focus { + outline: 0; } + +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + list-style: none; + font-size: 14px; + text-align: left; + background-color: #fff; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 4px; + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + background-clip: padding-box; } + .dropdown-menu.pull-right { + right: 0; + left: auto; } + .dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; } + .dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.42857; + color: #333333; + white-space: nowrap; } + +.dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus { + text-decoration: none; + color: #262626; + background-color: #f5f5f5; } + +.dropdown-menu > .active > a, .dropdown-menu > .active > a:hover, .dropdown-menu > .active > a:focus { + color: #fff; + text-decoration: none; + outline: 0; + background-color: #428bca; } + +.dropdown-menu > .disabled > a, .dropdown-menu > .disabled > a:hover, .dropdown-menu > .disabled > a:focus { + color: #999999; } + +.dropdown-menu > .disabled > a:hover, .dropdown-menu > .disabled > a:focus { + text-decoration: none; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + cursor: not-allowed; } + +.open > .dropdown-menu { + display: block; } +.open > a { + outline: 0; } + +.dropdown-menu-right { + left: auto; + right: 0; } + +.dropdown-menu-left { + left: 0; + right: auto; } + +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.42857; + color: #999999; } + +.dropdown-backdrop { + position: fixed; + left: 0; + right: 0; + bottom: 0; + top: 0; + z-index: 990; } + +.pull-right > .dropdown-menu { + right: 0; + left: auto; } + +.dropup .caret, .navbar-fixed-bottom .dropdown .caret { + border-top: 0; + border-bottom: 4px solid; + content: ""; } +.dropup .dropdown-menu, .navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 1px; } + +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + right: 0; + left: auto; } + .navbar-right .dropdown-menu-left { + left: 0; + right: auto; } } + +.btn-group, .btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; } + .btn-group > .btn, .btn-group-vertical > .btn { + position: relative; + float: left; } + .btn-group > .btn:hover, .btn-group > .btn:focus, .btn-group > .btn:active, .btn-group > .btn.active, .btn-group-vertical > .btn:hover, .btn-group-vertical > .btn:focus, .btn-group-vertical > .btn:active, .btn-group-vertical > .btn.active { + z-index: 2; } + .btn-group > .btn:focus, .btn-group-vertical > .btn:focus { + outline: 0; } + +.btn-group .btn + .btn, .btn-group .btn + .btn-group, .btn-group .btn-group + .btn, .btn-group .btn-group + .btn-group { + margin-left: -1px; } + +.btn-toolbar { + margin-left: -5px; } + .btn-toolbar:before, .btn-toolbar:after { + content: " "; + display: table; } + .btn-toolbar:after { + clear: both; } + .btn-toolbar .btn-group, .btn-toolbar .input-group { + float: left; } + .btn-toolbar > .btn, .btn-toolbar > .btn-group, .btn-toolbar > .input-group { + margin-left: 5px; } + +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; } + +.btn-group > .btn:first-child { + margin-left: 0; } + .btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-bottom-right-radius: 0; + border-top-right-radius: 0; } + +.btn-group > .btn:last-child:not(:first-child), .btn-group > .dropdown-toggle:not(:first-child) { + border-bottom-left-radius: 0; + border-top-left-radius: 0; } + +.btn-group > .btn-group { + float: left; } + +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; } + +.btn-group > .btn-group:first-child > .btn:last-child, .btn-group > .btn-group:first-child > .dropdown-toggle { + border-bottom-right-radius: 0; + border-top-right-radius: 0; } + +.btn-group > .btn-group:last-child > .btn:first-child { + border-bottom-left-radius: 0; + border-top-left-radius: 0; } + +.btn-group .dropdown-toggle:active, .btn-group.open .dropdown-toggle { + outline: 0; } + +.btn-group > .btn + .dropdown-toggle { + padding-left: 8px; + padding-right: 8px; } + +.btn-group > .btn-lg + .dropdown-toggle, .btn-group > .btn-lg + .btn-group-lg > .btn, .btn-group-lg > .btn-group > .btn-lg + .btn { + padding-left: 12px; + padding-right: 12px; } + +.btn-group.open .dropdown-toggle { + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); } + .btn-group.open .dropdown-toggle.btn-link { + box-shadow: none; } + +.btn .caret { + margin-left: 0; } + +.btn-lg .caret, .btn-lg .btn-group-lg > .btn, .btn-group-lg > .btn-lg .btn { + border-width: 5px 5px 0; + border-bottom-width: 0; } + +.dropup .btn-lg .caret, .dropup .btn-lg .btn-group-lg > .btn, .btn-group-lg > .dropup .btn-lg .btn { + border-width: 0 5px 5px; } + +.btn-group-vertical > .btn, .btn-group-vertical > .btn-group, .btn-group-vertical > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; } +.btn-group-vertical > .btn-group:before, .btn-group-vertical > .btn-group:after { + content: " "; + display: table; } +.btn-group-vertical > .btn-group:after { + clear: both; } +.btn-group-vertical > .btn-group > .btn { + float: none; } +.btn-group-vertical > .btn + .btn, .btn-group-vertical > .btn + .btn-group, .btn-group-vertical > .btn-group + .btn, .btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; } + +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; } +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; } +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-bottom-left-radius: 4px; + border-top-right-radius: 0; + border-top-left-radius: 0; } + +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; } + +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, .btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; } + +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-right-radius: 0; + border-top-left-radius: 0; } + +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; } + .btn-group-justified > .btn, .btn-group-justified > .btn-group { + float: none; + display: table-cell; + width: 1%; } + .btn-group-justified > .btn-group .btn { + width: 100%; } + +[data-toggle="buttons"] > .btn > input[type="radio"], [data-toggle="buttons"] > .btn > input[type="checkbox"] { + position: absolute; + z-index: -1; + opacity: 0; } + +.input-group { + position: relative; + display: table; + border-collapse: separate; } + .input-group[class*="col-"] { + float: none; + padding-left: 0; + padding-right: 0; } + .input-group .form-control { + position: relative; + z-index: 2; + float: left; + width: 100%; + margin-bottom: 0; } + +.input-group-addon, .input-group-btn, .input-group .form-control { + display: table-cell; } + .input-group-addon:not(:first-child):not(:last-child), .input-group-btn:not(:first-child):not(:last-child), .input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; } + +.input-group-addon, .input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; } + +.input-group-addon { + padding: 6px 12px; + font-size: 14px; + font-weight: normal; + line-height: 1; + color: #555555; + text-align: center; + background-color: #eeeeee; + border: 1px solid #ccc; + border-radius: 4px; } + .input-group-addon.input-sm, .input-group-sm > .input-group-addon.form-control, .input-group-sm > .input-group-addon, .input-group-sm > .input-group-btn > .input-group-addon.btn { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px; } + .input-group-addon.input-lg, .input-group-lg > .input-group-addon.form-control, .input-group-lg > .input-group-addon, .input-group-lg > .input-group-btn > .input-group-addon.btn { + padding: 10px 16px; + font-size: 18px; + border-radius: 6px; } + .input-group-addon input[type="radio"], .input-group-addon input[type="checkbox"] { + margin-top: 0; } + +.input-group .form-control:first-child, .input-group-addon:first-child, .input-group-btn:first-child > .btn, .input-group-btn:first-child > .btn-group > .btn, .input-group-btn:first-child > .dropdown-toggle, .input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), .input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + border-bottom-right-radius: 0; + border-top-right-radius: 0; } + +.input-group-addon:first-child { + border-right: 0; } + +.input-group .form-control:last-child, .input-group-addon:last-child, .input-group-btn:last-child > .btn, .input-group-btn:last-child > .btn-group > .btn, .input-group-btn:last-child > .dropdown-toggle, .input-group-btn:first-child > .btn:not(:first-child), .input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + border-bottom-left-radius: 0; + border-top-left-radius: 0; } + +.input-group-addon:last-child { + border-left: 0; } + +.input-group-btn { + position: relative; + font-size: 0; + white-space: nowrap; } + .input-group-btn > .btn { + position: relative; } + .input-group-btn > .btn + .btn { + margin-left: -1px; } + .input-group-btn > .btn:hover, .input-group-btn > .btn:focus, .input-group-btn > .btn:active { + z-index: 2; } + .input-group-btn:first-child > .btn, .input-group-btn:first-child > .btn-group { + margin-right: -1px; } + .input-group-btn:last-child > .btn, .input-group-btn:last-child > .btn-group { + margin-left: -1px; } + +.nav { + margin-bottom: 0; + padding-left: 0; + list-style: none; } + .nav:before, .nav:after { + content: " "; + display: table; } + .nav:after { + clear: both; } + .nav > li { + position: relative; + display: block; } + .nav > li > a { + position: relative; + display: block; + padding: 10px 15px; } + .nav > li > a:hover, .nav > li > a:focus { + text-decoration: none; + background-color: #eeeeee; } + .nav > li.disabled > a { + color: #999999; } + .nav > li.disabled > a:hover, .nav > li.disabled > a:focus { + color: #999999; + text-decoration: none; + background-color: transparent; + cursor: not-allowed; } + .nav .open > a, .nav .open > a:hover, .nav .open > a:focus { + background-color: #eeeeee; + border-color: #428bca; } + .nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; } + .nav > li > a > img { + max-width: none; } + +.nav-tabs { + border-bottom: 1px solid #ddd; } + .nav-tabs > li { + float: left; + margin-bottom: -1px; } + .nav-tabs > li > a { + margin-right: 2px; + line-height: 1.42857; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; } + .nav-tabs > li > a:hover { + border-color: #eeeeee #eeeeee #ddd; } + .nav-tabs > li.active > a, .nav-tabs > li.active > a:hover, .nav-tabs > li.active > a:focus { + color: #555555; + background-color: #fff; + border: 1px solid #ddd; + border-bottom-color: transparent; + cursor: default; } + +.nav-pills > li { + float: left; } + .nav-pills > li > a { + border-radius: 4px; } + .nav-pills > li + li { + margin-left: 2px; } + .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus { + color: #fff; + background-color: #428bca; } + +.nav-stacked > li { + float: none; } + .nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; } + +.nav-justified, .nav-tabs.nav-justified { + width: 100%; } + .nav-justified > li, .nav-justified > .nav-tabs.nav-justified { + float: none; } + .nav-justified > li > a, .nav-justified > li > .nav-tabs.nav-justified { + text-align: center; + margin-bottom: 5px; } + .nav-justified > .dropdown .dropdown-menu, .nav-justified > .dropdown .nav-tabs.nav-justified { + top: auto; + left: auto; } + @media (min-width: 768px) { + .nav-justified > li, .nav-justified > .nav-tabs.nav-justified { + display: table-cell; + width: 1%; } + .nav-justified > li > a, .nav-justified > li > .nav-tabs.nav-justified { + margin-bottom: 0; } } + +.nav-tabs-justified, .nav-tabs.nav-justified, .nav-tabs.nav-justified { + border-bottom: 0; } + .nav-tabs-justified > li > a, .nav-tabs-justified > li > .nav-tabs.nav-justified, .nav-tabs-justified > li > .nav-tabs.nav-justified { + margin-right: 0; + border-radius: 4px; } + .nav-tabs-justified > .active > a, .nav-tabs-justified > .active > .nav-tabs.nav-justified, .nav-tabs-justified > .active > .nav-tabs.nav-justified, .nav-tabs-justified > .active > a:hover, .nav-tabs-justified > .active > .nav-tabs.nav-justified, .nav-tabs-justified > .active > .nav-tabs.nav-justified, .nav-tabs-justified > .active > a:focus, .nav-tabs-justified > .active > .nav-tabs.nav-justified, .nav-tabs-justified > .active > .nav-tabs.nav-justified { + border: 1px solid #ddd; } + @media (min-width: 768px) { + .nav-tabs-justified > li > a, .nav-tabs-justified > li > .nav-tabs.nav-justified, .nav-tabs-justified > li > .nav-tabs.nav-justified { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; } + .nav-tabs-justified > .active > a, .nav-tabs-justified > .active > .nav-tabs.nav-justified, .nav-tabs-justified > .active > .nav-tabs.nav-justified, .nav-tabs-justified > .active > a:hover, .nav-tabs-justified > .active > .nav-tabs.nav-justified, .nav-tabs-justified > .active > .nav-tabs.nav-justified, .nav-tabs-justified > .active > a:focus, .nav-tabs-justified > .active > .nav-tabs.nav-justified, .nav-tabs-justified > .active > .nav-tabs.nav-justified { + border-bottom-color: #fff; } } + +.tab-content > .tab-pane { + display: none; } +.tab-content > .active { + display: block; } + +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-right-radius: 0; + border-top-left-radius: 0; } + +.navbar { + position: relative; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent; } + .navbar:before, .navbar:after { + content: " "; + display: table; } + .navbar:after { + clear: both; } + @media (min-width: 768px) { + .navbar { + border-radius: 4px; } } + +.navbar-header:before, .navbar-header:after { + content: " "; + display: table; } +.navbar-header:after { + clear: both; } +@media (min-width: 768px) { + .navbar-header { + float: left; } } + +.navbar-collapse { + overflow-x: visible; + padding-right: 15px; + padding-left: 15px; + border-top: 1px solid transparent; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1); + -webkit-overflow-scrolling: touch; } + .navbar-collapse:before, .navbar-collapse:after { + content: " "; + display: table; } + .navbar-collapse:after { + clear: both; } + .navbar-collapse.in { + overflow-y: auto; } + @media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + box-shadow: none; } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; } + .navbar-collapse.in { + overflow-y: visible; } + .navbar-fixed-top .navbar-collapse, .navbar-static-top .navbar-collapse, .navbar-fixed-bottom .navbar-collapse { + padding-left: 0; + padding-right: 0; } } + +.navbar-fixed-top .navbar-collapse, .navbar-fixed-bottom .navbar-collapse { + max-height: 340px; } + @media (max-width: 480px) and (orientation: landscape) { + .navbar-fixed-top .navbar-collapse, .navbar-fixed-bottom .navbar-collapse { + max-height: 200px; } } + +.container > .navbar-header, .container > .navbar-collapse, .container-fluid > .navbar-header, .container-fluid > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; } + @media (min-width: 768px) { + .container > .navbar-header, .container > .navbar-collapse, .container-fluid > .navbar-header, .container-fluid > .navbar-collapse { + margin-right: 0; + margin-left: 0; } } + +.navbar-static-top { + z-index: 1000; + border-width: 0 0 1px; } + @media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; } } + +.navbar-fixed-top, .navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; } + @media (min-width: 768px) { + .navbar-fixed-top, .navbar-fixed-bottom { + border-radius: 0; } } + +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; } + +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; + border-width: 1px 0 0; } + +.navbar-brand { + float: left; + padding: 15px 15px; + font-size: 18px; + line-height: 20px; + height: 50px; } + .navbar-brand:hover, .navbar-brand:focus { + text-decoration: none; } + @media (min-width: 768px) { + .navbar > .container .navbar-brand, .navbar > .container-fluid .navbar-brand { + margin-left: -15px; } } + +.navbar-toggle { + position: relative; + float: right; + margin-right: 15px; + padding: 9px 10px; + margin-top: 8px; + margin-bottom: 8px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; } + .navbar-toggle:focus { + outline: 0; } + .navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; } + .navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; } + @media (min-width: 768px) { + .navbar-toggle { + display: none; } } + +.navbar-nav { + margin: 7.5px -15px; } + .navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; } + @media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + box-shadow: none; } + .navbar-nav .open .dropdown-menu > li > a, .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; } + .navbar-nav .open .dropdown-menu > li > a:hover, .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; } } + @media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; } + .navbar-nav > li { + float: left; } + .navbar-nav > li > a { + padding-top: 15px; + padding-bottom: 15px; } + .navbar-nav.navbar-right:last-child { + margin-right: -15px; } } + +@media (min-width: 768px) { + .navbar-left { + float: left !important; } + .navbar-right { + float: right !important; } } + +.navbar-form { + margin-left: -15px; + margin-right: -15px; + padding: 10px 15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1); + margin-top: 8px; + margin-bottom: 8px; } + @media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; } } + @media (min-width: 768px) { + .navbar-form { + width: auto; + border: 0; + margin-left: 0; + margin-right: 0; + padding-top: 0; + padding-bottom: 0; + box-shadow: none; } + .navbar-form.navbar-right:last-child { + margin-right: -15px; } } + +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-right-radius: 0; + border-top-left-radius: 0; } + +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; } + +.navbar-btn { + margin-top: 8px; + margin-bottom: 8px; } + .navbar-btn.btn-sm, .btn-group-sm > .navbar-btn.btn { + margin-top: 10px; + margin-bottom: 10px; } + .navbar-btn.btn-xs, .btn-group-xs > .navbar-btn.btn { + margin-top: 14px; + margin-bottom: 14px; } + +.navbar-text { + margin-top: 15px; + margin-bottom: 15px; } + @media (min-width: 768px) { + .navbar-text { + float: left; + margin-left: 15px; + margin-right: 15px; } + .navbar-text.navbar-right:last-child { + margin-right: 0; } } + +.navbar-default { + background-color: #f8f8f8; + border-color: #e7e7e7; } + .navbar-default .navbar-brand { + color: #777; } + .navbar-default .navbar-brand:hover, .navbar-default .navbar-brand:focus { + color: #5e5e5e; + background-color: transparent; } + .navbar-default .navbar-text { + color: #777; } + .navbar-default .navbar-nav > li > a { + color: #777; } + .navbar-default .navbar-nav > li > a:hover, .navbar-default .navbar-nav > li > a:focus { + color: #333; + background-color: transparent; } + .navbar-default .navbar-nav > .active > a, .navbar-default .navbar-nav > .active > a:hover, .navbar-default .navbar-nav > .active > a:focus { + color: #555; + background-color: #e7e7e7; } + .navbar-default .navbar-nav > .disabled > a, .navbar-default .navbar-nav > .disabled > a:hover, .navbar-default .navbar-nav > .disabled > a:focus { + color: #ccc; + background-color: transparent; } + .navbar-default .navbar-toggle { + border-color: #ddd; } + .navbar-default .navbar-toggle:hover, .navbar-default .navbar-toggle:focus { + background-color: #ddd; } + .navbar-default .navbar-toggle .icon-bar { + background-color: #888; } + .navbar-default .navbar-collapse, .navbar-default .navbar-form { + border-color: #e7e7e7; } + .navbar-default .navbar-nav > .open > a, .navbar-default .navbar-nav > .open > a:hover, .navbar-default .navbar-nav > .open > a:focus { + background-color: #e7e7e7; + color: #555; } + @media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #777; } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #333; + background-color: transparent; } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #555; + background-color: #e7e7e7; } + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #ccc; + background-color: transparent; } } + .navbar-default .navbar-link { + color: #777; } + .navbar-default .navbar-link:hover { + color: #333; } + .navbar-default .btn-link { + color: #777; } + .navbar-default .btn-link:hover, .navbar-default .btn-link:focus { + color: #333; } + .navbar-default .btn-link[disabled]:hover, .navbar-default .btn-link[disabled]:focus, fieldset[disabled] .navbar-default .btn-link:hover, fieldset[disabled] .navbar-default .btn-link:focus { + color: #ccc; } + +.navbar-inverse { + background-color: #222; + border-color: #090909; } + .navbar-inverse .navbar-brand { + color: #999999; } + .navbar-inverse .navbar-brand:hover, .navbar-inverse .navbar-brand:focus { + color: #fff; + background-color: transparent; } + .navbar-inverse .navbar-text { + color: #999999; } + .navbar-inverse .navbar-nav > li > a { + color: #999999; } + .navbar-inverse .navbar-nav > li > a:hover, .navbar-inverse .navbar-nav > li > a:focus { + color: #fff; + background-color: transparent; } + .navbar-inverse .navbar-nav > .active > a, .navbar-inverse .navbar-nav > .active > a:hover, .navbar-inverse .navbar-nav > .active > a:focus { + color: #fff; + background-color: #090909; } + .navbar-inverse .navbar-nav > .disabled > a, .navbar-inverse .navbar-nav > .disabled > a:hover, .navbar-inverse .navbar-nav > .disabled > a:focus { + color: #444; + background-color: transparent; } + .navbar-inverse .navbar-toggle { + border-color: #333; } + .navbar-inverse .navbar-toggle:hover, .navbar-inverse .navbar-toggle:focus { + background-color: #333; } + .navbar-inverse .navbar-toggle .icon-bar { + background-color: #fff; } + .navbar-inverse .navbar-collapse, .navbar-inverse .navbar-form { + border-color: #101010; } + .navbar-inverse .navbar-nav > .open > a, .navbar-inverse .navbar-nav > .open > a:hover, .navbar-inverse .navbar-nav > .open > a:focus { + background-color: #090909; + color: #fff; } + @media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #090909; } + .navbar-inverse .navbar-nav .open .dropdown-menu .divider { + background-color: #090909; } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #999999; } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #fff; + background-color: transparent; } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #fff; + background-color: #090909; } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #444; + background-color: transparent; } } + .navbar-inverse .navbar-link { + color: #999999; } + .navbar-inverse .navbar-link:hover { + color: #fff; } + .navbar-inverse .btn-link { + color: #999999; } + .navbar-inverse .btn-link:hover, .navbar-inverse .btn-link:focus { + color: #fff; } + .navbar-inverse .btn-link[disabled]:hover, .navbar-inverse .btn-link[disabled]:focus, fieldset[disabled] .navbar-inverse .btn-link:hover, fieldset[disabled] .navbar-inverse .btn-link:focus { + color: #444; } + +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; } + .breadcrumb > li { + display: inline-block; } + .breadcrumb > li + li:before { + content: "/\00a0"; + padding: 0 5px; + color: #ccc; } + .breadcrumb > .active { + color: #999999; } + +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; } + .pagination > li { + display: inline; } + .pagination > li > a, .pagination > li > span { + position: relative; + float: left; + padding: 6px 12px; + line-height: 1.42857; + text-decoration: none; + color: #428bca; + background-color: #fff; + border: 1px solid #ddd; + margin-left: -1px; } + .pagination > li:first-child > a, .pagination > li:first-child > span { + margin-left: 0; + border-bottom-left-radius: 4px; + border-top-left-radius: 4px; } + .pagination > li:last-child > a, .pagination > li:last-child > span { + border-bottom-right-radius: 4px; + border-top-right-radius: 4px; } + .pagination > li > a:hover, .pagination > li > a:focus, .pagination > li > span:hover, .pagination > li > span:focus { + color: #2a6596; + background-color: #eeeeee; + border-color: #ddd; } + .pagination > .active > a, .pagination > .active > a:hover, .pagination > .active > a:focus, .pagination > .active > span, .pagination > .active > span:hover, .pagination > .active > span:focus { + z-index: 2; + color: #fff; + background-color: #428bca; + border-color: #428bca; + cursor: default; } + .pagination > .disabled > span, .pagination > .disabled > span:hover, .pagination > .disabled > span:focus, .pagination > .disabled > a, .pagination > .disabled > a:hover, .pagination > .disabled > a:focus { + color: #999999; + background-color: #fff; + border-color: #ddd; + cursor: not-allowed; } + +.pagination-lg > li > a, .pagination-lg > li > span { + padding: 10px 16px; + font-size: 18px; } +.pagination-lg > li:first-child > a, .pagination-lg > li:first-child > span { + border-bottom-left-radius: 6px; + border-top-left-radius: 6px; } +.pagination-lg > li:last-child > a, .pagination-lg > li:last-child > span { + border-bottom-right-radius: 6px; + border-top-right-radius: 6px; } + +.pagination-sm > li > a, .pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; } +.pagination-sm > li:first-child > a, .pagination-sm > li:first-child > span { + border-bottom-left-radius: 3px; + border-top-left-radius: 3px; } +.pagination-sm > li:last-child > a, .pagination-sm > li:last-child > span { + border-bottom-right-radius: 3px; + border-top-right-radius: 3px; } + +.pager { + padding-left: 0; + margin: 20px 0; + list-style: none; + text-align: center; } + .pager:before, .pager:after { + content: " "; + display: table; } + .pager:after { + clear: both; } + .pager li { + display: inline; } + .pager li > a, .pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 15px; } + .pager li > a:hover, .pager li > a:focus { + text-decoration: none; + background-color: #eeeeee; } + .pager .next > a, .pager .next > span { + float: right; } + .pager .previous > a, .pager .previous > span { + float: left; } + .pager .disabled > a, .pager .disabled > a:hover, .pager .disabled > a:focus, .pager .disabled > span { + color: #999999; + background-color: #fff; + cursor: not-allowed; } + +.label { + display: inline; + padding: 0.2em 0.6em 0.3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: 0.25em; } + .label:empty { + display: none; } + .btn .label { + position: relative; + top: -1px; } + +a.label:hover, a.label:focus { + color: #fff; + text-decoration: none; + cursor: pointer; } + +.label-default { + background-color: #999999; } + .label-default[href]:hover, .label-default[href]:focus { + background-color: #808080; } + +.label-primary { + background-color: #428bca; } + .label-primary[href]:hover, .label-primary[href]:focus { + background-color: #3073a9; } + +.label-success { + background-color: #5cb85c; } + .label-success[href]:hover, .label-success[href]:focus { + background-color: #469d44; } + +.label-info { + background-color: #5bc0de; } + .label-info[href]:hover, .label-info[href]:focus { + background-color: #31b2d5; } + +.label-warning { + background-color: #f0ad4e; } + .label-warning[href]:hover, .label-warning[href]:focus { + background-color: #ec971f; } + +.label-danger { + background-color: #d9534f; } + .label-danger[href]:hover, .label-danger[href]:focus { + background-color: #c92e2c; } + +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + color: #fff; + line-height: 1; + vertical-align: baseline; + white-space: nowrap; + text-align: center; + background-color: #999999; + border-radius: 10px; } + .badge:empty { + display: none; } + .btn .badge { + position: relative; + top: -1px; } + .btn-xs .badge, .btn-xs .btn-group-xs > .btn, .btn-group-xs > .btn-xs .btn { + top: 0; + padding: 1px 5px; } + a.list-group-item.active > .badge, .nav-pills > .active > a > .badge { + color: #428bca; + background-color: #fff; } + .nav-pills > li > a > .badge { + margin-left: 3px; } + +a.badge:hover, a.badge:focus { + color: #fff; + text-decoration: none; + cursor: pointer; } + +.jumbotron { + padding: 30px; + margin-bottom: 30px; + color: inherit; + background-color: #eeeeee; } + .jumbotron h1, .jumbotron .h1 { + color: inherit; } + .jumbotron p { + margin-bottom: 15px; + font-size: 21px; + font-weight: 200; } + .jumbotron > hr { + border-top-color: #d5d5d5; } + .container .jumbotron { + border-radius: 6px; } + .jumbotron .container { + max-width: 100%; } + @media screen and (min-width: 768px) { + .jumbotron { + padding-top: 48px; + padding-bottom: 48px; } + .container .jumbotron { + padding-left: 60px; + padding-right: 60px; } + .jumbotron h1, .jumbotron .h1 { + font-size: 63px; } } + +.thumbnail { + display: block; + padding: 4px; + margin-bottom: 20px; + line-height: 1.42857; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + transition: all 0.2s ease-in-out; } + .thumbnail > img, .thumbnail a > img { + display: block; + max-width: 100%; + height: auto; + margin-left: auto; + margin-right: auto; } + .thumbnail .caption { + padding: 9px; + color: #333333; } + +a.thumbnail:hover, a.thumbnail:focus, a.thumbnail.active { + border-color: #428bca; } + +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; } + .alert h4 { + margin-top: 0; + color: inherit; } + .alert .alert-link { + font-weight: bold; } + .alert > p, .alert > ul { + margin-bottom: 0; } + .alert > p + p { + margin-top: 5px; } + +.alert-dismissable { + padding-right: 35px; } + .alert-dismissable .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; } + +.alert-success { + background-color: #dff0d8; + border-color: #d7e9c6; + color: #3c763d; } + .alert-success hr { + border-top-color: #cae2b3; } + .alert-success .alert-link { + color: #2b542b; } + +.alert-info { + background-color: #d9edf7; + border-color: #bce9f1; + color: #31708f; } + .alert-info hr { + border-top-color: #a6e2ec; } + .alert-info .alert-link { + color: #245369; } + +.alert-warning { + background-color: #fcf8e3; + border-color: #faeacc; + color: #8a6d3b; } + .alert-warning hr { + border-top-color: #f7e0b5; } + .alert-warning .alert-link { + color: #66502c; } + +.alert-danger { + background-color: #f2dede; + border-color: #ebccd1; + color: #a94442; } + .alert-danger hr { + border-top-color: #e4b9c0; } + .alert-danger .alert-link { + color: #843534; } + +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; } + + to { + background-position: 0 0; } } + +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; } + + to { + background-position: 0 0; } } + +.progress { + overflow: hidden; + height: 20px; + margin-bottom: 20px; + background-color: #f5f5f5; + border-radius: 4px; + box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1); } + +.progress-bar { + float: left; + width: 0%; + height: 100%; + font-size: 12px; + line-height: 20px; + color: #fff; + text-align: center; + background-color: #428bca; + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15); + transition: width 0.6s ease; } + +.progress-striped .progress-bar { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); + background-size: 40px 40px; } + +.progress.active .progress-bar { + -webkit-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; } + +.progress-bar[aria-valuenow="1"], .progress-bar[aria-valuenow="2"] { + min-width: 30px; } +.progress-bar[aria-valuenow="0"] { + color: #999999; + min-width: 30px; + background-color: transparent; + background-image: none; + box-shadow: none; } + +.progress-bar-success { + background-color: #5cb85c; } + .progress-striped .progress-bar-success { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); } + +.progress-bar-info { + background-color: #5bc0de; } + .progress-striped .progress-bar-info { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); } + +.progress-bar-warning { + background-color: #f0ad4e; } + .progress-striped .progress-bar-warning { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); } + +.progress-bar-danger { + background-color: #d9534f; } + .progress-striped .progress-bar-danger { + background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent); } + +.media, .media-body { + overflow: hidden; + zoom: 1; } + +.media, .media .media { + margin-top: 15px; } + +.media:first-child { + margin-top: 0; } + +.media-object { + display: block; } + +.media-heading { + margin: 0 0 5px; } + +.media > .pull-left { + margin-right: 10px; } +.media > .pull-right { + margin-left: 10px; } + +.media-list { + padding-left: 0; + list-style: none; } + +.list-group { + margin-bottom: 20px; + padding-left: 0; } + +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid #ddd; } + .list-group-item:first-child { + border-top-right-radius: 4px; + border-top-left-radius: 4px; } + .list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; } + .list-group-item > .badge { + float: right; } + .list-group-item > .badge + .badge { + margin-right: 5px; } + +a.list-group-item { + color: #555; } + a.list-group-item .list-group-item-heading { + color: #333; } + a.list-group-item:hover, a.list-group-item:focus { + text-decoration: none; + color: #555; + background-color: #f5f5f5; } + +.list-group-item.disabled, .list-group-item.disabled:hover, .list-group-item.disabled:focus { + background-color: #eeeeee; + color: #999999; } + .list-group-item.disabled .list-group-item-heading, .list-group-item.disabled:hover .list-group-item-heading, .list-group-item.disabled:focus .list-group-item-heading { + color: inherit; } + .list-group-item.disabled .list-group-item-text, .list-group-item.disabled:hover .list-group-item-text, .list-group-item.disabled:focus .list-group-item-text { + color: #999999; } +.list-group-item.active, .list-group-item.active:hover, .list-group-item.active:focus { + z-index: 2; + color: #fff; + background-color: #428bca; + border-color: #428bca; } + .list-group-item.active .list-group-item-heading, .list-group-item.active:hover .list-group-item-heading, .list-group-item.active:focus .list-group-item-heading { + color: inherit; } + .list-group-item.active .list-group-item-text, .list-group-item.active:hover .list-group-item-text, .list-group-item.active:focus .list-group-item-text { + color: #e1edf7; } + +.list-group-item-success { + color: #3c763d; + background-color: #dff0d8; } + +a.list-group-item-success { + color: #3c763d; } + a.list-group-item-success .list-group-item-heading { + color: inherit; } + a.list-group-item-success:hover, a.list-group-item-success:focus { + color: #3c763d; + background-color: #d0e9c6; } + a.list-group-item-success.active, a.list-group-item-success.active:hover, a.list-group-item-success.active:focus { + color: #fff; + background-color: #3c763d; + border-color: #3c763d; } + +.list-group-item-info { + color: #31708f; + background-color: #d9edf7; } + +a.list-group-item-info { + color: #31708f; } + a.list-group-item-info .list-group-item-heading { + color: inherit; } + a.list-group-item-info:hover, a.list-group-item-info:focus { + color: #31708f; + background-color: #c4e4f3; } + a.list-group-item-info.active, a.list-group-item-info.active:hover, a.list-group-item-info.active:focus { + color: #fff; + background-color: #31708f; + border-color: #31708f; } + +.list-group-item-warning { + color: #8a6d3b; + background-color: #fcf8e3; } + +a.list-group-item-warning { + color: #8a6d3b; } + a.list-group-item-warning .list-group-item-heading { + color: inherit; } + a.list-group-item-warning:hover, a.list-group-item-warning:focus { + color: #8a6d3b; + background-color: #faf2cc; } + a.list-group-item-warning.active, a.list-group-item-warning.active:hover, a.list-group-item-warning.active:focus { + color: #fff; + background-color: #8a6d3b; + border-color: #8a6d3b; } + +.list-group-item-danger { + color: #a94442; + background-color: #f2dede; } + +a.list-group-item-danger { + color: #a94442; } + a.list-group-item-danger .list-group-item-heading { + color: inherit; } + a.list-group-item-danger:hover, a.list-group-item-danger:focus { + color: #a94442; + background-color: #ebcccc; } + a.list-group-item-danger.active, a.list-group-item-danger.active:hover, a.list-group-item-danger.active:focus { + color: #fff; + background-color: #a94442; + border-color: #a94442; } + +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; } + +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; } + +.panel { + margin-bottom: 20px; + background-color: #fff; + border: 1px solid transparent; + border-radius: 4px; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); } + +.panel-body { + padding: 15px; } + .panel-body:before, .panel-body:after { + content: " "; + display: table; } + .panel-body:after { + clear: both; } + +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-right-radius: 3px; + border-top-left-radius: 3px; } + .panel-heading > .dropdown .dropdown-toggle { + color: inherit; } + +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + color: inherit; } + .panel-title > a { + color: inherit; } + +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; } + +.panel > .list-group { + margin-bottom: 0; } + .panel > .list-group .list-group-item { + border-width: 1px 0; + border-radius: 0; } + .panel > .list-group:first-child .list-group-item:first-child { + border-top: 0; + border-top-right-radius: 3px; + border-top-left-radius: 3px; } + .panel > .list-group:last-child .list-group-item:last-child { + border-bottom: 0; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; } + +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; } + +.panel > .table, .panel > .table-responsive > .table { + margin-bottom: 0; } +.panel > .table:first-child, .panel > .table-responsive:first-child > .table:first-child { + border-top-right-radius: 3px; + border-top-left-radius: 3px; } + .panel > .table:first-child > thead:first-child > tr:first-child td:first-child, .panel > .table:first-child > thead:first-child > tr:first-child th:first-child, .panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, .panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { + border-top-left-radius: 3px; } + .panel > .table:first-child > thead:first-child > tr:first-child td:last-child, .panel > .table:first-child > thead:first-child > tr:first-child th:last-child, .panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, .panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, .panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, .panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { + border-top-right-radius: 3px; } +.panel > .table:last-child, .panel > .table-responsive:last-child > .table:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; } + .panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, .panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, .panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, .panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { + border-bottom-left-radius: 3px; } + .panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, .panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, .panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, .panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, .panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, .panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { + border-bottom-right-radius: 3px; } +.panel > .panel-body + .table, .panel > .panel-body + .table-responsive { + border-top: 1px solid #ddd; } +.panel > .table > tbody:first-child > tr:first-child th, .panel > .table > tbody:first-child > tr:first-child td { + border-top: 0; } +.panel > .table-bordered, .panel > .table-responsive > .table-bordered { + border: 0; } + .panel > .table-bordered > thead > tr > th:first-child, .panel > .table-bordered > thead > tr > td:first-child, .panel > .table-bordered > tbody > tr > th:first-child, .panel > .table-bordered > tbody > tr > td:first-child, .panel > .table-bordered > tfoot > tr > th:first-child, .panel > .table-bordered > tfoot > tr > td:first-child, .panel > .table-responsive > .table-bordered > thead > tr > th:first-child, .panel > .table-responsive > .table-bordered > thead > tr > td:first-child, .panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, .panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, .panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, .panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; } + .panel > .table-bordered > thead > tr > th:last-child, .panel > .table-bordered > thead > tr > td:last-child, .panel > .table-bordered > tbody > tr > th:last-child, .panel > .table-bordered > tbody > tr > td:last-child, .panel > .table-bordered > tfoot > tr > th:last-child, .panel > .table-bordered > tfoot > tr > td:last-child, .panel > .table-responsive > .table-bordered > thead > tr > th:last-child, .panel > .table-responsive > .table-bordered > thead > tr > td:last-child, .panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, .panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, .panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, .panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; } + .panel > .table-bordered > thead > tr:first-child > td, .panel > .table-bordered > thead > tr:first-child > th, .panel > .table-bordered > tbody > tr:first-child > td, .panel > .table-bordered > tbody > tr:first-child > th, .panel > .table-responsive > .table-bordered > thead > tr:first-child > td, .panel > .table-responsive > .table-bordered > thead > tr:first-child > th, .panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, .panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { + border-bottom: 0; } + .panel > .table-bordered > tbody > tr:last-child > td, .panel > .table-bordered > tbody > tr:last-child > th, .panel > .table-bordered > tfoot > tr:last-child > td, .panel > .table-bordered > tfoot > tr:last-child > th, .panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, .panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, .panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, .panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { + border-bottom: 0; } +.panel > .table-responsive { + border: 0; + margin-bottom: 0; } + +.panel-group { + margin-bottom: 20px; } + .panel-group .panel { + margin-bottom: 0; + border-radius: 4px; } + .panel-group .panel + .panel { + margin-top: 5px; } + .panel-group .panel-heading { + border-bottom: 0; } + .panel-group .panel-heading + .panel-collapse .panel-body { + border-top: 1px solid #ddd; } + .panel-group .panel-footer { + border-top: 0; } + .panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid #ddd; } + +.panel-default { + border-color: #ddd; } + .panel-default > .panel-heading { + color: #333333; + background-color: #f5f5f5; + border-color: #ddd; } + .panel-default > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ddd; } + .panel-default > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ddd; } + +.panel-primary { + border-color: #428bca; } + .panel-primary > .panel-heading { + color: #fff; + background-color: #428bca; + border-color: #428bca; } + .panel-primary > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #428bca; } + .panel-primary > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #428bca; } + +.panel-success { + border-color: #d7e9c6; } + .panel-success > .panel-heading { + color: #3c763d; + background-color: #dff0d8; + border-color: #d7e9c6; } + .panel-success > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #d7e9c6; } + .panel-success > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #d7e9c6; } + +.panel-info { + border-color: #bce9f1; } + .panel-info > .panel-heading { + color: #31708f; + background-color: #d9edf7; + border-color: #bce9f1; } + .panel-info > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #bce9f1; } + .panel-info > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #bce9f1; } + +.panel-warning { + border-color: #faeacc; } + .panel-warning > .panel-heading { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faeacc; } + .panel-warning > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #faeacc; } + .panel-warning > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #faeacc; } + +.panel-danger { + border-color: #ebccd1; } + .panel-danger > .panel-heading { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; } + .panel-danger > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ebccd1; } + .panel-danger > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ebccd1; } + +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden; } + .embed-responsive .embed-responsive-item, .embed-responsive iframe, .embed-responsive embed, .embed-responsive object { + position: absolute; + top: 0; + left: 0; + bottom: 0; + height: 100%; + width: 100%; + border: 0; } + .embed-responsive.embed-responsive-16by9 { + padding-bottom: 56.25%; } + .embed-responsive.embed-responsive-4by3 { + padding-bottom: 75%; } + +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05); } + .well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, 0.15); } + +.well-lg { + padding: 24px; + border-radius: 6px; } + +.well-sm { + padding: 9px; + border-radius: 3px; } + +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + opacity: 0.2; + filter: alpha(opacity=20); } + .close:hover, .close:focus { + color: #000; + text-decoration: none; + cursor: pointer; + opacity: 0.5; + filter: alpha(opacity=50); } + +button.close { + padding: 0; + cursor: pointer; + background: transparent; + border: 0; + -webkit-appearance: none; } + +.modal-open { + overflow: hidden; } + +.modal { + display: none; + overflow: auto; + overflow-y: scroll; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1050; + -webkit-overflow-scrolling: touch; + outline: 0; } + .modal.fade .modal-dialog { + -webkit-transform: translate(0, -25%); + transform: translate(0, -25%); + transition: -webkit-transform 0.3s ease-out; + transition: transform 0.3s ease-out; } + .modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + transform: translate(0, 0); } + +.modal-dialog { + position: relative; + width: auto; + margin: 10px; } + +.modal-content { + position: relative; + background-color: #fff; + border: 1px solid #999; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5); + background-clip: padding-box; + outline: 0; } + +.modal-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + background-color: #000; } + .modal-backdrop.fade { + opacity: 0; + filter: alpha(opacity=0); } + .modal-backdrop.in { + opacity: 0.5; + filter: alpha(opacity=50); } + +.modal-header { + padding: 15px; + border-bottom: 1px solid #e5e5e5; + min-height: 16.42857px; } + +.modal-header .close { + margin-top: -2px; } + +.modal-title { + margin: 0; + line-height: 1.42857; } + +.modal-body { + position: relative; + padding: 15px; } + +.modal-footer { + padding: 15px; + text-align: right; + border-top: 1px solid #e5e5e5; } + .modal-footer:before, .modal-footer:after { + content: " "; + display: table; } + .modal-footer:after { + clear: both; } + .modal-footer .btn + .btn { + margin-left: 5px; + margin-bottom: 0; } + .modal-footer .btn-group .btn + .btn { + margin-left: -1px; } + .modal-footer .btn-block + .btn-block { + margin-left: 0; } + +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; } + +@media (min-width: 768px) { + .modal-dialog { + width: 600px; + margin: 30px auto; } + .modal-content { + box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5); } + .modal-sm { + width: 300px; } } + +@media (min-width: 992px) { + .modal-lg { + width: 900px; } } + +.tooltip { + position: absolute; + z-index: 1070; + display: block; + visibility: visible; + font-size: 12px; + line-height: 1.4; + opacity: 0; + filter: alpha(opacity=0); } + .tooltip.in { + opacity: 0.9; + filter: alpha(opacity=90); } + .tooltip.top { + margin-top: -3px; + padding: 5px 0; } + .tooltip.right { + margin-left: 3px; + padding: 0 5px; } + .tooltip.bottom { + margin-top: 3px; + padding: 5px 0; } + .tooltip.left { + margin-left: -3px; + padding: 0 5px; } + +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #fff; + text-align: center; + text-decoration: none; + background-color: #000; + border-radius: 4px; } + +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; } + +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000; } +.tooltip.top-left .tooltip-arrow { + bottom: 0; + left: 5px; + border-width: 5px 5px 0; + border-top-color: #000; } +.tooltip.top-right .tooltip-arrow { + bottom: 0; + right: 5px; + border-width: 5px 5px 0; + border-top-color: #000; } +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000; } +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000; } +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; } +.tooltip.bottom-left .tooltip-arrow { + top: 0; + left: 5px; + border-width: 0 5px 5px; + border-bottom-color: #000; } +.tooltip.bottom-right .tooltip-arrow { + top: 0; + right: 5px; + border-width: 0 5px 5px; + border-bottom-color: #000; } + +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: none; + max-width: 276px; + padding: 1px; + text-align: left; + background-color: #fff; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, 0.2); + border-radius: 6px; + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2); + white-space: normal; } + .popover.top { + margin-top: -10px; } + .popover.right { + margin-left: 10px; } + .popover.bottom { + margin-top: 10px; } + .popover.left { + margin-left: -10px; } + +.popover-title { + margin: 0; + padding: 8px 14px; + font-size: 14px; + font-weight: normal; + line-height: 18px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; } + +.popover-content { + padding: 9px 14px; } + +.popover > .arrow, .popover > .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; } + +.popover > .arrow { + border-width: 11px; } + +.popover > .arrow:after { + border-width: 10px; + content: ""; } + +.popover.top > .arrow { + left: 50%; + margin-left: -11px; + border-bottom-width: 0; + border-top-color: #999999; + border-top-color: rgba(0, 0, 0, 0.05); + bottom: -11px; } + .popover.top > .arrow:after { + content: " "; + bottom: 1px; + margin-left: -10px; + border-bottom-width: 0; + border-top-color: #fff; } +.popover.right > .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-left-width: 0; + border-right-color: #999999; + border-right-color: rgba(0, 0, 0, 0.05); } + .popover.right > .arrow:after { + content: " "; + left: 1px; + bottom: -10px; + border-left-width: 0; + border-right-color: #fff; } +.popover.bottom > .arrow { + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999999; + border-bottom-color: rgba(0, 0, 0, 0.05); + top: -11px; } + .popover.bottom > .arrow:after { + content: " "; + top: 1px; + margin-left: -10px; + border-top-width: 0; + border-bottom-color: #fff; } +.popover.left > .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #999999; + border-left-color: rgba(0, 0, 0, 0.05); } + .popover.left > .arrow:after { + content: " "; + right: 1px; + border-right-width: 0; + border-left-color: #fff; + bottom: -10px; } + +.carousel { + position: relative; } + +.carousel-inner { + position: relative; + overflow: hidden; + width: 100%; } + .carousel-inner > .item { + display: none; + position: relative; + transition: 0.6s ease-in-out left; } + .carousel-inner > .item > img, .carousel-inner > .item > a > img { + display: block; + max-width: 100%; + height: auto; + line-height: 1; } + .carousel-inner > .active, .carousel-inner > .next, .carousel-inner > .prev { + display: block; } + .carousel-inner > .active { + left: 0; } + .carousel-inner > .next, .carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; } + .carousel-inner > .next { + left: 100%; } + .carousel-inner > .prev { + left: -100%; } + .carousel-inner > .next.left, .carousel-inner > .prev.right { + left: 0; } + .carousel-inner > .active.left { + left: -100%; } + .carousel-inner > .active.right { + left: 100%; } + +.carousel-control { + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 15%; + opacity: 0.5; + filter: alpha(opacity=50); + font-size: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); } + .carousel-control.left { + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); } + .carousel-control.right { + left: auto; + right: 0; + background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%); + background-repeat: repeat-x; + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); } + .carousel-control:hover, .carousel-control:focus { + outline: 0; + color: #fff; + text-decoration: none; + opacity: 0.9; + filter: alpha(opacity=90); } + .carousel-control .icon-prev, .carousel-control .icon-next, .carousel-control .glyphicon-chevron-left, .carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + z-index: 5; + display: inline-block; } + .carousel-control .icon-prev, .carousel-control .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; } + .carousel-control .icon-next, .carousel-control .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; } + .carousel-control .icon-prev, .carousel-control .icon-next { + width: 20px; + height: 20px; + margin-top: -10px; + font-family: serif; } + .carousel-control .icon-prev:before { + content: '\2039'; } + .carousel-control .icon-next:before { + content: '\203a'; } + +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + margin-left: -30%; + padding-left: 0; + list-style: none; + text-align: center; } + .carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + border: 1px solid #fff; + border-radius: 10px; + cursor: pointer; + background-color: #000 \9; + background-color: rgba(0, 0, 0, 0); } + .carousel-indicators .active { + margin: 0; + width: 12px; + height: 12px; + background-color: #fff; } + +.carousel-caption { + position: absolute; + left: 15%; + right: 15%; + bottom: 20px; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); } + .carousel-caption .btn { + text-shadow: none; } + +@media screen and (min-width: 768px) { + .carousel-control .glyphicon-chevron-left, .carousel-control .glyphicon-chevron-right, .carousel-control .icon-prev, .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -15px; + font-size: 30px; } + .carousel-control .glyphicon-chevron-left, .carousel-control .icon-prev { + margin-left: -15px; } + .carousel-control .glyphicon-chevron-right, .carousel-control .icon-next { + margin-right: -15px; } + .carousel-caption { + left: 20%; + right: 20%; + padding-bottom: 30px; } + .carousel-indicators { + bottom: 20px; } } + +.clearfix:before, .clearfix:after { + content: " "; + display: table; } +.clearfix:after { + clear: both; } + +.center-block { + display: block; + margin-left: auto; + margin-right: auto; } + +.pull-right { + float: right !important; } + +.pull-left { + float: left !important; } + +.hide { + display: none !important; } + +.show { + display: block !important; } + +.invisible { + visibility: hidden; } + +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; } + +.hidden { + display: none !important; + visibility: hidden !important; } + +.affix { + position: fixed; } + +@-ms-viewport { + width: device-width; } + +.visible-xs, .visible-sm, .visible-md, .visible-lg { + display: none !important; } + +.visible-xs-block, .visible-xs-inline, .visible-xs-inline-block, .visible-sm-block, .visible-sm-inline, .visible-sm-inline-block, .visible-md-block, .visible-md-inline, .visible-md-inline-block, .visible-lg-block, .visible-lg-inline, .visible-lg-inline-block { + display: none !important; } + +@media (max-width: 767px) { + .visible-xs { + display: block !important; } + table.visible-xs { + display: table; } + tr.visible-xs { + display: table-row !important; } + th.visible-xs, td.visible-xs { + display: table-cell !important; } } + +@media (max-width: 767px) { + .visible-xs-block { + display: block !important; } } + +@media (max-width: 767px) { + .visible-xs-inline { + display: inline !important; } } + +@media (max-width: 767px) { + .visible-xs-inline-block { + display: inline-block !important; } } + +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; } + table.visible-sm { + display: table; } + tr.visible-sm { + display: table-row !important; } + th.visible-sm, td.visible-sm { + display: table-cell !important; } } + +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-block { + display: block !important; } } + +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline { + display: inline !important; } } + +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline-block { + display: inline-block !important; } } + +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; } + table.visible-md { + display: table; } + tr.visible-md { + display: table-row !important; } + th.visible-md, td.visible-md { + display: table-cell !important; } } + +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-block { + display: block !important; } } + +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline { + display: inline !important; } } + +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline-block { + display: inline-block !important; } } + +@media (min-width: 1200px) { + .visible-lg { + display: block !important; } + table.visible-lg { + display: table; } + tr.visible-lg { + display: table-row !important; } + th.visible-lg, td.visible-lg { + display: table-cell !important; } } + +@media (min-width: 1200px) { + .visible-lg-block { + display: block !important; } } + +@media (min-width: 1200px) { + .visible-lg-inline { + display: inline !important; } } + +@media (min-width: 1200px) { + .visible-lg-inline-block { + display: inline-block !important; } } + +@media (max-width: 767px) { + .hidden-xs { + display: none !important; } } + +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; } } + +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; } } + +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; } } + +.visible-print { + display: none !important; } + +@media print { + .visible-print { + display: block !important; } + table.visible-print { + display: table; } + tr.visible-print { + display: table-row !important; } + th.visible-print, td.visible-print { + display: table-cell !important; } } + +.visible-print-block { + display: none !important; } + @media print { + .visible-print-block { + display: block !important; } } + +.visible-print-inline { + display: none !important; } + @media print { + .visible-print-inline { + display: inline !important; } } + +.visible-print-inline-block { + display: none !important; } + @media print { + .visible-print-inline-block { + display: inline-block !important; } } + +@media print { + .hidden-print { + display: none !important; } } + +.browsehappy { + margin: 0.2em 0; + background: #ccc; + color: #000; + padding: 0.2em 0; } + +/* Space out content a bit */ +body { + padding-top: 20px; + padding-bottom: 20px; } + +/* Everything but the jumbotron gets side spacing for mobile first views */ +.header, .marketing, .footer { + padding-left: 15px; + padding-right: 15px; } + +/* Custom page header */ +.header { + border-bottom: 1px solid #e5e5e5; + /* Make the masthead heading the same height as the navigation */ } + .header h3 { + margin-top: 0; + margin-bottom: 0; + line-height: 40px; + padding-bottom: 19px; } + +/* Custom page footer */ +.footer { + padding-top: 19px; + color: #777; + border-top: 1px solid #e5e5e5; } + +.container-narrow > hr { + margin: 30px 0; } + +/* Main marketing message and sign up button */ +.jumbotron { + text-align: center; + border-bottom: 1px solid #e5e5e5; } + .jumbotron .btn { + font-size: 21px; + padding: 14px 24px; } + +/* Supporting marketing content */ +.marketing { + margin: 40px 0; } + .marketing p + h4 { + margin-top: 28px; } + +/* Responsive: Portrait tablets and up */ +@media screen and (min-width: 768px) { + /* Remove the padding we set earlier */ + /* Space out the masthead */ + /* Remove the bottom border on the jumbotron for visual effect */ + .container { + max-width: 730px; } + .header, .marketing, .footer { + padding-left: 0; + padding-right: 0; } + .header { + margin-bottom: 30px; } + .jumbotron { + border-bottom: 300; } } + +/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFpbi5jc3MiLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIiwic291cmNlcyI6WyJtYWluLnNjc3MiXSwic291cmNlc0NvbnRlbnQiOlsiJGljb24tZm9udC1wYXRoOiBcIi4uL2Jvd2VyX2NvbXBvbmVudHMvYm9vdHN0cmFwLXNhc3Mtb2ZmaWNpYWwvdmVuZG9yL2Fzc2V0cy9mb250cy9ib290c3RyYXAvXCI7XG5cbi8vIGJvd2VyOnNjc3NcbkBpbXBvcnQgXCIuLi9ib3dlcl9jb21wb25lbnRzL2Jvb3RzdHJhcC1zYXNzLW9mZmljaWFsL3ZlbmRvci9hc3NldHMvc3R5bGVzaGVldHMvYm9vdHN0cmFwLnNjc3NcIjtcbi8vIGVuZGJvd2VyXG5cbi5icm93c2VoYXBweSB7XG4gICAgbWFyZ2luOiAwLjJlbSAwO1xuICAgIGJhY2tncm91bmQ6ICNjY2M7XG4gICAgY29sb3I6ICMwMDA7XG4gICAgcGFkZGluZzogMC4yZW0gMDtcbn1cblxuLyogU3BhY2Ugb3V0IGNvbnRlbnQgYSBiaXQgKi9cbmJvZHkge1xuICAgIHBhZGRpbmctdG9wOiAyMHB4O1xuICAgIHBhZGRpbmctYm90dG9tOiAyMHB4O1xufVxuXG4vKiBFdmVyeXRoaW5nIGJ1dCB0aGUganVtYm90cm9uIGdldHMgc2lkZSBzcGFjaW5nIGZvciBtb2JpbGUgZmlyc3Qgdmlld3MgKi9cbi5oZWFkZXIsXG4ubWFya2V0aW5nLFxuLmZvb3RlciB7XG4gICAgcGFkZGluZy1sZWZ0OiAxNXB4O1xuICAgIHBhZGRpbmctcmlnaHQ6IDE1cHg7XG59XG5cbi8qIEN1c3RvbSBwYWdlIGhlYWRlciAqL1xuLmhlYWRlciB7XG4gICAgYm9yZGVyLWJvdHRvbTogMXB4IHNvbGlkICNlNWU1ZTU7XG5cbiAgICAvKiBNYWtlIHRoZSBtYXN0aGVhZCBoZWFkaW5nIHRoZSBzYW1lIGhlaWdodCBhcyB0aGUgbmF2aWdhdGlvbiAqL1xuICAgIGgzIHtcbiAgICAgICAgbWFyZ2luLXRvcDogMDtcbiAgICAgICAgbWFyZ2luLWJvdHRvbTogMDtcbiAgICAgICAgbGluZS1oZWlnaHQ6IDQwcHg7XG4gICAgICAgIHBhZGRpbmctYm90dG9tOiAxOXB4O1xuICAgIH1cbn1cblxuLyogQ3VzdG9tIHBhZ2UgZm9vdGVyICovXG4uZm9vdGVyIHtcbiAgICBwYWRkaW5nLXRvcDogMTlweDtcbiAgICBjb2xvcjogIzc3NztcbiAgICBib3JkZXItdG9wOiAxcHggc29saWQgI2U1ZTVlNTtcbn1cblxuLmNvbnRhaW5lci1uYXJyb3cgPiBociB7XG4gICAgbWFyZ2luOiAzMHB4IDA7XG59XG5cbi8qIE1haW4gbWFya2V0aW5nIG1lc3NhZ2UgYW5kIHNpZ24gdXAgYnV0dG9uICovXG4uanVtYm90cm9uIHtcbiAgICB0ZXh0LWFsaWduOiBjZW50ZXI7XG4gICAgYm9yZGVyLWJvdHRvbTogMXB4IHNvbGlkICNlNWU1ZTU7XG4gICAgLmJ0biB7XG4gICAgICAgIGZvbnQtc2l6ZTogMjFweDtcbiAgICAgICAgcGFkZGluZzogMTRweCAyNHB4O1xuICAgIH1cbn1cblxuLyogU3VwcG9ydGluZyBtYXJrZXRpbmcgY29udGVudCAqL1xuLm1hcmtldGluZyB7XG4gICAgbWFyZ2luOiA0MHB4IDA7XG4gICAgcCArIGg0IHtcbiAgICAgICAgbWFyZ2luLXRvcDogMjhweDtcbiAgICB9XG59XG5cbi8qIFJlc3BvbnNpdmU6IFBvcnRyYWl0IHRhYmxldHMgYW5kIHVwICovXG5AbWVkaWEgc2NyZWVuIGFuZCAobWluLXdpZHRoOiA3NjhweCkge1xuICAgIC5jb250YWluZXIge1xuICAgICAgICBtYXgtd2lkdGg6IDczMHB4O1xuICAgIH1cblxuICAgIC8qIFJlbW92ZSB0aGUgcGFkZGluZyB3ZSBzZXQgZWFybGllciAqL1xuICAgIC5oZWFkZXIsXG4gICAgLm1hcmtldGluZyxcbiAgICAuZm9vdGVyIHtcbiAgICAgICAgcGFkZGluZy1sZWZ0OiAwO1xuICAgICAgICBwYWRkaW5nLXJpZ2h0OiAwO1xuICAgIH1cblxuICAgIC8qIFNwYWNlIG91dCB0aGUgbWFzdGhlYWQgKi9cbiAgICAuaGVhZGVyIHtcbiAgICAgICAgbWFyZ2luLWJvdHRvbTogMzBweDtcbiAgICB9XG5cbiAgICAvKiBSZW1vdmUgdGhlIGJvdHRvbSBib3JkZXIgb24gdGhlIGp1bWJvdHJvbiBmb3IgdmlzdWFsIGVmZmVjdCAqL1xuICAgIC5qdW1ib3Ryb24ge1xuICAgICAgICBib3JkZXItYm90dG9tOiAzMDA7XG4gICAgfVxufVxuXG4vLyB0aGlzIGlzIGEgY29tbWVudC4uLlxuIl0sInNvdXJjZVJvb3QiOiIvc291cmNlLyJ9 */ diff --git a/devtools/client/styleeditor/test/sourcemap-css/test-stylus.css b/devtools/client/styleeditor/test/sourcemap-css/test-stylus.css new file mode 100644 index 0000000000..0ec51da3b7 --- /dev/null +++ b/devtools/client/styleeditor/test/sourcemap-css/test-stylus.css @@ -0,0 +1,7 @@ +div { + color: #f06; +} +span { + background-color: #eee; +} +/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3Qtc3R5bHVzLnN0eWwiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUE7RUFDRSxPQUFPLEtBQVA7O0FBRUY7RUFDRSxrQkFBa0IsS0FBbEIiLCJmaWxlIjoidGVzdC1zdHlsdXMuY3NzIiwic291cmNlc0NvbnRlbnQiOlsicGF1bHJvdWdldHBpbmsgPSAjZjA2O1xuXG5kaXZcbiAgY29sb3I6IHBhdWxyb3VnZXRwaW5rXG5cbnNwYW5cbiAgYmFja2dyb3VuZC1jb2xvcjogI0VFRVxuIl19 */
\ No newline at end of file diff --git a/devtools/client/styleeditor/test/sourcemap-sass/media-rules.scss b/devtools/client/styleeditor/test/sourcemap-sass/media-rules.scss new file mode 100644 index 0000000000..4f1c8f216f --- /dev/null +++ b/devtools/client/styleeditor/test/sourcemap-sass/media-rules.scss @@ -0,0 +1,11 @@ +$break-small: 320px; +$break-large: 1200px; + +div { + @media screen and (max-width: $break-small) { + width: 100px; + } + @media screen and (min-width: $break-large) { + width: 400px; + } +} diff --git a/devtools/client/styleeditor/test/sourcemap-sass/sourcemaps.scss b/devtools/client/styleeditor/test/sourcemap-sass/sourcemaps.scss new file mode 100644 index 0000000000..0ff6c471bb --- /dev/null +++ b/devtools/client/styleeditor/test/sourcemap-sass/sourcemaps.scss @@ -0,0 +1,10 @@ + +$paulrougetpink: #f06; + +div { + color: $paulrougetpink; +} + +span { + background-color: #EEE; +}
\ No newline at end of file diff --git a/devtools/client/styleeditor/test/sourcemap-sass/sourcemaps.scss^headers^ b/devtools/client/styleeditor/test/sourcemap-sass/sourcemaps.scss^headers^ new file mode 100644 index 0000000000..866f3e2fb0 --- /dev/null +++ b/devtools/client/styleeditor/test/sourcemap-sass/sourcemaps.scss^headers^ @@ -0,0 +1,2 @@ +X-Content-Type-Options: nosniff +Content-Type: text/plain diff --git a/devtools/client/styleeditor/test/sourcemap-styl/test-stylus.styl b/devtools/client/styleeditor/test/sourcemap-styl/test-stylus.styl new file mode 100644 index 0000000000..76ff25c29e --- /dev/null +++ b/devtools/client/styleeditor/test/sourcemap-styl/test-stylus.styl @@ -0,0 +1,7 @@ +paulrougetpink = #f06; + +div + color: paulrougetpink + +span + background-color: #EEE
\ No newline at end of file diff --git a/devtools/client/styleeditor/test/sourcemaps-inline.html b/devtools/client/styleeditor/test/sourcemaps-inline.html new file mode 100644 index 0000000000..45846fe289 --- /dev/null +++ b/devtools/client/styleeditor/test/sourcemaps-inline.html @@ -0,0 +1,17 @@ +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <title>testcase for testing CSS source maps in inline style</title> + <style type="text/css">body { + background-color: black; +} +body > h1 { + color: white; +} +/*# sourceMappingURL=data:application/json;base64,ewoidmVyc2lvbiI6IDMsCiJtYXBwaW5ncyI6ICJBQUFBLElBQUs7RUFDSCxnQkFBZ0IsRUFBRSxLQUFLO0VBQ3ZCLFNBQU87SUFDTCxLQUFLLEVBQUUsS0FBSyIsCiJzb3VyY2VzIjogWyJ0ZXN0LnNjc3MiXSwKInNvdXJjZXNDb250ZW50IjogWyJib2R5IHtcbiAgYmFja2dyb3VuZC1jb2xvcjogYmxhY2s7XG4gICYgPiBoMSB7XG4gICAgY29sb3I6IHdoaXRlO1xuICB9XG59XG4iXSwKIm5hbWVzIjogW10sCiJmaWxlIjogInRlc3QuY3NzIgp9Cg== */</style> +</head> +<body> + <h1>Source maps testcase</div> +</body> +</html> diff --git a/devtools/client/styleeditor/test/sourcemaps-large.html b/devtools/client/styleeditor/test/sourcemaps-large.html new file mode 100644 index 0000000000..b8c92e0c9e --- /dev/null +++ b/devtools/client/styleeditor/test/sourcemaps-large.html @@ -0,0 +1,11 @@ +<!doctype html> +<html> +<head> + <meta charset="UTF-8"> + <title>testcase for a loading error with CSS source maps</title> + <link rel="stylesheet" type="text/css" href="sourcemap-css/test-bootstrap-scss.css"/> +</head> +<body> + <div>source maps <span>testcase</span> (see Bug 1128747)</div> +</body> +</html> diff --git a/devtools/client/styleeditor/test/sourcemaps-watching.html b/devtools/client/styleeditor/test/sourcemaps-watching.html new file mode 100644 index 0000000000..fc9909ea57 --- /dev/null +++ b/devtools/client/styleeditor/test/sourcemaps-watching.html @@ -0,0 +1,11 @@ +<!doctype html> +<html> +<head> + <title>testcase for testing CSS source maps</title> + <link rel="stylesheet" type="text/css" href="simple.css"/> + <link rel="stylesheet" type="text/css" href="sourcemap-css/sourcemaps.css?test=1"/> +</head> +<body> + <div>source maps <span>testcase</span></div> +</body> +</html> diff --git a/devtools/client/styleeditor/test/sourcemaps.html b/devtools/client/styleeditor/test/sourcemaps.html new file mode 100644 index 0000000000..887e0ed989 --- /dev/null +++ b/devtools/client/styleeditor/test/sourcemaps.html @@ -0,0 +1,13 @@ +<!doctype html> +<html> +<head> + <title>testcase for testing CSS source maps</title> + <link rel="stylesheet" type="text/css" href="simple.css"/> + <link rel="stylesheet" type="text/css" href="sourcemap-css/sourcemaps.css?test=1"/> + <link rel="stylesheet" type="text/css" href="sourcemap-css/contained.css"/> + <link rel="stylesheet" type="text/css" href="sourcemap-css/test-stylus.css"/> +</head> +<body> + <div>source maps <span>testcase</span></div> +</body> +</html> diff --git a/devtools/client/styleeditor/test/sync.html b/devtools/client/styleeditor/test/sync.html new file mode 100644 index 0000000000..83da8c57e9 --- /dev/null +++ b/devtools/client/styleeditor/test/sync.html @@ -0,0 +1,20 @@ +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <title>simple testcase</title> + <style type="text/css"> + body { + border-width: 15px; + color: red; + } + + #testid { + font-size: 4em; + } + </style> +</head> +<body> + <div id="testid">simple testcase</div> +</body> +</html> diff --git a/devtools/client/styleeditor/test/sync_with_csp.css b/devtools/client/styleeditor/test/sync_with_csp.css new file mode 100644 index 0000000000..18f1b89682 --- /dev/null +++ b/devtools/client/styleeditor/test/sync_with_csp.css @@ -0,0 +1,10 @@ + + body { + border-width: 15px; + color: red; + } + + #testid { + font-size: 4em; + } +
\ No newline at end of file diff --git a/devtools/client/styleeditor/test/sync_with_csp.html b/devtools/client/styleeditor/test/sync_with_csp.html new file mode 100644 index 0000000000..cdab014f59 --- /dev/null +++ b/devtools/client/styleeditor/test/sync_with_csp.html @@ -0,0 +1,12 @@ +<!doctype html> +<html> +<head> + <meta charset="utf-8"> + <meta http-equiv="Content-Security-Policy" content="style-src *"> + <title>simple testcase with content security policy</title> + <link rel="stylesheet" type="text/css" href="sync_with_csp.css" /> +</head> +<body> + <div id="testid">simple testcase with content security policy</div> +</body> +</html> diff --git a/devtools/client/styleeditor/test/test_private.css b/devtools/client/styleeditor/test/test_private.css new file mode 100644 index 0000000000..e8f24f94a3 --- /dev/null +++ b/devtools/client/styleeditor/test/test_private.css @@ -0,0 +1,3 @@ +body { + background-color: red; +} diff --git a/devtools/client/styleeditor/test/test_private.html b/devtools/client/styleeditor/test/test_private.html new file mode 100644 index 0000000000..bfde3520ef --- /dev/null +++ b/devtools/client/styleeditor/test/test_private.html @@ -0,0 +1,7 @@ +<html> +<head> +<link rel="stylesheet" href="test_private.css"></link> +</head> +<body> +</body> +</html> diff --git a/devtools/client/styleeditor/test/utf-16.css b/devtools/client/styleeditor/test/utf-16.css Binary files differnew file mode 100644 index 0000000000..92ff5eac53 --- /dev/null +++ b/devtools/client/styleeditor/test/utf-16.css diff --git a/devtools/client/styleeditor/test/veryveryverylongnamethatcanbreakthestyleeditor.css b/devtools/client/styleeditor/test/veryveryverylongnamethatcanbreakthestyleeditor.css new file mode 100644 index 0000000000..4d737f305e --- /dev/null +++ b/devtools/client/styleeditor/test/veryveryverylongnamethatcanbreakthestyleeditor.css @@ -0,0 +1,7 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +/* ☺ */ + +body { + margin: 0; +} |