diff options
author | Matt A. Tobin <email@mattatobin.com> | 2016-10-16 19:34:53 -0400 |
---|---|---|
committer | Matt A. Tobin <email@mattatobin.com> | 2016-10-16 19:34:53 -0400 |
commit | 81805ce3f63e2e4a799bd54f174083c58a9b5640 (patch) | |
tree | 6e13374b213ac9b2ae74c25d8aac875faf71fdd0 /toolkit/devtools/styleeditor | |
parent | 28c8da71bf521bb3ee76f27b8a241919e24b7cd5 (diff) | |
download | palemoon-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')
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><link ...></code> + &csscoverage.optimize.body2; + <code><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><style> +<loop foreach="rule in ${page.rules}" + onclick="${rule.onclick}">${rule.formattedCssText}</loop></style></textarea> + </div> + </div> + <p> + &csscoverage.footer1; + <a target="_blank" href="&csscoverage.footer2a;">&csscoverage.footer3;</a> + &csscoverage.footer4; + </p> + </div> + <p> </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 Binary files differnew file mode 100644 index 000000000..ee3b9efbc --- /dev/null +++ b/toolkit/devtools/styleeditor/test/simple.css.gz 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 |