summaryrefslogtreecommitdiff
path: root/toolkit/devtools/styleeditor
diff options
context:
space:
mode:
authorMatt A. Tobin <email@mattatobin.com>2016-10-16 19:34:53 -0400
committerMatt A. Tobin <email@mattatobin.com>2016-10-16 19:34:53 -0400
commit81805ce3f63e2e4a799bd54f174083c58a9b5640 (patch)
tree6e13374b213ac9b2ae74c25d8aac875faf71fdd0 /toolkit/devtools/styleeditor
parent28c8da71bf521bb3ee76f27b8a241919e24b7cd5 (diff)
downloadpalemoon-gre-81805ce3f63e2e4a799bd54f174083c58a9b5640.tar.gz
Move Mozilla DevTools to Platform - Part 3: Merge the browser/devtools and toolkit/devtools adjusting for directory collisions
Diffstat (limited to 'toolkit/devtools/styleeditor')
-rw-r--r--toolkit/devtools/styleeditor/StyleEditorUI.jsm879
-rw-r--r--toolkit/devtools/styleeditor/StyleEditorUtil.jsm234
-rw-r--r--toolkit/devtools/styleeditor/StyleSheetEditor.jsm810
-rw-r--r--toolkit/devtools/styleeditor/moz.build19
-rw-r--r--toolkit/devtools/styleeditor/styleeditor-commands.js42
-rw-r--r--toolkit/devtools/styleeditor/styleeditor-panel.js154
-rw-r--r--toolkit/devtools/styleeditor/styleeditor.css151
-rw-r--r--toolkit/devtools/styleeditor/styleeditor.xul222
-rw-r--r--toolkit/devtools/styleeditor/test/autocomplete.html22
-rw-r--r--toolkit/devtools/styleeditor/test/browser.ini80
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_autocomplete.js252
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_bug_740541_iframes.js78
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_bug_851132_middle_click.js75
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_bug_870339.js46
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_cmd_edit.html50
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_cmd_edit.js206
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_enabled.js70
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_fetch-from-cache.js39
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_filesave.js107
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_highlight-selector.js55
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_import.js73
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_import_rule.js37
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_init.js91
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_inline_friendly_names.js155
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_loading.js37
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_media_sidebar.js137
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_media_sidebar_sourcemaps.js69
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_new.js120
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_nostyle.js41
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_pretty.js56
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_private_perwindowpb.js52
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_reload.js105
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_selectstylesheet.js58
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_sourcemap_large.js31
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_sourcemap_watching.js195
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_sourcemaps.js149
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_sv_keynav.js82
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_sv_resize.js55
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_transition_rule.js48
-rw-r--r--toolkit/devtools/styleeditor/test/browser_styleeditor_xul.js20
-rw-r--r--toolkit/devtools/styleeditor/test/doc_uncached.css16
-rw-r--r--toolkit/devtools/styleeditor/test/doc_uncached.html10
-rw-r--r--toolkit/devtools/styleeditor/test/doc_xulpage.xul7
-rw-r--r--toolkit/devtools/styleeditor/test/four.html25
-rw-r--r--toolkit/devtools/styleeditor/test/head.js152
-rw-r--r--toolkit/devtools/styleeditor/test/import.css10
-rw-r--r--toolkit/devtools/styleeditor/test/import.html11
-rw-r--r--toolkit/devtools/styleeditor/test/import2.css10
-rw-r--r--toolkit/devtools/styleeditor/test/inline-1.html19
-rw-r--r--toolkit/devtools/styleeditor/test/inline-2.html19
-rw-r--r--toolkit/devtools/styleeditor/test/longload.html28
-rw-r--r--toolkit/devtools/styleeditor/test/media-rules-sourcemaps.html12
-rw-r--r--toolkit/devtools/styleeditor/test/media-rules.css23
-rw-r--r--toolkit/devtools/styleeditor/test/media-rules.html13
-rw-r--r--toolkit/devtools/styleeditor/test/media-small.css5
-rw-r--r--toolkit/devtools/styleeditor/test/media.html11
-rw-r--r--toolkit/devtools/styleeditor/test/minified.html15
-rw-r--r--toolkit/devtools/styleeditor/test/nostyle.html5
-rw-r--r--toolkit/devtools/styleeditor/test/pretty.css2
-rw-r--r--toolkit/devtools/styleeditor/test/resources_inpage.jsi12
-rw-r--r--toolkit/devtools/styleeditor/test/resources_inpage1.css11
-rw-r--r--toolkit/devtools/styleeditor/test/resources_inpage2.css11
-rw-r--r--toolkit/devtools/styleeditor/test/simple.css9
-rw-r--r--toolkit/devtools/styleeditor/test/simple.css.gzbin0 -> 166 bytes
-rw-r--r--toolkit/devtools/styleeditor/test/simple.css.gz^headers^4
-rw-r--r--toolkit/devtools/styleeditor/test/simple.gz.html23
-rw-r--r--toolkit/devtools/styleeditor/test/simple.html23
-rw-r--r--toolkit/devtools/styleeditor/test/sourcemap-css/contained.css4
-rw-r--r--toolkit/devtools/styleeditor/test/sourcemap-css/media-rules.css8
-rw-r--r--toolkit/devtools/styleeditor/test/sourcemap-css/media-rules.css.map6
-rw-r--r--toolkit/devtools/styleeditor/test/sourcemap-css/sourcemaps.css7
-rw-r--r--toolkit/devtools/styleeditor/test/sourcemap-css/sourcemaps.css.map6
-rw-r--r--toolkit/devtools/styleeditor/test/sourcemap-css/test-bootstrap-scss.css4513
-rw-r--r--toolkit/devtools/styleeditor/test/sourcemap-css/test-stylus.css7
-rw-r--r--toolkit/devtools/styleeditor/test/sourcemap-sass/media-rules.scss11
-rw-r--r--toolkit/devtools/styleeditor/test/sourcemap-sass/sourcemaps.scss10
-rw-r--r--toolkit/devtools/styleeditor/test/sourcemap-styl/test-stylus.styl7
-rw-r--r--toolkit/devtools/styleeditor/test/sourcemaps-large.html11
-rw-r--r--toolkit/devtools/styleeditor/test/sourcemaps-watching.html11
-rw-r--r--toolkit/devtools/styleeditor/test/sourcemaps.html13
-rw-r--r--toolkit/devtools/styleeditor/test/test_private.css3
-rw-r--r--toolkit/devtools/styleeditor/test/test_private.html7
-rw-r--r--toolkit/devtools/styleeditor/utils.js40
83 files changed, 10352 insertions, 0 deletions
diff --git a/toolkit/devtools/styleeditor/StyleEditorUI.jsm b/toolkit/devtools/styleeditor/StyleEditorUI.jsm
new file mode 100644
index 000000000..e9832d3d2
--- /dev/null
+++ b/toolkit/devtools/styleeditor/StyleEditorUI.jsm
@@ -0,0 +1,879 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["StyleEditorUI"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/devtools/event-emitter.js");
+Cu.import("resource:///modules/devtools/gDevTools.jsm");
+Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm");
+Cu.import("resource:///modules/devtools/SplitView.jsm");
+Cu.import("resource:///modules/devtools/StyleSheetEditor.jsm");
+const { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {});
+
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+ "resource://gre/modules/PluralForm.jsm");
+
+const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
+const { PrefObserver, PREF_ORIG_SOURCES } = require("devtools/styleeditor/utils");
+const csscoverage = require("devtools/server/actors/csscoverage");
+const console = require("resource://gre/modules/devtools/Console.jsm").console;
+
+const LOAD_ERROR = "error-load";
+const STYLE_EDITOR_TEMPLATE = "stylesheet";
+const SELECTOR_HIGHLIGHTER_TYPE = "SelectorHighlighter";
+const PREF_MEDIA_SIDEBAR = "devtools.styleeditor.showMediaSidebar";
+const PREF_SIDEBAR_WIDTH = "devtools.styleeditor.mediaSidebarWidth";
+const PREF_NAV_WIDTH = "devtools.styleeditor.navSidebarWidth";
+
+/**
+ * StyleEditorUI is controls and builds the UI of the Style Editor, including
+ * maintaining a list of editors for each stylesheet on a debuggee.
+ *
+ * Emits events:
+ * 'editor-added': A new editor was added to the UI
+ * 'editor-selected': An editor was selected
+ * 'error': An error occured
+ *
+ * @param {StyleEditorFront} debuggee
+ * Client-side front for interacting with the page's stylesheets
+ * @param {Target} target
+ * Interface for the page we're debugging
+ * @param {Document} panelDoc
+ * Document of the toolbox panel to populate UI in.
+ */
+function StyleEditorUI(debuggee, target, panelDoc) {
+ EventEmitter.decorate(this);
+
+ this._debuggee = debuggee;
+ this._target = target;
+ this._panelDoc = panelDoc;
+ this._window = this._panelDoc.defaultView;
+ this._root = this._panelDoc.getElementById("style-editor-chrome");
+
+ this.editors = [];
+ this.selectedEditor = null;
+ this.savedLocations = {};
+
+ this._onOptionsPopupShowing = this._onOptionsPopupShowing.bind(this);
+ this._onOptionsPopupHiding = this._onOptionsPopupHiding.bind(this);
+ this._onStyleSheetCreated = this._onStyleSheetCreated.bind(this);
+ this._onNewDocument = this._onNewDocument.bind(this);
+ this._onMediaPrefChanged = this._onMediaPrefChanged.bind(this);
+ this._updateMediaList = this._updateMediaList.bind(this);
+ this._clear = this._clear.bind(this);
+ this._onError = this._onError.bind(this);
+
+ this._prefObserver = new PrefObserver("devtools.styleeditor.");
+ this._prefObserver.on(PREF_ORIG_SOURCES, this._onNewDocument);
+ this._prefObserver.on(PREF_MEDIA_SIDEBAR, this._onMediaPrefChanged);
+}
+
+StyleEditorUI.prototype = {
+ /**
+ * Get whether any of the editors have unsaved changes.
+ *
+ * @return boolean
+ */
+ get isDirty() {
+ if (this._markedDirty === true) {
+ return true;
+ }
+ return this.editors.some((editor) => {
+ return editor.sourceEditor && !editor.sourceEditor.isClean();
+ });
+ },
+
+ /*
+ * Mark the style editor as having or not having unsaved changes.
+ */
+ set isDirty(value) {
+ this._markedDirty = value;
+ },
+
+ /*
+ * Index of selected stylesheet in document.styleSheets
+ */
+ get selectedStyleSheetIndex() {
+ return this.selectedEditor ?
+ this.selectedEditor.styleSheet.styleSheetIndex : -1;
+ },
+
+ /**
+ * Initiates the style editor ui creation, the inspector front to get
+ * reference to the walker and the selector highlighter if available
+ */
+ initialize: function() {
+ return Task.spawn(function*() {
+ let toolbox = gDevTools.getToolbox(this._target);
+ yield toolbox.initInspector();
+ this._walker = toolbox.walker;
+
+ let hUtils = toolbox.highlighterUtils;
+ if (hUtils.supportsCustomHighlighters()) {
+ try {
+ this._highlighter =
+ yield hUtils.getHighlighterByType(SELECTOR_HIGHLIGHTER_TYPE);
+ } catch (e) {
+ // The selectorHighlighter can't always be instantiated, for example
+ // it doesn't work with XUL windows (until bug 1094959 gets fixed);
+ // or the selectorHighlighter doesn't exist on the backend.
+ console.warn("The selectorHighlighter couldn't be instantiated, " +
+ "elements matching hovered selectors will not be highlighted");
+ }
+ }
+ }.bind(this)).then(() => {
+ this.createUI();
+ this._debuggee.getStyleSheets().then((styleSheets) => {
+ this._resetStyleSheetList(styleSheets);
+ this._target.on("will-navigate", this._clear);
+ this._target.on("navigate", this._onNewDocument);
+ }, Cu.reportError);
+ });
+ },
+
+ /**
+ * Build the initial UI and wire buttons with event handlers.
+ */
+ createUI: function() {
+ let viewRoot = this._root.parentNode.querySelector(".splitview-root");
+
+ this._view = new SplitView(viewRoot);
+
+ wire(this._view.rootElement, ".style-editor-newButton", () =>{
+ this._debuggee.addStyleSheet(null).then(this._onStyleSheetCreated);
+ });
+
+ wire(this._view.rootElement, ".style-editor-importButton", ()=> {
+ this._importFromFile(this._mockImportFile || null, this._window);
+ });
+
+ this._optionsButton = this._panelDoc.getElementById("style-editor-options");
+ this._optionsMenu = this._panelDoc.getElementById("style-editor-options-popup");
+ this._optionsMenu.addEventListener("popupshowing",
+ this._onOptionsPopupShowing);
+ this._optionsMenu.addEventListener("popuphiding",
+ this._onOptionsPopupHiding);
+
+ this._sourcesItem = this._panelDoc.getElementById("options-origsources");
+ this._sourcesItem.addEventListener("command",
+ this._toggleOrigSources);
+ this._mediaItem = this._panelDoc.getElementById("options-show-media");
+ this._mediaItem.addEventListener("command",
+ this._toggleMediaSidebar);
+
+ let nav = this._panelDoc.querySelector(".splitview-controller");
+ nav.setAttribute("width", Services.prefs.getIntPref(PREF_NAV_WIDTH));
+ },
+
+ /**
+ * Listener handling the 'gear menu' popup showing event.
+ * Update options menu items to reflect current preference settings.
+ */
+ _onOptionsPopupShowing: function() {
+ this._optionsButton.setAttribute("open", "true");
+ this._sourcesItem.setAttribute("checked",
+ Services.prefs.getBoolPref(PREF_ORIG_SOURCES));
+ this._mediaItem.setAttribute("checked",
+ Services.prefs.getBoolPref(PREF_MEDIA_SIDEBAR));
+ },
+
+ /**
+ * Listener handling the 'gear menu' popup hiding event.
+ */
+ _onOptionsPopupHiding: function() {
+ this._optionsButton.removeAttribute("open");
+ },
+
+ /**
+ * Refresh editors to reflect the stylesheets in the document.
+ *
+ * @param {string} event
+ * Event name
+ * @param {StyleSheet} styleSheet
+ * StyleSheet object for new sheet
+ */
+ _onNewDocument: function() {
+ this._debuggee.getStyleSheets().then((styleSheets) => {
+ this._resetStyleSheetList(styleSheets);
+ }, Cu.reportError);
+ },
+
+ /**
+ * Add editors for all the given stylesheets to the UI.
+ *
+ * @param {array} styleSheets
+ * Array of StyleSheetFront
+ */
+ _resetStyleSheetList: function(styleSheets) {
+ this._clear();
+
+ for (let sheet of styleSheets) {
+ this._addStyleSheet(sheet);
+ }
+
+ this._root.classList.remove("loading");
+
+ this.emit("stylesheets-reset");
+ },
+
+ /**
+ * Remove all editors and add loading indicator.
+ */
+ _clear: function() {
+ // remember selected sheet and line number for next load
+ if (this.selectedEditor && this.selectedEditor.sourceEditor) {
+ let href = this.selectedEditor.styleSheet.href;
+ let {line, ch} = this.selectedEditor.sourceEditor.getCursor();
+
+ this._styleSheetToSelect = {
+ stylesheet: href,
+ line: line,
+ col: ch
+ };
+ }
+
+ // remember saved file locations
+ for (let editor of this.editors) {
+ if (editor.savedFile) {
+ let identifier = this.getStyleSheetIdentifier(editor.styleSheet);
+ this.savedLocations[identifier] = editor.savedFile;
+ }
+ }
+
+ this._clearStyleSheetEditors();
+ this._view.removeAll();
+
+ this.selectedEditor = null;
+
+ this._root.classList.add("loading");
+ },
+
+ /**
+ * Add an editor for this stylesheet. Add editors for its original sources
+ * instead (e.g. Sass sources), if applicable.
+ *
+ * @param {StyleSheetFront} styleSheet
+ * Style sheet to add to style editor
+ */
+ _addStyleSheet: function(styleSheet) {
+ let editor = this._addStyleSheetEditor(styleSheet);
+
+ if (!Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) {
+ return;
+ }
+
+ styleSheet.getOriginalSources().then((sources) => {
+ if (sources && sources.length) {
+ this._removeStyleSheetEditor(editor);
+ sources.forEach((source) => {
+ // set so the first sheet will be selected, even if it's a source
+ source.styleSheetIndex = styleSheet.styleSheetIndex;
+ source.relatedStyleSheet = styleSheet;
+
+ this._addStyleSheetEditor(source);
+ });
+ }
+ }, Cu.reportError);
+ },
+
+ /**
+ * Add a new editor to the UI for a source.
+ *
+ * @param {StyleSheet} styleSheet
+ * Object representing stylesheet
+ * @param {nsIfile} file
+ * Optional file object that sheet was imported from
+ * @param {Boolean} isNew
+ * Optional if stylesheet is a new sheet created by user
+ */
+ _addStyleSheetEditor: function(styleSheet, file, isNew) {
+ // recall location of saved file for this sheet after page reload
+ let identifier = this.getStyleSheetIdentifier(styleSheet);
+ let savedFile = this.savedLocations[identifier];
+ if (savedFile && !file) {
+ file = savedFile;
+ }
+
+ let editor = new StyleSheetEditor(styleSheet, this._window, file, isNew,
+ this._walker, this._highlighter);
+
+ editor.on("property-change", this._summaryChange.bind(this, editor));
+ editor.on("media-rules-changed", this._updateMediaList.bind(this, editor));
+ editor.on("linked-css-file", this._summaryChange.bind(this, editor));
+ editor.on("linked-css-file-error", this._summaryChange.bind(this, editor));
+ editor.on("error", this._onError);
+
+ this.editors.push(editor);
+
+ editor.fetchSource(this._sourceLoaded.bind(this, editor))
+ .then(null, Cu.reportError);
+ return editor;
+ },
+
+ /**
+ * Import a style sheet from file and asynchronously create a
+ * new stylesheet on the debuggee for it.
+ *
+ * @param {mixed} file
+ * Optional nsIFile or filename string.
+ * If not set a file picker will be shown.
+ * @param {nsIWindow} parentWindow
+ * Optional parent window for the file picker.
+ */
+ _importFromFile: function(file, parentWindow) {
+ let onFileSelected = (file) => {
+ if (!file) {
+ // nothing selected
+ return;
+ }
+ NetUtil.asyncFetch2(file, (stream, status) => {
+ if (!Components.isSuccessCode(status)) {
+ this.emit("error", { key: LOAD_ERROR });
+ return;
+ }
+ let source = NetUtil.readInputStreamToString(stream, stream.available());
+ stream.close();
+
+ this._debuggee.addStyleSheet(source).then((styleSheet) => {
+ this._onStyleSheetCreated(styleSheet, file);
+ });
+ },
+ this._window.document,
+ null, // aLoadingPrincipal
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_NORMAL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+ };
+
+ showFilePicker(file, false, parentWindow, onFileSelected);
+ },
+
+
+ /**
+ * When a new or imported stylesheet has been added to the document.
+ * Add an editor for it.
+ */
+ _onStyleSheetCreated: function(styleSheet, file) {
+ this._addStyleSheetEditor(styleSheet, file, true);
+ },
+
+ /**
+ * Forward any error from a stylesheet.
+ *
+ * @param {string} event
+ * Event name
+ * @param {data} data
+ * The event data
+ */
+ _onError: function(event, data) {
+ this.emit("error", data);
+ },
+
+ /**
+ * Toggle the original sources pref.
+ */
+ _toggleOrigSources: function() {
+ let isEnabled = Services.prefs.getBoolPref(PREF_ORIG_SOURCES);
+ Services.prefs.setBoolPref(PREF_ORIG_SOURCES, !isEnabled);
+ },
+
+ /**
+ * Toggle the pref for showing a @media rules sidebar in each editor.
+ */
+ _toggleMediaSidebar: function() {
+ let isEnabled = Services.prefs.getBoolPref(PREF_MEDIA_SIDEBAR);
+ Services.prefs.setBoolPref(PREF_MEDIA_SIDEBAR, !isEnabled);
+ },
+
+ /**
+ * Toggle the @media sidebar in each editor depending on the setting.
+ */
+ _onMediaPrefChanged: function() {
+ this.editors.forEach(this._updateMediaList);
+ },
+
+ /**
+ * Remove a particular stylesheet editor from the UI
+ *
+ * @param {StyleSheetEditor} editor
+ * The editor to remove.
+ */
+ _removeStyleSheetEditor: function(editor) {
+ if (editor.summary) {
+ this._view.removeItem(editor.summary);
+ }
+ else {
+ let self = this;
+ this.on("editor-added", function onAdd(event, added) {
+ if (editor == added) {
+ self.off("editor-added", onAdd);
+ self._view.removeItem(editor.summary);
+ }
+ })
+ }
+
+ editor.destroy();
+ this.editors.splice(this.editors.indexOf(editor), 1);
+ },
+
+ /**
+ * Clear all the editors from the UI.
+ */
+ _clearStyleSheetEditors: function() {
+ for (let editor of this.editors) {
+ editor.destroy();
+ }
+ this.editors = [];
+ },
+
+ /**
+ * Called when a StyleSheetEditor's source has been fetched. Create a
+ * summary UI for the editor.
+ *
+ * @param {StyleSheetEditor} editor
+ * Editor to create UI for.
+ */
+ _sourceLoaded: function(editor) {
+ // add new sidebar item and editor to the UI
+ this._view.appendTemplatedItem(STYLE_EDITOR_TEMPLATE, {
+ data: {
+ editor: editor
+ },
+ disableAnimations: this._alwaysDisableAnimations,
+ ordinal: editor.styleSheet.styleSheetIndex,
+ onCreate: function(summary, details, data) {
+ let editor = data.editor;
+ editor.summary = summary;
+ editor.details = details;
+
+ wire(summary, ".stylesheet-enabled", function onToggleDisabled(event) {
+ event.stopPropagation();
+ event.target.blur();
+
+ editor.toggleDisabled();
+ });
+
+ wire(summary, ".stylesheet-name", {
+ events: {
+ "keypress": (aEvent) => {
+ if (aEvent.keyCode == aEvent.DOM_VK_RETURN) {
+ this._view.activeSummary = summary;
+ }
+ }
+ }
+ });
+
+ wire(summary, ".stylesheet-saveButton", function onSaveButton(event) {
+ event.stopPropagation();
+ event.target.blur();
+
+ editor.saveToFile(editor.savedFile);
+ });
+
+ this._updateSummaryForEditor(editor, summary);
+
+ summary.addEventListener("focus", function onSummaryFocus(event) {
+ if (event.target == summary) {
+ // autofocus the stylesheet name
+ summary.querySelector(".stylesheet-name").focus();
+ }
+ }, false);
+
+ let sidebar = details.querySelector(".stylesheet-sidebar");
+ sidebar.setAttribute("width",
+ Services.prefs.getIntPref(PREF_SIDEBAR_WIDTH));
+
+ let splitter = details.querySelector(".devtools-side-splitter");
+ splitter.addEventListener("mousemove", () => {
+ let sidebarWidth = sidebar.getAttribute("width");
+ Services.prefs.setIntPref(PREF_SIDEBAR_WIDTH, sidebarWidth);
+
+ // update all @media sidebars for consistency
+ let sidebars = [...this._panelDoc.querySelectorAll(".stylesheet-sidebar")];
+ for (let mediaSidebar of sidebars) {
+ mediaSidebar.setAttribute("width", sidebarWidth);
+ }
+ });
+
+ // autofocus if it's a new user-created stylesheet
+ if (editor.isNew) {
+ this._selectEditor(editor);
+ }
+
+ if (this._isEditorToSelect(editor)) {
+ this.switchToSelectedSheet();
+ }
+
+ // If this is the first stylesheet and there is no pending request to
+ // select a particular style sheet, select this sheet.
+ if (!this.selectedEditor && !this._styleSheetBoundToSelect
+ && editor.styleSheet.styleSheetIndex == 0) {
+ this._selectEditor(editor);
+ }
+ this.emit("editor-added", editor);
+ }.bind(this),
+
+ onShow: function(summary, details, data) {
+ let editor = data.editor;
+ this.selectedEditor = editor;
+
+ Task.spawn(function* () {
+ if (!editor.sourceEditor) {
+ // only initialize source editor when we switch to this view
+ let inputElement = details.querySelector(".stylesheet-editor-input");
+ yield editor.load(inputElement);
+ }
+
+ editor.onShow();
+
+ this.emit("editor-selected", editor);
+
+ // Is there any CSS coverage markup to include?
+ csscoverage.getUsage(this._target).then(usage => {
+ if (usage == null) {
+ return;
+ }
+
+ let href = csscoverage.sheetToUrl(editor.styleSheet);
+ usage.createEditorReport(href).then(data => {
+ editor.removeAllUnusedRegions();
+
+ if (data.reports.length > 0) {
+ // Only apply if this file isn't compressed. We detect a
+ // compressed file if there are more rules than lines.
+ let text = editor.sourceEditor.getText();
+ let lineCount = text.split("\n").length;
+ let ruleCount = editor.styleSheet.ruleCount;
+ if (lineCount >= ruleCount) {
+ editor.addUnusedRegions(data.reports);
+ }
+ else {
+ this.emit("error", { key: "error-compressed", level: "info" });
+ }
+ }
+ }, Cu.reportError);
+ }, Cu.reportError);
+ }.bind(this)).then(null, Cu.reportError);
+ }.bind(this)
+ });
+ },
+
+ /**
+ * Switch to the editor that has been marked to be selected.
+ *
+ * @return {Promise}
+ * Promise that will resolve when the editor is selected.
+ */
+ switchToSelectedSheet: function() {
+ let toSelect = this._styleSheetToSelect;
+
+ for (let editor of this.editors) {
+ if (this._isEditorToSelect(editor)) {
+ // The _styleSheetBoundToSelect will always hold the latest pending
+ // requested style sheet (with line and column) which is not yet
+ // selected by the source editor. Only after we select that particular
+ // editor and go the required line and column, it will become null.
+ this._styleSheetBoundToSelect = this._styleSheetToSelect;
+ this._styleSheetToSelect = null;
+ return this._selectEditor(editor, toSelect.line, toSelect.col);
+ }
+ }
+
+ return promise.resolve();
+ },
+
+ /**
+ * Returns whether a given editor is the current editor to be selected. Tests
+ * based on href or underlying stylesheet.
+ *
+ * @param {StyleSheetEditor} editor
+ * The editor to test.
+ */
+ _isEditorToSelect: function(editor) {
+ let toSelect = this._styleSheetToSelect;
+ if (!toSelect) {
+ return false;
+ }
+ let isHref = toSelect.stylesheet === null ||
+ typeof toSelect.stylesheet == "string";
+
+ return (isHref && editor.styleSheet.href == toSelect.stylesheet) ||
+ (toSelect.stylesheet == editor.styleSheet);
+ },
+
+ /**
+ * Select an editor in the UI.
+ *
+ * @param {StyleSheetEditor} editor
+ * Editor to switch to.
+ * @param {number} line
+ * Line number to jump to
+ * @param {number} col
+ * Column number to jump to
+ * @return {Promise}
+ * Promise that will resolve when the editor is selected.
+ */
+ _selectEditor: function(editor, line, col) {
+ line = line || 0;
+ col = col || 0;
+
+ let editorPromise = editor.getSourceEditor().then(() => {
+ editor.sourceEditor.setCursor({line: line, ch: col});
+ this._styleSheetBoundToSelect = null;
+ });
+
+ let summaryPromise = this.getEditorSummary(editor).then((summary) => {
+ this._view.activeSummary = summary;
+ });
+
+ return promise.all([editorPromise, summaryPromise]);
+ },
+
+ getEditorSummary: function(editor) {
+ if (editor.summary) {
+ return promise.resolve(editor.summary);
+ }
+
+ let deferred = promise.defer();
+ let self = this;
+
+ this.on("editor-added", function onAdd(e, selected) {
+ if (selected == editor) {
+ self.off("editor-added", onAdd);
+ deferred.resolve(editor.summary);
+ }
+ });
+
+ return deferred.promise;
+ },
+
+ getEditorDetails: function(editor) {
+ if (editor.details) {
+ return promise.resolve(editor.details);
+ }
+
+ let deferred = promise.defer();
+ let self = this;
+
+ this.on("editor-added", function onAdd(e, selected) {
+ if (selected == editor) {
+ self.off("editor-added", onAdd);
+ deferred.resolve(editor.details);
+ }
+ });
+
+ return deferred.promise;
+ },
+
+ /**
+ * Returns an identifier for the given style sheet.
+ *
+ * @param {StyleSheet} aStyleSheet
+ * The style sheet to be identified.
+ */
+ getStyleSheetIdentifier: function (aStyleSheet) {
+ // Identify inline style sheets by their host page URI and index at the page.
+ return aStyleSheet.href ? aStyleSheet.href :
+ "inline-" + aStyleSheet.styleSheetIndex + "-at-" + aStyleSheet.nodeHref;
+ },
+
+ /**
+ * selects a stylesheet and optionally moves the cursor to a selected line
+ *
+ * @param {StyleSheetFront} [stylesheet]
+ * Stylesheet to select or href of stylesheet to select
+ * @param {Number} [line]
+ * Line to which the caret should be moved (zero-indexed).
+ * @param {Number} [col]
+ * Column to which the caret should be moved (zero-indexed).
+ */
+ selectStyleSheet: function(stylesheet, line, col) {
+ this._styleSheetToSelect = {
+ stylesheet: stylesheet,
+ line: line,
+ col: col,
+ };
+
+ /* Switch to the editor for this sheet, if it exists yet.
+ Otherwise each editor will be checked when it's created. */
+ this.switchToSelectedSheet();
+ },
+
+
+ /**
+ * Handler for an editor's 'property-changed' event.
+ * Update the summary in the UI.
+ *
+ * @param {StyleSheetEditor} editor
+ * Editor for which a property has changed
+ */
+ _summaryChange: function(editor) {
+ this._updateSummaryForEditor(editor);
+ },
+
+ /**
+ * Update split view summary of given StyleEditor instance.
+ *
+ * @param {StyleSheetEditor} editor
+ * @param {DOMElement} summary
+ * Optional item's summary element to update. If none, item corresponding
+ * to passed editor is used.
+ */
+ _updateSummaryForEditor: function(editor, summary) {
+ summary = summary || editor.summary;
+ if (!summary) {
+ return;
+ }
+
+ let ruleCount = editor.styleSheet.ruleCount;
+ if (editor.styleSheet.relatedStyleSheet && editor.linkedCSSFile) {
+ ruleCount = editor.styleSheet.relatedStyleSheet.ruleCount;
+ }
+ if (ruleCount === undefined) {
+ ruleCount = "-";
+ }
+
+ var flags = [];
+ if (editor.styleSheet.disabled) {
+ flags.push("disabled");
+ }
+ if (editor.unsaved) {
+ flags.push("unsaved");
+ }
+ if (editor.linkedCSSFileError) {
+ flags.push("linked-file-error");
+ }
+ this._view.setItemClassName(summary, flags.join(" "));
+
+ let label = summary.querySelector(".stylesheet-name > label");
+ label.setAttribute("value", editor.friendlyName);
+ if (editor.styleSheet.href) {
+ label.setAttribute("tooltiptext", editor.styleSheet.href);
+ }
+
+ let linkedCSSFile = "";
+ if (editor.linkedCSSFile) {
+ linkedCSSFile = OS.Path.basename(editor.linkedCSSFile);
+ }
+ text(summary, ".stylesheet-linked-file", linkedCSSFile);
+ text(summary, ".stylesheet-title", editor.styleSheet.title || "");
+ text(summary, ".stylesheet-rule-count",
+ PluralForm.get(ruleCount, _("ruleCount.label")).replace("#1", ruleCount));
+ },
+
+ /**
+ * Update the @media rules sidebar for an editor. Hide if there are no rules
+ * Display a list of the @media rules in the editor's associated style sheet.
+ * Emits a 'media-list-changed' event after updating the UI.
+ *
+ * @param {StyleSheetEditor} editor
+ * Editor to update @media sidebar of
+ */
+ _updateMediaList: function(editor) {
+ Task.spawn(function* () {
+ let details = yield this.getEditorDetails(editor);
+ let list = details.querySelector(".stylesheet-media-list");
+
+ while (list.firstChild) {
+ list.removeChild(list.firstChild);
+ }
+
+ let rules = editor.mediaRules;
+ let showSidebar = Services.prefs.getBoolPref(PREF_MEDIA_SIDEBAR);
+ let sidebar = details.querySelector(".stylesheet-sidebar");
+
+ let inSource = false;
+
+ for (let rule of rules) {
+ let {line, column, parentStyleSheet} = rule;
+
+ let location = {
+ line: line,
+ column: column,
+ source: editor.styleSheet.href,
+ styleSheet: parentStyleSheet
+ };
+ if (editor.styleSheet.isOriginalSource) {
+ location = yield editor.cssSheet.getOriginalLocation(line, column);
+ }
+
+ // this @media rule is from a different original source
+ if (location.source != editor.styleSheet.href) {
+ continue;
+ }
+ inSource = true;
+
+ let div = this._panelDoc.createElement("div");
+ div.className = "media-rule-label";
+ div.addEventListener("click", this._jumpToLocation.bind(this, location));
+
+ let cond = this._panelDoc.createElement("div");
+ cond.textContent = rule.conditionText;
+ cond.className = "media-rule-condition"
+ if (!rule.matches) {
+ cond.classList.add("media-condition-unmatched");
+ }
+ div.appendChild(cond);
+
+ let link = this._panelDoc.createElement("div");
+ link.className = "media-rule-line theme-link";
+ if (location.line != -1) {
+ link.textContent = ":" + location.line;
+ }
+ div.appendChild(link);
+
+ list.appendChild(div);
+ }
+
+ sidebar.hidden = !showSidebar || !inSource;
+
+ this.emit("media-list-changed", editor);
+ }.bind(this)).then(null, Cu.reportError);
+ },
+
+ /**
+ * Jump cursor to the editor for a stylesheet and line number for a rule.
+ *
+ * @param {object} location
+ * Location object with 'line', 'column', and 'source' properties.
+ */
+ _jumpToLocation: function(location) {
+ let source = location.styleSheet || location.source;
+ this.selectStyleSheet(source, location.line - 1, location.column - 1);
+ },
+
+ destroy: function() {
+ if (this._highlighter) {
+ this._highlighter.finalize();
+ this._highlighter = null;
+ }
+
+ this._clearStyleSheetEditors();
+
+ let sidebar = this._panelDoc.querySelector(".splitview-controller");
+ let sidebarWidth = sidebar.getAttribute("width");
+ Services.prefs.setIntPref(PREF_NAV_WIDTH, sidebarWidth);
+
+ this._optionsMenu.removeEventListener("popupshowing",
+ this._onOptionsPopupShowing);
+ this._optionsMenu.removeEventListener("popuphiding",
+ this._onOptionsPopupHiding);
+
+ this._prefObserver.off(PREF_ORIG_SOURCES, this._onNewDocument);
+ this._prefObserver.off(PREF_MEDIA_SIDEBAR, this._onMediaPrefChanged);
+ this._prefObserver.destroy();
+ }
+}
diff --git a/toolkit/devtools/styleeditor/StyleEditorUtil.jsm b/toolkit/devtools/styleeditor/StyleEditorUtil.jsm
new file mode 100644
index 000000000..53df0da28
--- /dev/null
+++ b/toolkit/devtools/styleeditor/StyleEditorUtil.jsm
@@ -0,0 +1,234 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = [
+ "_",
+ "assert",
+ "log",
+ "text",
+ "wire",
+ "showFilePicker"
+];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const PROPERTIES_URL = "chrome://browser/locale/devtools/styleeditor.properties";
+
+const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
+const console = require("resource://gre/modules/devtools/Console.jsm").console;
+const gStringBundle = Services.strings.createBundle(PROPERTIES_URL);
+
+
+/**
+ * Returns a localized string with the given key name from the string bundle.
+ *
+ * @param aName
+ * @param ...rest
+ * Optional arguments to format in the string.
+ * @return string
+ */
+this._ = function _(aName)
+{
+ try {
+ if (arguments.length == 1) {
+ return gStringBundle.GetStringFromName(aName);
+ }
+ let rest = Array.prototype.slice.call(arguments, 1);
+ return gStringBundle.formatStringFromName(aName, rest, rest.length);
+ }
+ catch (ex) {
+ console.error(ex);
+ throw new Error("L10N error. '" + aName + "' is missing from " + PROPERTIES_URL);
+ }
+}
+
+/**
+ * Assert an expression is true or throw if false.
+ *
+ * @param aExpression
+ * @param aMessage
+ * Optional message.
+ * @return aExpression
+ */
+this.assert = function assert(aExpression, aMessage)
+{
+ if (!!!(aExpression)) {
+ let msg = aMessage ? "ASSERTION FAILURE:" + aMessage : "ASSERTION FAILURE";
+ log(msg);
+ throw new Error(msg);
+ }
+ return aExpression;
+}
+
+/**
+ * Retrieve or set the text content of an element.
+ *
+ * @param DOMElement aRoot
+ * The element to use for querySelector.
+ * @param string aSelector
+ * Selector string for the element to get/set the text content.
+ * @param string aText
+ * Optional text to set.
+ * @return string
+ * Text content of matching element or null if there were no element
+ * matching aSelector.
+ */
+this.text = function text(aRoot, aSelector, aText)
+{
+ let element = aRoot.querySelector(aSelector);
+ if (!element) {
+ return null;
+ }
+
+ if (aText === undefined) {
+ return element.textContent;
+ }
+ element.textContent = aText;
+ return aText;
+}
+
+/**
+ * Iterates _own_ properties of an object.
+ *
+ * @param aObject
+ * The object to iterate.
+ * @param function aCallback(aKey, aValue)
+ */
+function forEach(aObject, aCallback)
+{
+ for (let key in aObject) {
+ if (aObject.hasOwnProperty(key)) {
+ aCallback(key, aObject[key]);
+ }
+ }
+}
+
+/**
+ * Log a message to the console.
+ *
+ * @param ...rest
+ * One or multiple arguments to log.
+ * If multiple arguments are given, they will be joined by " " in the log.
+ */
+this.log = function log()
+{
+ console.logStringMessage(Array.prototype.slice.call(arguments).join(" "));
+}
+
+/**
+ * Wire up element(s) matching selector with attributes, event listeners, etc.
+ *
+ * @param DOMElement aRoot
+ * The element to use for querySelectorAll.
+ * Can be null if aSelector is a DOMElement.
+ * @param string|DOMElement aSelectorOrElement
+ * Selector string or DOMElement for the element(s) to wire up.
+ * @param object aDescriptor
+ * An object describing how to wire matching selector, supported properties
+ * are "events" and "attributes" taking objects themselves.
+ * Each key of properties above represents the name of the event or
+ * attribute, with the value being a function used as an event handler or
+ * string to use as attribute value.
+ * If aDescriptor is a function, the argument is equivalent to :
+ * {events: {'click': aDescriptor}}
+ */
+this.wire = function wire(aRoot, aSelectorOrElement, aDescriptor)
+{
+ let matches;
+ if (typeof(aSelectorOrElement) == "string") { // selector
+ matches = aRoot.querySelectorAll(aSelectorOrElement);
+ if (!matches.length) {
+ return;
+ }
+ } else {
+ matches = [aSelectorOrElement]; // element
+ }
+
+ if (typeof(aDescriptor) == "function") {
+ aDescriptor = {events: {click: aDescriptor}};
+ }
+
+ for (let i = 0; i < matches.length; i++) {
+ let element = matches[i];
+ forEach(aDescriptor.events, function (aName, aHandler) {
+ element.addEventListener(aName, aHandler, false);
+ });
+ forEach(aDescriptor.attributes, element.setAttribute);
+ }
+}
+
+/**
+ * Show file picker and return the file user selected.
+ *
+ * @param mixed file
+ * Optional nsIFile or string representing the filename to auto-select.
+ * @param boolean toSave
+ * If true, the user is selecting a filename to save.
+ * @param nsIWindow parentWindow
+ * Optional parent window. If null the parent window of the file picker
+ * will be the window of the attached input element.
+ * @param callback
+ * The callback method, which will be called passing in the selected
+ * file or null if the user did not pick one.
+ * @param AString suggestedFilename
+ * The suggested filename when toSave is true.
+ */
+this.showFilePicker = function showFilePicker(path, toSave, parentWindow,
+ callback, suggestedFilename)
+{
+ if (typeof(path) == "string") {
+ try {
+ if (Services.io.extractScheme(path) == "file") {
+ let uri = Services.io.newURI(path, null, null);
+ let file = uri.QueryInterface(Ci.nsIFileURL).file;
+ callback(file);
+ return;
+ }
+ } catch (ex) {
+ callback(null);
+ return;
+ }
+ try {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+ file.initWithPath(path);
+ callback(file);
+ return;
+ } catch (ex) {
+ callback(null);
+ return;
+ }
+ }
+ if (path) { // "path" is an nsIFile
+ callback(path);
+ return;
+ }
+
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ let mode = toSave ? fp.modeSave : fp.modeOpen;
+ let key = toSave ? "saveStyleSheet" : "importStyleSheet";
+ let fpCallback = function(result) {
+ if (result == Ci.nsIFilePicker.returnCancel) {
+ callback(null);
+ } else {
+ callback(fp.file);
+ }
+ };
+
+ if (toSave && suggestedFilename) {
+ fp.defaultString = suggestedFilename;
+ }
+
+ fp.init(parentWindow, _(key + ".title"), mode);
+ fp.appendFilters(_(key + ".filter"), "*.css");
+ fp.appendFilters(fp.filterAll);
+ fp.open(fpCallback);
+ return;
+}
diff --git a/toolkit/devtools/styleeditor/StyleSheetEditor.jsm b/toolkit/devtools/styleeditor/StyleSheetEditor.jsm
new file mode 100644
index 000000000..141ea7c60
--- /dev/null
+++ b/toolkit/devtools/styleeditor/StyleSheetEditor.jsm
@@ -0,0 +1,810 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+this.EXPORTED_SYMBOLS = ["StyleSheetEditor", "prettifyCSS"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
+const Editor = require("devtools/sourceeditor/editor");
+const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+const {CssLogic} = require("devtools/styleinspector/css-logic");
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/devtools/event-emitter.js");
+Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm");
+
+const LOAD_ERROR = "error-load";
+const SAVE_ERROR = "error-save";
+
+// max update frequency in ms (avoid potential typing lag and/or flicker)
+// @see StyleEditor.updateStylesheet
+const UPDATE_STYLESHEET_THROTTLE_DELAY = 500;
+
+// Pref which decides if CSS autocompletion is enabled in Style Editor or not.
+const AUTOCOMPLETION_PREF = "devtools.styleeditor.autocompletion-enabled";
+
+// Pref which decides whether updates to the stylesheet use transitions
+const TRANSITION_PREF = "devtools.styleeditor.transitions";
+
+// How long to wait to update linked CSS file after original source was saved
+// to disk. Time in ms.
+const CHECK_LINKED_SHEET_DELAY=500;
+
+// How many times to check for linked file changes
+const MAX_CHECK_COUNT=10;
+
+// The classname used to show a line that is not used
+const UNUSED_CLASS = "cm-unused-line";
+
+// How much time should the mouse be still before the selector at that position
+// gets highlighted?
+const SELECTOR_HIGHLIGHT_TIMEOUT = 500;
+
+/**
+ * StyleSheetEditor controls the editor linked to a particular StyleSheet
+ * object.
+ *
+ * Emits events:
+ * 'property-change': A property on the underlying stylesheet has changed
+ * 'source-editor-load': The source editor for this editor has been loaded
+ * 'error': An error has occured
+ *
+ * @param {StyleSheet|OriginalSource} styleSheet
+ * Stylesheet or original source to show
+ * @param {DOMWindow} win
+ * panel window for style editor
+ * @param {nsIFile} file
+ * Optional file that the sheet was imported from
+ * @param {boolean} isNew
+ * Optional whether the sheet was created by the user
+ * @param {Walker} walker
+ * Optional walker used for selectors autocompletion
+ * @param {CustomHighlighterFront} highlighter
+ * Optional highlighter front for the SelectorHighligher used to
+ * highlight selectors
+ */
+function StyleSheetEditor(styleSheet, win, file, isNew, walker, highlighter) {
+ EventEmitter.decorate(this);
+
+ this.styleSheet = styleSheet;
+ this._inputElement = null;
+ this.sourceEditor = null;
+ this._window = win;
+ this._isNew = isNew;
+ this.walker = walker;
+ this.highlighter = highlighter;
+
+ this._state = { // state to use when inputElement attaches
+ text: "",
+ selection: {
+ start: {line: 0, ch: 0},
+ end: {line: 0, ch: 0}
+ },
+ topIndex: 0 // the first visible line
+ };
+
+ this._styleSheetFilePath = null;
+ if (styleSheet.href &&
+ Services.io.extractScheme(this.styleSheet.href) == "file") {
+ this._styleSheetFilePath = this.styleSheet.href;
+ }
+
+ this._onPropertyChange = this._onPropertyChange.bind(this);
+ this._onError = this._onError.bind(this);
+ this._onMediaRuleMatchesChange = this._onMediaRuleMatchesChange.bind(this);
+ this._onMediaRulesChanged = this._onMediaRulesChanged.bind(this)
+ this.checkLinkedFileForChanges = this.checkLinkedFileForChanges.bind(this);
+ this.markLinkedFileBroken = this.markLinkedFileBroken.bind(this);
+ this.saveToFile = this.saveToFile.bind(this);
+ this.updateStyleSheet = this.updateStyleSheet.bind(this);
+ this._onMouseMove = this._onMouseMove.bind(this);
+
+ this._focusOnSourceEditorReady = false;
+ this.cssSheet.on("property-change", this._onPropertyChange);
+ this.styleSheet.on("error", this._onError);
+ this.mediaRules = [];
+ if (this.cssSheet.getMediaRules) {
+ this.cssSheet.getMediaRules().then(this._onMediaRulesChanged, Cu.reportError);
+ }
+ this.cssSheet.on("media-rules-changed", this._onMediaRulesChanged);
+ this.savedFile = file;
+ this.linkCSSFile();
+}
+
+StyleSheetEditor.prototype = {
+ /**
+ * Whether there are unsaved changes in the editor
+ */
+ get unsaved() {
+ return this.sourceEditor && !this.sourceEditor.isClean();
+ },
+
+ /**
+ * Whether the editor is for a stylesheet created by the user
+ * through the style editor UI.
+ */
+ get isNew() {
+ return this._isNew;
+ },
+
+ /**
+ * The style sheet or the generated style sheet for this source if it's an
+ * original source.
+ */
+ get cssSheet() {
+ if (this.styleSheet.isOriginalSource) {
+ return this.styleSheet.relatedStyleSheet;
+ }
+ return this.styleSheet;
+ },
+
+ get savedFile() {
+ return this._savedFile;
+ },
+
+ set savedFile(name) {
+ this._savedFile = name;
+
+ this.linkCSSFile();
+ },
+
+ /**
+ * Get a user-friendly name for the style sheet.
+ *
+ * @return string
+ */
+ get friendlyName() {
+ if (this.savedFile) {
+ return this.savedFile.leafName;
+ }
+
+ if (this._isNew) {
+ let index = this.styleSheet.styleSheetIndex + 1;
+ return _("newStyleSheet", index);
+ }
+
+ if (!this.styleSheet.href) {
+ let index = this.styleSheet.styleSheetIndex + 1;
+ return _("inlineStyleSheet", index);
+ }
+
+ if (!this._friendlyName) {
+ let sheetURI = this.styleSheet.href;
+ this._friendlyName = CssLogic.shortSource({ href: sheetURI });
+ try {
+ this._friendlyName = decodeURI(this._friendlyName);
+ } catch (ex) {
+ }
+ }
+ return this._friendlyName;
+ },
+
+ /**
+ * If this is an original source, get the path of the CSS file it generated.
+ */
+ linkCSSFile: function() {
+ if (!this.styleSheet.isOriginalSource) {
+ return;
+ }
+
+ let relatedSheet = this.styleSheet.relatedStyleSheet;
+
+ let path;
+ let href = removeQuery(relatedSheet.href);
+ let uri = NetUtil.newURI(href);
+
+ if (uri.scheme == "file") {
+ let file = uri.QueryInterface(Ci.nsIFileURL).file;
+ path = file.path;
+ }
+ else if (this.savedFile) {
+ let origHref = removeQuery(this.styleSheet.href);
+ let origUri = NetUtil.newURI(origHref);
+ path = findLinkedFilePath(uri, origUri, this.savedFile);
+ }
+ else {
+ // we can't determine path to generated file on disk
+ return;
+ }
+
+ if (this.linkedCSSFile == path) {
+ return;
+ }
+
+ this.linkedCSSFile = path;
+
+ this.linkedCSSFileError = null;
+
+ // save last file change time so we can compare when we check for changes.
+ OS.File.stat(path).then((info) => {
+ this._fileModDate = info.lastModificationDate.getTime();
+ }, this.markLinkedFileBroken);
+
+ this.emit("linked-css-file");
+ },
+
+ /**
+ * Start fetching the full text source for this editor's sheet.
+ */
+ fetchSource: function(callback) {
+ return this.styleSheet.getText().then((longStr) => {
+ longStr.string().then((source) => {
+ let ruleCount = this.styleSheet.ruleCount;
+ if (!this.styleSheet.isOriginalSource) {
+ source = CssLogic.prettifyCSS(source, ruleCount);
+ }
+ this._state.text = source;
+ this.sourceLoaded = true;
+
+ if (callback) {
+ callback(source);
+ }
+ return source;
+ });
+ }, e => {
+ this.emit("error", { key: LOAD_ERROR, append: this.styleSheet.href });
+ throw e;
+ })
+ },
+
+ /**
+ * Add markup to a region. UNUSED_CLASS is added to specified lines
+ * @param region An object shaped like
+ * {
+ * start: { line: L1, column: C1 },
+ * end: { line: L2, column: C2 } // optional
+ * }
+ */
+ addUnusedRegion: function(region) {
+ this.sourceEditor.addLineClass(region.start.line - 1, UNUSED_CLASS);
+ if (region.end) {
+ for (let i = region.start.line; i <= region.end.line; i++) {
+ this.sourceEditor.addLineClass(i - 1, UNUSED_CLASS);
+ }
+ }
+ },
+
+ /**
+ * As addUnusedRegion except that it takes an array of regions
+ */
+ addUnusedRegions: function(regions) {
+ for (let region of regions) {
+ this.addUnusedRegion(region);
+ }
+ },
+
+ /**
+ * Remove all the unused markup regions added by addUnusedRegion
+ */
+ removeAllUnusedRegions: function() {
+ for (let i = 0; i < this.sourceEditor.lineCount(); i++) {
+ this.sourceEditor.removeLineClass(i, UNUSED_CLASS);
+ }
+ },
+
+ /**
+ * Forward property-change event from stylesheet.
+ *
+ * @param {string} event
+ * Event type
+ * @param {string} property
+ * Property that has changed on sheet
+ */
+ _onPropertyChange: function(property, value) {
+ this.emit("property-change", property, value);
+ },
+
+ /**
+ * Handles changes to the list of @media rules in the stylesheet.
+ * Emits 'media-rules-changed' if the list has changed.
+ *
+ * @param {array} rules
+ * Array of MediaRuleFronts for new media rules of sheet.
+ */
+ _onMediaRulesChanged: function(rules) {
+ if (!rules.length && !this.mediaRules.length) {
+ return;
+ }
+ for (let rule of this.mediaRules) {
+ rule.off("matches-change", this._onMediaRuleMatchesChange);
+ rule.destroy();
+ }
+ this.mediaRules = rules;
+
+ for (let rule of rules) {
+ rule.on("matches-change", this._onMediaRuleMatchesChange);
+ }
+ this.emit("media-rules-changed", rules);
+ },
+
+ /**
+ * Forward media-rules-changed event from stylesheet.
+ */
+ _onMediaRuleMatchesChange: function() {
+ this.emit("media-rules-changed", this.mediaRules);
+ },
+
+ /**
+ * Forward error event from stylesheet.
+ *
+ * @param {string} event
+ * Event type
+ * @param {string} errorCode
+ */
+ _onError: function(event, data) {
+ this.emit("error", data);
+ },
+
+ /**
+ * Create source editor and load state into it.
+ * @param {DOMElement} inputElement
+ * Element to load source editor in
+ *
+ * @return {Promise}
+ * Promise that will resolve when the style editor is loaded.
+ */
+ load: function(inputElement) {
+ this._inputElement = inputElement;
+
+ let config = {
+ value: this._state.text,
+ lineNumbers: true,
+ mode: Editor.modes.css,
+ readOnly: false,
+ autoCloseBrackets: "{}()[]",
+ extraKeys: this._getKeyBindings(),
+ contextMenu: "sourceEditorContextMenu",
+ autocomplete: Services.prefs.getBoolPref(AUTOCOMPLETION_PREF),
+ autocompleteOpts: { walker: this.walker }
+ };
+ let sourceEditor = this._sourceEditor = new Editor(config);
+
+ sourceEditor.on("dirty-change", this._onPropertyChange);
+
+ return sourceEditor.appendTo(inputElement).then(() => {
+ sourceEditor.on("save", this.saveToFile);
+
+ if (this.styleSheet.update) {
+ sourceEditor.on("change", this.updateStyleSheet);
+ }
+
+ this.sourceEditor = sourceEditor;
+
+ if (this._focusOnSourceEditorReady) {
+ this._focusOnSourceEditorReady = false;
+ sourceEditor.focus();
+ }
+
+ sourceEditor.setFirstVisibleLine(this._state.topIndex);
+ sourceEditor.setSelection(this._state.selection.start,
+ this._state.selection.end);
+
+ if (this.highlighter && this.walker) {
+ sourceEditor.container.addEventListener("mousemove", this._onMouseMove);
+ }
+
+ this.emit("source-editor-load");
+ });
+ },
+
+ /**
+ * Get the source editor for this editor.
+ *
+ * @return {Promise}
+ * Promise that will resolve with the editor.
+ */
+ getSourceEditor: function() {
+ let deferred = promise.defer();
+
+ if (this.sourceEditor) {
+ return promise.resolve(this);
+ }
+ this.on("source-editor-load", () => {
+ deferred.resolve(this);
+ });
+ return deferred.promise;
+ },
+
+ /**
+ * Focus the Style Editor input.
+ */
+ focus: function() {
+ if (this.sourceEditor) {
+ this.sourceEditor.focus();
+ } else {
+ this._focusOnSourceEditorReady = true;
+ }
+ },
+
+ /**
+ * Event handler for when the editor is shown.
+ */
+ onShow: function() {
+ if (this.sourceEditor) {
+ this.sourceEditor.setFirstVisibleLine(this._state.topIndex);
+ }
+ this.focus();
+ },
+
+ /**
+ * Toggled the disabled state of the underlying stylesheet.
+ */
+ toggleDisabled: function() {
+ this.styleSheet.toggleDisabled().then(null, Cu.reportError);
+ },
+
+ /**
+ * Queue a throttled task to update the live style sheet.
+ *
+ * @param boolean immediate
+ * Optional. If true the update is performed immediately.
+ */
+ updateStyleSheet: function(immediate) {
+ if (this._updateTask) {
+ // cancel previous queued task not executed within throttle delay
+ this._window.clearTimeout(this._updateTask);
+ }
+
+ if (immediate) {
+ this._updateStyleSheet();
+ } else {
+ this._updateTask = this._window.setTimeout(this._updateStyleSheet.bind(this),
+ UPDATE_STYLESHEET_THROTTLE_DELAY);
+ }
+ },
+
+ /**
+ * Update live style sheet according to modifications.
+ */
+ _updateStyleSheet: function() {
+ if (this.styleSheet.disabled) {
+ return; // TODO: do we want to do this?
+ }
+
+ this._updateTask = null; // reset only if we actually perform an update
+ // (stylesheet is enabled) so that 'missed' updates
+ // while the stylesheet is disabled can be performed
+ // when it is enabled back. @see enableStylesheet
+
+ if (this.sourceEditor) {
+ this._state.text = this.sourceEditor.getText();
+ }
+
+ let transitionsEnabled = Services.prefs.getBoolPref(TRANSITION_PREF);
+
+ this.styleSheet.update(this._state.text, transitionsEnabled)
+ .then(null, Cu.reportError);
+ },
+
+ /**
+ * Handle mousemove events, calling _highlightSelectorAt after a delay only
+ * and reseting the delay everytime.
+ */
+ _onMouseMove: function(e) {
+ this.highlighter.hide();
+
+ if (this.mouseMoveTimeout) {
+ this._window.clearTimeout(this.mouseMoveTimeout);
+ this.mouseMoveTimeout = null;
+ }
+
+ this.mouseMoveTimeout = this._window.setTimeout(() => {
+ this._highlightSelectorAt(e.clientX, e.clientY);
+ }, SELECTOR_HIGHLIGHT_TIMEOUT);
+ },
+
+ /**
+ * Highlight nodes matching the selector found at coordinates x,y in the
+ * editor, if any.
+ *
+ * @param {Number} x
+ * @param {Number} y
+ */
+ _highlightSelectorAt: Task.async(function*(x, y) {
+ // Need to catch parsing exceptions as long as bug 1051900 isn't fixed
+ let info;
+ try {
+ let pos = this.sourceEditor.getPositionFromCoords({left: x, top: y});
+ info = this.sourceEditor.getInfoAt(pos);
+ } catch (e) {}
+ if (!info || info.state !== "selector") {
+ return;
+ }
+
+ let node = yield this.walker.getStyleSheetOwnerNode(this.styleSheet.actorID);
+ yield this.highlighter.show(node, {
+ selector: info.selector,
+ hideInfoBar: true,
+ showOnly: "border",
+ region: "border"
+ });
+
+ this.emit("node-highlighted");
+ }),
+
+ /**
+ * Save the editor contents into a file and set savedFile property.
+ * A file picker UI will open if file is not set and editor is not headless.
+ *
+ * @param mixed file
+ * Optional nsIFile or string representing the filename to save in the
+ * background, no UI will be displayed.
+ * If not specified, the original style sheet URI is used.
+ * To implement 'Save' instead of 'Save as', you can pass savedFile here.
+ * @param function(nsIFile aFile) callback
+ * Optional callback called when the operation has finished.
+ * aFile has the nsIFile object for saved file or null if the operation
+ * has failed or has been canceled by the user.
+ * @see savedFile
+ */
+ saveToFile: function(file, callback) {
+ let onFile = (returnFile) => {
+ if (!returnFile) {
+ if (callback) {
+ callback(null);
+ }
+ return;
+ }
+
+ if (this.sourceEditor) {
+ this._state.text = this.sourceEditor.getText();
+ }
+
+ let ostream = FileUtils.openSafeFileOutputStream(returnFile);
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ let istream = converter.convertToInputStream(this._state.text);
+
+ NetUtil.asyncCopy(istream, ostream, (status) => {
+ if (!Components.isSuccessCode(status)) {
+ if (callback) {
+ callback(null);
+ }
+ this.emit("error", { key: SAVE_ERROR });
+ return;
+ }
+ FileUtils.closeSafeFileOutputStream(ostream);
+
+ this.onFileSaved(returnFile);
+
+ if (callback) {
+ callback(returnFile);
+ }
+ });
+ };
+
+ let defaultName;
+ if (this._friendlyName) {
+ defaultName = OS.Path.basename(this._friendlyName);
+ }
+ showFilePicker(file || this._styleSheetFilePath, true, this._window,
+ onFile, defaultName);
+ },
+
+ /**
+ * Called when this source has been successfully saved to disk.
+ */
+ onFileSaved: function(returnFile) {
+ this._friendlyName = null;
+ this.savedFile = returnFile;
+
+ if (this.sourceEditor) {
+ this.sourceEditor.setClean();
+ }
+
+ this.emit("property-change");
+
+ // TODO: replace with file watching
+ this._modCheckCount = 0;
+ this._window.clearTimeout(this._timeout);
+
+ if (this.linkedCSSFile && !this.linkedCSSFileError) {
+ this._timeout = this._window.setTimeout(this.checkLinkedFileForChanges,
+ CHECK_LINKED_SHEET_DELAY);
+ }
+ },
+
+ /**
+ * Check to see if our linked CSS file has changed on disk, and
+ * if so, update the live style sheet.
+ */
+ checkLinkedFileForChanges: function() {
+ OS.File.stat(this.linkedCSSFile).then((info) => {
+ let lastChange = info.lastModificationDate.getTime();
+
+ if (this._fileModDate && lastChange != this._fileModDate) {
+ this._fileModDate = lastChange;
+ this._modCheckCount = 0;
+
+ this.updateLinkedStyleSheet();
+ return;
+ }
+
+ if (++this._modCheckCount > MAX_CHECK_COUNT) {
+ this.updateLinkedStyleSheet();
+ return;
+ }
+
+ // try again in a bit
+ this._timeout = this._window.setTimeout(this.checkLinkedFileForChanges,
+ CHECK_LINKED_SHEET_DELAY);
+ }, this.markLinkedFileBroken);
+ },
+
+ /**
+ * Notify that the linked CSS file (if this is an original source)
+ * doesn't exist on disk in the place we think it does.
+ *
+ * @param string error
+ * The error we got when trying to access the file.
+ */
+ markLinkedFileBroken: function(error) {
+ this.linkedCSSFileError = error || true;
+ this.emit("linked-css-file-error");
+
+ error += " querying " + this.linkedCSSFile +
+ " original source location: " + this.savedFile.path
+ Cu.reportError(error);
+ },
+
+ /**
+ * For original sources (e.g. Sass files). Fetch contents of linked CSS
+ * file from disk and live update the stylesheet object with the contents.
+ */
+ updateLinkedStyleSheet: function() {
+ OS.File.read(this.linkedCSSFile).then((array) => {
+ let decoder = new TextDecoder();
+ let text = decoder.decode(array);
+
+ let relatedSheet = this.styleSheet.relatedStyleSheet;
+ relatedSheet.update(text, true);
+ }, this.markLinkedFileBroken);
+ },
+
+ /**
+ * Retrieve custom key bindings objects as expected by Editor.
+ * Editor action names are not displayed to the user.
+ *
+ * @return {array} key binding objects for the source editor
+ */
+ _getKeyBindings: function() {
+ let bindings = {};
+
+ bindings[Editor.accel(_("saveStyleSheet.commandkey"))] = () => {
+ this.saveToFile(this.savedFile);
+ };
+
+ bindings["Shift-" + Editor.accel(_("saveStyleSheet.commandkey"))] = () => {
+ this.saveToFile();
+ };
+
+ bindings["Esc"] = false;
+
+ return bindings;
+ },
+
+ /**
+ * Clean up for this editor.
+ */
+ destroy: function() {
+ if (this._sourceEditor) {
+ this._sourceEditor.off("dirty-change", this._onPropertyChange);
+ this._sourceEditor.off("save", this.saveToFile);
+ this._sourceEditor.off("change", this.updateStyleSheet);
+ if (this.highlighter && this.walker && this._sourceEditor.container) {
+ this._sourceEditor.container.removeEventListener("mousemove",
+ this._onMouseMove);
+ }
+ this._sourceEditor.destroy();
+ }
+ this.cssSheet.off("property-change", this._onPropertyChange);
+ this.cssSheet.off("media-rules-changed", this._onMediaRulesChanged);
+ this.styleSheet.off("error", this._onError);
+ }
+}
+
+/**
+ * Find a path on disk for a file given it's hosted uri, the uri of the
+ * original resource that generated it (e.g. Sass file), and the location of the
+ * local file for that source.
+ *
+ * @param {nsIURI} uri
+ * The uri of the resource
+ * @param {nsIURI} origUri
+ * The uri of the original source for the resource
+ * @param {nsIFile} file
+ * The local file for the resource on disk
+ *
+ * @return {string}
+ * The path of original file on disk
+ */
+function findLinkedFilePath(uri, origUri, file) {
+ let { origBranch, branch } = findUnsharedBranches(origUri, uri);
+ let project = findProjectPath(file, origBranch);
+
+ let parts = project.concat(branch);
+ let path = OS.Path.join.apply(this, parts);
+
+ return path;
+}
+
+/**
+ * Find the path of a project given a file in the project and its branch
+ * off the root. e.g.:
+ * /Users/moz/proj/src/a.css" and "src/a.css"
+ * would yield ["Users", "moz", "proj"]
+ *
+ * @param {nsIFile} file
+ * file for that resource on disk
+ * @param {array} branch
+ * path parts for branch to chop off file path.
+ * @return {array}
+ * array of path parts
+ */
+function findProjectPath(file, branch) {
+ let path = OS.Path.split(file.path).components;
+
+ for (let i = 2; i <= branch.length; i++) {
+ // work backwards until we find a differing directory name
+ if (path[path.length - i] != branch[branch.length - i]) {
+ return path.slice(0, path.length - i + 1);
+ }
+ }
+
+ // if we don't find a differing directory, just chop off the branch
+ return path.slice(0, path.length - branch.length);
+}
+
+/**
+ * Find the parts of a uri past the root it shares with another uri. e.g:
+ * "http://localhost/built/a.scss" and "http://localhost/src/a.css"
+ * would yield ["built", "a.scss"] and ["src", "a.css"]
+ *
+ * @param {nsIURI} origUri
+ * uri to find unshared branch of. Usually is uri for original source.
+ * @param {nsIURI} uri
+ * uri to compare against to get a shared root
+ * @return {object}
+ * object with 'branch' and 'origBranch' array of path parts for branch
+ */
+function findUnsharedBranches(origUri, uri) {
+ origUri = OS.Path.split(origUri.path).components;
+ uri = OS.Path.split(uri.path).components;
+
+ for (let i = 0; i < uri.length - 1; i++) {
+ if (uri[i] != origUri[i]) {
+ return {
+ branch: uri.slice(i),
+ origBranch: origUri.slice(i)
+ };
+ }
+ }
+ return {
+ branch: uri,
+ origBranch: origUri
+ };
+}
+
+/**
+ * Remove the query string from a url.
+ *
+ * @param {string} href
+ * Url to remove query string from
+ * @return {string}
+ * Url without query string
+ */
+function removeQuery(href) {
+ return href.replace(/\?.*/, "");
+}
diff --git a/toolkit/devtools/styleeditor/moz.build b/toolkit/devtools/styleeditor/moz.build
new file mode 100644
index 000000000..1dd27aa12
--- /dev/null
+++ b/toolkit/devtools/styleeditor/moz.build
@@ -0,0 +1,19 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+
+EXTRA_JS_MODULES.devtools += [
+ 'StyleEditorUI.jsm',
+ 'StyleEditorUtil.jsm',
+ 'StyleSheetEditor.jsm',
+]
+
+EXTRA_JS_MODULES.devtools.styleeditor += [
+ 'styleeditor-commands.js',
+ 'styleeditor-panel.js',
+ 'utils.js',
+]
diff --git a/toolkit/devtools/styleeditor/styleeditor-commands.js b/toolkit/devtools/styleeditor/styleeditor-commands.js
new file mode 100644
index 000000000..dd2c75bc4
--- /dev/null
+++ b/toolkit/devtools/styleeditor/styleeditor-commands.js
@@ -0,0 +1,42 @@
+/* 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";
+
+const gcli = require("gcli/index");
+
+exports.items = [{
+ name: "edit",
+ description: gcli.lookup("editDesc"),
+ manual: gcli.lookup("editManual2"),
+ params: [
+ {
+ name: 'resource',
+ type: {
+ name: 'resource',
+ include: 'text/css'
+ },
+ description: gcli.lookup("editResourceDesc")
+ },
+ {
+ name: "line",
+ defaultValue: 1,
+ type: {
+ name: "number",
+ min: 1,
+ step: 10
+ },
+ description: gcli.lookup("editLineToJumpToDesc")
+ }
+ ],
+ exec: function(args, context) {
+ let target = context.environment.target;
+ let gDevTools = require("resource:///modules/devtools/gDevTools.jsm").gDevTools;
+ return gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
+ let styleEditor = toolbox.getCurrentPanel();
+ styleEditor.selectStyleSheet(args.resource.element, args.line);
+ return null;
+ });
+ }
+}];
diff --git a/toolkit/devtools/styleeditor/styleeditor-panel.js b/toolkit/devtools/styleeditor/styleeditor-panel.js
new file mode 100644
index 000000000..4d7fde5fb
--- /dev/null
+++ b/toolkit/devtools/styleeditor/styleeditor-panel.js
@@ -0,0 +1,154 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const {Cc, Ci, Cu, Cr} = require("chrome");
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+let EventEmitter = require("devtools/toolkit/event-emitter");
+
+Cu.import("resource:///modules/devtools/StyleEditorUI.jsm");
+Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm");
+
+loader.lazyGetter(this, "StyleSheetsFront",
+ () => require("devtools/server/actors/stylesheets").StyleSheetsFront);
+
+loader.lazyGetter(this, "StyleEditorFront",
+ () => require("devtools/server/actors/styleeditor").StyleEditorFront);
+
+this.StyleEditorPanel = function StyleEditorPanel(panelWin, toolbox) {
+ EventEmitter.decorate(this);
+
+ this._toolbox = toolbox;
+ this._target = toolbox.target;
+ this._panelWin = panelWin;
+ this._panelDoc = panelWin.document;
+
+ this.destroy = this.destroy.bind(this);
+ this._showError = this._showError.bind(this);
+}
+
+exports.StyleEditorPanel = StyleEditorPanel;
+
+StyleEditorPanel.prototype = {
+ get target() this._toolbox.target,
+
+ get panelWindow() this._panelWin,
+
+ /**
+ * open is effectively an asynchronous constructor
+ */
+ open: function() {
+ let deferred = promise.defer();
+
+ let targetPromise;
+ // We always interact with the target as if it were remote
+ if (!this.target.isRemote) {
+ targetPromise = this.target.makeRemote();
+ } else {
+ targetPromise = promise.resolve(this.target);
+ }
+
+ targetPromise.then(() => {
+ this.target.on("close", this.destroy);
+
+ if (this.target.form.styleSheetsActor) {
+ this._debuggee = StyleSheetsFront(this.target.client, this.target.form);
+ }
+ else {
+ /* We're talking to a pre-Firefox 29 server-side */
+ this._debuggee = StyleEditorFront(this.target.client, this.target.form);
+ }
+ this.UI = new StyleEditorUI(this._debuggee, this.target, this._panelDoc);
+ this.UI.initialize().then(() => {
+ this.UI.on("error", this._showError);
+
+ this.isReady = true;
+
+ deferred.resolve(this);
+ });
+ }, console.error);
+
+ return deferred.promise;
+ },
+
+ /**
+ * Show an error message from the style editor in the toolbox
+ * notification box.
+ *
+ * @param {string} event
+ * Type of event
+ * @param {string} data
+ * The parameters to customize the error message
+ */
+ _showError: function(event, data) {
+ if (!this._toolbox) {
+ // could get an async error after we've been destroyed
+ return;
+ }
+
+ let errorMessage = _(data.key);
+ if (data.append) {
+ errorMessage += " " + data.append;
+ }
+
+ let notificationBox = this._toolbox.getNotificationBox();
+ let notification = notificationBox.getNotificationWithValue("styleeditor-error");
+ let level = (data.level === "info") ?
+ notificationBox.PRIORITY_INFO_LOW :
+ notificationBox.PRIORITY_CRITICAL_LOW;
+
+ if (!notification) {
+ notificationBox.appendNotification(errorMessage, "styleeditor-error",
+ "", level);
+ }
+ },
+
+ /**
+ * Select a stylesheet.
+ *
+ * @param {string} href
+ * Url of stylesheet to find and select in editor
+ * @param {number} line
+ * Line number to jump to after selecting. One-indexed
+ * @param {number} col
+ * Column number to jump to after selecting. One-indexed
+ */
+ selectStyleSheet: function(href, line, col) {
+ if (!this._debuggee || !this.UI) {
+ return;
+ }
+ this.UI.selectStyleSheet(href, line - 1, col ? col - 1 : 0);
+ },
+
+ /**
+ * Destroy the style editor.
+ */
+ destroy: function() {
+ if (!this._destroyed) {
+ this._destroyed = true;
+
+ this._target.off("close", this.destroy);
+ this._target = null;
+ this._toolbox = null;
+ this._panelDoc = null;
+ this._debuggee.destroy();
+ this._debuggee = null;
+
+ this.UI.destroy();
+ }
+
+ return promise.resolve(null);
+ },
+}
+
+XPCOMUtils.defineLazyGetter(StyleEditorPanel.prototype, "strings",
+ function () {
+ return Services.strings.createBundle(
+ "chrome://browser/locale/devtools/styleeditor.properties");
+ });
diff --git a/toolkit/devtools/styleeditor/styleeditor.css b/toolkit/devtools/styleeditor/styleeditor.css
new file mode 100644
index 000000000..7cc984155
--- /dev/null
+++ b/toolkit/devtools/styleeditor/styleeditor.css
@@ -0,0 +1,151 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#style-editor-chrome {
+ -moz-box-flex: 1;
+}
+
+.stylesheet-error-message {
+ display: none;
+}
+
+li.error > .stylesheet-info > .stylesheet-more > .stylesheet-error-message {
+ display: block;
+}
+
+.devtools-toolbar > spacer {
+ -moz-box-flex: 1;
+}
+
+.splitview-nav > li,
+.stylesheet-info,
+.stylesheet-more {
+ display: -moz-box;
+}
+
+.stylesheet-details-container {
+ -moz-box-flex: 1;
+}
+
+.stylesheet-media-container {
+ overflow-y: auto;
+}
+
+.media-rule-label {
+ display: flex;
+}
+
+.media-rule-condition {
+ flex: 1;
+ overflow: hidden;
+}
+
+.splitview-nav > li {
+ -moz-box-orient: horizontal;
+}
+
+.splitview-nav > li > hgroup {
+ display: -moz-box;
+ -moz-box-orient: vertical;
+ -moz-box-flex: 1;
+}
+
+.stylesheet-info > h1 {
+ -moz-box-flex: 1;
+}
+
+.stylesheet-name > label {
+ display: inline;
+ cursor: pointer;
+}
+
+.splitview-nav > li > hgroup.stylesheet-info {
+ -moz-box-pack: center;
+}
+
+.stylesheet-name {
+ white-space: nowrap;
+}
+
+li.unsaved > hgroup > h1 > .stylesheet-name:before {
+ content: "*";
+}
+
+li.linked-file-error .stylesheet-linked-file {
+ text-decoration: line-through;
+}
+
+li.linked-file-error .stylesheet-linked-file:after {
+ content: " ✘";
+}
+
+li.linked-file-error .stylesheet-rule-count {
+ visibility: hidden;
+}
+
+.stylesheet-linked-file:not(:empty):before {
+ content: " ↳ ";
+}
+
+.stylesheet-enabled {
+ display: -moz-box;
+ cursor: pointer;
+}
+
+.stylesheet-saveButton {
+ display: none;
+ margin-top: 0px;
+ margin-bottom: 0px;
+}
+
+.stylesheet-rule-count,
+li.splitview-active > hgroup > .stylesheet-more > h3 > .stylesheet-saveButton,
+li:hover > hgroup > .stylesheet-more > h3 > .stylesheet-saveButton {
+ display: -moz-box;
+}
+
+.stylesheet-more > spacer {
+ -moz-box-flex: 1;
+}
+
+/* portrait mode */
+@media (max-width: 550px) {
+ li.splitview-active > hgroup > .stylesheet-more > .stylesheet-rule-count,
+ li:hover > hgroup > .stylesheet-more > .stylesheet-rule-count {
+ display: none;
+ }
+
+ .stylesheet-more {
+ -moz-box-flex: 1;
+ -moz-box-pack: end;
+ }
+
+ .splitview-nav > li > hgroup.stylesheet-info {
+ -moz-box-orient: horizontal;
+ -moz-box-flex: 1;
+ }
+
+ .stylesheet-more > spacer {
+ -moz-box-flex: 0;
+ }
+}
+
+.csscoverage-report-container {
+ -moz-box-flex: 1;
+ overflow-x: hidden;
+ overflow-y: auto;
+}
+
+.csscoverage-report-content > * {
+ display: inline-block;
+}
+
+.csscoverage-report {
+ -moz-box-orient: horizontal;
+}
+
+.csscoverage-report .pie-table-chart-container {
+ -moz-box-orient: vertical;
+}
diff --git a/toolkit/devtools/styleeditor/styleeditor.xul b/toolkit/devtools/styleeditor/styleeditor.xul
new file mode 100644
index 000000000..0966da60a
--- /dev/null
+++ b/toolkit/devtools/styleeditor/styleeditor.xul
@@ -0,0 +1,222 @@
+<?xml version="1.0"?>
+<!-- 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/. -->
+<!DOCTYPE window [
+<!ENTITY % styleEditorDTD SYSTEM "chrome://browser/locale/devtools/styleeditor.dtd" >
+ %styleEditorDTD;
+<!ENTITY % editMenuStrings SYSTEM "chrome://global/locale/editMenuOverlay.dtd">
+ %editMenuStrings;
+<!ENTITY % sourceEditorStrings SYSTEM "chrome://browser/locale/devtools/sourceeditor.dtd">
+ %sourceEditorStrings;
+<!ENTITY % csscoverageDTD SYSTEM "chrome://global/locale/devtools/csscoverage.dtd">
+ %csscoverageDTD;
+]>
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/devtools/widgets.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/devtools/splitview.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/devtools/widgets.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/devtools/splitview.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/devtools/styleeditor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/devtools/styleeditor.css" type="text/css"?>
+<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
+
+<xul:window xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns="http://www.w3.org/1999/xhtml"
+ id="style-editor-chrome-window">
+
+ <script type="application/javascript;version=1.8"
+ src="chrome://browser/content/devtools/theme-switching.js"/>
+ <xul:script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+ <xul:script type="application/javascript">
+ function goUpdateSourceEditorMenuItems() {
+ goUpdateGlobalEditMenuItems();
+
+ ['cmd_undo', 'cmd_redo', 'cmd_cut', 'cmd_paste',
+ 'cmd_delete', 'cmd_find', 'cmd_findAgain'].forEach(goUpdateCommand);
+ }
+ </xul:script>
+
+ <xul:popupset id="style-editor-popups">
+ <xul:menupopup id="sourceEditorContextMenu"
+ onpopupshowing="goUpdateSourceEditorMenuItems()">
+ <xul:menuitem id="cMenu_undo"/>
+ <xul:menuseparator/>
+ <xul:menuitem id="cMenu_cut"/>
+ <xul:menuitem id="cMenu_copy"/>
+ <xul:menuitem id="cMenu_paste"/>
+ <xul:menuitem id="cMenu_delete"/>
+ <xul:menuseparator/>
+ <xul:menuitem id="cMenu_selectAll"/>
+ <xul:menuseparator/>
+ <xul:menuitem id="se-menu-find"
+ label="&findCmd.label;" accesskey="&findCmd.accesskey;" command="cmd_find"/>
+ <xul:menuitem id="cMenu_findAgain"/>
+ <xul:menuseparator/>
+ <xul:menuitem id="se-menu-gotoLine"
+ label="&gotoLineCmd.label;"
+ accesskey="&gotoLineCmd.accesskey;"
+ key="key_gotoLine"
+ command="cmd_gotoLine"/>
+ </xul:menupopup>
+ <xul:menupopup id="style-editor-options-popup"
+ position="before_start">
+ <xul:menuitem id="options-origsources"
+ type="checkbox"
+ label="&showOriginalSources.label;"
+ accesskey="&showOriginalSources.accesskey;"/>
+ <xul:menuitem id="options-show-media"
+ type="checkbox"
+ label="&showMediaSidebar.label;"
+ accesskey="&showMediaSidebar.accesskey;"/>
+ </xul:menupopup>
+ </xul:popupset>
+
+ <xul:commandset id="editMenuCommands"/>
+
+ <xul:commandset id="sourceEditorCommands">
+ <xul:command id="cmd_gotoLine" oncommand="goDoCommand('cmd_gotoLine')"/>
+ <xul:command id="cmd_find" oncommand="goDoCommand('cmd_find')"/>
+ <xul:command id="cmd_findAgain" oncommand="goDoCommand('cmd_findAgain')"/>
+ </xul:commandset>
+
+ <xul:keyset id="sourceEditorKeys"/>
+
+ <xul:stack id="style-editor-chrome" class="loading theme-body">
+
+ <xul:box class="splitview-root" context="sidebar-context">
+ <xul:box class="splitview-controller">
+ <xul:box class="splitview-main">
+ <xul:toolbar class="devtools-toolbar">
+ <xul:hbox class="devtools-toolbarbutton-group">
+ <xul:toolbarbutton class="style-editor-newButton devtools-toolbarbutton"
+ accesskey="&newButton.accesskey;"
+ tooltiptext="&newButton.tooltip;"
+ label="&newButton.label;"/>
+ <xul:toolbarbutton class="style-editor-importButton devtools-toolbarbutton"
+ accesskey="&importButton.accesskey;"
+ tooltiptext="&importButton.tooltip;"
+ label="&importButton.label;"/>
+ </xul:hbox>
+ <xul:spacer/>
+ <xul:toolbarbutton id="style-editor-options"
+ class="devtools-toolbarbutton devtools-option-toolbarbutton"
+ tooltiptext="&optionsButton.tooltip;"
+ popup="style-editor-options-popup"/>
+ </xul:toolbar>
+ </xul:box>
+ <xul:box id="splitview-resizer-target" class="theme-sidebar splitview-nav-container"
+ persist="height">
+ <div class="devtools-throbber"></div>
+ <ol class="splitview-nav" tabindex="0"></ol>
+ <div class="splitview-nav placeholder empty">
+ <p><strong>&noStyleSheet.label;</strong></p>
+ <p>&noStyleSheet-tip-start.label;
+ <a href="#"
+ class="style-editor-newButton">&noStyleSheet-tip-action.label;</a>
+ &noStyleSheet-tip-end.label;</p>
+ </div>
+ </xul:box> <!-- .splitview-nav-container -->
+ </xul:box> <!-- .splitview-controller -->
+ <xul:splitter class="devtools-side-splitter splitview-landscape-splitter devtools-invisible-splitter"/>
+ <xul:box class="splitview-side-details devtools-main-content"/>
+
+ <div id="splitview-templates" hidden="true">
+ <li id="splitview-tpl-summary-stylesheet" tabindex="0">
+ <xul:label class="stylesheet-enabled" tabindex="0"
+ tooltiptext="&visibilityToggle.tooltip;"
+ accesskey="&saveButton.accesskey;"></xul:label>
+ <hgroup class="stylesheet-info">
+ <h1><a class="stylesheet-name" tabindex="0"><xul:label crop="start"/></a></h1>
+ <div class="stylesheet-more">
+ <h3 class="stylesheet-title"></h3>
+ <h3 class="stylesheet-linked-file"></h3>
+ <h3 class="stylesheet-rule-count"></h3>
+ <xul:spacer/>
+ <h3><xul:label class="stylesheet-saveButton"
+ tooltiptext="&saveButton.tooltip;"
+ accesskey="&saveButton.accesskey;">&saveButton.label;</xul:label></h3>
+ </div>
+ </hgroup>
+ </li>
+
+ <xul:box id="splitview-tpl-details-stylesheet" class="splitview-details">
+ <xul:resizer class="splitview-portrait-resizer"
+ dir="bottom"
+ element="splitview-resizer-target"/>
+ <xul:hbox class="stylesheet-details-container">
+ <xul:box class="stylesheet-editor-input textbox"
+ data-placeholder="&editorTextbox.placeholder;"/>
+ <xul:splitter class="devtools-side-splitter"/>
+ <xul:vbox class="stylesheet-sidebar theme-sidebar" hidden="true">
+ <xul:toolbar class="devtools-toolbar">
+ &mediaRules.label;
+ </xul:toolbar>
+ <xul:vbox class="stylesheet-media-container" flex="1">
+ <div class="stylesheet-media-list" />
+ </xul:vbox>
+ </xul:vbox>
+ </xul:hbox>
+ </xul:box>
+ </div> <!-- #splitview-templates -->
+ </xul:box> <!-- .splitview-root -->
+
+ <xul:box class="csscoverage-template" hidden="true">
+ <xul:toolbar class="devtools-toolbar csscoverage-toolbar">
+ <xul:button class="devtools-toolbarbutton csscoverage-toolbarbutton"
+ label="&csscoverage.backButton;"
+ onclick="${onback}"/>
+ </xul:toolbar>
+ <!-- The data for this comes from CSSUsageActor.createPageReport -->
+ <div class="csscoverage-report-container">
+ <div class="csscoverage-report-content">
+ <div class="csscoverage-report-summary">
+ <div class="csscoverage-report-chart"/>
+ </div>
+ <div class="csscoverage-report-unused">
+ <h2>&csscoverage.unused;</h2>
+ <p>&csscoverage.noMatches;</p>
+ <div foreach="page in ${unused}">
+ <h3>${page.url}</h3>
+ <code foreach="rule in ${page.rules}"
+ href="${rule.url}"
+ class="csscoverage-list">${rule.selectorText}</code>
+ </div>
+ </div>
+ <div class="csscoverage-report-optimize">
+ <h2>&csscoverage.optimize.header;</h2>
+ <p>
+ &csscoverage.optimize.body1;
+ <code>&lt;link ...></code>
+ &csscoverage.optimize.body2;
+ <code>&lt;style>...</code>
+ &csscoverage.optimize.body3;
+ </p>
+ <div if="${preload.length == 0}">&csscoverage.optimize.bodyX;</div>
+ <div if="${preload.length > 0}">
+ <div foreach="page in ${preload}">
+ <h3>${page.url}</h3>
+ <textarea>&lt;style>
+<loop foreach="rule in ${page.rules}"
+ onclick="${rule.onclick}">${rule.formattedCssText}</loop>&lt;/style></textarea>
+ </div>
+ </div>
+ <p>
+ &csscoverage.footer1;
+ <a target="_blank" href="&csscoverage.footer2a;">&csscoverage.footer3;</a>
+ &csscoverage.footer4;
+ </p>
+ </div>
+ <p>&#160;</p>
+ </div>
+ </div>
+ </xul:box>
+
+ <xul:box class="csscoverage-report" hidden="true">
+ </xul:box>
+
+ </xul:stack>
+
+</xul:window>
diff --git a/toolkit/devtools/styleeditor/test/autocomplete.html b/toolkit/devtools/styleeditor/test/autocomplete.html
new file mode 100644
index 000000000..c4eb56bd5
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/autocomplete.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<html>
+<head>
+ <title>testcase for autocomplete testing</title>
+ <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/toolkit/devtools/styleeditor/test/browser.ini b/toolkit/devtools/styleeditor/test/browser.ini
new file mode 100644
index 000000000..fb62995b1
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser.ini
@@ -0,0 +1,80 @@
+[DEFAULT]
+subsuite = devtools
+support-files =
+ autocomplete.html
+ browser_styleeditor_cmd_edit.html
+ four.html
+ head.js
+ import.css
+ import.html
+ import2.css
+ inline-1.html
+ inline-2.html
+ longload.html
+ media-small.css
+ media.html
+ media-rules.html
+ media-rules.css
+ media-rules-sourcemaps.html
+ minified.html
+ nostyle.html
+ pretty.css
+ resources_inpage.jsi
+ resources_inpage1.css
+ resources_inpage2.css
+ simple.css
+ simple.css.gz
+ simple.css.gz^headers^
+ simple.gz.html
+ simple.html
+ sourcemap-css/contained.css
+ sourcemap-css/sourcemaps.css
+ sourcemap-css/sourcemaps.css.map
+ sourcemap-css/media-rules.css
+ sourcemap-css/media-rules.css.map
+ sourcemap-css/test-bootstrap-scss.css
+ sourcemap-css/test-stylus.css
+ sourcemap-sass/sourcemaps.scss
+ sourcemap-sass/media-rules.scss
+ sourcemap-styl/test-stylus.styl
+ sourcemaps.html
+ sourcemaps-large.html
+ sourcemaps-watching.html
+ test_private.css
+ test_private.html
+ doc_uncached.css
+ doc_uncached.html
+ doc_xulpage.xul
+
+[browser_styleeditor_autocomplete.js]
+[browser_styleeditor_bug_740541_iframes.js]
+skip-if = os == "linux" || "mac" # bug 949355
+[browser_styleeditor_bug_851132_middle_click.js]
+[browser_styleeditor_bug_870339.js]
+[browser_styleeditor_cmd_edit.js]
+skip-if = e10s # Bug 1055333 - style editor tests disabled with e10s
+[browser_styleeditor_enabled.js]
+[browser_styleeditor_fetch-from-cache.js]
+[browser_styleeditor_filesave.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]
+[browser_styleeditor_media_sidebar.js]
+[browser_styleeditor_media_sidebar_sourcemaps.js]
+[browser_styleeditor_new.js]
+[browser_styleeditor_nostyle.js]
+[browser_styleeditor_pretty.js]
+[browser_styleeditor_private_perwindowpb.js]
+[browser_styleeditor_reload.js]
+[browser_styleeditor_sv_keynav.js]
+[browser_styleeditor_sv_resize.js]
+[browser_styleeditor_selectstylesheet.js]
+[browser_styleeditor_sourcemaps.js]
+[browser_styleeditor_sourcemap_large.js]
+[browser_styleeditor_sourcemap_watching.js]
+skip-if = e10s # Bug 1055333 - style editor tests disabled with e10s
+[browser_styleeditor_transition_rule.js]
+[browser_styleeditor_xul.js]
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_autocomplete.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_autocomplete.js
new file mode 100644
index 000000000..c78470096
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_autocomplete.js
@@ -0,0 +1,252 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+///////////////////
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
+
+const TESTCASE_URI = TEST_BASE + "autocomplete.html";
+const MAX_SUGGESTIONS = 15;
+
+// Pref which decides if CSS autocompletion is enabled in Style Editor or not.
+const AUTOCOMPLETION_PREF = "devtools.styleeditor.autocompletion-enabled";
+
+const {CSSProperties, CSSValues} = getCSSKeywords();
+
+// 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
+// }
+// ]
+let TEST_CASES = [
+ ['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_TAB', {total: getSuggestionNumberFor("ba"), current: 1, inserted: 1}],
+ ['VK_RETURN', {current: 1, 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}],
+];
+
+let gEditor;
+let gPopup;
+let index = 0;
+
+function test()
+{
+ waitForExplicitFinish();
+
+ addTabAndOpenStyleEditors(1, testEditorAdded);
+
+ content.location = TESTCASE_URI;
+}
+
+function testEditorAdded(panel) {
+ info("Editor added, getting the source editor and starting tests");
+ panel.UI.editors[0].getSourceEditor().then(editor => {
+ info("source editor found, starting tests.");
+ gEditor = editor.sourceEditor;
+ gPopup = gEditor.getAutocompletionPopup();
+ waitForFocus(testState, gPanelWindow);
+ });
+}
+
+function testState() {
+ if (index == TEST_CASES.length) {
+ testAutocompletionDisabled();
+ return;
+ }
+
+ let [key, details] = TEST_CASES[index];
+ let entered;
+ if (details) {
+ entered = details.entered;
+ }
+ let mods = {};
+
+ info("pressing key " + key + " to get result: " +
+ JSON.stringify(TEST_CASES[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)/ig.test(key) ||
+ (key == "VK_DOWN" && !gPopup.isOpen)) {
+ evt = "cursorActivity";
+ }
+ else if (key == "VK_TAB" || key == "VK_UP" || key == "VK_DOWN") {
+ evt = "suggestion-entered";
+ }
+
+ gEditor.once(evt, checkState);
+ EventUtils.synthesizeKey(key, mods, gPanelWindow);
+}
+
+function checkState() {
+ executeSoon(() => {
+ let [key, details] = TEST_CASES[index];
+ details = details || {};
+ let {total, current, inserted} = details;
+
+ if (total != undefined) {
+ ok(gPopup.isOpen, "Popup is open for index " + index);
+ is(total, gPopup.itemCount,
+ "Correct total suggestions for index " + index);
+ is(current, gPopup.selectedIndex,
+ "Correct index is selected for index " + index);
+ if (inserted) {
+ let { preLabel, label, text } = gPopup.getItemAtIndex(current);
+ let { line, ch } = gEditor.getCursor();
+ let lineText = gEditor.getText(line);
+ is(lineText.substring(ch - text.length, ch), text,
+ "Current suggestion from the popup is inserted into the editor.");
+ }
+ }
+ else {
+ ok(!gPopup.isOpen, "Popup is closed for index " + index);
+ if (inserted) {
+ let { preLabel, label, text } = gPopup.getItemAtIndex(current);
+ let { line, ch } = gEditor.getCursor();
+ let lineText = gEditor.getText(line);
+ is(lineText.substring(ch - text.length, ch), text,
+ "Current suggestion from the popup is inserted into the editor.");
+ }
+ }
+ index++;
+ testState();
+ });
+}
+
+function testAutocompletionDisabled() {
+ gBrowser.removeCurrentTab();
+
+ index = 0;
+ info("Starting test to check if autocompletion is disabled correctly.")
+ Services.prefs.setBoolPref(AUTOCOMPLETION_PREF, false);
+
+ addTabAndOpenStyleEditors(1, testEditorAddedDisabled);
+
+ content.location = TESTCASE_URI;
+}
+
+function testEditorAddedDisabled(panel) {
+ info("Editor added, getting the source editor and starting tests");
+ panel.UI.editors[0].getSourceEditor().then(editor => {
+ is(editor.sourceEditor.getOption("autocomplete"), false,
+ "Autocompletion option does not exist");
+ ok(!editor.sourceEditor.getAutocompletionPopup(),
+ "Autocompletion popup does not exist");
+ cleanup();
+ });
+}
+
+function cleanup() {
+ Services.prefs.clearUserPref(AUTOCOMPLETION_PREF);
+ gEditor = null;
+ gPopup = null;
+ finish();
+}
+
+/**
+ * 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() {
+ let domUtils = Cc["@mozilla.org/inspector/dom-utils;1"]
+ .getService(Ci.inIDOMUtils);
+ let props = {};
+ let propNames = domUtils.getCSSPropertyNames(domUtils.INCLUDE_ALIASES);
+ propNames.forEach(prop => {
+ props[prop] = domUtils.getCSSValuesForProperty(prop).sort();
+ });
+ return {
+ CSSValues: props,
+ CSSProperties: propNames.sort()
+ };
+}
+
+/**
+ * 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 getSuggestionNumberFor(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/toolkit/devtools/styleeditor/test/browser_styleeditor_bug_740541_iframes.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_bug_740541_iframes.js
new file mode 100644
index 000000000..2d2f9f6a3
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_bug_740541_iframes.js
@@ -0,0 +1,78 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test()
+{
+
+ 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;
+
+ waitForExplicitFinish();
+
+ // Wait for events until the right number of editors has been opened.
+ addTabAndOpenStyleEditors(EXPECTED_STYLE_SHEET_COUNT, () => finish());
+
+ content.location = TESTCASE_URI;
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_bug_851132_middle_click.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_bug_851132_middle_click.js
new file mode 100644
index 000000000..d51773e70
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_bug_851132_middle_click.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+///////////////////
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
+
+const TESTCASE_URI = TEST_BASE + "four.html";
+
+let gUI;
+
+function test() {
+ waitForExplicitFinish();
+
+ addTabAndOpenStyleEditors(4, runTests);
+
+ content.location = TESTCASE_URI;
+}
+
+let timeoutID;
+
+function runTests(panel) {
+ gUI = panel.UI;
+
+ gBrowser.tabContainer.addEventListener("TabOpen", onTabAdded, false);
+ gUI.editors[0].getSourceEditor().then(onEditor0Attach);
+ gUI.editors[1].getSourceEditor().then(onEditor1Attach);
+}
+
+function getStylesheetNameLinkFor(aEditor) {
+ return aEditor.summary.querySelector(".stylesheet-name");
+}
+
+function onEditor0Attach(aEditor) {
+ info("first editor selected");
+
+ waitForFocus(function () {
+ // left mouse click should focus editor 1
+ EventUtils.synthesizeMouseAtCenter(
+ getStylesheetNameLinkFor(gUI.editors[1]),
+ {button: 0},
+ gPanelWindow);
+ }, gPanelWindow);
+}
+
+function onEditor1Attach(aEditor) {
+ info("second editor selected");
+
+ // Wait for the focus to be set.
+ executeSoon(function () {
+ ok(aEditor.sourceEditor.hasFocus(),
+ "left mouse click has given editor 1 focus");
+
+ // right mouse click should not open a new tab
+ EventUtils.synthesizeMouseAtCenter(
+ getStylesheetNameLinkFor(gUI.editors[2]),
+ {button: 1},
+ gPanelWindow);
+
+ setTimeout(finish, 0);
+ });
+}
+
+function onTabAdded() {
+ ok(false, "middle mouse click has opened a new tab");
+ finish();
+}
+
+registerCleanupFunction(function () {
+ gBrowser.tabContainer.removeEventListener("TabOpen", onTabAdded, false);
+ gUI = null;
+});
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_bug_870339.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_bug_870339.js
new file mode 100644
index 000000000..51078b7eb
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_bug_870339.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test()
+{
+ 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"));
+
+ waitForExplicitFinish();
+ addTabAndOpenStyleEditors(1, function (aPanel) {
+ let UI = aPanel.UI;
+
+ // Spam the _onNewDocument callback multiple times before the
+ // StyleEditorActor has a chance to respond to the first one.
+ const SPAM_COUNT = 2;
+ for (let i=0; i<SPAM_COUNT; ++i) {
+ UI._onNewDocument();
+ }
+
+ // Wait for the StyleEditorActor to respond to each "newDocument"
+ // message.
+ let loadCount = 0;
+ UI.on("stylesheets-reset", function () {
+ ++loadCount;
+ if (loadCount == SPAM_COUNT) {
+ // 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");
+ finish();
+ }
+ });
+ });
+ content.location = DOCUMENT_WITH_ONE_STYLESHEET;
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_cmd_edit.html b/toolkit/devtools/styleeditor/test/browser_styleeditor_cmd_edit.html
new file mode 100644
index 000000000..28fccb331
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_cmd_edit.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+ <meta charset="utf-8">
+ <title>Resources</title>
+ <script type="text/javascript" id="script1">
+ window.addEventListener('load', function() {
+ var pid = document.getElementById('pid');
+ var div = document.createElement('div');
+ div.id = 'divid';
+ div.classList.add('divclass');
+ div.appendChild(document.createTextNode('div'));
+ div.setAttribute('data-a1', 'div');
+ pid.parentNode.appendChild(div);
+ });
+ </script>
+ <script src="resources_inpage.jsi"></script>
+ <link rel="stylesheet" type="text/css" href="resources_inpage1.css"/>
+ <link rel="stylesheet" type="text/css" href="resources_inpage2.css"/>
+ <style type="text/css">
+ p { color: #800; }
+ div { color: #008; }
+ h4 { color: #080; }
+ h3 { color: #880; }
+ </style>
+</head>
+<body>
+ <style type="text/css" id=style2>
+ .pclass { background-color: #FEE; }
+ .divclass { background-color: #EEF; }
+ .h4class { background-color: #EFE; }
+ .h3class { background-color: #FFE; }
+ </style>
+
+ <p class="pclass" id="pid" data-a1="p">paragraph</p>
+
+ <script>
+ var pid = document.getElementById('pid');
+ var h4 = document.createElement('h4');
+ h4.id = 'h4id';
+ h4.classList.add('h4class');
+ h4.appendChild(document.createTextNode('h4'));
+ h4.setAttribute('data-a1', 'h4');
+ pid.parentNode.appendChild(h4);
+ </script>
+
+</body>
+</html>
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_cmd_edit.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_cmd_edit.js
new file mode 100644
index 000000000..ffd8fdc06
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_cmd_edit.js
@@ -0,0 +1,206 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the edit command works
+
+const TEST_URI = "http://example.com/browser/browser/devtools/styleeditor/" +
+ "test/browser_styleeditor_cmd_edit.html";
+
+function test() {
+ return Task.spawn(spawnTest).then(finish, helpers.handleError);
+}
+
+function spawnTest() {
+ let options = yield helpers.openTab(TEST_URI);
+ yield helpers.openToolbar(options);
+
+ yield helpers.audit(options, [
+ {
+ setup: "edit",
+ check: {
+ input: 'edit',
+ hints: ' <resource> [line]',
+ markup: 'VVVV',
+ status: 'ERROR',
+ args: {
+ resource: { status: 'INCOMPLETE' },
+ line: { status: 'VALID' },
+ }
+ },
+ },
+ {
+ setup: "edit i",
+ check: {
+ input: 'edit i',
+ hints: 'nline-css [line]',
+ markup: 'VVVVVI',
+ status: 'ERROR',
+ args: {
+ resource: { arg: ' i', status: 'INCOMPLETE' },
+ line: { status: 'VALID' },
+ }
+ },
+ },
+ {
+ setup: "edit c",
+ check: {
+ input: 'edit c',
+ hints: 'ss#style2 [line]',
+ markup: 'VVVVVI',
+ status: 'ERROR',
+ args: {
+ resource: { arg: ' c', status: 'INCOMPLETE' },
+ line: { status: 'VALID' },
+ }
+ },
+ },
+ {
+ setup: "edit http",
+ check: {
+ input: 'edit http',
+ hints: '://example.com/browser/browser/devtools/styleeditor/test/resources_inpage1.css [line]',
+ markup: 'VVVVVIIII',
+ status: 'ERROR',
+ args: {
+ resource: {
+ arg: ' http',
+ status: 'INCOMPLETE',
+ message: 'Value required for \'resource\'.'
+ },
+ line: { status: 'VALID' },
+ }
+ },
+ },
+ {
+ setup: "edit page1",
+ check: {
+ input: 'edit page1',
+ hints: ' [line] -> http://example.com/browser/browser/devtools/styleeditor/test/resources_inpage1.css',
+ markup: 'VVVVVIIIII',
+ status: 'ERROR',
+ args: {
+ resource: {
+ arg: ' page1',
+ status: 'INCOMPLETE',
+ message: 'Value required for \'resource\'.'
+ },
+ line: { status: 'VALID' },
+ }
+ },
+ },
+ {
+ setup: "edit page2",
+ check: {
+ input: 'edit page2',
+ hints: ' [line] -> http://example.com/browser/browser/devtools/styleeditor/test/resources_inpage2.css',
+ markup: 'VVVVVIIIII',
+ status: 'ERROR',
+ args: {
+ resource: {
+ arg: ' page2',
+ status: 'INCOMPLETE',
+ message: 'Value required for \'resource\'.'
+ },
+ line: { status: 'VALID' },
+ }
+ },
+ },
+ {
+ setup: "edit stylez",
+ check: {
+ input: 'edit stylez',
+ hints: ' [line]',
+ markup: 'VVVVVEEEEEE',
+ status: 'ERROR',
+ args: {
+ resource: { arg: ' stylez', status: 'ERROR', message: 'Can\'t use \'stylez\'.' },
+ line: { status: 'VALID' },
+ }
+ },
+ },
+ {
+ setup: "edit css#style2",
+ check: {
+ input: 'edit css#style2',
+ hints: ' [line]',
+ markup: 'VVVVVVVVVVVVVVV',
+ status: 'VALID',
+ args: {
+ resource: { arg: ' css#style2', status: 'VALID', message: '' },
+ line: { status: 'VALID' },
+ }
+ },
+ },
+ {
+ setup: "edit css#style2 5",
+ check: {
+ input: 'edit css#style2 5',
+ hints: '',
+ markup: 'VVVVVVVVVVVVVVVVV',
+ status: 'VALID',
+ args: {
+ resource: { arg: ' css#style2', status: 'VALID', message: '' },
+ line: { value: 5, arg: ' 5', status: 'VALID' },
+ }
+ },
+ },
+ {
+ setup: "edit css#style2 0",
+ check: {
+ input: 'edit css#style2 0',
+ hints: '',
+ markup: 'VVVVVVVVVVVVVVVVE',
+ status: 'ERROR',
+ args: {
+ resource: { arg: ' css#style2', status: 'VALID', message: '' },
+ line: { arg: ' 0', status: 'ERROR', message: '0 is smaller than minimum allowed: 1.' },
+ }
+ },
+ },
+ {
+ setup: "edit css#style2 -1",
+ check: {
+ input: 'edit css#style2 -1',
+ hints: '',
+ markup: 'VVVVVVVVVVVVVVVVEE',
+ status: 'ERROR',
+ args: {
+ resource: { arg: ' css#style2', status: 'VALID', message: '' },
+ line: { arg: ' -1', status: 'ERROR', message: '-1 is smaller than minimum allowed: 1.' },
+ }
+ },
+ }
+ ]);
+
+ let toolbox = gDevTools.getToolbox(options.target);
+ ok(toolbox == null, 'toolbox is closed');
+
+ yield helpers.audit(options, [
+ {
+ setup: "edit css#style2",
+ check: {
+ input: "edit css#style2",
+ args: {
+ resource: {
+ value: function(resource) {
+ let style2 = options.window.document.getElementById("style2");
+ return resource.element.ownerNode == style2;
+ }
+ }
+ }
+ },
+ exec: { output: "" }
+ },
+ ]);
+
+ toolbox = gDevTools.getToolbox(options.target);
+ ok(toolbox != null, "toolbox is open");
+
+ let styleEditor = toolbox.getCurrentPanel();
+ ok(typeof styleEditor.selectStyleSheet === "function", "styleeditor is open");
+
+ yield toolbox.destroy();
+
+ yield helpers.closeToolbar(options);
+ yield helpers.closeTab(options);
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_enabled.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_enabled.js
new file mode 100644
index 000000000..5edf27449
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_enabled.js
@@ -0,0 +1,70 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// https rather than chrome to improve coverage
+const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
+
+function test()
+{
+ waitForExplicitFinish();
+
+ let count = 0;
+ addTabAndOpenStyleEditors(2, function(panel) {
+ // we test against first stylesheet after all are ready
+ let UI = panel.UI;
+ let editor = UI.editors[0];
+ editor.getSourceEditor().then(runTests.bind(this, UI, editor));
+ });
+
+ content.location = TESTCASE_URI;
+}
+
+function runTests(UI, editor)
+{
+ testEnabledToggle(UI, editor);
+}
+
+function testEnabledToggle(UI, editor)
+{
+ let summary = editor.summary;
+ let enabledToggle = summary.querySelector(".stylesheet-enabled");
+ ok(enabledToggle, "enabled 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");
+
+ let disabledToggleCount = 0;
+ editor.on("property-change", function(event, property) {
+ if (property != "disabled") {
+ return;
+ }
+ disabledToggleCount++;
+
+ if (disabledToggleCount == 1) {
+ 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");
+
+ // now toggle it back to enabled
+ waitForFocus(function () {
+ EventUtils.synthesizeMouseAtCenter(enabledToggle, {}, gPanelWindow);
+ }, gPanelWindow);
+ return;
+ }
+
+ // disabledToggleCount == 2
+ 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");
+
+ finish();
+ });
+
+ waitForFocus(function () {
+ EventUtils.synthesizeMouseAtCenter(enabledToggle, {}, gPanelWindow);
+ }, gPanelWindow);
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_fetch-from-cache.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_fetch-from-cache.js
new file mode 100644
index 000000000..23754509a
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_fetch-from-cache.js
@@ -0,0 +1,39 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// A test to ensure Style Editor doesn't bybass cache when loading style sheet
+// contents (bug 978688).
+
+const TEST_URL = TEST_BASE_HTTP + "doc_uncached.html";
+
+add_task(function() {
+ waitForExplicitFinish();
+
+ info("Opening netmonitor");
+ let tab = yield addTab("about:blank");
+ let target = TargetFactory.forTab(tab);
+ let toolbox = yield gDevTools.showToolbox(target, "netmonitor");
+ let netmonitor = toolbox.getPanel("netmonitor");
+
+ info("Navigating to test page");
+ yield navigateTo(TEST_URL);
+
+ info("Opening Style Editor");
+ let styleeditor = yield toolbox.selectTool("styleeditor");
+
+ info("Waiting for an editor to be selected.");
+ yield styleeditor.UI.once("editor-selected");
+
+ info("Checking Netmonitor contents.");
+ let requestsForCss = 0;
+ for (let item of netmonitor._view.RequestsMenu) {
+ if (item.attachment.url.endsWith("doc_uncached.css")) {
+ requestsForCss++;
+ }
+ }
+
+ is(requestsForCss, 1,
+ "Got one request for doc_uncached.css after Style Editor was loaded.");
+});
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_filesave.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_filesave.js
new file mode 100644
index 000000000..990d29005
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_filesave.js
@@ -0,0 +1,107 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI_HTML = TEST_BASE + "simple.html";
+const TESTCASE_URI_CSS = TEST_BASE + "simple.css";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+let tempScope = {};
+Components.utils.import("resource://gre/modules/FileUtils.jsm", tempScope);
+Components.utils.import("resource://gre/modules/NetUtil.jsm", tempScope);
+let FileUtils = tempScope.FileUtils;
+let NetUtil = tempScope.NetUtil;
+
+function test()
+{
+ waitForExplicitFinish();
+
+ copy(TESTCASE_URI_HTML, "simple.html", function(htmlFile) {
+ copy(TESTCASE_URI_CSS, "simple.css", function(cssFile) {
+ addTabAndOpenStyleEditors(1, function(panel) {
+ let UI = panel.UI;
+ let editor = UI.editors[0];
+ editor.getSourceEditor().then(runTests.bind(this, editor));
+ });
+
+ let uri = Services.io.newFileURI(htmlFile);
+ let filePath = uri.resolve("");
+ content.location = filePath;
+ });
+ });
+}
+
+function runTests(editor)
+{
+ editor.sourceEditor.once("dirty-change", () => {
+ is(editor.sourceEditor.isClean(), false, "Editor is dirty.");
+ ok(editor.summary.classList.contains("unsaved"),
+ "Star icon is present in the corresponding summary.");
+ });
+ let beginCursor = {line: 0, ch: 0};
+ editor.sourceEditor.replaceText("DIRTY TEXT", beginCursor, beginCursor);
+
+ editor.sourceEditor.once("dirty-change", () => {
+ is(editor.sourceEditor.isClean(), true, "Editor is clean.");
+ ok(!editor.summary.classList.contains("unsaved"),
+ "Star icon is not present in the corresponding summary.");
+ finish();
+ });
+ editor.saveToFile(null, function (file) {
+ ok(file, "file should get saved directly when using a file:// URI");
+ });
+}
+
+function copy(aSrcChromeURL, aDestFileName, aCallback)
+{
+ let destFile = FileUtils.getFile("ProfD", [aDestFileName]);
+ write(read(aSrcChromeURL), destFile, aCallback);
+}
+
+function read(aSrcChromeURL)
+{
+ let scriptableStream = Cc["@mozilla.org/scriptableinputstream;1"]
+ .getService(Ci.nsIScriptableInputStream);
+
+ let channel = Services.io.newChannel2(aSrcChromeURL,
+ null,
+ null,
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_NORMAL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+ let 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(aData, aFile, aCallback)
+{
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+
+ converter.charset = "UTF-8";
+
+ let istream = converter.convertToInputStream(aData);
+ let ostream = FileUtils.openSafeFileOutputStream(aFile);
+
+ NetUtil.asyncCopy(istream, ostream, function(status) {
+ if (!Components.isSuccessCode(status)) {
+ info("Coudln't write to " + aFile.path);
+ return;
+ }
+
+ aCallback(aFile);
+ });
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_highlight-selector.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_highlight-selector.js
new file mode 100644
index 000000000..5c247df25
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_highlight-selector.js
@@ -0,0 +1,55 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+///////////////////
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
+
+// Test that hovering over a simple selector in the style-editor requests the
+// highlighting of the corresponding nodes
+
+waitForExplicitFinish();
+
+const TEST_URL = "data:text/html;charset=utf8," +
+ "<style>div{color:red}</style><div>highlighter test</div>";
+
+add_task(function*() {
+ let {UI} = yield addTabAndOpenStyleEditors(1, null, TEST_URL);
+ let editor = UI.editors[0];
+
+ // Mock the highlighter so we can locally assert that things happened
+ // correctly instead of accessing the highlighter elements
+ editor.highlighter = {
+ isShown: false,
+ options: null,
+
+ show: function(node, options) {
+ this.isShown = true;
+ this.options = options;
+ return promise.resolve();
+ },
+
+ hide: function() {
+ this.isShown = false;
+ }
+ };
+
+ info("Expecting a node-highlighted event");
+ let onHighlighted = editor.once("node-highlighted");
+
+ info("Simulate a mousemove event on the div selector");
+ editor._onMouseMove({clientX: 56, clientY: 10});
+ yield onHighlighted;
+
+ ok(editor.highlighter.isShown, "The highlighter is now shown");
+ is(editor.highlighter.options.selector, "div", "The selector is correct");
+
+ info("Simulate a mousemove event elsewhere in the editor");
+ editor._onMouseMove({clientX: 16, clientY: 0});
+
+ ok(!editor.highlighter.isShown, "The highlighter is now hidden");
+});
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_import.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_import.js
new file mode 100644
index 000000000..6b60fef98
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_import.js
@@ -0,0 +1,73 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// http rather than chrome to improve coverage
+const TESTCASE_URI = TEST_BASE_HTTP + "simple.html";
+
+let tempScope = {};
+Components.utils.import("resource://gre/modules/FileUtils.jsm", tempScope);
+let FileUtils = tempScope.FileUtils;
+
+const FILENAME = "styleeditor-import-test.css";
+const SOURCE = "body{background:red;}";
+
+
+let gUI;
+
+function test()
+{
+ waitForExplicitFinish();
+
+ addTabAndCheckOnStyleEditorAdded(panel => gUI = panel.UI, testEditorAdded);
+
+ content.location = TESTCASE_URI;
+}
+
+function testImport()
+{
+ // create file to import first
+ let file = FileUtils.getFile("ProfD", [FILENAME]);
+ let ostream = FileUtils.openSafeFileOutputStream(file);
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ let istream = converter.convertToInputStream(SOURCE);
+ NetUtil.asyncCopy(istream, ostream, function (status) {
+ FileUtils.closeSafeFileOutputStream(ostream);
+
+ // click the import button now that the file to import is ready
+ gUI._mockImportFile = file;
+
+ waitForFocus(function () {
+ let document = gPanelWindow.document
+ let importButton = document.querySelector(".style-editor-importButton");
+ ok(importButton, "import button exists");
+
+ EventUtils.synthesizeMouseAtCenter(importButton, {}, gPanelWindow);
+ }, gPanelWindow);
+ });
+}
+
+let gAddedCount = 0;
+function testEditorAdded(aEditor)
+{
+ if (++gAddedCount == 2) {
+ // test import after the 2 initial stylesheets have been loaded
+ gUI.editors[0].getSourceEditor().then(function() {
+ testImport();
+ });
+ }
+
+ if (!aEditor.savedFile) {
+ return;
+ }
+
+ is(aEditor.savedFile.leafName, FILENAME,
+ "imported stylesheet will be saved directly into the same file");
+ is(aEditor.friendlyName, FILENAME,
+ "imported stylesheet has the same name as the filename");
+
+ gUI = null;
+ finish();
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_import_rule.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_import_rule.js
new file mode 100644
index 000000000..8380a464d
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_import_rule.js
@@ -0,0 +1,37 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// http rather than chrome to improve coverage
+const TESTCASE_URI = TEST_BASE_HTTP + "import.html";
+
+let gUI;
+
+function test()
+{
+ waitForExplicitFinish();
+
+ addTabAndOpenStyleEditors(3, onEditorAdded);
+
+ content.location = TESTCASE_URI;
+}
+
+function onEditorAdded(panel)
+{
+ gUI = panel.UI;
+
+ is(gUI.editors.length, 3,
+ "there are 3 stylesheets after loading @imports");
+
+ is(gUI.editors[0].styleSheet.href, TEST_BASE_HTTP + "simple.css",
+ "stylesheet 1 is simple.css");
+
+ is(gUI.editors[1].styleSheet.href, TEST_BASE_HTTP + "import.css",
+ "stylesheet 2 is import.css");
+
+ is(gUI.editors[2].styleSheet.href, TEST_BASE_HTTP + "import2.css",
+ "stylesheet 3 is import2.css");
+
+ gUI = null;
+ finish();
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_init.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_init.js
new file mode 100644
index 000000000..dd3550215
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_init.js
@@ -0,0 +1,91 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+///////////////////
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("TypeError: summary is undefined");
+
+const TESTCASE_URI = TEST_BASE + "simple.html";
+
+let gUI;
+
+function test()
+{
+ waitForExplicitFinish();
+
+ addTabAndCheckOnStyleEditorAdded(panel => gUI = panel.UI, testEditorAdded);
+
+ content.location = TESTCASE_URI;
+}
+
+let gEditorAddedCount = 0;
+function testEditorAdded(aEditor)
+{
+ if (aEditor.styleSheet.styleSheetIndex == 0) {
+ gEditorAddedCount++;
+ gUI.editors[0].getSourceEditor().then(testFirstStyleSheetEditor);
+ }
+ if (aEditor.styleSheet.styleSheetIndex == 1) {
+ gEditorAddedCount++;
+ testSecondStyleSheetEditor(aEditor);
+ }
+
+ if (gEditorAddedCount == 2) {
+ gUI = null;
+ finish();
+ }
+}
+
+function testFirstStyleSheetEditor(aEditor)
+{
+ // Note: the html <link> contains charset="UTF-8".
+ ok(aEditor._state.text.indexOf("\u263a") >= 0,
+ "stylesheet is unicode-aware.");
+
+ //testing TESTCASE's simple.css stylesheet
+ is(aEditor.styleSheet.styleSheetIndex, 0,
+ "first stylesheet is at index 0");
+
+ is(aEditor, gUI.editors[0],
+ "first stylesheet corresponds to StyleEditorChrome.editors[0]");
+
+ let summary = aEditor.summary;
+
+ let name = summary.querySelector(".stylesheet-name > label").getAttribute("value");
+ is(name, "simple.css",
+ "first stylesheet's name is `simple.css`");
+
+ let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
+ is(parseInt(ruleCount), 1,
+ "first stylesheet UI shows rule count as 1");
+
+ ok(summary.classList.contains("splitview-active"),
+ "first stylesheet UI is focused/active");
+}
+
+function testSecondStyleSheetEditor(aEditor)
+{
+ //testing TESTCASE's inline stylesheet
+ is(aEditor.styleSheet.styleSheetIndex, 1,
+ "second stylesheet is at index 1");
+
+ is(aEditor, gUI.editors[1],
+ "second stylesheet corresponds to StyleEditorChrome.editors[1]");
+
+ let summary = aEditor.summary;
+
+ let name = summary.querySelector(".stylesheet-name > label").getAttribute("value");
+ ok(/^<.*>$/.test(name),
+ "second stylesheet's name is surrounded by `<>`");
+
+ let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
+ is(parseInt(ruleCount), 3,
+ "second stylesheet UI shows rule count as 3");
+
+ ok(!summary.classList.contains("splitview-active"),
+ "second stylesheet UI is NOT focused/active");
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_inline_friendly_names.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_inline_friendly_names.js
new file mode 100644
index 000000000..9411b639f
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_inline_friendly_names.js
@@ -0,0 +1,155 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+///////////////////
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
+
+let gUI;
+
+const FIRST_TEST_PAGE = TEST_BASE + "inline-1.html"
+const SECOND_TEST_PAGE = TEST_BASE + "inline-2.html"
+const SAVE_PATH = "test.css";
+
+function test()
+{
+ waitForExplicitFinish();
+
+ addTabAndOpenStyleEditors(2, function(panel) {
+ gUI = panel.UI;
+
+ // First test that identifiers are correcly generated. If not other tests
+ // are likely to fail.
+ testIndentifierGeneration();
+
+ saveFirstInlineStyleSheet()
+ .then(testFriendlyNamesAfterSave)
+ .then(reloadPage)
+ .then(testFriendlyNamesAfterSave)
+ .then(navigateToAnotherPage)
+ .then(testFriendlyNamesAfterNavigation)
+ .then(finishTests);
+ });
+
+ content.location = FIRST_TEST_PAGE;
+}
+
+function testIndentifierGeneration() {
+ let fakeStyleSheetFile = {
+ "href": "http://example.com/test.css",
+ "nodeHref": "http://example.com/",
+ "styleSheetIndex": 1
+ }
+
+ let fakeInlineStyleSheet = {
+ "href": null,
+ "nodeHref": "http://example.com/",
+ "styleSheetIndex": 2
+ }
+
+ is(gUI.getStyleSheetIdentifier(fakeStyleSheetFile), "http://example.com/test.css",
+ "URI is the identifier of style sheet file.");
+
+ is(gUI.getStyleSheetIdentifier(fakeInlineStyleSheet), "inline-2-at-http://example.com/",
+ "Inline style sheets are identified by their page and position at that page.");
+}
+
+function saveFirstInlineStyleSheet() {
+ let deferred = promise.defer();
+ let editor = gUI.editors[0];
+
+ let destFile = FileUtils.getFile("ProfD", [SAVE_PATH]);
+
+ editor.saveToFile(destFile, function (file) {
+ ok(file, "File was correctly saved.");
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+function testFriendlyNamesAfterSave() {
+ let firstEditor = gUI.editors[0];
+ let secondEditor = gUI.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 is for the second inline style sheet is not the same as first.");
+
+ return promise.resolve(null);
+}
+
+function reloadPage() {
+ info("Reloading page.");
+ content.location.reload();
+ return waitForEditors(2);
+}
+
+function navigateToAnotherPage() {
+ info("Navigating to another page.");
+ let deferred = promise.defer();
+ gBrowser.removeCurrentTab();
+
+ gUI = null;
+
+ addTabAndOpenStyleEditors(2, function(panel) {
+ gUI = panel.UI;
+ deferred.resolve();
+ });
+
+ content.location = SECOND_TEST_PAGE;
+ return deferred.promise;
+}
+
+function testFriendlyNamesAfterNavigation() {
+ let firstEditor = gUI.editors[0];
+ let secondEditor = gUI.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);
+}
+
+function finishTests() {
+ gUI = null;
+ finish();
+}
+
+/**
+ * Waits for all editors to be added.
+ *
+ * @param {int} aNumberOfEditors
+ * The number of editors to wait until proceeding.
+ *
+ * Returns a promise that's resolved once all editors are added.
+ */
+function waitForEditors(aNumberOfEditors) {
+ let deferred = promise.defer();
+ let count = 0;
+
+ info("Waiting for " + aNumberOfEditors + " editors to be added");
+ gUI.on("editor-added", function editorAdded(event, editor) {
+ if (++count == aNumberOfEditors) {
+ info("All editors added. Resolving promise.");
+ gUI.off("editor-added", editorAdded);
+ gUI.editors[0].getSourceEditor().then(deferred.resolve);
+ }
+ else {
+ info ("Editor " + count + " of " + aNumberOfEditors + " added.");
+ }
+ });
+
+ return deferred.promise;
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_loading.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_loading.js
new file mode 100644
index 000000000..291381ee7
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_loading.js
@@ -0,0 +1,37 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE_HTTP + "longload.html";
+
+
+function test()
+{
+ waitForExplicitFinish();
+
+ // 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.
+
+ addTabAndCheckOnStyleEditorAdded(function(panel) {
+ content.location = TESTCASE_URI;
+ }, testEditorAdded);
+}
+
+function testEditorAdded(event, editor)
+{
+ let root = gPanelWindow.document.querySelector(".splitview-root");
+ ok(!root.classList.contains("loading"),
+ "style editor root element does not have 'loading' class name anymore");
+
+ let button = gPanelWindow.document.querySelector(".style-editor-newButton");
+ ok(!button.hasAttribute("disabled"),
+ "new style sheet button is enabled");
+
+ button = gPanelWindow.document.querySelector(".style-editor-importButton");
+ ok(!button.hasAttribute("disabled"),
+ "import button is enabled");
+
+ finish();
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_media_sidebar.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_media_sidebar.js
new file mode 100644
index 000000000..7f4d846ca
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_media_sidebar.js
@@ -0,0 +1,137 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// https rather than chrome to improve coverage
+const TESTCASE_URI = TEST_BASE_HTTPS + "media-rules.html";
+const MEDIA_PREF = "devtools.styleeditor.showMediaSidebar";
+
+const RESIZE = 300;
+const LABELS = ["not all", "all", "(max-width: 400px)", "(max-width: 600px)"];
+const LINE_NOS = [1, 7, 19, 25];
+const NEW_RULE = "\n@media (max-width: 600px) { div { color: blue; } }";
+
+waitForExplicitFinish();
+
+add_task(function*() {
+ let {UI} = yield addTabAndOpenStyleEditors(2, null, TESTCASE_URI);
+
+ is(UI.editors.length, 2, "correct number of editors");
+
+ // Test first plain css editor
+ let plainEditor = UI.editors[0];
+ yield openEditor(plainEditor);
+ testPlainEditor(plainEditor);
+
+ // Test editor with @media rules
+ let mediaEditor = UI.editors[1];
+ yield openEditor(mediaEditor);
+ testMediaEditor(mediaEditor);
+
+ // Test that sidebar hides when flipping pref
+ yield testShowHide(UI, mediaEditor);
+
+ // Test adding a rule updates the list
+ yield testMediaRuleAdded(UI, mediaEditor);
+
+ // Test resizing and seeing @media matching state change
+ let originalWidth = window.outerWidth;
+ let originalHeight = window.outerHeight;
+
+ let onMatchesChange = listenForMediaChange(UI);
+ window.resizeTo(RESIZE, RESIZE);
+ yield onMatchesChange;
+
+ testMediaMatchChanged(mediaEditor);
+
+ window.resizeTo(originalWidth, originalHeight);
+});
+
+function testPlainEditor(editor) {
+ let sidebar = editor.details.querySelector(".stylesheet-sidebar");
+ is(sidebar.hidden, true, "sidebar is hidden on editor without @media");
+}
+
+function testMediaEditor(editor) {
+ let sidebar = editor.details.querySelector(".stylesheet-sidebar");
+ is(sidebar.hidden, false, "sidebar is showing on editor with @media");
+
+ let entries = [...sidebar.querySelectorAll(".media-rule-label")];
+ is(entries.length, 3, "three @media rules displayed in sidebar");
+
+ testRule(entries[0], LABELS[0], false, LINE_NOS[0]);
+ testRule(entries[1], LABELS[1], true, LINE_NOS[1]);
+ testRule(entries[2], LABELS[2], false, LINE_NOS[2]);
+}
+
+function testMediaMatchChanged(editor) {
+ let sidebar = editor.details.querySelector(".stylesheet-sidebar");
+
+ let cond = sidebar.querySelectorAll(".media-rule-condition")[2];
+ is(cond.textContent, "(max-width: 400px)", "third rule condition text is correct");
+ ok(!cond.classList.contains("media-condition-unmatched"),
+ "media rule is now matched after resizing");
+}
+
+function* testShowHide(UI, editor) {
+ let sidebarChange = listenForMediaChange(UI);
+ Services.prefs.setBoolPref(MEDIA_PREF, false);
+ yield sidebarChange;
+
+ let sidebar = editor.details.querySelector(".stylesheet-sidebar");
+ is(sidebar.hidden, true, "sidebar is hidden after flipping pref");
+
+ sidebarChange = listenForMediaChange(UI);
+ Services.prefs.clearUserPref(MEDIA_PREF);
+ yield sidebarChange;
+
+ is(sidebar.hidden, false, "sidebar is showing after flipping pref back");
+}
+
+function* testMediaRuleAdded(UI, editor) {
+ yield editor.getSourceEditor();
+ let text = editor.sourceEditor.getText();
+ text += NEW_RULE;
+
+ let listChange = listenForMediaChange(UI);
+ editor.sourceEditor.setText(text);
+ yield listChange;
+
+ let sidebar = editor.details.querySelector(".stylesheet-sidebar");
+ let entries = [...sidebar.querySelectorAll(".media-rule-label")];
+ is(entries.length, 4, "four @media rules after changing text");
+
+ testRule(entries[3], LABELS[3], false, LINE_NOS[3]);
+}
+
+function testRule(rule, text, matches, lineno) {
+ let cond = rule.querySelector(".media-rule-condition");
+ is(cond.textContent, text, "media label is correct for " + text);
+
+ let matched = !cond.classList.contains("media-condition-unmatched");
+ ok(matches ? matched : !matched,
+ "media rule is " + (matches ? "matched" : "unmatched"));
+
+ let line = rule.querySelector(".media-rule-line");
+ is(line.textContent, ":" + lineno, "correct line number shown");
+}
+
+/* Helpers */
+
+function openEditor(editor) {
+ getLinkFor(editor).click();
+
+ return editor.getSourceEditor();
+}
+
+function listenForMediaChange(UI) {
+ let deferred = promise.defer();
+ UI.once("media-list-changed", () => {
+ deferred.resolve();
+ })
+ return deferred.promise;
+}
+
+function getLinkFor(editor) {
+ return editor.summary.querySelector(".stylesheet-name");
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_media_sidebar_sourcemaps.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_media_sidebar_sourcemaps.js
new file mode 100644
index 000000000..3bf4ab902
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_media_sidebar_sourcemaps.js
@@ -0,0 +1,69 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// https rather than chrome to improve coverage
+const TESTCASE_URI = TEST_BASE_HTTPS + "media-rules-sourcemaps.html";
+const MAP_PREF = "devtools.styleeditor.source-maps-enabled";
+
+const LABELS = ["screen and (max-width: 320px)",
+ "screen and (min-width: 1200px)"];
+const LINE_NOS = [5, 8];
+
+waitForExplicitFinish();
+
+add_task(function*() {
+ Services.prefs.setBoolPref(MAP_PREF, true);
+
+ let {UI} = yield addTabAndOpenStyleEditors(2, null, TESTCASE_URI);
+
+ yield listenForMediaChange(UI);
+
+ is(UI.editors.length, 1, "correct number of editors");
+
+ // Test editor with @media rules
+ let mediaEditor = UI.editors[0];
+ yield openEditor(mediaEditor);
+ testMediaEditor(mediaEditor);
+
+ Services.prefs.clearUserPref(MAP_PREF);
+});
+
+function testMediaEditor(editor) {
+ let sidebar = editor.details.querySelector(".stylesheet-sidebar");
+ is(sidebar.hidden, false, "sidebar is showing on editor with @media");
+
+ let entries = [...sidebar.querySelectorAll(".media-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) {
+ let cond = rule.querySelector(".media-rule-condition");
+ is(cond.textContent, text, "media label is correct for " + text);
+
+ let line = rule.querySelector(".media-rule-line");
+ is(line.textContent, ":" + lineno, "correct line number shown");
+}
+
+/* Helpers */
+
+function openEditor(editor) {
+ getLinkFor(editor).click();
+
+ return editor.getSourceEditor();
+}
+
+function listenForMediaChange(UI) {
+ let deferred = promise.defer();
+ UI.once("media-list-changed", () => {
+ deferred.resolve();
+ })
+ return deferred.promise;
+}
+
+function getLinkFor(editor) {
+ return editor.summary.querySelector(".stylesheet-name");
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_new.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_new.js
new file mode 100644
index 000000000..2ecd025f7
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_new.js
@@ -0,0 +1,120 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+///////////////////
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
+
+const TESTCASE_URI = TEST_BASE + "simple.html";
+
+let TESTCASE_CSS_SOURCE = "body{background-color:red;";
+
+let gOriginalHref;
+let gUI;
+
+waitForExplicitFinish();
+
+add_task(function*() {
+ let panel = yield addTabAndOpenStyleEditors(2, null, TESTCASE_URI);
+ gUI = panel.UI;
+
+ let editor = yield createNew();
+ testInitialState(editor);
+
+ let waitForPropertyChange = onPropertyChange(editor);
+
+ yield typeInEditor(editor);
+
+ yield waitForPropertyChange;
+
+ testUpdated(editor);
+
+ gUI = null;
+});
+
+function createNew() {
+ info("Creating a new stylesheet now");
+ let deferred = promise.defer();
+
+ gUI.once("editor-added", (ev, editor) => {
+ editor.getSourceEditor().then(deferred.resolve);
+ });
+
+ waitForFocus(function () {// create a new style sheet
+ let newButton = gPanelWindow.document.querySelector(".style-editor-newButton");
+ ok(newButton, "'new' button exists");
+
+ EventUtils.synthesizeMouseAtCenter(newButton, {}, gPanelWindow);
+ }, gPanelWindow);
+
+ return deferred.promise;
+}
+
+function onPropertyChange(aEditor) {
+ let deferred = promise.defer();
+
+ aEditor.styleSheet.on("property-change", function onProp(property, value) {
+ // wait for text to be entered fully
+ let text = aEditor.sourceEditor.getText();
+ if (property == "ruleCount" && text == TESTCASE_CSS_SOURCE + "}") {
+ aEditor.styleSheet.off("property-change", onProp);
+ deferred.resolve();
+ }
+ });
+
+ return deferred.promise;
+}
+
+function testInitialState(aEditor) {
+ info("Testing the initial state of the new editor");
+ gOriginalHref = aEditor.styleSheet.href;
+
+ let summary = aEditor.summary;
+
+ ok(aEditor.sourceLoaded, "new editor is loaded when attached");
+ ok(aEditor.isNew, "new editor has isNew flag");
+
+ ok(aEditor.sourceEditor.hasFocus(), "new editor has focus");
+
+ summary = aEditor.summary;
+ let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
+ is(parseInt(ruleCount), 0,
+ "new editor initially shows 0 rules");
+
+ let computedStyle = content.getComputedStyle(content.document.body, null);
+ is(computedStyle.backgroundColor, "rgb(255, 255, 255)",
+ "content's background color is initially white");
+}
+
+function typeInEditor(aEditor) {
+ let deferred = promise.defer();
+
+ waitForFocus(function () {
+ for each (let c in TESTCASE_CSS_SOURCE) {
+ EventUtils.synthesizeKey(c, {}, gPanelWindow);
+ }
+ ok(aEditor.unsaved, "new editor has unsaved flag");
+
+ deferred.resolve();
+ }, gPanelWindow);
+
+ return deferred.promise;
+}
+
+function testUpdated(aEditor) {
+ info("Testing the state of the new editor after editing it");
+
+ is(aEditor.sourceEditor.getText(), TESTCASE_CSS_SOURCE + "}",
+ "rule bracket has been auto-closed");
+
+ let ruleCount = aEditor.summary.querySelector(".stylesheet-rule-count").textContent;
+ is(parseInt(ruleCount), 1,
+ "new editor shows 1 rule after modification");
+
+ is(aEditor.styleSheet.href, gOriginalHref,
+ "style sheet href did not change");
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_nostyle.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_nostyle.js
new file mode 100644
index 000000000..bc4263f88
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_nostyle.js
@@ -0,0 +1,41 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE + "nostyle.html";
+
+
+function test()
+{
+ waitForExplicitFinish();
+
+ // 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.
+
+ addTabAndCheckOnStyleEditorAdded(function(panel) {
+ panel.UI.once("stylesheets-reset", testDocumentLoad);
+
+ content.location = TESTCASE_URI;
+ }, () => {});
+}
+
+function testDocumentLoad(event)
+{
+ let root = gPanelWindow.document.querySelector(".splitview-root");
+ ok(!root.classList.contains("loading"),
+ "style editor root element does not have 'loading' class name anymore");
+
+ ok(root.querySelector(".empty.placeholder"), "showing 'no style' indicator");
+
+ let button = gPanelWindow.document.querySelector(".style-editor-newButton");
+ ok(!button.hasAttribute("disabled"),
+ "new style sheet button is enabled");
+
+ button = gPanelWindow.document.querySelector(".style-editor-importButton");
+ ok(!button.hasAttribute("disabled"),
+ "import button is enabled");
+
+ finish();
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_pretty.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_pretty.js
new file mode 100644
index 000000000..1ed771b64
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_pretty.js
@@ -0,0 +1,56 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+///////////////////
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
+
+const TESTCASE_URI = TEST_BASE + "minified.html";
+
+let gUI;
+
+function test()
+{
+ waitForExplicitFinish();
+
+ addTabAndCheckOnStyleEditorAdded(panel => gUI = panel.UI, editor => {
+ editor.getSourceEditor().then(function() {
+ testEditor(editor);
+ });
+ });
+
+ content.location = TESTCASE_URI;
+}
+
+let editorTestedCount = 0;
+function testEditor(aEditor)
+{
+ if (aEditor.styleSheet.styleSheetIndex == 0) {
+ let prettifiedSource = "body\{\r?\n\tbackground\:white;\r?\n\}\r?\n\r?\ndiv\{\r?\n\tfont\-size\:4em;\r?\n\tcolor\:red\r?\n\}\r?\n\r?\nspan\{\r?\n\tcolor\:green;\r?\n\}\r?\n";
+ let prettifiedSourceRE = new RegExp(prettifiedSource);
+
+ ok(prettifiedSourceRE.test(aEditor.sourceEditor.getText()),
+ "minified source has been prettified automatically");
+ editorTestedCount++;
+ let summary = gUI.editors[1].summary;
+ EventUtils.synthesizeMouseAtCenter(summary, {}, gPanelWindow);
+ }
+
+ if (aEditor.styleSheet.styleSheetIndex == 1) {
+ let originalSource = "body \{ background\: red; \}\r?\ndiv \{\r?\nfont\-size\: 5em;\r?\ncolor\: red\r?\n\}";
+ let originalSourceRE = new RegExp(originalSource);
+
+ ok(originalSourceRE.test(aEditor.sourceEditor.getText()),
+ "non-minified source has been left untouched");
+ editorTestedCount++;
+ }
+
+ if (editorTestedCount == 2) {
+ gUI = null;
+ finish();
+ }
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_private_perwindowpb.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_private_perwindowpb.js
new file mode 100644
index 000000000..4ccb086bc
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_private_perwindowpb.js
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This test makes sure that the style editor does not store any
+// content CSS files in the permanent cache when opened from PB mode.
+
+function test() {
+ waitForExplicitFinish();
+ let gUI;
+ let testURI = 'http://' + TEST_HOST + '/browser/browser/devtools/styleeditor/test/test_private.html';
+
+ info("Opening a new private window");
+ let win = OpenBrowserWindow({private: true});
+ win.addEventListener("load", function onLoad() {
+ win.removeEventListener("load", onLoad, false);
+ executeSoon(startTest);
+ }, false);
+
+ function startTest() {
+ win.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
+ win.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
+
+ info("Clearing the browser cache");
+ cache.clear();
+
+ info("Opening the style editor in the private window");
+ openStyleEditorInWindow(win, function(panel) {
+ gUI = panel.UI;
+ gUI.on("editor-added", onEditorAdded);
+ });
+ }, true);
+
+ info("Loading the test URL in the new private window");
+ win.content.location = testURI;
+ }
+
+ function onEditorAdded(aEvent, aEditor) {
+ info("The style editor is ready")
+ aEditor.getSourceEditor().then(checkCache);
+ }
+
+ function checkCache() {
+ checkDiskCacheFor(TEST_HOST, function() {
+ gUI.off("editor-added", onEditorAdded);
+ win.close();
+ win = null;
+ gUI = null;
+ finish();
+ });
+ }
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_reload.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_reload.js
new file mode 100644
index 000000000..11781909e
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_reload.js
@@ -0,0 +1,105 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+///////////////////
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
+
+const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
+const NEW_URI = TEST_BASE_HTTPS + "media.html";
+
+const LINE_NO = 5;
+const COL_NO = 3;
+
+let gContentWin;
+let gUI;
+
+function test()
+{
+ waitForExplicitFinish();
+
+ addTabAndOpenStyleEditors(2, function(panel) {
+ gContentWin = gBrowser.selectedBrowser.contentWindow.wrappedJSObject;
+ gUI = panel.UI;
+ gUI.editors[0].getSourceEditor().then(runTests);
+ });
+
+ content.location = TESTCASE_URI;
+}
+
+function runTests()
+{
+ let count = 0;
+ gUI.on("editor-selected", function editorSelected(event, editor) {
+ if (editor.styleSheet != gUI.editors[1].styleSheet) {
+ return;
+ }
+ gUI.off("editor-selected", editorSelected);
+ editor.getSourceEditor().then(() => {
+ info("selected second editor, about to reload page");
+ reloadPage();
+
+ gUI.on("editor-added", function editorAdded(event, editor) {
+ if (++count == 2) {
+ info("all editors added after reload");
+ gUI.off("editor-added", editorAdded);
+ gUI.editors[1].getSourceEditor().then(testRemembered);
+ }
+ })
+ });
+ });
+ gUI.selectStyleSheet(gUI.editors[1].styleSheet, LINE_NO, COL_NO);
+}
+
+function testRemembered()
+{
+ is(gUI.selectedEditor, gUI.editors[1], "second editor is selected");
+
+ let {line, ch} = gUI.selectedEditor.sourceEditor.getCursor();
+ is(line, LINE_NO, "correct line selected");
+ is(ch, COL_NO, "correct column selected");
+
+ testNewPage();
+}
+
+function testNewPage()
+{
+ let count = 0;
+ gUI.on("editor-added", function editorAdded(event, editor) {
+ info("editor added here")
+ if (++count == 2) {
+ info("all editors added after navigating page");
+ gUI.off("editor-added", editorAdded);
+ gUI.editors[0].getSourceEditor().then(testNotRemembered);
+ }
+ })
+
+ info("navigating to a different page");
+ navigatePage();
+}
+
+function testNotRemembered()
+{
+ is(gUI.selectedEditor, gUI.editors[0], "first editor is selected");
+
+ let {line, ch} = gUI.selectedEditor.sourceEditor.getCursor();
+ is(line, 0, "first line is selected");
+ is(ch, 0, "first column is selected");
+
+ gUI = null;
+ finish();
+}
+
+function reloadPage()
+{
+ gContentWin.location.reload();
+}
+
+function navigatePage()
+{
+ gContentWin.location = NEW_URI;
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_selectstylesheet.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_selectstylesheet.js
new file mode 100644
index 000000000..33b4bb304
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_selectstylesheet.js
@@ -0,0 +1,58 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+///////////////////
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
+
+const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
+const NEW_URI = TEST_BASE_HTTPS + "media.html";
+
+const LINE_NO = 5;
+const COL_NO = 0;
+
+let gContentWin;
+let gUI;
+
+function test()
+{
+ waitForExplicitFinish();
+
+ addTabAndOpenStyleEditors(2, function(panel) {
+ gContentWin = gBrowser.selectedBrowser.contentWindow.wrappedJSObject;
+ gUI = panel.UI;
+ gUI.editors[0].getSourceEditor().then(runTests);
+ });
+
+ content.location = TESTCASE_URI;
+}
+
+function runTests()
+{
+ let count = 0;
+
+ // Make sure Editor doesn't go into an infinite loop when
+ // column isn't passed. See bug 941018.
+ gUI.on("editor-selected", function editorSelected(event, editor) {
+ if (editor.styleSheet != gUI.editors[1].styleSheet) {
+ return;
+ }
+ gUI.off("editor-selected", editorSelected);
+
+ editor.getSourceEditor().then(() => {
+ is(gUI.selectedEditor, gUI.editors[1], "second editor is selected");
+ let {line, ch} = gUI.selectedEditor.sourceEditor.getCursor();
+
+ is(line, LINE_NO, "correct line selected");
+ is(ch, COL_NO, "correct column selected");
+
+ gUI = null;
+ finish();
+ });
+ });
+ gUI.selectStyleSheet(gUI.editors[1].styleSheet.href, LINE_NO);
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_sourcemap_large.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_sourcemap_large.js
new file mode 100644
index 000000000..65c2bef46
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_sourcemap_large.js
@@ -0,0 +1,31 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// 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(function*() {
+ let {UI} = yield addTabAndOpenStyleEditors(2, null, TESTCASE_URI);
+
+ yield openEditor(UI.editors[0]);
+ let 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");
+} \ No newline at end of file
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_sourcemap_watching.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_sourcemap_watching.js
new file mode 100644
index 000000000..db16907a6
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_sourcemap_watching.js
@@ -0,0 +1,195 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+Components.utils.import("resource://gre/modules/Task.jsm");
+let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+
+const TESTCASE_URI_HTML = TEST_BASE + "sourcemaps-watching.html";
+const TESTCASE_URI_CSS = TEST_BASE + "sourcemap-css/sourcemaps.css";
+const TESTCASE_URI_REG_CSS = TEST_BASE + "simple.css";
+const TESTCASE_URI_SCSS = TEST_BASE + "sourcemap-sass/sourcemaps.scss";
+const TESTCASE_URI_MAP = TEST_BASE + "sourcemap-css/sourcemaps.css.map";
+const TESTCASE_SCSS_NAME = "sourcemaps.scss";
+
+const TRANSITIONS_PREF = "devtools.styleeditor.transitions";
+
+const CSS_TEXT = "* { color: blue }";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+let tempScope = {};
+Components.utils.import("resource://gre/modules/FileUtils.jsm", tempScope);
+Components.utils.import("resource://gre/modules/NetUtil.jsm", tempScope);
+let FileUtils = tempScope.FileUtils;
+let NetUtil = tempScope.NetUtil;
+
+function test()
+{
+ waitForExplicitFinish();
+
+ Services.prefs.setBoolPref(TRANSITIONS_PREF, false);
+
+ Task.spawn(function() {
+ // copy all our files over so we don't screw them up for other tests
+ let HTMLFile = yield copy(TESTCASE_URI_HTML, ["sourcemaps.html"]);
+ let CSSFile = yield copy(TESTCASE_URI_CSS, ["sourcemap-css", "sourcemaps.css"]);
+ yield copy(TESTCASE_URI_SCSS, ["sourcemap-sass", "sourcemaps.scss"]);
+ yield copy(TESTCASE_URI_MAP, ["sourcemap-css", "sourcemaps.css.map"]);
+ yield copy(TESTCASE_URI_REG_CSS, ["simple.css"]);
+
+ let uri = Services.io.newFileURI(HTMLFile);
+ let testcaseURI = uri.resolve("");
+
+ let editor = yield openEditor(testcaseURI);
+
+ let element = content.document.querySelector("div");
+ let style = content.getComputedStyle(element, null);
+
+ is(style.color, "rgb(255, 0, 102)", "div is red before saving file");
+
+ editor.styleSheet.relatedStyleSheet.once("style-applied", function() {
+ is(style.color, "rgb(0, 0, 255)", "div is blue after saving file");
+ finishUp();
+ });
+
+ yield pauseForTimeChange();
+
+ // Edit and save Sass in the editor. This will start off a file-watching
+ // process waiting for the CSS file to change.
+ yield editSCSS(editor);
+
+ // We can't run Sass or another compiler, so we fake it by just
+ // directly changing the CSS file.
+ yield editCSSFile(CSSFile);
+
+ info("wrote to CSS file");
+ })
+}
+
+function openEditor(testcaseURI) {
+ let deferred = promise.defer();
+
+ addTabAndOpenStyleEditors(3, panel => {
+ let UI = panel.UI;
+
+ // wait for 5 editors - 1 for first style sheet, 2 for the
+ // generated style sheets, and 2 for original source after it
+ // loads and replaces the generated style sheets.
+ let editor = UI.editors[1];
+ if (getStylesheetNameFor(editor) != TESTCASE_SCSS_NAME) {
+ editor = UI.editors[2];
+ }
+ is(getStylesheetNameFor(editor), TESTCASE_SCSS_NAME, "found scss editor");
+
+ let link = getLinkFor(editor);
+ link.click();
+
+ editor.getSourceEditor().then(deferred.resolve);
+ });
+ content.location = testcaseURI;
+
+ return deferred.promise;
+}
+
+function editSCSS(editor) {
+ let deferred = promise.defer();
+
+ let pos = {line: 0, ch: 0};
+ editor.sourceEditor.replaceText(CSS_TEXT, pos, pos);
+
+ editor.saveToFile(null, function (file) {
+ ok(file, "Scss file should be saved");
+ deferred.resolve();
+ });
+
+ return deferred.promise;
+}
+
+function editCSSFile(CSSFile) {
+ return write(CSS_TEXT, CSSFile);
+}
+
+function pauseForTimeChange() {
+ let deferred = promise.defer();
+
+ // 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(deferred.resolve, 2000);
+
+ return deferred.promise;
+}
+
+function finishUp() {
+ Services.prefs.clearUserPref(TRANSITIONS_PREF);
+ finish();
+}
+
+/* Helpers */
+
+function getLinkFor(editor) {
+ return editor.summary.querySelector(".stylesheet-name");
+}
+
+function getStylesheetNameFor(editor) {
+ return editor.summary.querySelector(".stylesheet-name > label")
+ .getAttribute("value")
+}
+
+function copy(aSrcChromeURL, aDestFilePath)
+{
+ let destFile = FileUtils.getFile("ProfD", aDestFilePath);
+ return write(read(aSrcChromeURL), destFile);
+}
+
+function read(aSrcChromeURL)
+{
+ let scriptableStream = Cc["@mozilla.org/scriptableinputstream;1"]
+ .getService(Ci.nsIScriptableInputStream);
+
+ let channel = Services.io.newChannel2(aSrcChromeURL,
+ null,
+ null,
+ null, // aLoadingNode
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ null, // aTriggeringPrincipal
+ Ci.nsILoadInfo.SEC_NORMAL,
+ Ci.nsIContentPolicy.TYPE_OTHER);
+ let 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(aData, aFile)
+{
+ let deferred = promise.defer();
+
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+
+ converter.charset = "UTF-8";
+
+ let istream = converter.convertToInputStream(aData);
+ let ostream = FileUtils.openSafeFileOutputStream(aFile);
+
+ NetUtil.asyncCopy(istream, ostream, function(status) {
+ if (!Components.isSuccessCode(status)) {
+ info("Coudln't write to " + aFile.path);
+ return;
+ }
+ deferred.resolve(aFile);
+ });
+
+ return deferred.promise;
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_sourcemaps.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_sourcemaps.js
new file mode 100644
index 000000000..09b0962ab
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_sourcemaps.js
@@ -0,0 +1,149 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// https rather than chrome to improve coverage
+const TESTCASE_URI = TEST_BASE_HTTPS + "sourcemaps.html";
+const PREF = "devtools.styleeditor.source-maps-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,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiIiwic291cmNlcyI6WyJzYXNzL2NvbnRhaW5lZC5zY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBO0VBQ0UsT0FISyIsInNvdXJjZXNDb250ZW50IjpbIiRwaW5rOiAjZjA2O1xuXG4jaGVhZGVyIHtcbiAgY29sb3I6ICRwaW5rO1xufSJdfQ==*/"
+ ].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,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3Qtc3R5bHVzLnN0eWwiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBRUE7RUFDRSxPQUFPLEtBQVA7O0FBRUY7RUFDRSxrQkFBa0IsS0FBbEIiLCJmaWxlIjoidGVzdC1zdHlsdXMuY3NzIiwic291cmNlc0NvbnRlbnQiOlsicGF1bHJvdWdldHBpbmsgPSAjZjA2O1xuXG5kaXZcbiAgY29sb3I6IHBhdWxyb3VnZXRwaW5rXG5cbnNwYW5cbiAgYmFja2dyb3VuZC1jb2xvcjogI0VFRVxuIl19 */"
+ ].join("\n")
+}
+
+const cssNames = ["sourcemaps.css", "contained.css", "test-stylus.css"];
+const origNames = ["sourcemaps.scss", "contained.scss", "test-stylus.styl"];
+
+waitForExplicitFinish();
+
+add_task(function*() {
+ let {UI} = yield addTabAndOpenStyleEditors(7, null, 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
+ yield testEditor(UI.editors[1], origNames);
+ yield testEditor(UI.editors[2], origNames);
+ yield testEditor(UI.editors[3], origNames);
+
+ // Test disabling original sources
+ yield togglePref(UI);
+
+ is(UI.editors.length, 4, "correct number of editors after pref toggled");
+
+ // Test CSS editors
+ yield testEditor(UI.editors[1], cssNames);
+ yield testEditor(UI.editors[2], cssNames);
+ yield testEditor(UI.editors[3], cssNames);
+
+ Services.prefs.clearUserPref(PREF);
+});
+
+function testFirstEditor(editor) {
+ let name = getStylesheetNameFor(editor);
+ is(name, "simple.css", "First style sheet display name is correct");
+}
+
+function testEditor(editor, possibleNames) {
+ let name = getStylesheetNameFor(editor);
+ ok(possibleNames.indexOf(name) >= 0, name + " editor name is correct");
+
+ return openEditor(editor).then(() => {
+ let expectedText = contents[name];
+
+ let text = editor.sourceEditor.getText();
+
+ is(text, expectedText, name + " editor contains expected text");
+ });
+}
+
+/* Helpers */
+
+function togglePref(UI) {
+ let deferred = promise.defer();
+ let count = 0;
+
+ UI.on("editor-added", (event, editor) => {
+ if (++count == 3) {
+ deferred.resolve();
+ }
+ })
+ let editorsPromise = deferred.promise;
+
+ let 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/toolkit/devtools/styleeditor/test/browser_styleeditor_sv_keynav.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_sv_keynav.js
new file mode 100644
index 000000000..c77c3b5e0
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_sv_keynav.js
@@ -0,0 +1,82 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+///////////////////
+//
+// Whitelisting this test.
+// As part of bug 1077403, the leaking uncaught rejection should be fixed.
+//
+thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
+
+const TESTCASE_URI = TEST_BASE + "four.html";
+
+let gUI;
+
+function test()
+{
+ waitForExplicitFinish();
+
+ addTabAndOpenStyleEditors(4, runTests);
+
+ content.location = TESTCASE_URI;
+}
+
+function runTests(panel)
+{
+ gUI = panel.UI;
+ gUI.editors[0].getSourceEditor().then(onEditor0Attach);
+ gUI.editors[2].getSourceEditor().then(onEditor2Attach);
+}
+
+function getStylesheetNameLinkFor(aEditor)
+{
+ return aEditor.summary.querySelector(".stylesheet-name");
+}
+
+function onEditor0Attach(aEditor)
+{
+ waitForFocus(function () {
+ let summary = aEditor.summary;
+ EventUtils.synthesizeMouseAtCenter(summary, {}, gPanelWindow);
+
+ let item = getStylesheetNameLinkFor(gUI.editors[0]);
+ is(gPanelWindow.document.activeElement, item,
+ "editor 0 item is the active element");
+
+ EventUtils.synthesizeKey("VK_DOWN", {}, gPanelWindow);
+ item = getStylesheetNameLinkFor(gUI.editors[1]);
+ is(gPanelWindow.document.activeElement, item,
+ "editor 1 item is the active element");
+
+ EventUtils.synthesizeKey("VK_HOME", {}, gPanelWindow);
+ item = getStylesheetNameLinkFor(gUI.editors[0]);
+ is(gPanelWindow.document.activeElement, item,
+ "fist editor item is the active element");
+
+ EventUtils.synthesizeKey("VK_END", {}, gPanelWindow);
+ item = getStylesheetNameLinkFor(gUI.editors[3]);
+ is(gPanelWindow.document.activeElement, item,
+ "last editor item is the active element");
+
+ EventUtils.synthesizeKey("VK_UP", {}, gPanelWindow);
+ item = getStylesheetNameLinkFor(gUI.editors[2]);
+ is(gPanelWindow.document.activeElement, item,
+ "editor 2 item is the active element");
+
+ EventUtils.synthesizeKey("VK_RETURN", {}, gPanelWindow);
+ // this will attach and give focus editor 2
+ }, gPanelWindow);
+}
+
+function onEditor2Attach(aEditor)
+{
+ // Wait for the focus to be set.
+ executeSoon(function () {
+ ok(aEditor.sourceEditor.hasFocus(),
+ "editor 2 has focus");
+
+ gUI = null;
+ finish();
+ });
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_sv_resize.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_sv_resize.js
new file mode 100644
index 000000000..0d25da293
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_sv_resize.js
@@ -0,0 +1,55 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE + "simple.html";
+
+let gOriginalWidth; // these are set by runTests()
+let gOriginalHeight;
+
+function test()
+{
+ waitForExplicitFinish();
+
+ addTabAndOpenStyleEditors(2, panel => runTests(panel.UI));
+
+ content.location = TESTCASE_URI;
+}
+
+function runTests(aUI)
+{
+ is(aUI.editors.length, 2,
+ "there is 2 stylesheets initially");
+
+ aUI.editors[0].getSourceEditor().then(aEditor => {
+ executeSoon(function () {
+ waitForFocus(function () {
+ // queue a resize to inverse aspect ratio
+ // this will trigger a detach and reattach (to workaround bug 254144)
+ let originalSourceEditor = aEditor.sourceEditor;
+ let editor = aEditor.sourceEditor;
+ editor.setCursor(editor.getPosition(4)); // to check the caret is preserved
+
+ gOriginalWidth = gPanelWindow.outerWidth;
+ gOriginalHeight = gPanelWindow.outerHeight;
+ gPanelWindow.resizeTo(120, 480);
+
+ executeSoon(function () {
+ is(aEditor.sourceEditor, originalSourceEditor,
+ "the editor still references the same Editor instance");
+ let editor = aEditor.sourceEditor;
+ is(editor.getOffset(editor.getCursor()), 4,
+ "the caret position has been preserved");
+
+ // queue a resize to original aspect ratio
+ waitForFocus(function () {
+ gPanelWindow.resizeTo(gOriginalWidth, gOriginalHeight);
+ executeSoon(function () {
+ finish();
+ });
+ }, gPanelWindow);
+ });
+ }, gPanelWindow);
+ });
+ });
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_transition_rule.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_transition_rule.js
new file mode 100644
index 000000000..66f41d7a1
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_transition_rule.js
@@ -0,0 +1,48 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
+
+waitForExplicitFinish();
+
+const NEW_RULE = "body { background-color: purple; }";
+
+add_task(function*() {
+ let {UI} = yield addTabAndOpenStyleEditors(2, null, TESTCASE_URI);
+
+ is(UI.editors.length, 2, "correct number of editors");
+
+ let editor = UI.editors[0];
+ yield openEditor(editor);
+
+ // Set text twice in a row
+ let styleChanges = listenForStyleChange(editor.styleSheet);
+
+ editor.sourceEditor.setText(NEW_RULE);
+ editor.sourceEditor.setText(NEW_RULE + " ");
+
+ yield styleChanges;
+
+ let sheet = content.document.styleSheets[0];
+
+ // Test that we removed the transition rule, but kept the rule we added
+ is(sheet.cssRules.length, 1, "only one rule in stylesheet");
+ is(sheet.cssRules[0].cssText, NEW_RULE,
+ "stylesheet only contains rule we added");
+});
+
+/* Helpers */
+
+function openEditor(editor) {
+ let link = editor.summary.querySelector(".stylesheet-name");
+ link.click();
+
+ return editor.getSourceEditor();
+}
+
+function listenForStyleChange(sheet) {
+ let deferred = promise.defer();
+ sheet.on("style-applied", deferred.resolve);
+ return deferred.promise;
+}
diff --git a/toolkit/devtools/styleeditor/test/browser_styleeditor_xul.js b/toolkit/devtools/styleeditor/test/browser_styleeditor_xul.js
new file mode 100644
index 000000000..70887f6a1
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/browser_styleeditor_xul.js
@@ -0,0 +1,20 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the style-editor initializes correctly for XUL windows.
+
+waitForExplicitFinish();
+
+const TEST_URL = TEST_BASE + "doc_xulpage.xul";
+
+add_task(function*() {
+ let tab = yield addTab(TEST_URL);
+ let target = TargetFactory.forTab(tab);
+
+ let toolbox = yield gDevTools.showToolbox(target, "styleeditor");
+ let panel = toolbox.getCurrentPanel();
+ yield panel.UI.once("editor-added");
+
+ ok(panel, "The style-editor panel did initialize correctly for the XUL window");
+});
diff --git a/toolkit/devtools/styleeditor/test/doc_uncached.css b/toolkit/devtools/styleeditor/test/doc_uncached.css
new file mode 100644
index 000000000..492256c93
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/doc_uncached.css
@@ -0,0 +1,16 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/toolkit/devtools/styleeditor/test/doc_uncached.html b/toolkit/devtools/styleeditor/test/doc_uncached.html
new file mode 100644
index 000000000..bd83d1db6
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/doc_uncached.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<html>
+<head>
+ <title>uncached testcase</title>
+ <link rel="stylesheet" charset="UTF-8" type="text/css" media="screen" href="doc_uncached.css"/>
+</head>
+<body>
+ <div>uncached <span>testcase</span></div>
+</body>
+</html>
diff --git a/toolkit/devtools/styleeditor/test/doc_xulpage.xul b/toolkit/devtools/styleeditor/test/doc_xulpage.xul
new file mode 100644
index 000000000..155be25ec
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/doc_xulpage.xul
@@ -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> \ No newline at end of file
diff --git a/toolkit/devtools/styleeditor/test/four.html b/toolkit/devtools/styleeditor/test/four.html
new file mode 100644
index 000000000..c0d51d691
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/test/head.js b/toolkit/devtools/styleeditor/test/head.js
new file mode 100644
index 000000000..d174df44e
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/head.js
@@ -0,0 +1,152 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_BASE = "chrome://mochitests/content/browser/browser/devtools/styleeditor/test/";
+const TEST_BASE_HTTP = "http://example.com/browser/browser/devtools/styleeditor/test/";
+const TEST_BASE_HTTPS = "https://example.com/browser/browser/devtools/styleeditor/test/";
+const TEST_HOST = 'mochi.test:8888';
+
+let {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+let TargetFactory = devtools.TargetFactory;
+let {LoadContextInfo} = Cu.import("resource://gre/modules/LoadContextInfo.jsm", {});
+let {console} = Cu.import("resource://gre/modules/devtools/Console.jsm", {});
+let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+
+let gPanelWindow;
+let cache = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
+ .getService(Ci.nsICacheStorageService);
+
+
+// Import the GCLI test helper
+let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
+Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this);
+
+gDevTools.testing = true;
+SimpleTest.registerCleanupFunction(() => {
+ gDevTools.testing = false;
+});
+
+/**
+ * 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
+ * @return a promise that resolves to the tab object when the url is loaded
+ */
+function addTab(url) {
+ info("Adding a new tab with URL: '" + url + "'");
+ let def = promise.defer();
+
+ let tab = gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function onload() {
+ gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+ info("URL '" + url + "' loading complete");
+ def.resolve(tab);
+ }, true);
+ content.location = url;
+
+ return def.promise;
+}
+
+/**
+ * Navigate the currently selected tab to a new URL and wait for it to load.
+ * @param {String} url The url to be loaded in the current tab.
+ * @return a promise that resolves when the page has fully loaded.
+ */
+function navigateTo(url) {
+ let navigating = promise.defer();
+ gBrowser.selectedBrowser.addEventListener("load", function onload() {
+ gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+ navigating.resolve();
+ }, true);
+ content.location = url;
+ return navigating.promise;
+}
+
+function* cleanup()
+{
+ gPanelWindow = null;
+ while (gBrowser.tabs.length > 1) {
+ let target = TargetFactory.forTab(gBrowser.selectedTab);
+ yield gDevTools.closeToolbox(target);
+
+ gBrowser.removeCurrentTab();
+ }
+}
+
+function addTabAndOpenStyleEditors(count, callback, uri) {
+ let deferred = promise.defer();
+ let currentCount = 0;
+ let panel;
+ addTabAndCheckOnStyleEditorAdded(p => panel = p, function (editor) {
+ currentCount++;
+ info(currentCount + " of " + count + " editors opened: "
+ + editor.styleSheet.href);
+ if (currentCount == count) {
+ if (callback) {
+ callback(panel);
+ }
+ deferred.resolve(panel);
+ }
+ });
+
+ if (uri) {
+ content.location = uri;
+ }
+ return deferred.promise;
+}
+
+function addTabAndCheckOnStyleEditorAdded(callbackOnce, callbackOnAdded) {
+ gBrowser.selectedTab = gBrowser.addTab();
+ gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
+ gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
+ openStyleEditorInWindow(window, function (panel) {
+ // Execute the individual callback with the panel argument.
+ callbackOnce(panel);
+ // Report editors that already opened while loading.
+ for (let editor of panel.UI.editors) {
+ callbackOnAdded(editor);
+ }
+ // Report new editors added afterwards.
+ panel.UI.on("editor-added", (event, editor) => callbackOnAdded(editor));
+ });
+ }, true);
+}
+
+function openStyleEditorInWindow(win, callback) {
+ let target = TargetFactory.forTab(win.gBrowser.selectedTab);
+ win.gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
+ let panel = toolbox.getCurrentPanel();
+ gPanelWindow = panel._panelWin;
+
+ panel.UI._alwaysDisableAnimations = true;
+ callback(panel);
+ });
+}
+
+function checkDiskCacheFor(host, done)
+{
+ let foundPrivateData = false;
+
+ Visitor.prototype = {
+ onCacheStorageInfo: function(num, consumption)
+ {
+ info("disk storage contains " + num + " entries");
+ },
+ onCacheEntryInfo: function(uri)
+ {
+ var urispec = uri.asciiSpec;
+ info(urispec);
+ foundPrivateData |= urispec.contains(host);
+ },
+ onCacheEntryVisitCompleted: function()
+ {
+ is(foundPrivateData, false, "web content present in disk cache");
+ done();
+ }
+ };
+ function Visitor() {}
+
+ var storage = cache.diskCacheStorage(LoadContextInfo.default, false);
+ storage.asyncVisitStorage(new Visitor(), true /* Do walk entries */);
+}
+
+registerCleanupFunction(cleanup);
diff --git a/toolkit/devtools/styleeditor/test/import.css b/toolkit/devtools/styleeditor/test/import.css
new file mode 100644
index 000000000..df532fb96
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/import.css
@@ -0,0 +1,10 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/toolkit/devtools/styleeditor/test/import.html b/toolkit/devtools/styleeditor/test/import.html
new file mode 100644
index 000000000..bc92baeba
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/test/import2.css b/toolkit/devtools/styleeditor/test/import2.css
new file mode 100644
index 000000000..fbbe14d9a
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/import2.css
@@ -0,0 +1,10 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/toolkit/devtools/styleeditor/test/inline-1.html b/toolkit/devtools/styleeditor/test/inline-1.html
new file mode 100644
index 000000000..76478893b
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/test/inline-2.html b/toolkit/devtools/styleeditor/test/inline-2.html
new file mode 100644
index 000000000..e25285c31
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/test/longload.html b/toolkit/devtools/styleeditor/test/longload.html
new file mode 100644
index 000000000..8e58daeb7
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/longload.html
@@ -0,0 +1,28 @@
+<!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>
+ for (i = 0; i < 5000; i++) {
+ document.write("<br>...");
+ }
+ </script>
+</body>
+</html>
diff --git a/toolkit/devtools/styleeditor/test/media-rules-sourcemaps.html b/toolkit/devtools/styleeditor/test/media-rules-sourcemaps.html
new file mode 100644
index 000000000..4876ef795
--- /dev/null
+++ b/toolkit/devtools/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> \ No newline at end of file
diff --git a/toolkit/devtools/styleeditor/test/media-rules.css b/toolkit/devtools/styleeditor/test/media-rules.css
new file mode 100644
index 000000000..b4db3f216
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/media-rules.css
@@ -0,0 +1,23 @@
+@media not all {
+ div {
+ color: blue;
+ }
+}
+
+@media all {
+ div {
+ color: red;
+ }
+}
+
+div {
+ width: 20px;
+ height: 20px;
+ background-color: ghostwhite;
+}
+
+@media (max-width: 400px) {
+ div {
+ color: green;
+ }
+}
diff --git a/toolkit/devtools/styleeditor/test/media-rules.html b/toolkit/devtools/styleeditor/test/media-rules.html
new file mode 100644
index 000000000..edc45ccae
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/media-rules.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="UTF-8">
+ <link rel="stylesheet" href="simple.css"/>
+ <link rel="stylesheet" href="media-rules.css"/>
+</head>
+<body>
+ <div>
+ Testing style editor media sidebar
+ </div>
+</body>
+</html> \ No newline at end of file
diff --git a/toolkit/devtools/styleeditor/test/media-small.css b/toolkit/devtools/styleeditor/test/media-small.css
new file mode 100644
index 000000000..d64756ddc
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/media-small.css
@@ -0,0 +1,5 @@
+/* this stylesheet applies when min-width<400px */
+body {
+ background: red;
+}
+
diff --git a/toolkit/devtools/styleeditor/test/media.html b/toolkit/devtools/styleeditor/test/media.html
new file mode 100644
index 000000000..ef05818c5
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/media.html
@@ -0,0 +1,11 @@
+<!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/toolkit/devtools/styleeditor/test/minified.html b/toolkit/devtools/styleeditor/test/minified.html
new file mode 100644
index 000000000..ab8c67d25
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/test/nostyle.html b/toolkit/devtools/styleeditor/test/nostyle.html
new file mode 100644
index 000000000..f6a6769e6
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/nostyle.html
@@ -0,0 +1,5 @@
+<html>
+ <div>
+ Page with no stylesheets
+ </div>
+</html> \ No newline at end of file
diff --git a/toolkit/devtools/styleeditor/test/pretty.css b/toolkit/devtools/styleeditor/test/pretty.css
new file mode 100644
index 000000000..e597afa4b
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/pretty.css
@@ -0,0 +1,2 @@
+
+body{background:white;}div{font-size:4em;color:red}span{color:green;}
diff --git a/toolkit/devtools/styleeditor/test/resources_inpage.jsi b/toolkit/devtools/styleeditor/test/resources_inpage.jsi
new file mode 100644
index 000000000..8b7895af5
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/test/resources_inpage1.css b/toolkit/devtools/styleeditor/test/resources_inpage1.css
new file mode 100644
index 000000000..644deaaea
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/test/resources_inpage2.css b/toolkit/devtools/styleeditor/test/resources_inpage2.css
new file mode 100644
index 000000000..e4fa48e53
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/test/simple.css b/toolkit/devtools/styleeditor/test/simple.css
new file mode 100644
index 000000000..829fe9e6c
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/simple.css
@@ -0,0 +1,9 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+/* ☺ */
+
+body {
+ margin: 0;
+}
+
diff --git a/toolkit/devtools/styleeditor/test/simple.css.gz b/toolkit/devtools/styleeditor/test/simple.css.gz
new file mode 100644
index 000000000..ee3b9efbc
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/simple.css.gz
Binary files differ
diff --git a/toolkit/devtools/styleeditor/test/simple.css.gz^headers^ b/toolkit/devtools/styleeditor/test/simple.css.gz^headers^
new file mode 100644
index 000000000..092020ab0
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/simple.css.gz^headers^
@@ -0,0 +1,4 @@
+Vary: Accept-Encoding
+Content-Encoding: gzip
+Content-Type: text/css
+
diff --git a/toolkit/devtools/styleeditor/test/simple.gz.html b/toolkit/devtools/styleeditor/test/simple.gz.html
new file mode 100644
index 000000000..d63362b8e
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/test/simple.html b/toolkit/devtools/styleeditor/test/simple.html
new file mode 100644
index 000000000..2e7ce3eac
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/simple.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html>
+<head>
+ <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/toolkit/devtools/styleeditor/test/sourcemap-css/contained.css b/toolkit/devtools/styleeditor/test/sourcemap-css/contained.css
new file mode 100644
index 000000000..79572f606
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/test/sourcemap-css/media-rules.css b/toolkit/devtools/styleeditor/test/sourcemap-css/media-rules.css
new file mode 100644
index 000000000..fad540a96
--- /dev/null
+++ b/toolkit/devtools/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 */ \ No newline at end of file
diff --git a/toolkit/devtools/styleeditor/test/sourcemap-css/media-rules.css.map b/toolkit/devtools/styleeditor/test/sourcemap-css/media-rules.css.map
new file mode 100644
index 000000000..76cd48fe2
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/test/sourcemap-css/sourcemaps.css b/toolkit/devtools/styleeditor/test/sourcemap-css/sourcemaps.css
new file mode 100644
index 000000000..7246a9082
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/test/sourcemap-css/sourcemaps.css.map b/toolkit/devtools/styleeditor/test/sourcemap-css/sourcemaps.css.map
new file mode 100644
index 000000000..2e8f2911c
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/test/sourcemap-css/test-bootstrap-scss.css b/toolkit/devtools/styleeditor/test/sourcemap-css/test-bootstrap-scss.css
new file mode 100644
index 000000000..1a5aaff1b
--- /dev/null
+++ b/toolkit/devtools/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::-moz-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 */ \ No newline at end of file
diff --git a/toolkit/devtools/styleeditor/test/sourcemap-css/test-stylus.css b/toolkit/devtools/styleeditor/test/sourcemap-css/test-stylus.css
new file mode 100644
index 000000000..0ec51da3b
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/test/sourcemap-sass/media-rules.scss b/toolkit/devtools/styleeditor/test/sourcemap-sass/media-rules.scss
new file mode 100644
index 000000000..4f1c8f216
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/test/sourcemap-sass/sourcemaps.scss b/toolkit/devtools/styleeditor/test/sourcemap-sass/sourcemaps.scss
new file mode 100644
index 000000000..0ff6c471b
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/test/sourcemap-styl/test-stylus.styl b/toolkit/devtools/styleeditor/test/sourcemap-styl/test-stylus.styl
new file mode 100644
index 000000000..76ff25c29
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/test/sourcemaps-large.html b/toolkit/devtools/styleeditor/test/sourcemaps-large.html
new file mode 100644
index 000000000..b8c92e0c9
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/test/sourcemaps-watching.html b/toolkit/devtools/styleeditor/test/sourcemaps-watching.html
new file mode 100644
index 000000000..fc9909ea5
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/test/sourcemaps.html b/toolkit/devtools/styleeditor/test/sourcemaps.html
new file mode 100644
index 000000000..887e0ed98
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/test/test_private.css b/toolkit/devtools/styleeditor/test/test_private.css
new file mode 100644
index 000000000..438954d36
--- /dev/null
+++ b/toolkit/devtools/styleeditor/test/test_private.css
@@ -0,0 +1,3 @@
+body {
+ background-color: red;
+} \ No newline at end of file
diff --git a/toolkit/devtools/styleeditor/test/test_private.html b/toolkit/devtools/styleeditor/test/test_private.html
new file mode 100644
index 000000000..bfde3520e
--- /dev/null
+++ b/toolkit/devtools/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/toolkit/devtools/styleeditor/utils.js b/toolkit/devtools/styleeditor/utils.js
new file mode 100644
index 000000000..e90e252d8
--- /dev/null
+++ b/toolkit/devtools/styleeditor/utils.js
@@ -0,0 +1,40 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const {Cc, Ci, Cu, Cr} = require("chrome");
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/devtools/event-emitter.js");
+
+exports.PREF_ORIG_SOURCES = "devtools.styleeditor.source-maps-enabled";
+
+/**
+ * A PreferenceObserver observes a pref branch for pref changes.
+ * It emits an event for each preference change.
+ */
+function PrefObserver(branchName) {
+ this.branchName = branchName;
+ this.branch = Services.prefs.getBranch(branchName);
+ this.branch.addObserver("", this, false);
+
+ EventEmitter.decorate(this);
+}
+
+exports.PrefObserver = PrefObserver;
+
+PrefObserver.prototype = {
+ observe: function(subject, topic, data) {
+ if (topic == "nsPref:changed") {
+ this.emit(this.branchName + data);
+ }
+ },
+
+ destroy: function() {
+ if (this.branch) {
+ this.branch.removeObserver('', this);
+ }
+ }
+}; \ No newline at end of file