зеркало из https://github.com/mozilla/gecko-dev.git
Bug 816967 - Remotable Style Editor; r=dcamp
This commit is contained in:
Родитель
fed38bcd06
Коммит
4c21257368
|
@ -143,7 +143,7 @@ let styleEditorDefinition = {
|
||||||
tooltip: l10n("ToolboxStyleEditor.tooltip", styleEditorStrings),
|
tooltip: l10n("ToolboxStyleEditor.tooltip", styleEditorStrings),
|
||||||
|
|
||||||
isTargetSupported: function(target) {
|
isTargetSupported: function(target) {
|
||||||
return !target.isRemote;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
build: function(iframeWindow, toolbox) {
|
build: function(iframeWindow, toolbox) {
|
||||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1,627 +0,0 @@
|
||||||
/* 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 = ["StyleEditorChrome"];
|
|
||||||
|
|
||||||
const Cc = Components.classes;
|
|
||||||
const Ci = Components.interfaces;
|
|
||||||
const Cu = Components.utils;
|
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/Services.jsm");
|
|
||||||
Cu.import("resource://gre/modules/PluralForm.jsm");
|
|
||||||
Cu.import("resource:///modules/devtools/StyleEditor.jsm");
|
|
||||||
Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm");
|
|
||||||
Cu.import("resource:///modules/devtools/SplitView.jsm");
|
|
||||||
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
|
||||||
|
|
||||||
const STYLE_EDITOR_TEMPLATE = "stylesheet";
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* StyleEditorChrome constructor.
|
|
||||||
*
|
|
||||||
* The 'chrome' of the Style Editor is all the around the actual editor (textbox).
|
|
||||||
* Manages the sheet selector, history, and opened editor(s) for the attached
|
|
||||||
* content window.
|
|
||||||
*
|
|
||||||
* @param DOMElement aRoot
|
|
||||||
* Element that owns the chrome UI.
|
|
||||||
* @param DOMWindow aContentWindow
|
|
||||||
* Content DOMWindow to attach to this chrome.
|
|
||||||
*/
|
|
||||||
this.StyleEditorChrome = function StyleEditorChrome(aRoot, aContentWindow)
|
|
||||||
{
|
|
||||||
assert(aRoot, "Argument 'aRoot' is required to initialize StyleEditorChrome.");
|
|
||||||
|
|
||||||
this._root = aRoot;
|
|
||||||
this._document = this._root.ownerDocument;
|
|
||||||
this._window = this._document.defaultView;
|
|
||||||
|
|
||||||
this._editors = [];
|
|
||||||
this._listeners = []; // @see addChromeListener
|
|
||||||
|
|
||||||
// Store the content window so that we can call the real contentWindow setter
|
|
||||||
// in the open method.
|
|
||||||
this._contentWindowTemp = aContentWindow;
|
|
||||||
|
|
||||||
this._contentWindow = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
StyleEditorChrome.prototype = {
|
|
||||||
_styleSheetToSelect: null,
|
|
||||||
|
|
||||||
open: function() {
|
|
||||||
let deferred = Promise.defer();
|
|
||||||
let initializeUI = function (aEvent) {
|
|
||||||
if (aEvent) {
|
|
||||||
this._window.removeEventListener("load", initializeUI, false);
|
|
||||||
}
|
|
||||||
let viewRoot = this._root.parentNode.querySelector(".splitview-root");
|
|
||||||
this._view = new SplitView(viewRoot);
|
|
||||||
this._setupChrome();
|
|
||||||
|
|
||||||
// We need to juggle arount the contentWindow items because we need to
|
|
||||||
// trigger the setter at the appropriate time.
|
|
||||||
this.contentWindow = this._contentWindowTemp; // calls setter
|
|
||||||
this._contentWindowTemp = null;
|
|
||||||
|
|
||||||
deferred.resolve();
|
|
||||||
}.bind(this);
|
|
||||||
|
|
||||||
if (this._document.readyState == "complete") {
|
|
||||||
initializeUI();
|
|
||||||
} else {
|
|
||||||
this._window.addEventListener("load", initializeUI, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the content window attached to this chrome.
|
|
||||||
*
|
|
||||||
* @return DOMWindow
|
|
||||||
* Content window or null if no content window is attached.
|
|
||||||
*/
|
|
||||||
get contentWindow() this._contentWindow,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the ID of the content window attached to this chrome.
|
|
||||||
*
|
|
||||||
* @return number
|
|
||||||
* Window ID or -1 if no content window is attached.
|
|
||||||
*/
|
|
||||||
get contentWindowID()
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
return this._contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).
|
|
||||||
getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
|
|
||||||
} catch (ex) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the content window attached to this chrome.
|
|
||||||
* Content attach or detach events/notifications are triggered after the
|
|
||||||
* operation is complete (possibly asynchronous if the content is not fully
|
|
||||||
* loaded yet).
|
|
||||||
*
|
|
||||||
* @param DOMWindow aContentWindow
|
|
||||||
* @see addChromeListener
|
|
||||||
*/
|
|
||||||
set contentWindow(aContentWindow)
|
|
||||||
{
|
|
||||||
if (this._contentWindow == aContentWindow) {
|
|
||||||
return; // no change
|
|
||||||
}
|
|
||||||
|
|
||||||
this._contentWindow = aContentWindow;
|
|
||||||
|
|
||||||
if (!aContentWindow) {
|
|
||||||
this._disableChrome();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let onContentUnload = function () {
|
|
||||||
aContentWindow.removeEventListener("unload", onContentUnload, false);
|
|
||||||
if (this.contentWindow == aContentWindow) {
|
|
||||||
this.contentWindow = null; // detach
|
|
||||||
}
|
|
||||||
}.bind(this);
|
|
||||||
aContentWindow.addEventListener("unload", onContentUnload, false);
|
|
||||||
|
|
||||||
if (aContentWindow.document.readyState == "complete") {
|
|
||||||
this._root.classList.remove("loading");
|
|
||||||
this._populateChrome();
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
this._root.classList.add("loading");
|
|
||||||
let onContentReady = function () {
|
|
||||||
aContentWindow.removeEventListener("load", onContentReady, false);
|
|
||||||
this._root.classList.remove("loading");
|
|
||||||
this._populateChrome();
|
|
||||||
}.bind(this);
|
|
||||||
aContentWindow.addEventListener("load", onContentReady, false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the content document attached to this chrome.
|
|
||||||
*
|
|
||||||
* @return DOMDocument
|
|
||||||
*/
|
|
||||||
get contentDocument()
|
|
||||||
{
|
|
||||||
return this._contentWindow ? this._contentWindow.document : null;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve an array with the StyleEditor instance for each live style sheet,
|
|
||||||
* ordered by style sheet index.
|
|
||||||
*
|
|
||||||
* @return Array<StyleEditor>
|
|
||||||
*/
|
|
||||||
get editors()
|
|
||||||
{
|
|
||||||
let editors = [];
|
|
||||||
this._editors.forEach(function (aEditor) {
|
|
||||||
if (aEditor.styleSheetIndex >= 0) {
|
|
||||||
editors[aEditor.styleSheetIndex] = aEditor;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return editors;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get whether any of the editors have unsaved changes.
|
|
||||||
*
|
|
||||||
* @return boolean
|
|
||||||
*/
|
|
||||||
get isDirty()
|
|
||||||
{
|
|
||||||
if (this._markedDirty === true) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return this.editors.some(function(editor) {
|
|
||||||
return editor.sourceEditor && editor.sourceEditor.dirty;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Mark the style editor as having unsaved changes.
|
|
||||||
*/
|
|
||||||
markDirty: function SEC_markDirty() {
|
|
||||||
this._markedDirty = true;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a listener for StyleEditorChrome events.
|
|
||||||
*
|
|
||||||
* The listener implements IStyleEditorChromeListener := {
|
|
||||||
* onContentDetach: Called when the content window has been detached.
|
|
||||||
* Arguments: (StyleEditorChrome aChrome)
|
|
||||||
* @see contentWindow
|
|
||||||
*
|
|
||||||
* onEditorAdded: Called when a stylesheet (therefore a StyleEditor
|
|
||||||
* instance) has been added to the UI.
|
|
||||||
* Arguments (StyleEditorChrome aChrome,
|
|
||||||
* StyleEditor aEditor)
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* All listener methods are optional.
|
|
||||||
*
|
|
||||||
* @param IStyleEditorChromeListener aListener
|
|
||||||
* @see removeChromeListener
|
|
||||||
*/
|
|
||||||
addChromeListener: function SEC_addChromeListener(aListener)
|
|
||||||
{
|
|
||||||
this._listeners.push(aListener);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove a listener for Chrome events from the current list of listeners.
|
|
||||||
*
|
|
||||||
* @param IStyleEditorChromeListener aListener
|
|
||||||
* @see addChromeListener
|
|
||||||
*/
|
|
||||||
removeChromeListener: function SEC_removeChromeListener(aListener)
|
|
||||||
{
|
|
||||||
let index = this._listeners.indexOf(aListener);
|
|
||||||
if (index != -1) {
|
|
||||||
this._listeners.splice(index, 1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Trigger named handlers in StyleEditorChrome listeners.
|
|
||||||
*
|
|
||||||
* @param string aName
|
|
||||||
* Name of the event to trigger.
|
|
||||||
* @param Array aArgs
|
|
||||||
* Optional array of arguments to pass to the listener(s).
|
|
||||||
* @see addActionListener
|
|
||||||
*/
|
|
||||||
_triggerChromeListeners: function SE__triggerChromeListeners(aName, aArgs)
|
|
||||||
{
|
|
||||||
// insert the origin Chrome instance as first argument
|
|
||||||
if (!aArgs) {
|
|
||||||
aArgs = [this];
|
|
||||||
} else {
|
|
||||||
aArgs.unshift(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
// copy the list of listeners to allow adding/removing listeners in handlers
|
|
||||||
let listeners = this._listeners.concat();
|
|
||||||
// trigger all listeners that have this named handler.
|
|
||||||
for (let i = 0; i < listeners.length; i++) {
|
|
||||||
let listener = listeners[i];
|
|
||||||
let handler = listener["on" + aName];
|
|
||||||
if (handler) {
|
|
||||||
handler.apply(listener, aArgs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new style editor, add to the list of editors, and bind this
|
|
||||||
* object as an action listener.
|
|
||||||
* @param DOMDocument aDocument
|
|
||||||
* The document that the stylesheet is being referenced in.
|
|
||||||
* @param CSSStyleSheet aSheet
|
|
||||||
* Optional stylesheet to edit from the document.
|
|
||||||
* @return StyleEditor
|
|
||||||
*/
|
|
||||||
_createStyleEditor: function SEC__createStyleEditor(aDocument, aSheet) {
|
|
||||||
let editor = new StyleEditor(aDocument, aSheet);
|
|
||||||
this._editors.push(editor);
|
|
||||||
editor.addActionListener(this);
|
|
||||||
return editor;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set up the chrome UI. Install event listeners and so on.
|
|
||||||
*/
|
|
||||||
_setupChrome: function SEC__setupChrome()
|
|
||||||
{
|
|
||||||
// wire up UI elements
|
|
||||||
wire(this._view.rootElement, ".style-editor-newButton", function onNewButton() {
|
|
||||||
let editor = this._createStyleEditor(this.contentDocument);
|
|
||||||
editor.load();
|
|
||||||
}.bind(this));
|
|
||||||
|
|
||||||
wire(this._view.rootElement, ".style-editor-importButton", function onImportButton() {
|
|
||||||
let editor = this._createStyleEditor(this.contentDocument);
|
|
||||||
editor.importFromFile(this._mockImportFile || null, this._window);
|
|
||||||
}.bind(this));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset the chrome UI to an empty and ready state.
|
|
||||||
*/
|
|
||||||
resetChrome: function SEC__resetChrome()
|
|
||||||
{
|
|
||||||
this._editors.forEach(function (aEditor) {
|
|
||||||
aEditor.removeActionListener(this);
|
|
||||||
}.bind(this));
|
|
||||||
this._editors = [];
|
|
||||||
|
|
||||||
this._view.removeAll();
|
|
||||||
|
|
||||||
// (re)enable UI
|
|
||||||
let matches = this._root.querySelectorAll("toolbarbutton,input,select");
|
|
||||||
for (let i = 0; i < matches.length; i++) {
|
|
||||||
matches[i].removeAttribute("disabled");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add all imported stylesheets to chrome UI, recursively
|
|
||||||
*
|
|
||||||
* @param CSSStyleSheet aSheet
|
|
||||||
* A stylesheet we're going to browse to look for all imported sheets.
|
|
||||||
*/
|
|
||||||
_showImportedStyleSheets: function SEC__showImportedStyleSheets(aSheet)
|
|
||||||
{
|
|
||||||
let document = this.contentDocument;
|
|
||||||
for (let j = 0; j < aSheet.cssRules.length; j++) {
|
|
||||||
let rule = aSheet.cssRules.item(j);
|
|
||||||
if (rule.type == Ci.nsIDOMCSSRule.IMPORT_RULE) {
|
|
||||||
// Associated styleSheet may be null if it has already been seen due to
|
|
||||||
// duplicate @imports for the same URL.
|
|
||||||
if (!rule.styleSheet) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._createStyleEditor(document, rule.styleSheet);
|
|
||||||
|
|
||||||
this._showImportedStyleSheets(rule.styleSheet);
|
|
||||||
} else if (rule.type != Ci.nsIDOMCSSRule.CHARSET_RULE) {
|
|
||||||
// @import rules must precede all others except @charset
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populate the chrome UI according to the content document.
|
|
||||||
*
|
|
||||||
* @see StyleEditor._setupShadowStyleSheet
|
|
||||||
*/
|
|
||||||
_populateChrome: function SEC__populateChrome()
|
|
||||||
{
|
|
||||||
this.resetChrome();
|
|
||||||
|
|
||||||
let document = this.contentDocument;
|
|
||||||
this._document.title = _("chromeWindowTitle",
|
|
||||||
document.title || document.location.href);
|
|
||||||
|
|
||||||
for (let i = 0; i < document.styleSheets.length; i++) {
|
|
||||||
let styleSheet = document.styleSheets[i];
|
|
||||||
|
|
||||||
this._createStyleEditor(document, styleSheet);
|
|
||||||
|
|
||||||
this._showImportedStyleSheets(styleSheet);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Queue editors loading so that ContentAttach is consistently triggered
|
|
||||||
// right after all editor instances are available (this.editors) but are
|
|
||||||
// NOT loaded/ready yet. This also helps responsivity during loading when
|
|
||||||
// there are many heavy stylesheets.
|
|
||||||
this._editors.forEach(function (aEditor) {
|
|
||||||
this._window.setTimeout(aEditor.load.bind(aEditor), 0);
|
|
||||||
}, this);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* selects a stylesheet and optionally moves the cursor to a selected line
|
|
||||||
*
|
|
||||||
* @param {CSSStyleSheet} [aSheet]
|
|
||||||
* Stylesheet that should be selected. If a stylesheet is not passed
|
|
||||||
* and the editor is not initialized we focus the first stylesheet. If
|
|
||||||
* a stylesheet is not passed and the editor is initialized we ignore
|
|
||||||
* the call.
|
|
||||||
* @param {Number} [aLine]
|
|
||||||
* Line to which the caret should be moved (one-indexed).
|
|
||||||
* @param {Number} [aCol]
|
|
||||||
* Column to which the caret should be moved (one-indexed).
|
|
||||||
*/
|
|
||||||
selectStyleSheet: function SEC_selectSheet(aSheet, aLine, aCol)
|
|
||||||
{
|
|
||||||
let alreadyCalled = !!this._styleSheetToSelect;
|
|
||||||
|
|
||||||
this._styleSheetToSelect = {
|
|
||||||
sheet: aSheet,
|
|
||||||
line: aLine,
|
|
||||||
col: aCol,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (alreadyCalled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let select = function DEC_select(aEditor) {
|
|
||||||
let sheet = this._styleSheetToSelect.sheet;
|
|
||||||
let line = this._styleSheetToSelect.line || 1;
|
|
||||||
let col = this._styleSheetToSelect.col || 1;
|
|
||||||
|
|
||||||
if (!aEditor.sourceEditor) {
|
|
||||||
let onAttach = function SEC_selectSheet_onAttach() {
|
|
||||||
aEditor.removeActionListener(this);
|
|
||||||
this.selectedStyleSheetIndex = aEditor.styleSheetIndex;
|
|
||||||
aEditor.sourceEditor.setCaretPosition(line - 1, col - 1);
|
|
||||||
|
|
||||||
let newSheet = this._styleSheetToSelect.sheet;
|
|
||||||
let newLine = this._styleSheetToSelect.line;
|
|
||||||
let newCol = this._styleSheetToSelect.col;
|
|
||||||
this._styleSheetToSelect = null;
|
|
||||||
if (newSheet != sheet) {
|
|
||||||
this.selectStyleSheet.bind(this, newSheet, newLine, newCol);
|
|
||||||
}
|
|
||||||
}.bind(this);
|
|
||||||
|
|
||||||
aEditor.addActionListener({
|
|
||||||
onAttach: onAttach
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// If a line or column was specified we move the caret appropriately.
|
|
||||||
aEditor.sourceEditor.setCaretPosition(line - 1, col - 1);
|
|
||||||
this._styleSheetToSelect = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
let summary = sheet ? this.getSummaryElementForEditor(aEditor)
|
|
||||||
: this._view.getSummaryElementByOrdinal(0);
|
|
||||||
this._view.activeSummary = summary;
|
|
||||||
this.selectedStyleSheetIndex = aEditor.styleSheetIndex;
|
|
||||||
}.bind(this);
|
|
||||||
|
|
||||||
if (!this.editors.length) {
|
|
||||||
// We are in the main initialization phase so we wait for the editor
|
|
||||||
// containing the target stylesheet to be added and select the target
|
|
||||||
// stylesheet, optionally moving the cursor to a selected line.
|
|
||||||
let self = this;
|
|
||||||
this.addChromeListener({
|
|
||||||
onEditorAdded: function SEC_selectSheet_onEditorAdded(aChrome, aEditor) {
|
|
||||||
let sheet = self._styleSheetToSelect.sheet;
|
|
||||||
if ((sheet && aEditor.styleSheet == sheet) ||
|
|
||||||
(aEditor.styleSheetIndex == 0 && sheet == null)) {
|
|
||||||
aChrome.removeChromeListener(this);
|
|
||||||
aEditor.addActionListener(self);
|
|
||||||
select(aEditor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (aSheet) {
|
|
||||||
// We are already initialized and a stylesheet has been specified. Here
|
|
||||||
// we iterate through the editors and select the one containing the target
|
|
||||||
// stylesheet, optionally moving the cursor to a selected line.
|
|
||||||
for each (let editor in this.editors) {
|
|
||||||
if (editor.styleSheet == aSheet) {
|
|
||||||
select(editor);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable all UI, effectively making editors read-only.
|
|
||||||
* This is automatically called when no content window is attached.
|
|
||||||
*
|
|
||||||
* @see contentWindow
|
|
||||||
*/
|
|
||||||
_disableChrome: function SEC__disableChrome()
|
|
||||||
{
|
|
||||||
let matches = this._root.querySelectorAll("button,toolbarbutton,textbox");
|
|
||||||
for (let i = 0; i < matches.length; i++) {
|
|
||||||
matches[i].setAttribute("disabled", "disabled");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.editors.forEach(function onEnterReadOnlyMode(aEditor) {
|
|
||||||
aEditor.readOnly = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
this._view.rootElement.setAttribute("disabled", "disabled");
|
|
||||||
|
|
||||||
this._triggerChromeListeners("ContentDetach");
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieve the summary element for a given editor.
|
|
||||||
*
|
|
||||||
* @param StyleEditor aEditor
|
|
||||||
* @return DOMElement
|
|
||||||
* Item's summary element or null if not found.
|
|
||||||
* @see SplitView
|
|
||||||
*/
|
|
||||||
getSummaryElementForEditor: function SEC_getSummaryElementForEditor(aEditor)
|
|
||||||
{
|
|
||||||
return this._view.getSummaryElementByOrdinal(aEditor.styleSheetIndex);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update split view summary of given StyleEditor instance.
|
|
||||||
*
|
|
||||||
* @param StyleEditor aEditor
|
|
||||||
* @param DOMElement aSummary
|
|
||||||
* Optional item's summary element to update. If none, item corresponding
|
|
||||||
* to passed aEditor is used.
|
|
||||||
*/
|
|
||||||
_updateSummaryForEditor: function SEC__updateSummaryForEditor(aEditor, aSummary)
|
|
||||||
{
|
|
||||||
let summary = aSummary || this.getSummaryElementForEditor(aEditor);
|
|
||||||
let ruleCount = aEditor.styleSheet.cssRules.length;
|
|
||||||
|
|
||||||
this._view.setItemClassName(summary, aEditor.flags);
|
|
||||||
|
|
||||||
let label = summary.querySelector(".stylesheet-name > label");
|
|
||||||
label.setAttribute("value", aEditor.getFriendlyName());
|
|
||||||
|
|
||||||
text(summary, ".stylesheet-title", aEditor.styleSheet.title || "");
|
|
||||||
text(summary, ".stylesheet-rule-count",
|
|
||||||
PluralForm.get(ruleCount, _("ruleCount.label")).replace("#1", ruleCount));
|
|
||||||
text(summary, ".stylesheet-error-message", aEditor.errorMessage);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* IStyleEditorActionListener implementation
|
|
||||||
* @See StyleEditor.addActionListener.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when source has been loaded and editor is ready for some action.
|
|
||||||
*
|
|
||||||
* @param StyleEditor aEditor
|
|
||||||
*/
|
|
||||||
onLoad: function SEAL_onLoad(aEditor)
|
|
||||||
{
|
|
||||||
let item = this._view.appendTemplatedItem(STYLE_EDITOR_TEMPLATE, {
|
|
||||||
data: {
|
|
||||||
editor: aEditor
|
|
||||||
},
|
|
||||||
disableAnimations: this._alwaysDisableAnimations,
|
|
||||||
ordinal: aEditor.styleSheetIndex,
|
|
||||||
onCreate: function ASV_onItemCreate(aSummary, aDetails, aData) {
|
|
||||||
let editor = aData.editor;
|
|
||||||
|
|
||||||
wire(aSummary, ".stylesheet-enabled", function onToggleEnabled(aEvent) {
|
|
||||||
aEvent.stopPropagation();
|
|
||||||
aEvent.target.blur();
|
|
||||||
|
|
||||||
editor.enableStyleSheet(editor.styleSheet.disabled);
|
|
||||||
});
|
|
||||||
|
|
||||||
wire(aSummary, ".stylesheet-name", {
|
|
||||||
events: {
|
|
||||||
"keypress": function onStylesheetNameActivate(aEvent) {
|
|
||||||
if (aEvent.keyCode == aEvent.DOM_VK_RETURN) {
|
|
||||||
this._view.activeSummary = aSummary;
|
|
||||||
}
|
|
||||||
}.bind(this)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
wire(aSummary, ".stylesheet-saveButton", function onSaveButton(aEvent) {
|
|
||||||
aEvent.stopPropagation();
|
|
||||||
aEvent.target.blur();
|
|
||||||
|
|
||||||
editor.saveToFile(editor.savedFile);
|
|
||||||
});
|
|
||||||
|
|
||||||
this._updateSummaryForEditor(editor, aSummary);
|
|
||||||
|
|
||||||
aSummary.addEventListener("focus", function onSummaryFocus(aEvent) {
|
|
||||||
if (aEvent.target == aSummary) {
|
|
||||||
// autofocus the stylesheet name
|
|
||||||
aSummary.querySelector(".stylesheet-name").focus();
|
|
||||||
}
|
|
||||||
}, false);
|
|
||||||
|
|
||||||
// autofocus new stylesheets
|
|
||||||
if (editor.hasFlag(StyleEditorFlags.NEW)) {
|
|
||||||
this._view.activeSummary = aSummary;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._triggerChromeListeners("EditorAdded", [editor]);
|
|
||||||
}.bind(this),
|
|
||||||
onHide: function ASV_onItemShow(aSummary, aDetails, aData) {
|
|
||||||
aData.editor.onHide();
|
|
||||||
},
|
|
||||||
onShow: function ASV_onItemShow(aSummary, aDetails, aData) {
|
|
||||||
let editor = aData.editor;
|
|
||||||
if (!editor.inputElement) {
|
|
||||||
// attach editor to input element the first time it is shown
|
|
||||||
editor.inputElement = aDetails.querySelector(".stylesheet-editor-input");
|
|
||||||
}
|
|
||||||
editor.onShow();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when an editor flag changed.
|
|
||||||
*
|
|
||||||
* @param StyleEditor aEditor
|
|
||||||
* @param string aFlagName
|
|
||||||
* @see StyleEditor.flags
|
|
||||||
*/
|
|
||||||
onFlagChange: function SEAL_onFlagChange(aEditor, aFlagName)
|
|
||||||
{
|
|
||||||
this._updateSummaryForEditor(aEditor);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when when changes have been committed/applied to the live DOM
|
|
||||||
* stylesheet.
|
|
||||||
*
|
|
||||||
* @param StyleEditor aEditor
|
|
||||||
*/
|
|
||||||
onCommit: function SEAL_onCommit(aEditor)
|
|
||||||
{
|
|
||||||
this._updateSummaryForEditor(aEditor);
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -0,0 +1,332 @@
|
||||||
|
/* 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 = ["StyleEditorDebuggee", "StyleSheet"];
|
||||||
|
|
||||||
|
const Cc = Components.classes;
|
||||||
|
const Ci = Components.interfaces;
|
||||||
|
const Cu = Components.utils;
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
|
Cu.import("resource:///modules/devtools/EventEmitter.jsm");
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||||
|
"resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A StyleEditorDebuggee represents the document the style editor is debugging.
|
||||||
|
* It maintains a list of StyleSheet objects that represent the stylesheets in
|
||||||
|
* the target's document. It wraps remote debugging protocol comunications.
|
||||||
|
*
|
||||||
|
* It emits these events:
|
||||||
|
* 'stylesheet-added': A stylesheet has been added to the debuggee's document
|
||||||
|
* 'stylesheets-cleared': The debuggee's stylesheets have been reset (e.g. the
|
||||||
|
* page navigated)
|
||||||
|
*
|
||||||
|
* @param {Target} target
|
||||||
|
* The target the debuggee is listening to
|
||||||
|
*/
|
||||||
|
let StyleEditorDebuggee = function(target) {
|
||||||
|
EventEmitter.decorate(this);
|
||||||
|
|
||||||
|
this.styleSheets = [];
|
||||||
|
|
||||||
|
this.clear = this.clear.bind(this);
|
||||||
|
this._onNewDocument = this._onNewDocument.bind(this);
|
||||||
|
this._onStyleSheetsAdded = this._onStyleSheetsAdded.bind(this);
|
||||||
|
|
||||||
|
this._target = target;
|
||||||
|
this._actor = this.target.form.styleEditorActor;
|
||||||
|
|
||||||
|
this.client.addListener("styleSheetsAdded", this._onStyleSheetsAdded);
|
||||||
|
this._target.on("navigate", this._onNewDocument);
|
||||||
|
|
||||||
|
this._onNewDocument();
|
||||||
|
}
|
||||||
|
|
||||||
|
StyleEditorDebuggee.prototype = {
|
||||||
|
/**
|
||||||
|
* list of StyleSheet objects for this target
|
||||||
|
*/
|
||||||
|
styleSheets: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* baseURIObject for the current document
|
||||||
|
*/
|
||||||
|
baseURI: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The target we're debugging
|
||||||
|
*/
|
||||||
|
get target() {
|
||||||
|
return this._target;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client for communicating with server with remote debug protocol.
|
||||||
|
*/
|
||||||
|
get client() {
|
||||||
|
return this._target.client;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the StyleSheet object with the given href.
|
||||||
|
*
|
||||||
|
* @param {string} href
|
||||||
|
* Url of the stylesheet to find
|
||||||
|
* @return {StyleSheet}
|
||||||
|
* StyleSheet with the matching href
|
||||||
|
*/
|
||||||
|
styleSheetFromHref: function(href) {
|
||||||
|
for (let sheet of this.styleSheets) {
|
||||||
|
if (sheet.href == href) {
|
||||||
|
return sheet;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear stylesheets and state.
|
||||||
|
*/
|
||||||
|
clear: function() {
|
||||||
|
this.baseURI = null;
|
||||||
|
|
||||||
|
for (let stylesheet of this.styleSheets) {
|
||||||
|
stylesheet.destroy();
|
||||||
|
}
|
||||||
|
this.styleSheets = [];
|
||||||
|
|
||||||
|
this.emit("stylesheets-cleared");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when target is created or has navigated.
|
||||||
|
* Clear previous sheets and request new document's
|
||||||
|
*/
|
||||||
|
_onNewDocument: function() {
|
||||||
|
this.clear();
|
||||||
|
|
||||||
|
this._getBaseURI();
|
||||||
|
|
||||||
|
let message = { type: "newDocument" };
|
||||||
|
this._sendRequest(message);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* request baseURIObject information from the document
|
||||||
|
*/
|
||||||
|
_getBaseURI: function() {
|
||||||
|
let message = { type: "getBaseURI" };
|
||||||
|
this._sendRequest(message, (response) => {
|
||||||
|
this.baseURI = response.baseURI;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle stylesheet-added event from the target
|
||||||
|
*
|
||||||
|
* @param {string} type
|
||||||
|
* Type of event
|
||||||
|
* @param {object} request
|
||||||
|
* Event details
|
||||||
|
*/
|
||||||
|
_onStyleSheetsAdded: function(type, request) {
|
||||||
|
for (let form of request.styleSheets) {
|
||||||
|
let sheet = this._addStyleSheet(form);
|
||||||
|
this.emit("stylesheet-added", sheet);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new StyleSheet object from the form
|
||||||
|
* and add to our stylesheet list.
|
||||||
|
*
|
||||||
|
* @param {object} form
|
||||||
|
* Initial properties of the stylesheet
|
||||||
|
*/
|
||||||
|
_addStyleSheet: function(form) {
|
||||||
|
let sheet = new StyleSheet(form, this);
|
||||||
|
this.styleSheets.push(sheet);
|
||||||
|
return sheet;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new stylesheet with the given text
|
||||||
|
* and attach it to the document.
|
||||||
|
*
|
||||||
|
* @param {string} text
|
||||||
|
* Initial text of the stylesheet
|
||||||
|
* @param {function} callback
|
||||||
|
* Function to call when the stylesheet has been added to the document
|
||||||
|
*/
|
||||||
|
createStyleSheet: function(text, callback) {
|
||||||
|
let message = { type: "newStyleSheet", text: text };
|
||||||
|
this._sendRequest(message, (response) => {
|
||||||
|
let sheet = this._addStyleSheet(response.styleSheet);
|
||||||
|
callback(sheet);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a request to our actor on the server
|
||||||
|
*
|
||||||
|
* @param {object} message
|
||||||
|
* Message to send to the actor
|
||||||
|
* @param {function} callback
|
||||||
|
* Function to call with reponse from actor
|
||||||
|
*/
|
||||||
|
_sendRequest: function(message, callback) {
|
||||||
|
message.to = this._actor;
|
||||||
|
this.client.request(message, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up and remove listeners
|
||||||
|
*/
|
||||||
|
destroy: function() {
|
||||||
|
this.clear();
|
||||||
|
|
||||||
|
this._target.off("will-navigate", this.clear);
|
||||||
|
this._target.off("navigate", this._onNewDocument);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A StyleSheet object represents a stylesheet on the debuggee. It wraps
|
||||||
|
* communication with a complimentary StyleSheetActor on the server.
|
||||||
|
*
|
||||||
|
* It emits these events:
|
||||||
|
* 'source-load' - The full text source of the stylesheet has been fetched
|
||||||
|
* 'property-change' - Any property (e.g 'disabled') has changed
|
||||||
|
* 'style-applied' - A change has been applied to the live stylesheet on the server
|
||||||
|
* 'error' - An error occured when loading or saving stylesheet
|
||||||
|
*
|
||||||
|
* @param {object} form
|
||||||
|
* Initial properties of the stylesheet
|
||||||
|
* @param {StyleEditorDebuggee} debuggee
|
||||||
|
* Owner of the stylesheet
|
||||||
|
*/
|
||||||
|
let StyleSheet = function(form, debuggee) {
|
||||||
|
EventEmitter.decorate(this);
|
||||||
|
|
||||||
|
this.debuggee = debuggee;
|
||||||
|
this._client = debuggee.client;
|
||||||
|
this._actor = form.actor;
|
||||||
|
|
||||||
|
this._onSourceLoad = this._onSourceLoad.bind(this);
|
||||||
|
this._onPropertyChange = this._onPropertyChange.bind(this);
|
||||||
|
this._onError = this._onError.bind(this);
|
||||||
|
this._onStyleApplied = this._onStyleApplied.bind(this);
|
||||||
|
|
||||||
|
this._client.addListener("sourceLoad-" + this._actor, this._onSourceLoad);
|
||||||
|
this._client.addListener("propertyChange-" + this._actor, this._onPropertyChange);
|
||||||
|
this._client.addListener("error-" + this._actor, this._onError);
|
||||||
|
this._client.addListener("styleApplied-" + this._actor, this._onStyleApplied);
|
||||||
|
|
||||||
|
// set initial property values
|
||||||
|
for (let attr in form) {
|
||||||
|
this[attr] = form[attr];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyleSheet.prototype = {
|
||||||
|
/**
|
||||||
|
* Toggle the disabled attribute of the stylesheet
|
||||||
|
*/
|
||||||
|
toggleDisabled: function() {
|
||||||
|
let message = { type: "toggleDisabled" };
|
||||||
|
this._sendRequest(message);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request that the source of the stylesheet be fetched.
|
||||||
|
* 'source-load' event will be fired when it's been fetched.
|
||||||
|
*/
|
||||||
|
fetchSource: function() {
|
||||||
|
let message = { type: "fetchSource" };
|
||||||
|
this._sendRequest(message);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the stylesheet in place with the given full source.
|
||||||
|
*
|
||||||
|
* @param {string} sheetText
|
||||||
|
* Full text to update the stylesheet with
|
||||||
|
*/
|
||||||
|
update: function(sheetText) {
|
||||||
|
let message = { type: "update", text: sheetText, transition: true };
|
||||||
|
this._sendRequest(message);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle source load event from the client.
|
||||||
|
*
|
||||||
|
* @param {string} type
|
||||||
|
* Event type
|
||||||
|
* @param {object} request
|
||||||
|
* Event details
|
||||||
|
*/
|
||||||
|
_onSourceLoad: function(type, request) {
|
||||||
|
this.emit("source-load", request.source);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a property change on the stylesheet
|
||||||
|
*
|
||||||
|
* @param {string} type
|
||||||
|
* Event type
|
||||||
|
* @param {object} request
|
||||||
|
* Event details
|
||||||
|
*/
|
||||||
|
_onPropertyChange: function(type, request) {
|
||||||
|
this[request.property] = request.value;
|
||||||
|
this.emit("property-change", request.property);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Propogate errors from the server that relate to this stylesheet.
|
||||||
|
*
|
||||||
|
* @param {string} type
|
||||||
|
* Event type
|
||||||
|
* @param {object} request
|
||||||
|
* Event details
|
||||||
|
*/
|
||||||
|
_onError: function(type, request) {
|
||||||
|
this.emit("error", request.errorMessage);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle event when update has been successfully applied and propogate it.
|
||||||
|
*/
|
||||||
|
_onStyleApplied: function() {
|
||||||
|
this.emit("style-applied");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a request to our actor on the server
|
||||||
|
*
|
||||||
|
* @param {object} message
|
||||||
|
* Message to send to the actor
|
||||||
|
* @param {function} callback
|
||||||
|
* Function to call with reponse from actor
|
||||||
|
*/
|
||||||
|
_sendRequest: function(message, callback) {
|
||||||
|
message.to = this._actor;
|
||||||
|
this._client.request(message, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up and remove event listeners
|
||||||
|
*/
|
||||||
|
destroy: function() {
|
||||||
|
this._client.removeListener("sourceLoad-" + this._actor, this._onSourceLoad);
|
||||||
|
this._client.removeListener("propertyChange-" + this._actor, this._onPropertyChange);
|
||||||
|
this._client.removeListener("error-" + this._actor, this._onError);
|
||||||
|
this._client.removeListener("styleApplied-" + this._actor, this._onStyleApplied);
|
||||||
|
}
|
||||||
|
}
|
|
@ -12,6 +12,10 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
Cu.import("resource://gre/modules/Services.jsm");
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||||
Cu.import("resource:///modules/devtools/EventEmitter.jsm");
|
Cu.import("resource:///modules/devtools/EventEmitter.jsm");
|
||||||
|
Cu.import("resource:///modules/devtools/StyleEditorDebuggee.jsm");
|
||||||
|
Cu.import("resource:///modules/devtools/StyleEditorUI.jsm");
|
||||||
|
Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm");
|
||||||
|
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "StyleEditorChrome",
|
XPCOMUtils.defineLazyModuleGetter(this, "StyleEditorChrome",
|
||||||
"resource:///modules/devtools/StyleEditorChrome.jsm");
|
"resource:///modules/devtools/StyleEditorChrome.jsm");
|
||||||
|
@ -21,183 +25,99 @@ this.StyleEditorPanel = function StyleEditorPanel(panelWin, toolbox) {
|
||||||
|
|
||||||
this._toolbox = toolbox;
|
this._toolbox = toolbox;
|
||||||
this._target = toolbox.target;
|
this._target = toolbox.target;
|
||||||
|
|
||||||
this.newPage = this.newPage.bind(this);
|
|
||||||
this.destroy = this.destroy.bind(this);
|
|
||||||
this.beforeNavigate = this.beforeNavigate.bind(this);
|
|
||||||
|
|
||||||
this._target.on("will-navigate", this.beforeNavigate);
|
|
||||||
this._target.on("navigate", this.newPage);
|
|
||||||
this._target.on("close", this.destroy);
|
|
||||||
|
|
||||||
this._panelWin = panelWin;
|
this._panelWin = panelWin;
|
||||||
this._panelDoc = panelWin.document;
|
this._panelDoc = panelWin.document;
|
||||||
|
|
||||||
|
this.destroy = this.destroy.bind(this);
|
||||||
|
this._showError = this._showError.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
StyleEditorPanel.prototype = {
|
StyleEditorPanel.prototype = {
|
||||||
|
get target() this._toolbox.target,
|
||||||
|
|
||||||
|
get panelWindow() this._panelWin,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* open is effectively an asynchronous constructor
|
* open is effectively an asynchronous constructor
|
||||||
*/
|
*/
|
||||||
open: function StyleEditor_open() {
|
open: function() {
|
||||||
let contentWin = this._toolbox.target.window;
|
|
||||||
let deferred = Promise.defer();
|
let deferred = Promise.defer();
|
||||||
|
|
||||||
this.setPage(contentWin).then(function() {
|
let promise;
|
||||||
|
// We always interact with the target as if it were remote
|
||||||
|
if (!this.target.isRemote) {
|
||||||
|
promise = this.target.makeRemote();
|
||||||
|
} else {
|
||||||
|
promise = Promise.resolve(this.target);
|
||||||
|
}
|
||||||
|
|
||||||
|
promise.then(() => {
|
||||||
|
this.target.on("close", this.destroy);
|
||||||
|
|
||||||
|
this._debuggee = new StyleEditorDebuggee(this.target);
|
||||||
|
|
||||||
|
this.UI = new StyleEditorUI(this._debuggee, this._panelDoc);
|
||||||
|
this.UI.on("error", this._showError);
|
||||||
|
|
||||||
this.isReady = true;
|
this.isReady = true;
|
||||||
deferred.resolve(this);
|
deferred.resolve(this);
|
||||||
}.bind(this));
|
})
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Target getter.
|
* Show an error message from the style editor in the toolbox
|
||||||
|
* notification box.
|
||||||
|
*
|
||||||
|
* @param {string} event
|
||||||
|
* Type of event
|
||||||
|
* @param {string} errorCode
|
||||||
|
* Error code of error to report
|
||||||
*/
|
*/
|
||||||
get target() this._target,
|
_showError: function(event, errorCode) {
|
||||||
|
let message = _(errorCode);
|
||||||
/**
|
let notificationBox = this._toolbox.getNotificationBox();
|
||||||
* Panel window getter.
|
let notification = notificationBox.getNotificationWithValue("styleeditor-error");
|
||||||
*/
|
if (!notification) {
|
||||||
get panelWindow() this._panelWin,
|
notificationBox.appendNotification(message,
|
||||||
|
"styleeditor-error", "", notificationBox.PRIORITY_CRITICAL_LOW);
|
||||||
/**
|
|
||||||
* StyleEditorChrome instance getter.
|
|
||||||
*/
|
|
||||||
get styleEditorChrome() this._panelWin.styleEditorChrome,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the page to target.
|
|
||||||
*/
|
|
||||||
setPage: function StyleEditor_setPage(contentWindow) {
|
|
||||||
if (this._panelWin.styleEditorChrome) {
|
|
||||||
this._panelWin.styleEditorChrome.contentWindow = contentWindow;
|
|
||||||
this.selectStyleSheet(null, null, null);
|
|
||||||
} else {
|
|
||||||
let chromeRoot = this._panelDoc.getElementById("style-editor-chrome");
|
|
||||||
let chrome = new StyleEditorChrome(chromeRoot, contentWindow);
|
|
||||||
let promise = chrome.open();
|
|
||||||
|
|
||||||
this._panelWin.styleEditorChrome = chrome;
|
|
||||||
this.selectStyleSheet(null, null, null);
|
|
||||||
return promise;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
|
||||||
* Navigated to a new page.
|
|
||||||
*/
|
|
||||||
newPage: function StyleEditor_newPage(event, payload) {
|
|
||||||
let window = payload._navPayload || payload;
|
|
||||||
this.reset();
|
|
||||||
this.setPage(window);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Before navigating to a new page or reloading the page.
|
|
||||||
*/
|
|
||||||
beforeNavigate: function StyleEditor_beforeNavigate(event, payload) {
|
|
||||||
let request = payload._navPayload || payload;
|
|
||||||
if (this.styleEditorChrome.isDirty) {
|
|
||||||
this.preventNavigate(request);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show a notificiation about losing unsaved changes.
|
|
||||||
*/
|
|
||||||
preventNavigate: function StyleEditor_preventNavigate(request) {
|
|
||||||
request.suspend();
|
|
||||||
|
|
||||||
let notificationBox = null;
|
|
||||||
if (this.target.isLocalTab) {
|
|
||||||
let gBrowser = this.target.tab.ownerDocument.defaultView.gBrowser;
|
|
||||||
notificationBox = gBrowser.getNotificationBox();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
notificationBox = this._toolbox.getNotificationBox();
|
|
||||||
}
|
|
||||||
|
|
||||||
let notification = notificationBox.
|
|
||||||
getNotificationWithValue("styleeditor-page-navigation");
|
|
||||||
|
|
||||||
if (notification) {
|
|
||||||
notificationBox.removeNotification(notification, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
let cancelRequest = function onCancelRequest() {
|
|
||||||
if (request) {
|
|
||||||
request.cancel(Cr.NS_BINDING_ABORTED);
|
|
||||||
request.resume(); // needed to allow the connection to be cancelled.
|
|
||||||
request = null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let eventCallback = function onNotificationCallback(event) {
|
|
||||||
if (event == "removed") {
|
|
||||||
cancelRequest();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let buttons = [
|
|
||||||
{
|
|
||||||
id: "styleeditor.confirmNavigationAway.buttonLeave",
|
|
||||||
label: this.strings.GetStringFromName("confirmNavigationAway.buttonLeave"),
|
|
||||||
accessKey: this.strings.GetStringFromName("confirmNavigationAway.buttonLeaveAccesskey"),
|
|
||||||
callback: function onButtonLeave() {
|
|
||||||
if (request) {
|
|
||||||
request.resume();
|
|
||||||
request = null;
|
|
||||||
}
|
|
||||||
}.bind(this),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "styleeditor.confirmNavigationAway.buttonStay",
|
|
||||||
label: this.strings.GetStringFromName("confirmNavigationAway.buttonStay"),
|
|
||||||
accessKey: this.strings.GetStringFromName("confirmNavigationAway.buttonStayAccesskey"),
|
|
||||||
callback: cancelRequest
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
let message = this.strings.GetStringFromName("confirmNavigationAway.message");
|
|
||||||
|
|
||||||
notification = notificationBox.appendNotification(message,
|
|
||||||
"styleeditor-page-navigation", "chrome://browser/skin/Info.png",
|
|
||||||
notificationBox.PRIORITY_WARNING_HIGH, buttons, eventCallback);
|
|
||||||
|
|
||||||
// Make sure this not a transient notification, to avoid the automatic
|
|
||||||
// transient notification removal.
|
|
||||||
notification.persistence = -1;
|
|
||||||
},
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* No window available anymore.
|
|
||||||
*/
|
|
||||||
reset: function StyleEditor_reset() {
|
|
||||||
this._panelWin.styleEditorChrome.resetChrome();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Select a stylesheet.
|
* 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
|
||||||
|
* @param {number} col
|
||||||
|
* Column number to jump to after selecting
|
||||||
*/
|
*/
|
||||||
selectStyleSheet: function StyleEditor_selectStyleSheet(stylesheet, line, col) {
|
selectStyleSheet: function(href, line, col) {
|
||||||
this._panelWin.styleEditorChrome.selectStyleSheet(stylesheet, line, col);
|
if (!this._debuggee || !this.UI) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let stylesheet = this._debuggee.styleSheetFromHref(href);
|
||||||
|
this.UI.selectStyleSheet(href, line, col);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Destroy StyleEditor
|
* Destroy the style editor.
|
||||||
*/
|
*/
|
||||||
destroy: function StyleEditor_destroy() {
|
destroy: function() {
|
||||||
if (!this._destroyed) {
|
if (!this._destroyed) {
|
||||||
this._destroyed = true;
|
this._destroyed = true;
|
||||||
|
|
||||||
this._target.off("will-navigate", this.beforeNavigate);
|
this._target.off("will-navigate", this.beforeNavigate);
|
||||||
this._target.off("navigate", this.newPage);
|
|
||||||
this._target.off("close", this.destroy);
|
this._target.off("close", this.destroy);
|
||||||
this._target = null;
|
this._target = null;
|
||||||
this._toolbox = null;
|
this._toolbox = null;
|
||||||
this._panelWin = null;
|
|
||||||
this._panelDoc = null;
|
this._panelDoc = null;
|
||||||
|
|
||||||
|
this._debuggee.destroy();
|
||||||
|
this.UI.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
|
|
|
@ -0,0 +1,427 @@
|
||||||
|
/* 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/Services.jsm");
|
||||||
|
Cu.import("resource://gre/modules/PluralForm.jsm");
|
||||||
|
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||||
|
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||||
|
Cu.import("resource:///modules/devtools/EventEmitter.jsm");
|
||||||
|
Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm");
|
||||||
|
Cu.import("resource:///modules/devtools/SplitView.jsm");
|
||||||
|
Cu.import("resource:///modules/devtools/StyleSheetEditor.jsm");
|
||||||
|
|
||||||
|
|
||||||
|
const LOAD_ERROR = "error-load";
|
||||||
|
|
||||||
|
const STYLE_EDITOR_TEMPLATE = "stylesheet";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* 'error': An error occured
|
||||||
|
*
|
||||||
|
* @param {StyleEditorDebuggee} debuggee
|
||||||
|
* Debuggee of whose stylesheets should be shown in the UI
|
||||||
|
* @param {Document} panelDoc
|
||||||
|
* Document of the toolbox panel to populate UI in.
|
||||||
|
*/
|
||||||
|
function StyleEditorUI(debuggee, panelDoc) {
|
||||||
|
EventEmitter.decorate(this);
|
||||||
|
|
||||||
|
this._debuggee = debuggee;
|
||||||
|
this._panelDoc = panelDoc;
|
||||||
|
this._window = this._panelDoc.defaultView;
|
||||||
|
this._root = this._panelDoc.getElementById("style-editor-chrome");
|
||||||
|
|
||||||
|
this.editors = [];
|
||||||
|
this.selectedStyleSheetIndex = -1;
|
||||||
|
|
||||||
|
this._onStyleSheetAdded = this._onStyleSheetAdded.bind(this);
|
||||||
|
this._onStyleSheetCreated = this._onStyleSheetCreated.bind(this);
|
||||||
|
this._onStyleSheetsCleared = this._onStyleSheetsCleared.bind(this);
|
||||||
|
this._onError = this._onError.bind(this);
|
||||||
|
|
||||||
|
debuggee.on("stylesheet-added", this._onStyleSheetAdded);
|
||||||
|
debuggee.on("stylesheets-cleared", this._onStyleSheetsCleared);
|
||||||
|
|
||||||
|
this.createUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
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.dirty;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mark the style editor as having or not having unsaved changes.
|
||||||
|
*/
|
||||||
|
set isDirty(value) {
|
||||||
|
this._markedDirty = value;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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", function onNew() {
|
||||||
|
this._debuggee.createStyleSheet(null, this._onStyleSheetCreated);
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
wire(this._view.rootElement, ".style-editor-importButton", function onImport() {
|
||||||
|
this._importFromFile(this._mockImportFile || null, this._window);
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 = function(file) {
|
||||||
|
if (!file) {
|
||||||
|
this.emit("error", LOAD_ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
NetUtil.asyncFetch(file, (stream, status) => {
|
||||||
|
if (!Components.isSuccessCode(status)) {
|
||||||
|
this.emit("error", LOAD_ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let source = NetUtil.readInputStreamToString(stream, stream.available());
|
||||||
|
stream.close();
|
||||||
|
|
||||||
|
this._debuggee.createStyleSheet(source, (styleSheet) => {
|
||||||
|
this._onStyleSheetCreated(styleSheet, file);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
showFilePicker(file, false, parentWindow, onFileSelected);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for debuggee's 'stylesheets-cleared' event. Remove all editors.
|
||||||
|
*/
|
||||||
|
_onStyleSheetsCleared: function() {
|
||||||
|
this._clearStyleSheetEditors();
|
||||||
|
|
||||||
|
this._view.removeAll();
|
||||||
|
this.selectedStyleSheetIndex = -1;
|
||||||
|
|
||||||
|
this._root.classList.add("loading");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for debuggee's 'stylesheet-added' event. Add an editor.
|
||||||
|
*
|
||||||
|
* @param {string} event
|
||||||
|
* Event name
|
||||||
|
* @param {StyleSheet} styleSheet
|
||||||
|
* StyleSheet object for new sheet
|
||||||
|
*/
|
||||||
|
_onStyleSheetAdded: function(event, styleSheet) {
|
||||||
|
// this might be the first stylesheet, so remove loading indicator
|
||||||
|
this._root.classList.remove("loading");
|
||||||
|
this._addStyleSheetEditor(styleSheet);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forward any error from a stylesheet.
|
||||||
|
*
|
||||||
|
* @param {string} event
|
||||||
|
* Event name
|
||||||
|
* @param {string} errorCode
|
||||||
|
* Code represeting type of error
|
||||||
|
*/
|
||||||
|
_onError: function(event, errorCode) {
|
||||||
|
this.emit("error", errorCode);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new editor to the UI for a stylesheet.
|
||||||
|
*
|
||||||
|
* @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) {
|
||||||
|
let editor = new StyleSheetEditor(styleSheet, this._window, file, isNew);
|
||||||
|
|
||||||
|
editor.once("source-load", this._sourceLoaded.bind(this, editor));
|
||||||
|
editor.on("property-change", this._summaryChange.bind(this, editor));
|
||||||
|
editor.on("style-applied", this._summaryChange.bind(this, editor));
|
||||||
|
editor.on("error", this._onError);
|
||||||
|
|
||||||
|
this.editors.push(editor);
|
||||||
|
|
||||||
|
// Queue editor loading. This helps responsivity during loading when
|
||||||
|
// there are many heavy stylesheets.
|
||||||
|
this._window.setTimeout(editor.fetchSource.bind(editor), 0);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all the editors from the UI.
|
||||||
|
*/
|
||||||
|
_clearStyleSheetEditors: function() {
|
||||||
|
for (let editor of this.editors) {
|
||||||
|
editor.destroy();
|
||||||
|
}
|
||||||
|
this.editors = [];
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for an StyleSheetEditor's 'source-load' event.
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
wire(summary, ".stylesheet-enabled", function onToggleDisabled(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.target.blur();
|
||||||
|
|
||||||
|
editor.toggleDisabled();
|
||||||
|
});
|
||||||
|
|
||||||
|
wire(summary, ".stylesheet-name", {
|
||||||
|
events: {
|
||||||
|
"keypress": function onStylesheetNameActivate(aEvent) {
|
||||||
|
if (aEvent.keyCode == aEvent.DOM_VK_RETURN) {
|
||||||
|
this._view.activeSummary = summary;
|
||||||
|
}
|
||||||
|
}.bind(this)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// autofocus if it's a new user-created stylesheet
|
||||||
|
if (editor.isNew) {
|
||||||
|
this._selectEditor(editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._styleSheetToSelect
|
||||||
|
&& this._styleSheetToSelect.href == editor.styleSheet.href) {
|
||||||
|
this.switchToSelectedSheet();
|
||||||
|
}
|
||||||
|
|
||||||
|
// If this is the first stylesheet, select it
|
||||||
|
if (this.selectedStyleSheetIndex == -1
|
||||||
|
&& !this._styleSheetToSelect
|
||||||
|
&& editor.styleSheet.styleSheetIndex == 0) {
|
||||||
|
this._selectEditor(editor);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.emit("editor-added", editor);
|
||||||
|
}.bind(this),
|
||||||
|
|
||||||
|
onShow: function(summary, details, data) {
|
||||||
|
let editor = data.editor;
|
||||||
|
if (!editor.sourceEditor) {
|
||||||
|
// only initialize source editor when we switch to this view
|
||||||
|
let inputElement = details.querySelector(".stylesheet-editor-input");
|
||||||
|
editor.load(inputElement);
|
||||||
|
}
|
||||||
|
editor.onShow();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Switch to the editor that has been marked to be selected.
|
||||||
|
*/
|
||||||
|
switchToSelectedSheet: function() {
|
||||||
|
let sheet = this._styleSheetToSelect;
|
||||||
|
|
||||||
|
for each (let editor in this.editors) {
|
||||||
|
if (editor.styleSheet.href == sheet.href) {
|
||||||
|
this._selectEditor(editor, sheet.line, sheet.col);
|
||||||
|
this._styleSheetToSelect = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
_selectEditor: function(editor, line, col) {
|
||||||
|
line = line || 1;
|
||||||
|
col = col || 1;
|
||||||
|
|
||||||
|
this.selectedStyleSheetIndex = editor.styleSheet.styleSheetIndex;
|
||||||
|
|
||||||
|
editor.getSourceEditor().then(() => {
|
||||||
|
editor.sourceEditor.setCaretPosition(line - 1, col - 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
this._view.activeSummary = editor.summary;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* selects a stylesheet and optionally moves the cursor to a selected line
|
||||||
|
*
|
||||||
|
* @param {string} [href]
|
||||||
|
* Href of stylesheet that should be selected. If a stylesheet is not passed
|
||||||
|
* and the editor is not initialized we focus the first stylesheet. If
|
||||||
|
* a stylesheet is not passed and the editor is initialized we ignore
|
||||||
|
* the call.
|
||||||
|
* @param {Number} [line]
|
||||||
|
* Line to which the caret should be moved (one-indexed).
|
||||||
|
* @param {Number} [col]
|
||||||
|
* Column to which the caret should be moved (one-indexed).
|
||||||
|
*/
|
||||||
|
selectStyleSheet: function(href, line, col)
|
||||||
|
{
|
||||||
|
let alreadyCalled = !!this._styleSheetToSelect;
|
||||||
|
|
||||||
|
this._styleSheetToSelect = {
|
||||||
|
href: href,
|
||||||
|
line: line,
|
||||||
|
col: col,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (alreadyCalled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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 = "-";
|
||||||
|
if (editor.styleSheet.ruleCount !== undefined) {
|
||||||
|
ruleCount = editor.styleSheet.ruleCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
var flags = [];
|
||||||
|
if (editor.styleSheet.disabled) {
|
||||||
|
flags.push("disabled");
|
||||||
|
}
|
||||||
|
if (editor.unsaved) {
|
||||||
|
flags.push("unsaved");
|
||||||
|
}
|
||||||
|
this._view.setItemClassName(summary, flags.join(" "));
|
||||||
|
|
||||||
|
let label = summary.querySelector(".stylesheet-name > label");
|
||||||
|
label.setAttribute("value", editor.friendlyName);
|
||||||
|
|
||||||
|
text(summary, ".stylesheet-title", editor.styleSheet.title || "");
|
||||||
|
text(summary, ".stylesheet-rule-count",
|
||||||
|
PluralForm.get(ruleCount, _("ruleCount.label")).replace("#1", ruleCount));
|
||||||
|
text(summary, ".stylesheet-error-message", editor.errorMessage);
|
||||||
|
},
|
||||||
|
|
||||||
|
destroy: function() {
|
||||||
|
this._clearStyleSheetEditors();
|
||||||
|
|
||||||
|
this._debuggee.off("stylesheet-added", this._onStyleSheetAdded);
|
||||||
|
this._debuggee.off("stylesheets-cleared", this._onStyleSheetsCleared);
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,11 +8,10 @@
|
||||||
this.EXPORTED_SYMBOLS = [
|
this.EXPORTED_SYMBOLS = [
|
||||||
"_",
|
"_",
|
||||||
"assert",
|
"assert",
|
||||||
"attr", // XXXkhuey unused?
|
|
||||||
"getCurrentBrowserTabContentWindow", // XXXkhuey unused?
|
|
||||||
"log",
|
"log",
|
||||||
"text",
|
"text",
|
||||||
"wire"
|
"wire",
|
||||||
|
"showFilePicker"
|
||||||
];
|
];
|
||||||
|
|
||||||
const Cc = Components.classes;
|
const Cc = Components.classes;
|
||||||
|
@ -108,7 +107,7 @@ function forEach(aObject, aCallback)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Log a message to the console.
|
* Log a message to the console.
|
||||||
*
|
*
|
||||||
* @param ...rest
|
* @param ...rest
|
||||||
* One or multiple arguments to log.
|
* One or multiple arguments to log.
|
||||||
* If multiple arguments are given, they will be joined by " " in the log.
|
* If multiple arguments are given, they will be joined by " " in the log.
|
||||||
|
@ -162,3 +161,63 @@ this.wire = function wire(aRoot, aSelectorOrElement, aDescriptor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
this.showFilePicker = function showFilePicker(path, toSave, parentWindow, callback)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fp.init(parentWindow, _(key + ".title"), mode);
|
||||||
|
fp.appendFilters(_(key + ".filter"), "*.css");
|
||||||
|
fp.appendFilters(fp.filterAll);
|
||||||
|
fp.open(fpCallback);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,548 @@
|
||||||
|
/* 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"];
|
||||||
|
|
||||||
|
const Cc = Components.classes;
|
||||||
|
const Ci = Components.interfaces;
|
||||||
|
const Cu = Components.utils;
|
||||||
|
|
||||||
|
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||||
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
|
Cu.import("resource://gre/modules/FileUtils.jsm");
|
||||||
|
Cu.import("resource://gre/modules/NetUtil.jsm");
|
||||||
|
Cu.import("resource:///modules/devtools/EventEmitter.jsm");
|
||||||
|
Cu.import("resource:///modules/source-editor.jsm");
|
||||||
|
Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm");
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* StyleSheetEditor controls the editor linked to a particular StyleSheet
|
||||||
|
* object.
|
||||||
|
*
|
||||||
|
* Emits events:
|
||||||
|
* 'source-load': The source of the stylesheet has been fetched
|
||||||
|
* '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} styleSheet
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
function StyleSheetEditor(styleSheet, win, file, isNew) {
|
||||||
|
EventEmitter.decorate(this);
|
||||||
|
|
||||||
|
this.styleSheet = styleSheet;
|
||||||
|
this._inputElement = null;
|
||||||
|
this._sourceEditor = null;
|
||||||
|
this._window = win;
|
||||||
|
this._isNew = isNew;
|
||||||
|
this.savedFile = file;
|
||||||
|
|
||||||
|
this.errorMessage = null;
|
||||||
|
|
||||||
|
this._state = { // state to use when inputElement attaches
|
||||||
|
text: "",
|
||||||
|
selection: {start: 0, end: 0},
|
||||||
|
readOnly: false,
|
||||||
|
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._onSourceLoad = this._onSourceLoad.bind(this);
|
||||||
|
this._onPropertyChange = this._onPropertyChange.bind(this);
|
||||||
|
this._onError = this._onError.bind(this);
|
||||||
|
|
||||||
|
this._focusOnSourceEditorReady = false;
|
||||||
|
|
||||||
|
this.styleSheet.once("source-load", this._onSourceLoad);
|
||||||
|
this.styleSheet.on("property-change", this._onPropertyChange);
|
||||||
|
this.styleSheet.on("error", this._onError);
|
||||||
|
}
|
||||||
|
|
||||||
|
StyleSheetEditor.prototype = {
|
||||||
|
/**
|
||||||
|
* This editor's source editor
|
||||||
|
*/
|
||||||
|
get sourceEditor() {
|
||||||
|
return this._sourceEditor;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether there are unsaved changes in the editor
|
||||||
|
*/
|
||||||
|
get unsaved() {
|
||||||
|
return this._sourceEditor && this._sourceEditor.dirty;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the editor is for a stylesheet created by the user
|
||||||
|
* through the style editor UI.
|
||||||
|
*/
|
||||||
|
get isNew() {
|
||||||
|
return this._isNew;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a user-friendly name for the style sheet.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
get friendlyName() {
|
||||||
|
if (this.savedFile) { // reuse the saved filename if any
|
||||||
|
return this.savedFile.leafName;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this._isNew) {
|
||||||
|
let index = this.styleSheet.styleSheetIndex + 1; // 0-indexing only works for devs
|
||||||
|
return _("newStyleSheet", index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.styleSheet.href) {
|
||||||
|
let index = this.styleSheet.styleSheetIndex + 1; // 0-indexing only works for devs
|
||||||
|
return _("inlineStyleSheet", index);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._friendlyName) {
|
||||||
|
let sheetURI = this.styleSheet.href;
|
||||||
|
let contentURI = this.styleSheet.debuggee.baseURI;
|
||||||
|
let contentURIScheme = contentURI.scheme;
|
||||||
|
let contentURILeafIndex = contentURI.specIgnoringRef.lastIndexOf("/");
|
||||||
|
contentURI = contentURI.specIgnoringRef;
|
||||||
|
|
||||||
|
// get content base URI without leaf name (if any)
|
||||||
|
if (contentURILeafIndex > contentURIScheme.length) {
|
||||||
|
contentURI = contentURI.substring(0, contentURILeafIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// avoid verbose repetition of absolute URI when the style sheet URI
|
||||||
|
// is relative to the content URI
|
||||||
|
this._friendlyName = (sheetURI.indexOf(contentURI) == 0)
|
||||||
|
? sheetURI.substring(contentURI.length)
|
||||||
|
: sheetURI;
|
||||||
|
try {
|
||||||
|
this._friendlyName = decodeURI(this._friendlyName);
|
||||||
|
} catch (ex) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this._friendlyName;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start fetching the full text source for this editor's sheet.
|
||||||
|
*/
|
||||||
|
fetchSource: function() {
|
||||||
|
this.styleSheet.fetchSource();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle source fetched event. Forward source-load event.
|
||||||
|
*
|
||||||
|
* @param {string} event
|
||||||
|
* Event type
|
||||||
|
* @param {string} source
|
||||||
|
* Full-text source of the stylesheet
|
||||||
|
*/
|
||||||
|
_onSourceLoad: function(event, source) {
|
||||||
|
this._state.text = prettifyCSS(source);
|
||||||
|
this.sourceLoaded = true;
|
||||||
|
this.emit("source-load");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forward property-change event from stylesheet.
|
||||||
|
*
|
||||||
|
* @param {string} event
|
||||||
|
* Event type
|
||||||
|
* @param {string} property
|
||||||
|
* Property that has changed on sheet
|
||||||
|
*/
|
||||||
|
_onPropertyChange: function(event, property) {
|
||||||
|
this.emit("property-change", property);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forward error event from stylesheet.
|
||||||
|
*
|
||||||
|
* @param {string} event
|
||||||
|
* Event type
|
||||||
|
* @param {string} errorCode
|
||||||
|
*/
|
||||||
|
_onError: function(event, errorCode) {
|
||||||
|
this.emit("error", errorCode);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create source editor and load state into it.
|
||||||
|
* @param {DOMElement} inputElement
|
||||||
|
* Element to load source editor in
|
||||||
|
*/
|
||||||
|
load: function(inputElement) {
|
||||||
|
this._inputElement = inputElement;
|
||||||
|
|
||||||
|
let sourceEditor = new SourceEditor();
|
||||||
|
let config = {
|
||||||
|
initialText: this._state.text,
|
||||||
|
showLineNumbers: true,
|
||||||
|
mode: SourceEditor.MODES.CSS,
|
||||||
|
readOnly: this._state.readOnly,
|
||||||
|
keys: this._getKeyBindings()
|
||||||
|
};
|
||||||
|
|
||||||
|
sourceEditor.init(inputElement, config, function onSourceEditorReady() {
|
||||||
|
setupBracketCompletion(sourceEditor);
|
||||||
|
sourceEditor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
|
||||||
|
function onTextChanged(event) {
|
||||||
|
this.updateStyleSheet();
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
this._sourceEditor = sourceEditor;
|
||||||
|
|
||||||
|
if (this._focusOnSourceEditorReady) {
|
||||||
|
this._focusOnSourceEditorReady = false;
|
||||||
|
sourceEditor.focus();
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceEditor.setTopIndex(this._state.topIndex);
|
||||||
|
sourceEditor.setSelection(this._state.selection.start,
|
||||||
|
this._state.selection.end);
|
||||||
|
|
||||||
|
this.emit("source-editor-load");
|
||||||
|
}.bind(this));
|
||||||
|
|
||||||
|
sourceEditor.addEventListener(SourceEditor.EVENTS.DIRTY_CHANGED,
|
||||||
|
this._onPropertyChange);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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", (event) => {
|
||||||
|
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.setTopIndex(this._state.topIndex);
|
||||||
|
}
|
||||||
|
this.focus();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggled the disabled state of the underlying stylesheet.
|
||||||
|
*/
|
||||||
|
toggleDisabled: function() {
|
||||||
|
this.styleSheet.toggleDisabled();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.styleSheet.update(this._state.text);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, function onStreamCopied(status) {
|
||||||
|
if (!Components.isSuccessCode(status)) {
|
||||||
|
if (callback) {
|
||||||
|
callback(null);
|
||||||
|
}
|
||||||
|
this.emit("error", SAVE_ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FileUtils.closeSafeFileOutputStream(ostream);
|
||||||
|
// remember filename for next save if any
|
||||||
|
this._friendlyName = null;
|
||||||
|
this.savedFile = returnFile;
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
callback(returnFile);
|
||||||
|
}
|
||||||
|
this.sourceEditor.dirty = false;
|
||||||
|
}.bind(this));
|
||||||
|
};
|
||||||
|
|
||||||
|
showFilePicker(file || this._styleSheetFilePath, true, this._window, onFile);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve custom key bindings objects as expected by SourceEditor.
|
||||||
|
* SourceEditor action names are not displayed to the user.
|
||||||
|
*
|
||||||
|
* @return {array} key binding objects for the source editor
|
||||||
|
*/
|
||||||
|
_getKeyBindings: function() {
|
||||||
|
let bindings = [];
|
||||||
|
|
||||||
|
bindings.push({
|
||||||
|
action: "StyleEditor.save",
|
||||||
|
code: _("saveStyleSheet.commandkey"),
|
||||||
|
accel: true,
|
||||||
|
callback: function save() {
|
||||||
|
this.saveToFile(this.savedFile);
|
||||||
|
return true;
|
||||||
|
}.bind(this)
|
||||||
|
});
|
||||||
|
|
||||||
|
bindings.push({
|
||||||
|
action: "StyleEditor.saveAs",
|
||||||
|
code: _("saveStyleSheet.commandkey"),
|
||||||
|
accel: true,
|
||||||
|
shift: true,
|
||||||
|
callback: function saveAs() {
|
||||||
|
this.saveToFile();
|
||||||
|
return true;
|
||||||
|
}.bind(this)
|
||||||
|
});
|
||||||
|
|
||||||
|
return bindings;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean up for this editor.
|
||||||
|
*/
|
||||||
|
destroy: function() {
|
||||||
|
this.styleSheet.off("source-load", this._onSourceLoad);
|
||||||
|
this.styleSheet.off("property-change", this._onPropertyChange);
|
||||||
|
this.styleSheet.off("error", this._onError);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const TAB_CHARS = "\t";
|
||||||
|
|
||||||
|
const OS = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
|
||||||
|
const LINE_SEPARATOR = OS === "WINNT" ? "\r\n" : "\n";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return string that repeats text for aCount times.
|
||||||
|
*
|
||||||
|
* @param string text
|
||||||
|
* @param number aCount
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
function repeat(text, aCount)
|
||||||
|
{
|
||||||
|
return (new Array(aCount + 1)).join(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prettify minified CSS text.
|
||||||
|
* This prettifies CSS code where there is no indentation in usual places while
|
||||||
|
* keeping original indentation as-is elsewhere.
|
||||||
|
*
|
||||||
|
* @param string text
|
||||||
|
* The CSS source to prettify.
|
||||||
|
* @return string
|
||||||
|
* Prettified CSS source
|
||||||
|
*/
|
||||||
|
function prettifyCSS(text)
|
||||||
|
{
|
||||||
|
// remove initial and terminating HTML comments and surrounding whitespace
|
||||||
|
text = text.replace(/(?:^\s*<!--[\r\n]*)|(?:\s*-->\s*$)/g, "");
|
||||||
|
|
||||||
|
let parts = []; // indented parts
|
||||||
|
let partStart = 0; // start offset of currently parsed part
|
||||||
|
let indent = "";
|
||||||
|
let indentLevel = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < text.length; i++) {
|
||||||
|
let c = text[i];
|
||||||
|
let shouldIndent = false;
|
||||||
|
|
||||||
|
switch (c) {
|
||||||
|
case "}":
|
||||||
|
if (i - partStart > 1) {
|
||||||
|
// there's more than just } on the line, add line
|
||||||
|
parts.push(indent + text.substring(partStart, i));
|
||||||
|
partStart = i;
|
||||||
|
}
|
||||||
|
indent = repeat(TAB_CHARS, --indentLevel);
|
||||||
|
/* fallthrough */
|
||||||
|
case ";":
|
||||||
|
case "{":
|
||||||
|
shouldIndent = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldIndent) {
|
||||||
|
let la = text[i+1]; // one-character lookahead
|
||||||
|
if (!/\s/.test(la)) {
|
||||||
|
// following character should be a new line (or whitespace) but it isn't
|
||||||
|
// force indentation then
|
||||||
|
parts.push(indent + text.substring(partStart, i + 1));
|
||||||
|
if (c == "}") {
|
||||||
|
parts.push(""); // for extra line separator
|
||||||
|
}
|
||||||
|
partStart = i + 1;
|
||||||
|
} else {
|
||||||
|
return text; // assume it is not minified, early exit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == "{") {
|
||||||
|
indent = repeat(TAB_CHARS, ++indentLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parts.join(LINE_SEPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up bracket completion on a given SourceEditor.
|
||||||
|
* This automatically closes the following CSS brackets: "{", "(", "["
|
||||||
|
*
|
||||||
|
* @param SourceEditor sourceEditor
|
||||||
|
*/
|
||||||
|
function setupBracketCompletion(sourceEditor)
|
||||||
|
{
|
||||||
|
let editorElement = sourceEditor.editorElement;
|
||||||
|
let pairs = {
|
||||||
|
123: { // {
|
||||||
|
closeString: "}",
|
||||||
|
closeKeyCode: Ci.nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET
|
||||||
|
},
|
||||||
|
40: { // (
|
||||||
|
closeString: ")",
|
||||||
|
closeKeyCode: Ci.nsIDOMKeyEvent.DOM_VK_0
|
||||||
|
},
|
||||||
|
91: { // [
|
||||||
|
closeString: "]",
|
||||||
|
closeKeyCode: Ci.nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
editorElement.addEventListener("keypress", function onKeyPress(event) {
|
||||||
|
let pair = pairs[event.charCode];
|
||||||
|
if (!pair || event.ctrlKey || event.metaKey ||
|
||||||
|
event.accelKey || event.altKey) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We detected an open bracket, sending closing character
|
||||||
|
let keyCode = pair.closeKeyCode;
|
||||||
|
let charCode = pair.closeString.charCodeAt(0);
|
||||||
|
let modifiers = 0;
|
||||||
|
let utils = editorElement.ownerDocument.defaultView.
|
||||||
|
QueryInterface(Ci.nsIInterfaceRequestor).
|
||||||
|
getInterface(Ci.nsIDOMWindowUtils);
|
||||||
|
let handled = utils.sendKeyEvent("keydown", keyCode, 0, modifiers);
|
||||||
|
utils.sendKeyEvent("keypress", 0, charCode, modifiers, !handled);
|
||||||
|
utils.sendKeyEvent("keyup", keyCode, 0, modifiers);
|
||||||
|
// and rewind caret
|
||||||
|
sourceEditor.setCaretOffset(sourceEditor.getCaretOffset() - 1);
|
||||||
|
}, false);
|
||||||
|
}
|
||||||
|
|
|
@ -51,13 +51,11 @@
|
||||||
<xul:toolbarbutton class="style-editor-newButton devtools-toolbarbutton"
|
<xul:toolbarbutton class="style-editor-newButton devtools-toolbarbutton"
|
||||||
accesskey="&newButton.accesskey;"
|
accesskey="&newButton.accesskey;"
|
||||||
tooltiptext="&newButton.tooltip;"
|
tooltiptext="&newButton.tooltip;"
|
||||||
label="&newButton.label;"
|
label="&newButton.label;"/>
|
||||||
disabled="true"/>
|
|
||||||
<xul:toolbarbutton class="style-editor-importButton devtools-toolbarbutton"
|
<xul:toolbarbutton class="style-editor-importButton devtools-toolbarbutton"
|
||||||
accesskey="&importButton.accesskey;"
|
accesskey="&importButton.accesskey;"
|
||||||
tooltiptext="&importButton.tooltip;"
|
tooltiptext="&importButton.tooltip;"
|
||||||
label="&importButton.label;"
|
label="&importButton.label;"/>
|
||||||
disabled="true"/>
|
|
||||||
</xul:toolbar>
|
</xul:toolbar>
|
||||||
</xul:box>
|
</xul:box>
|
||||||
<xul:box id="splitview-resizer-target" class="splitview-nav-container"
|
<xul:box id="splitview-resizer-target" class="splitview-nav-container"
|
||||||
|
|
|
@ -21,14 +21,10 @@ _BROWSER_TEST_FILES = \
|
||||||
browser_styleeditor_init.js \
|
browser_styleeditor_init.js \
|
||||||
browser_styleeditor_loading.js \
|
browser_styleeditor_loading.js \
|
||||||
browser_styleeditor_new.js \
|
browser_styleeditor_new.js \
|
||||||
browser_styleeditor_passedinsheet.js \
|
|
||||||
browser_styleeditor_pretty.js \
|
browser_styleeditor_pretty.js \
|
||||||
browser_styleeditor_private_perwindowpb.js \
|
browser_styleeditor_private_perwindowpb.js \
|
||||||
browser_styleeditor_readonly.js \
|
|
||||||
browser_styleeditor_reopen.js \
|
|
||||||
browser_styleeditor_sv_keynav.js \
|
browser_styleeditor_sv_keynav.js \
|
||||||
browser_styleeditor_sv_resize.js \
|
browser_styleeditor_sv_resize.js \
|
||||||
browser_styleeditor_bug_826982_location_changed.js \
|
|
||||||
browser_styleeditor_bug_851132_middle_click.js \
|
browser_styleeditor_bug_851132_middle_click.js \
|
||||||
head.js \
|
head.js \
|
||||||
helpers.js \
|
helpers.js \
|
||||||
|
|
|
@ -1,123 +0,0 @@
|
||||||
/* Any copyright is dedicated to the Public Domain.
|
|
||||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
||||||
|
|
||||||
let tempScope = {};
|
|
||||||
Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
|
|
||||||
let TargetFactory = tempScope.TargetFactory;
|
|
||||||
|
|
||||||
function test() {
|
|
||||||
let notificationBox, styleEditor;
|
|
||||||
let alertActive1_called = false;
|
|
||||||
let alertActive2_called = false;
|
|
||||||
|
|
||||||
function startLocationTests() {
|
|
||||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
|
||||||
|
|
||||||
gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
|
|
||||||
runTests(toolbox.getCurrentPanel(), toolbox);
|
|
||||||
}).then(null, console.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
function runTests(aStyleEditor) {
|
|
||||||
styleEditor = aStyleEditor;
|
|
||||||
let para = content.document.querySelector("p");
|
|
||||||
ok(para, "found the paragraph element");
|
|
||||||
is(para.textContent, "init", "paragraph content is correct");
|
|
||||||
|
|
||||||
styleEditor.styleEditorChrome.markDirty();
|
|
||||||
|
|
||||||
notificationBox = gBrowser.getNotificationBox();
|
|
||||||
notificationBox.addEventListener("AlertActive", alertActive1, false);
|
|
||||||
|
|
||||||
gBrowser.selectedBrowser.addEventListener("load", onPageLoad, true);
|
|
||||||
|
|
||||||
content.location = "data:text/html,<div>location change test 1 for " +
|
|
||||||
"styleeditor</div><p>test1</p>";
|
|
||||||
}
|
|
||||||
|
|
||||||
function alertActive1() {
|
|
||||||
alertActive1_called = true;
|
|
||||||
notificationBox.removeEventListener("AlertActive", alertActive1, false);
|
|
||||||
|
|
||||||
let notification = notificationBox.
|
|
||||||
getNotificationWithValue("styleeditor-page-navigation");
|
|
||||||
ok(notification, "found the styleeditor-page-navigation notification");
|
|
||||||
|
|
||||||
// By closing the notification it is expected that page navigation is
|
|
||||||
// canceled.
|
|
||||||
executeSoon(function() {
|
|
||||||
notification.close();
|
|
||||||
locationTest2();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function locationTest2() {
|
|
||||||
// Location did not change.
|
|
||||||
let para = content.document.querySelector("p");
|
|
||||||
ok(para, "found the paragraph element, second time");
|
|
||||||
is(para.textContent, "init", "paragraph content is correct");
|
|
||||||
|
|
||||||
notificationBox.addEventListener("AlertActive", alertActive2, false);
|
|
||||||
|
|
||||||
content.location = "data:text/html,<div>location change test 2 for " +
|
|
||||||
"styleeditor</div><p>test2</p>";
|
|
||||||
}
|
|
||||||
|
|
||||||
function alertActive2() {
|
|
||||||
alertActive2_called = true;
|
|
||||||
notificationBox.removeEventListener("AlertActive", alertActive2, false);
|
|
||||||
|
|
||||||
let notification = notificationBox.
|
|
||||||
getNotificationWithValue("styleeditor-page-navigation");
|
|
||||||
ok(notification, "found the styleeditor-page-navigation notification");
|
|
||||||
|
|
||||||
let buttons = notification.querySelectorAll("button");
|
|
||||||
let buttonLeave = null;
|
|
||||||
for (let i = 0; i < buttons.length; i++) {
|
|
||||||
if (buttons[i].buttonInfo.id == "styleeditor.confirmNavigationAway.buttonLeave") {
|
|
||||||
buttonLeave = buttons[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ok(buttonLeave, "the Leave page button was found");
|
|
||||||
|
|
||||||
// Accept page navigation.
|
|
||||||
executeSoon(function(){
|
|
||||||
buttonLeave.doCommand();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function onPageLoad() {
|
|
||||||
gBrowser.selectedBrowser.removeEventListener("load", onPageLoad, true);
|
|
||||||
|
|
||||||
isnot(content.location.href.indexOf("test2"), -1,
|
|
||||||
"page navigated to the correct location");
|
|
||||||
|
|
||||||
let para = content.document.querySelector("p");
|
|
||||||
ok(para, "found the paragraph element, third time");
|
|
||||||
is(para.textContent, "test2", "paragraph content is correct");
|
|
||||||
|
|
||||||
ok(alertActive1_called, "first notification box has been shown");
|
|
||||||
ok(alertActive2_called, "second notification box has been shown");
|
|
||||||
testEnd();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function testEnd() {
|
|
||||||
notificationBox = null;
|
|
||||||
gBrowser.removeCurrentTab();
|
|
||||||
executeSoon(finish);
|
|
||||||
}
|
|
||||||
|
|
||||||
waitForExplicitFinish();
|
|
||||||
|
|
||||||
gBrowser.selectedTab = gBrowser.addTab();
|
|
||||||
gBrowser.selectedBrowser.addEventListener("load", function onBrowserLoad() {
|
|
||||||
gBrowser.selectedBrowser.removeEventListener("load", onBrowserLoad, true);
|
|
||||||
waitForFocus(startLocationTests, content);
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
content.location = "data:text/html,<div>location change tests for " +
|
|
||||||
"styleeditor.</div><p>init</p>";
|
|
||||||
}
|
|
|
@ -3,37 +3,45 @@
|
||||||
|
|
||||||
const TESTCASE_URI = TEST_BASE + "four.html";
|
const TESTCASE_URI = TEST_BASE + "four.html";
|
||||||
|
|
||||||
|
let gUI;
|
||||||
|
|
||||||
function test() {
|
function test() {
|
||||||
waitForExplicitFinish();
|
waitForExplicitFinish();
|
||||||
|
|
||||||
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
let count = 0;
|
||||||
run(aChrome);
|
addTabAndOpenStyleEditor(function(panel) {
|
||||||
|
gUI = panel.UI;
|
||||||
|
gUI.on("editor-added", function(event, editor) {
|
||||||
|
count++;
|
||||||
|
if (count == 2) {
|
||||||
|
runTests();
|
||||||
|
}
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
content.location = TESTCASE_URI;
|
content.location = TESTCASE_URI;
|
||||||
}
|
}
|
||||||
|
|
||||||
let gSEChrome, timeoutID;
|
let timeoutID;
|
||||||
|
|
||||||
function run(aChrome) {
|
function runTests() {
|
||||||
gSEChrome = aChrome;
|
|
||||||
gBrowser.tabContainer.addEventListener("TabOpen", onTabAdded, false);
|
gBrowser.tabContainer.addEventListener("TabOpen", onTabAdded, false);
|
||||||
aChrome.editors[0].addActionListener({onAttach: onEditor0Attach});
|
gUI.editors[0].getSourceEditor().then(onEditor0Attach);
|
||||||
aChrome.editors[1].addActionListener({onAttach: onEditor1Attach});
|
gUI.editors[1].getSourceEditor().then(onEditor1Attach);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStylesheetNameLinkFor(aEditor) {
|
function getStylesheetNameLinkFor(aEditor) {
|
||||||
return gSEChrome.getSummaryElementForEditor(aEditor).querySelector(".stylesheet-name");
|
return aEditor.summary.querySelector(".stylesheet-name");
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEditor0Attach(aEditor) {
|
function onEditor0Attach(aEditor) {
|
||||||
waitForFocus(function () {
|
waitForFocus(function () {
|
||||||
// left mouse click should focus editor 1
|
// left mouse click should focus editor 1
|
||||||
EventUtils.synthesizeMouseAtCenter(
|
EventUtils.synthesizeMouseAtCenter(
|
||||||
getStylesheetNameLinkFor(gSEChrome.editors[1]),
|
getStylesheetNameLinkFor(gUI.editors[1]),
|
||||||
{button: 0},
|
{button: 0},
|
||||||
gChromeWindow);
|
gPanelWindow);
|
||||||
}, gChromeWindow);
|
}, gPanelWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEditor1Attach(aEditor) {
|
function onEditor1Attach(aEditor) {
|
||||||
|
@ -42,9 +50,9 @@ function onEditor1Attach(aEditor) {
|
||||||
|
|
||||||
// right mouse click should not open a new tab
|
// right mouse click should not open a new tab
|
||||||
EventUtils.synthesizeMouseAtCenter(
|
EventUtils.synthesizeMouseAtCenter(
|
||||||
getStylesheetNameLinkFor(gSEChrome.editors[2]),
|
getStylesheetNameLinkFor(gUI.editors[2]),
|
||||||
{button: 1},
|
{button: 1},
|
||||||
gChromeWindow);
|
gPanelWindow);
|
||||||
|
|
||||||
setTimeout(finish, 0);
|
setTimeout(finish, 0);
|
||||||
}
|
}
|
||||||
|
@ -56,5 +64,5 @@ function onTabAdded() {
|
||||||
|
|
||||||
registerCleanupFunction(function () {
|
registerCleanupFunction(function () {
|
||||||
gBrowser.tabContainer.removeEventListener("TabOpen", onTabAdded, false);
|
gBrowser.tabContainer.removeEventListener("TabOpen", onTabAdded, false);
|
||||||
gSEChrome = null;
|
gUI = null;
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,97 +5,71 @@
|
||||||
// https rather than chrome to improve coverage
|
// https rather than chrome to improve coverage
|
||||||
const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
|
const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
|
||||||
|
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
{
|
{
|
||||||
waitForExplicitFinish();
|
waitForExplicitFinish();
|
||||||
|
|
||||||
let count = 0;
|
let count = 0;
|
||||||
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
addTabAndOpenStyleEditor(function(panel) {
|
||||||
aChrome.addChromeListener({
|
let UI = panel.UI;
|
||||||
onEditorAdded: function (aChrome, aEditor) {
|
UI.on("editor-added", function(event, editor) {
|
||||||
count++;
|
count++;
|
||||||
if (count == 2) {
|
if (count == 2) {
|
||||||
// we test against first stylesheet after all are ready
|
// we test against first stylesheet after all are ready
|
||||||
let editor = aChrome.editors[0];
|
let editor = UI.editors[0];
|
||||||
if (!editor.sourceEditor) {
|
editor.getSourceEditor().then(runTests.bind(this, UI, editor));
|
||||||
editor.addActionListener({
|
|
||||||
onAttach: function (aEditor) {
|
|
||||||
run(aChrome, aEditor);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
run(aChrome, editor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
content.location = TESTCASE_URI;
|
content.location = TESTCASE_URI;
|
||||||
}
|
}
|
||||||
|
|
||||||
function run(aChrome, aEditor)
|
function runTests(UI, editor)
|
||||||
{
|
{
|
||||||
testEnabledToggle(aChrome, aEditor);
|
testEnabledToggle(UI, editor);
|
||||||
}
|
}
|
||||||
|
|
||||||
function testEnabledToggle(aChrome, aEditor)
|
function testEnabledToggle(UI, editor)
|
||||||
{
|
{
|
||||||
is(aEditor, aChrome.editors[0],
|
let summary = editor.summary;
|
||||||
"stylesheet with index 0 is the first stylesheet listed in the UI");
|
let enabledToggle = summary.querySelector(".stylesheet-enabled");
|
||||||
|
ok(enabledToggle, "enabled toggle button exists");
|
||||||
|
|
||||||
let firstStyleSheetEditor = aEditor;
|
is(editor.styleSheet.disabled, false,
|
||||||
let firstStyleSheetUI = aChrome.getSummaryElementForEditor(aEditor);
|
|
||||||
let enabledToggle = firstStyleSheetUI.querySelector(".stylesheet-enabled");
|
|
||||||
|
|
||||||
is(firstStyleSheetEditor.contentDocument.styleSheets[0].disabled, false,
|
|
||||||
"first stylesheet is initially enabled");
|
"first stylesheet is initially enabled");
|
||||||
is(firstStyleSheetEditor.hasFlag("disabled"), false,
|
|
||||||
"first stylesheet is initially enabled, it does not have DISABLED flag");
|
is(summary.classList.contains("disabled"), false,
|
||||||
is(firstStyleSheetUI.classList.contains("disabled"), false,
|
|
||||||
"first stylesheet is initially enabled, UI does not have DISABLED class");
|
"first stylesheet is initially enabled, UI does not have DISABLED class");
|
||||||
|
|
||||||
let disabledToggleCount = 0;
|
let disabledToggleCount = 0;
|
||||||
firstStyleSheetEditor.addActionListener({
|
editor.on("property-change", function(event, property) {
|
||||||
onFlagChange: function (aEditor, aFlagName) {
|
if (property != "disabled") {
|
||||||
if (aFlagName != "disabled") {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
disabledToggleCount++;
|
|
||||||
|
|
||||||
if (disabledToggleCount == 1) {
|
|
||||||
is(firstStyleSheetEditor, aEditor,
|
|
||||||
"FlagChange handler triggered for DISABLED flag on the first editor");
|
|
||||||
is(firstStyleSheetEditor.styleSheet.disabled, true,
|
|
||||||
"first stylesheet is now disabled");
|
|
||||||
is(firstStyleSheetEditor.hasFlag("disabled"), true,
|
|
||||||
"first stylesheet is now disabled, it has DISABLED flag");
|
|
||||||
is(firstStyleSheetUI.classList.contains("disabled"), true,
|
|
||||||
"first stylesheet is now disabled, UI has DISABLED class");
|
|
||||||
|
|
||||||
// now toggle it back to enabled
|
|
||||||
waitForFocus(function () {
|
|
||||||
EventUtils.synthesizeMouseAtCenter(enabledToggle, {}, gChromeWindow);
|
|
||||||
}, gChromeWindow);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// disabledToggleCount == 2
|
|
||||||
is(firstStyleSheetEditor, aEditor,
|
|
||||||
"FlagChange handler triggered for DISABLED flag on the first editor (2)");
|
|
||||||
is(firstStyleSheetEditor.styleSheet.disabled, false,
|
|
||||||
"first stylesheet is now enabled again");
|
|
||||||
is(firstStyleSheetEditor.hasFlag("disabled"), false,
|
|
||||||
"first stylesheet is now enabled again, it does not have DISABLED flag");
|
|
||||||
is(firstStyleSheetUI.classList.contains("disabled"), false,
|
|
||||||
"first stylesheet is now enabled again, UI does not have DISABLED class");
|
|
||||||
|
|
||||||
finish();
|
|
||||||
}
|
}
|
||||||
|
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 () {
|
waitForFocus(function () {
|
||||||
EventUtils.synthesizeMouseAtCenter(enabledToggle, {}, gChromeWindow);
|
EventUtils.synthesizeMouseAtCenter(enabledToggle, {}, gPanelWindow);
|
||||||
}, gChromeWindow);
|
}, gPanelWindow);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,39 +21,28 @@ function test()
|
||||||
|
|
||||||
copy(TESTCASE_URI_HTML, "simple.html", function(htmlFile) {
|
copy(TESTCASE_URI_HTML, "simple.html", function(htmlFile) {
|
||||||
copy(TESTCASE_URI_CSS, "simple.css", function(cssFile) {
|
copy(TESTCASE_URI_CSS, "simple.css", function(cssFile) {
|
||||||
|
addTabAndOpenStyleEditor(function(panel) {
|
||||||
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
let UI = panel.UI;
|
||||||
aChrome.addChromeListener({
|
UI.on("editor-added", function(event, editor) {
|
||||||
onEditorAdded: function (aChrome, aEditor) {
|
if (editor.styleSheet.styleSheetIndex != 0) {
|
||||||
if (aEditor.styleSheetIndex != 0) {
|
return; // we want to test against the first stylesheet
|
||||||
return; // we want to test against the first stylesheet
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aEditor.sourceEditor) {
|
|
||||||
run(aEditor); // already attached to input element
|
|
||||||
} else {
|
|
||||||
aEditor.addActionListener({
|
|
||||||
onAttach: run
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
let editor = UI.editors[0];
|
||||||
|
editor.getSourceEditor().then(runTests.bind(this, editor));
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let uri = Services.io.newFileURI(htmlFile);
|
let uri = Services.io.newFileURI(htmlFile);
|
||||||
let filePath = uri.resolve("");
|
let filePath = uri.resolve("");
|
||||||
|
|
||||||
content.location = filePath;
|
content.location = filePath;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function run(aEditor)
|
function runTests(editor)
|
||||||
{
|
{
|
||||||
aEditor.saveToFile(null, function (aFile) {
|
editor.saveToFile(null, function (file) {
|
||||||
ok(aFile, "file should get saved directly when using a file:// URI");
|
ok(file, "file should get saved directly when using a file:// URI");
|
||||||
|
|
||||||
gChromeWindow.close();
|
|
||||||
finish();
|
finish();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,27 +13,21 @@ const FILENAME = "styleeditor-import-test.css";
|
||||||
const SOURCE = "body{background:red;}";
|
const SOURCE = "body{background:red;}";
|
||||||
|
|
||||||
|
|
||||||
|
let gUI;
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
{
|
{
|
||||||
waitForExplicitFinish();
|
waitForExplicitFinish();
|
||||||
|
|
||||||
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
addTabAndOpenStyleEditor(function(panel) {
|
||||||
aChrome.addChromeListener({
|
gUI = panel.UI;
|
||||||
onEditorAdded: testEditorAdded
|
gUI.on("editor-added", testEditorAdded);
|
||||||
});
|
|
||||||
run(aChrome);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
content.location = TESTCASE_URI;
|
content.location = TESTCASE_URI;
|
||||||
}
|
}
|
||||||
|
|
||||||
function run(aChrome)
|
function testImport()
|
||||||
{
|
|
||||||
is(aChrome.editors.length, 2,
|
|
||||||
"there is 2 stylesheets initially");
|
|
||||||
}
|
|
||||||
|
|
||||||
function testImport(aChrome, aEditor)
|
|
||||||
{
|
{
|
||||||
// create file to import first
|
// create file to import first
|
||||||
let file = FileUtils.getFile("ProfD", [FILENAME]);
|
let file = FileUtils.getFile("ProfD", [FILENAME]);
|
||||||
|
@ -46,42 +40,37 @@ function testImport(aChrome, aEditor)
|
||||||
FileUtils.closeSafeFileOutputStream(ostream);
|
FileUtils.closeSafeFileOutputStream(ostream);
|
||||||
|
|
||||||
// click the import button now that the file to import is ready
|
// click the import button now that the file to import is ready
|
||||||
aChrome._mockImportFile = file;
|
gUI._mockImportFile = file;
|
||||||
|
|
||||||
waitForFocus(function () {
|
waitForFocus(function () {
|
||||||
let document = gChromeWindow.document
|
let document = gPanelWindow.document
|
||||||
let importButton = document.querySelector(".style-editor-importButton");
|
let importButton = document.querySelector(".style-editor-importButton");
|
||||||
EventUtils.synthesizeMouseAtCenter(importButton, {}, gChromeWindow);
|
ok(importButton, "import button exists");
|
||||||
}, gChromeWindow);
|
|
||||||
|
EventUtils.synthesizeMouseAtCenter(importButton, {}, gPanelWindow);
|
||||||
|
}, gPanelWindow);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let gAddedCount = 0;
|
let gAddedCount = 0;
|
||||||
function testEditorAdded(aChrome, aEditor)
|
function testEditorAdded(aEvent, aEditor)
|
||||||
{
|
{
|
||||||
if (++gAddedCount == 2) {
|
if (++gAddedCount == 2) {
|
||||||
// test import after the 2 initial stylesheets have been loaded
|
// test import after the 2 initial stylesheets have been loaded
|
||||||
if (!aChrome.editors[0].sourceEditor) {
|
gUI.editors[0].getSourceEditor().then(function() {
|
||||||
aChrome.editors[0].addActionListener({
|
testImport();
|
||||||
onAttach: function () {
|
});
|
||||||
testImport(aChrome);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
testImport(aChrome);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!aEditor.hasFlag("imported")) {
|
if (!aEditor.savedFile) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok(!aEditor.hasFlag("inline"),
|
is(aEditor.savedFile.leafName, FILENAME,
|
||||||
"imported stylesheet does not have INLINE flag");
|
|
||||||
ok(aEditor.savedFile,
|
|
||||||
"imported stylesheet will be saved directly into the same file");
|
"imported stylesheet will be saved directly into the same file");
|
||||||
is(aEditor.getFriendlyName(), FILENAME,
|
is(aEditor.friendlyName, FILENAME,
|
||||||
"imported stylesheet has the same name as the filename");
|
"imported stylesheet has the same name as the filename");
|
||||||
|
|
||||||
|
gUI = null;
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,30 +5,39 @@
|
||||||
// http rather than chrome to improve coverage
|
// http rather than chrome to improve coverage
|
||||||
const TESTCASE_URI = TEST_BASE_HTTP + "import.html";
|
const TESTCASE_URI = TEST_BASE_HTTP + "import.html";
|
||||||
|
|
||||||
|
let gUI;
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
{
|
{
|
||||||
waitForExplicitFinish();
|
waitForExplicitFinish();
|
||||||
|
|
||||||
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
addTabAndOpenStyleEditor(function(panel) {
|
||||||
run(aChrome);
|
gUI = panel.UI;
|
||||||
|
gUI.on("editor-added", onEditorAdded);
|
||||||
});
|
});
|
||||||
|
|
||||||
content.location = TESTCASE_URI;
|
content.location = TESTCASE_URI;
|
||||||
}
|
}
|
||||||
|
|
||||||
function run(aChrome)
|
let gAddedCount = 0;
|
||||||
|
function onEditorAdded()
|
||||||
{
|
{
|
||||||
is(aChrome.editors.length, 3,
|
if (++gAddedCount != 3) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
is(gUI.editors.length, 3,
|
||||||
"there are 3 stylesheets after loading @imports");
|
"there are 3 stylesheets after loading @imports");
|
||||||
|
|
||||||
is(aChrome.editors[0]._styleSheet.href, TEST_BASE_HTTP + "simple.css",
|
is(gUI.editors[0].styleSheet.href, TEST_BASE_HTTP + "simple.css",
|
||||||
"stylesheet 1 is simple.css");
|
"stylesheet 1 is simple.css");
|
||||||
|
|
||||||
is(aChrome.editors[1]._styleSheet.href, TEST_BASE_HTTP + "import.css",
|
is(gUI.editors[1].styleSheet.href, TEST_BASE_HTTP + "import.css",
|
||||||
"stylesheet 2 is import.css");
|
"stylesheet 2 is import.css");
|
||||||
|
|
||||||
is(aChrome.editors[2]._styleSheet.href, TEST_BASE_HTTP + "import2.css",
|
is(gUI.editors[2].styleSheet.href, TEST_BASE_HTTP + "import2.css",
|
||||||
"stylesheet 3 is import2.css");
|
"stylesheet 3 is import2.css");
|
||||||
|
|
||||||
|
gUI = null;
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,71 +4,52 @@
|
||||||
|
|
||||||
const TESTCASE_URI = TEST_BASE + "simple.html";
|
const TESTCASE_URI = TEST_BASE + "simple.html";
|
||||||
|
|
||||||
|
let gUI;
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
{
|
{
|
||||||
waitForExplicitFinish();
|
waitForExplicitFinish();
|
||||||
|
|
||||||
launchStyleEditorChrome(function(aChrome) {
|
addTabAndOpenStyleEditor(function(panel) {
|
||||||
aChrome.addChromeListener({
|
gUI = panel.UI;
|
||||||
onEditorAdded: testEditorAdded
|
gUI.on("editor-added", testEditorAdded);
|
||||||
});
|
|
||||||
run(aChrome);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
content.location = TESTCASE_URI;
|
content.location = TESTCASE_URI;
|
||||||
}
|
}
|
||||||
|
|
||||||
function run(aChrome)
|
|
||||||
{
|
|
||||||
is(aChrome.contentWindow.document.readyState, "complete",
|
|
||||||
"content document is complete");
|
|
||||||
|
|
||||||
let SEC = gChromeWindow.styleEditorChrome;
|
|
||||||
is(SEC, aChrome, "StyleEditorChrome object exists as new window property");
|
|
||||||
|
|
||||||
// check editors are instantiated
|
|
||||||
is(SEC.editors.length, 2,
|
|
||||||
"there is two StyleEditor instances managed");
|
|
||||||
ok(SEC.editors[0].styleSheetIndex < SEC.editors[1].styleSheetIndex,
|
|
||||||
"editors are ordered by styleSheetIndex");
|
|
||||||
}
|
|
||||||
|
|
||||||
let gEditorAddedCount = 0;
|
let gEditorAddedCount = 0;
|
||||||
function testEditorAdded(aChrome, aEditor)
|
function testEditorAdded(aEvent, aEditor)
|
||||||
{
|
{
|
||||||
if (aEditor.styleSheetIndex == 0) {
|
if (aEditor.styleSheet.styleSheetIndex == 0) {
|
||||||
gEditorAddedCount++;
|
gEditorAddedCount++;
|
||||||
testFirstStyleSheetEditor(aChrome, aEditor);
|
testFirstStyleSheetEditor(aEditor);
|
||||||
}
|
}
|
||||||
if (aEditor.styleSheetIndex == 1) {
|
if (aEditor.styleSheet.styleSheetIndex == 1) {
|
||||||
gEditorAddedCount++;
|
gEditorAddedCount++;
|
||||||
testSecondStyleSheetEditor(aChrome, aEditor);
|
testSecondStyleSheetEditor(aEditor);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gEditorAddedCount == 2) {
|
if (gEditorAddedCount == 2) {
|
||||||
|
gUI = null;
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function testFirstStyleSheetEditor(aChrome, aEditor)
|
function testFirstStyleSheetEditor(aEditor)
|
||||||
{
|
{
|
||||||
// Note: the html <link> contains charset="UTF-8".
|
// Note: the html <link> contains charset="UTF-8".
|
||||||
ok(aEditor._state.text.indexOf("\u263a") >= 0,
|
ok(aEditor._state.text.indexOf("\u263a") >= 0,
|
||||||
"stylesheet is unicode-aware.");
|
"stylesheet is unicode-aware.");
|
||||||
|
|
||||||
//testing TESTCASE's simple.css stylesheet
|
//testing TESTCASE's simple.css stylesheet
|
||||||
is(aEditor.styleSheetIndex, 0,
|
is(aEditor.styleSheet.styleSheetIndex, 0,
|
||||||
"first stylesheet is at index 0");
|
"first stylesheet is at index 0");
|
||||||
|
|
||||||
is(aEditor, aChrome.editors[0],
|
is(aEditor, gUI.editors[0],
|
||||||
"first stylesheet corresponds to StyleEditorChrome.editors[0]");
|
"first stylesheet corresponds to StyleEditorChrome.editors[0]");
|
||||||
|
|
||||||
ok(!aEditor.hasFlag("inline"),
|
let summary = aEditor.summary;
|
||||||
"first stylesheet does not have INLINE flag");
|
|
||||||
|
|
||||||
let summary = aChrome.getSummaryElementForEditor(aEditor);
|
|
||||||
ok(!summary.classList.contains("inline"),
|
|
||||||
"first stylesheet UI does not have INLINE class");
|
|
||||||
|
|
||||||
let name = summary.querySelector(".stylesheet-name > label").getAttribute("value");
|
let name = summary.querySelector(".stylesheet-name > label").getAttribute("value");
|
||||||
is(name, "simple.css",
|
is(name, "simple.css",
|
||||||
|
@ -82,21 +63,16 @@ function testFirstStyleSheetEditor(aChrome, aEditor)
|
||||||
"first stylesheet UI is focused/active");
|
"first stylesheet UI is focused/active");
|
||||||
}
|
}
|
||||||
|
|
||||||
function testSecondStyleSheetEditor(aChrome, aEditor)
|
function testSecondStyleSheetEditor(aEditor)
|
||||||
{
|
{
|
||||||
//testing TESTCASE's inline stylesheet
|
//testing TESTCASE's inline stylesheet
|
||||||
is(aEditor.styleSheetIndex, 1,
|
is(aEditor.styleSheet.styleSheetIndex, 1,
|
||||||
"second stylesheet is at index 1");
|
"second stylesheet is at index 1");
|
||||||
|
|
||||||
is(aEditor, aChrome.editors[1],
|
is(aEditor, gUI.editors[1],
|
||||||
"second stylesheet corresponds to StyleEditorChrome.editors[1]");
|
"second stylesheet corresponds to StyleEditorChrome.editors[1]");
|
||||||
|
|
||||||
ok(aEditor.hasFlag("inline"),
|
let summary = aEditor.summary;
|
||||||
"second stylesheet has INLINE flag");
|
|
||||||
|
|
||||||
let summary = aChrome.getSummaryElementForEditor(aEditor);
|
|
||||||
ok(summary.classList.contains("inline"),
|
|
||||||
"second stylesheet UI has INLINE class");
|
|
||||||
|
|
||||||
let name = summary.querySelector(".stylesheet-name > label").getAttribute("value");
|
let name = summary.querySelector(".stylesheet-name > label").getAttribute("value");
|
||||||
ok(/^<.*>$/.test(name),
|
ok(/^<.*>$/.test(name),
|
||||||
|
|
|
@ -9,29 +9,31 @@ function test()
|
||||||
{
|
{
|
||||||
waitForExplicitFinish();
|
waitForExplicitFinish();
|
||||||
|
|
||||||
gBrowser.selectedTab = gBrowser.addTab();
|
|
||||||
|
|
||||||
// launch Style Editor right when the tab is created (before load)
|
// launch Style Editor right when the tab is created (before load)
|
||||||
// this checks that the Style Editor still launches correctly when it is opened
|
// 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
|
// *while* the page is still loading. The Style Editor should not signal that
|
||||||
// it is loaded until the accompanying content page is loaded.
|
// it is loaded until the accompanying content page is loaded.
|
||||||
launchStyleEditorChrome(function (aChrome) {
|
|
||||||
|
addTabAndOpenStyleEditor(function(panel) {
|
||||||
|
panel.UI.on("editor-added", testEditorAdded);
|
||||||
|
|
||||||
content.location = TESTCASE_URI;
|
content.location = TESTCASE_URI;
|
||||||
is(aChrome.contentWindow.document.readyState, "complete",
|
|
||||||
"content document is complete");
|
|
||||||
|
|
||||||
let root = gChromeWindow.document.querySelector(".splitview-root");
|
|
||||||
ok(!root.classList.contains("loading"),
|
|
||||||
"style editor root element does not have 'loading' class name anymore");
|
|
||||||
|
|
||||||
let button = gChromeWindow.document.querySelector(".style-editor-newButton");
|
|
||||||
ok(!button.hasAttribute("disabled"),
|
|
||||||
"new style sheet button is enabled");
|
|
||||||
|
|
||||||
button = gChromeWindow.document.querySelector(".style-editor-importButton");
|
|
||||||
ok(!button.hasAttribute("disabled"),
|
|
||||||
"import button is enabled");
|
|
||||||
|
|
||||||
finish();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
|
@ -4,166 +4,121 @@
|
||||||
|
|
||||||
const TESTCASE_URI = TEST_BASE + "simple.html";
|
const TESTCASE_URI = TEST_BASE + "simple.html";
|
||||||
|
|
||||||
const TRANSITION_CLASS = "moz-styleeditor-transitioning";
|
let TRANSITION_CLASS = "moz-styleeditor-transitioning";
|
||||||
const TESTCASE_CSS_SOURCE = "body{background-color:red;";
|
let TESTCASE_CSS_SOURCE = "body{background-color:red;";
|
||||||
|
|
||||||
|
let gUI;
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
{
|
{
|
||||||
waitForExplicitFinish();
|
waitForExplicitFinish();
|
||||||
|
|
||||||
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
addTabAndOpenStyleEditor(function(panel) {
|
||||||
aChrome.addChromeListener({
|
gUI = panel.UI;
|
||||||
onEditorAdded: testEditorAdded
|
gUI.on("editor-added", testEditorAdded);
|
||||||
});
|
|
||||||
run(aChrome);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
content.location = TESTCASE_URI;
|
content.location = TESTCASE_URI;
|
||||||
}
|
}
|
||||||
|
|
||||||
function run(aChrome)
|
|
||||||
{
|
|
||||||
is(aChrome.editors.length, 2,
|
|
||||||
"there is 2 stylesheets initially");
|
|
||||||
}
|
|
||||||
|
|
||||||
let gAddedCount = 0; // to add new stylesheet after the 2 initial stylesheets
|
let gAddedCount = 0; // to add new stylesheet after the 2 initial stylesheets
|
||||||
let gNewEditor; // to make sure only one new stylesheet got created
|
let gNewEditor; // to make sure only one new stylesheet got created
|
||||||
let gUpdateCount = 0; // to make sure only one Update event is triggered
|
|
||||||
let gCommitCount = 0; // to make sure only one Commit event is triggered
|
|
||||||
let gTransitionEndCount = 0;
|
|
||||||
let gOriginalStyleSheet;
|
|
||||||
let gOriginalOwnerNode;
|
|
||||||
let gOriginalHref;
|
let gOriginalHref;
|
||||||
|
|
||||||
|
function testEditorAdded(aEvent, aEditor)
|
||||||
function finishOnTransitionEndAndCommit() {
|
|
||||||
if (gCommitCount && gTransitionEndCount) {
|
|
||||||
is(gUpdateCount, 1, "received one Update event");
|
|
||||||
is(gCommitCount, 1, "received one Commit event");
|
|
||||||
is(gTransitionEndCount, 1, "received one transitionend event");
|
|
||||||
|
|
||||||
if (gNewEditor) {
|
|
||||||
is(gNewEditor.styleSheet, gOriginalStyleSheet,
|
|
||||||
"style sheet object did not change");
|
|
||||||
is(gNewEditor.styleSheet.ownerNode, gOriginalOwnerNode,
|
|
||||||
"style sheet owner node did not change");
|
|
||||||
is(gNewEditor.styleSheet.href, gOriginalHref,
|
|
||||||
"style sheet href did not change");
|
|
||||||
|
|
||||||
gNewEditor = null;
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function testEditorAdded(aChrome, aEditor)
|
|
||||||
{
|
{
|
||||||
gAddedCount++;
|
gAddedCount++;
|
||||||
if (gAddedCount == 2) {
|
if (gAddedCount == 2) {
|
||||||
waitForFocus(function () { // create a new style sheet
|
waitForFocus(function () {// create a new style sheet
|
||||||
let newButton = gChromeWindow.document.querySelector(".style-editor-newButton");
|
let newButton = gPanelWindow.document.querySelector(".style-editor-newButton");
|
||||||
EventUtils.synthesizeMouseAtCenter(newButton, {}, gChromeWindow);
|
ok(newButton, "'new' button exists");
|
||||||
}, gChromeWindow);
|
|
||||||
|
EventUtils.synthesizeMouseAtCenter(newButton, {}, gPanelWindow);
|
||||||
|
}, gPanelWindow);
|
||||||
}
|
}
|
||||||
if (gAddedCount != 3) {
|
if (gAddedCount < 3) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ok(!gNewEditor, "creating a new stylesheet triggers one EditorAdded event");
|
ok(!gNewEditor, "creating a new stylesheet triggers one EditorAdded event");
|
||||||
gNewEditor = aEditor; // above test will fail if we get a duplicate event
|
gNewEditor = aEditor; // above test will fail if we get a duplicate event
|
||||||
|
|
||||||
is(aChrome.editors.length, 3,
|
is(gUI.editors.length, 3,
|
||||||
"creating a new stylesheet added a new StyleEditor instance");
|
"creating a new stylesheet added a new StyleEditor instance");
|
||||||
|
|
||||||
let listener = {
|
aEditor.getSourceEditor().then(testEditor);
|
||||||
onAttach: function (aEditor) {
|
|
||||||
waitForFocus(function () {
|
|
||||||
gOriginalStyleSheet = aEditor.styleSheet;
|
|
||||||
gOriginalOwnerNode = aEditor.styleSheet.ownerNode;
|
|
||||||
gOriginalHref = aEditor.styleSheet.href;
|
|
||||||
|
|
||||||
ok(aEditor.isLoaded,
|
aEditor.styleSheet.once("style-applied", function() {
|
||||||
"new editor is loaded when attached");
|
// when changes have been completely applied to live stylesheet after transisiton
|
||||||
ok(aEditor.hasFlag("new"),
|
let summary = aEditor.summary;
|
||||||
"new editor has NEW flag");
|
let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
|
||||||
ok(aEditor.hasFlag("unsaved"),
|
is(parseInt(ruleCount), 1,
|
||||||
"new editor has UNSAVED flag");
|
"new editor shows 1 rule after modification");
|
||||||
|
|
||||||
ok(aEditor.inputElement,
|
ok(!content.document.documentElement.classList.contains(TRANSITION_CLASS),
|
||||||
"new editor has an input element attached");
|
"StyleEditor's transition class has been removed from content");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
ok(aEditor.sourceEditor.hasFocus(),
|
function testEditor(aEditor) {
|
||||||
"new editor has focus");
|
waitForFocus(function () {
|
||||||
|
gOriginalHref = aEditor.styleSheet.href;
|
||||||
|
|
||||||
let summary = aChrome.getSummaryElementForEditor(aEditor);
|
let 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);
|
ok(aEditor.sourceLoaded,
|
||||||
is(computedStyle.backgroundColor, "rgb(255, 255, 255)",
|
"new editor is loaded when attached");
|
||||||
"content's background color is initially white");
|
ok(aEditor.isNew,
|
||||||
|
"new editor has isNew flag");
|
||||||
|
|
||||||
EventUtils.synthesizeKey("[", {accelKey: true}, gChromeWindow);
|
ok(aEditor.sourceEditor.hasFocus(),
|
||||||
is(aEditor.sourceEditor.getText(), "",
|
"new editor has focus");
|
||||||
"Nothing happened as it is a known shortcut in source editor");
|
|
||||||
|
|
||||||
EventUtils.synthesizeKey("]", {accelKey: true}, gChromeWindow);
|
let summary = aEditor.summary;
|
||||||
is(aEditor.sourceEditor.getText(), "",
|
let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
|
||||||
"Nothing happened as it is a known shortcut in source editor");
|
is(parseInt(ruleCount), 0,
|
||||||
|
"new editor initially shows 0 rules");
|
||||||
|
|
||||||
for each (let c in TESTCASE_CSS_SOURCE) {
|
let computedStyle = content.getComputedStyle(content.document.body, null);
|
||||||
EventUtils.synthesizeKey(c, {}, gChromeWindow);
|
is(computedStyle.backgroundColor, "rgb(255, 255, 255)",
|
||||||
}
|
"content's background color is initially white");
|
||||||
|
|
||||||
is(aEditor.sourceEditor.getText(), TESTCASE_CSS_SOURCE + "}",
|
EventUtils.synthesizeKey("[", {accelKey: true}, gPanelWindow);
|
||||||
"rule bracket has been auto-closed");
|
is(aEditor.sourceEditor.getText(), "",
|
||||||
|
"Nothing happened as it is a known shortcut in source editor");
|
||||||
|
|
||||||
// we know that the testcase above will start a CSS transition
|
EventUtils.synthesizeKey("]", {accelKey: true}, gPanelWindow);
|
||||||
content.addEventListener("transitionend", function () {
|
is(aEditor.sourceEditor.getText(), "",
|
||||||
gTransitionEndCount++;
|
"Nothing happened as it is a known shortcut in source editor");
|
||||||
|
|
||||||
let computedStyle = content.getComputedStyle(content.document.body, null);
|
for each (let c in TESTCASE_CSS_SOURCE) {
|
||||||
is(computedStyle.backgroundColor, "rgb(255, 0, 0)",
|
EventUtils.synthesizeKey(c, {}, gPanelWindow);
|
||||||
"content's background color has been updated to red");
|
}
|
||||||
|
|
||||||
executeSoon(finishOnTransitionEndAndCommit);
|
is(aEditor.sourceEditor.getText(), TESTCASE_CSS_SOURCE + "}",
|
||||||
}, false);
|
"rule bracket has been auto-closed");
|
||||||
}, gChromeWindow) ;
|
|
||||||
},
|
|
||||||
|
|
||||||
onUpdate: function (aEditor) {
|
ok(aEditor.unsaved,
|
||||||
gUpdateCount++;
|
"new editor has unsaved flag");
|
||||||
|
|
||||||
ok(content.document.documentElement.classList.contains(TRANSITION_CLASS),
|
// we know that the testcase above will start a CSS transition
|
||||||
"StyleEditor's transition class has been added to content");
|
content.addEventListener("transitionend", onTransitionEnd, false);
|
||||||
},
|
}, gPanelWindow) ;
|
||||||
|
}
|
||||||
|
|
||||||
onCommit: function (aEditor) {
|
function onTransitionEnd() {
|
||||||
gCommitCount++;
|
content.removeEventListener("transitionend", onTransitionEnd, false);
|
||||||
|
|
||||||
ok(aEditor.hasFlag("new"),
|
let computedStyle = content.getComputedStyle(content.document.body, null);
|
||||||
"new editor still has NEW flag");
|
is(computedStyle.backgroundColor, "rgb(255, 0, 0)",
|
||||||
ok(aEditor.hasFlag("unsaved"),
|
"content's background color has been updated to red");
|
||||||
"new editor has UNSAVED flag after modification");
|
|
||||||
|
|
||||||
let summary = aChrome.getSummaryElementForEditor(aEditor);
|
if (gNewEditor) {
|
||||||
let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
|
is(gNewEditor.styleSheet.href, gOriginalHref,
|
||||||
is(parseInt(ruleCount), 1,
|
"style sheet href did not change");
|
||||||
"new editor shows 1 rule after modification");
|
|
||||||
|
|
||||||
ok(!content.document.documentElement.classList.contains(TRANSITION_CLASS),
|
gNewEditor = null;
|
||||||
"StyleEditor's transition class has been removed from content");
|
gUI = null;
|
||||||
|
finish();
|
||||||
aEditor.removeActionListener(listener);
|
|
||||||
|
|
||||||
executeSoon(finishOnTransitionEndAndCommit);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
aEditor.addActionListener(listener);
|
|
||||||
if (aEditor.sourceEditor) {
|
|
||||||
listener.onAttach(aEditor);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
/* 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";
|
|
||||||
const LINE = 6;
|
|
||||||
const COL = 2;
|
|
||||||
|
|
||||||
function test()
|
|
||||||
{
|
|
||||||
let editor = null;
|
|
||||||
let sheet = null;
|
|
||||||
|
|
||||||
waitForExplicitFinish();
|
|
||||||
|
|
||||||
gBrowser.selectedTab = gBrowser.addTab();
|
|
||||||
gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
|
|
||||||
gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
|
|
||||||
run();
|
|
||||||
}, true);
|
|
||||||
content.location = TESTCASE_URI;
|
|
||||||
|
|
||||||
function run()
|
|
||||||
{
|
|
||||||
sheet = content.document.styleSheets[1];
|
|
||||||
launchStyleEditorChrome(function attachListeners(aChrome) {
|
|
||||||
aChrome.addChromeListener({
|
|
||||||
onEditorAdded: checkSourceEditor
|
|
||||||
});
|
|
||||||
}, sheet, LINE, COL);
|
|
||||||
}
|
|
||||||
|
|
||||||
function checkSourceEditor(aChrome, aEditor)
|
|
||||||
{
|
|
||||||
aChrome.removeChromeListener(this);
|
|
||||||
if (!aEditor.sourceEditor) {
|
|
||||||
aEditor.addActionListener({
|
|
||||||
onAttach: function (aEditor) {
|
|
||||||
aEditor.removeActionListener(this);
|
|
||||||
validate(aEditor);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
validate(aEditor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function validate(aEditor)
|
|
||||||
{
|
|
||||||
info("validating style editor");
|
|
||||||
let sourceEditor = aEditor.sourceEditor;
|
|
||||||
let caretPosition = sourceEditor.getCaretPosition();
|
|
||||||
is(caretPosition.line, LINE - 1, "caret row is correct"); // index based
|
|
||||||
is(caretPosition.col, COL - 1, "caret column is correct");
|
|
||||||
is(aEditor.styleSheet, sheet, "loaded stylesheet matches document stylesheet");
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,22 +4,18 @@
|
||||||
|
|
||||||
const TESTCASE_URI = TEST_BASE + "minified.html";
|
const TESTCASE_URI = TEST_BASE + "minified.html";
|
||||||
|
|
||||||
|
let gUI;
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
{
|
{
|
||||||
waitForExplicitFinish();
|
waitForExplicitFinish();
|
||||||
|
|
||||||
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
addTabAndOpenStyleEditor(function(panel) {
|
||||||
aChrome.addChromeListener({
|
gUI = panel.UI;
|
||||||
onEditorAdded: function (aChrome, aEditor) {
|
gUI.on("editor-added", function(event, editor) {
|
||||||
if (aEditor.sourceEditor) {
|
editor.getSourceEditor().then(function() {
|
||||||
run(aEditor); // already attached to input element
|
testEditor(editor);
|
||||||
} else {
|
});
|
||||||
aEditor.addActionListener({
|
|
||||||
onAttach: run
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -27,21 +23,20 @@ function test()
|
||||||
}
|
}
|
||||||
|
|
||||||
let editorTestedCount = 0;
|
let editorTestedCount = 0;
|
||||||
function run(aEditor)
|
function testEditor(aEditor)
|
||||||
{
|
{
|
||||||
if (aEditor.styleSheetIndex == 0) {
|
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";
|
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";
|
||||||
let prettifiedSourceRE = new RegExp(prettifiedSource);
|
let prettifiedSourceRE = new RegExp(prettifiedSource);
|
||||||
|
|
||||||
ok(prettifiedSourceRE.test(aEditor.sourceEditor.getText()),
|
ok(prettifiedSourceRE.test(aEditor.sourceEditor.getText()),
|
||||||
"minified source has been prettified automatically");
|
"minified source has been prettified automatically");
|
||||||
editorTestedCount++;
|
editorTestedCount++;
|
||||||
let chrome = gChromeWindow.styleEditorChrome;
|
let summary = gUI.editors[1].summary;
|
||||||
let summary = chrome.getSummaryElementForEditor(chrome.editors[1]);
|
EventUtils.synthesizeMouseAtCenter(summary, {}, gPanelWindow);
|
||||||
EventUtils.synthesizeMouseAtCenter(summary, {}, gChromeWindow);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (aEditor.styleSheetIndex == 1) {
|
if (aEditor.styleSheet.styleSheetIndex == 1) {
|
||||||
let originalSource = "body \{ background\: red; \}\r?\ndiv \{\r?\nfont\-size\: 5em;\r?\ncolor\: red\r?\n\}";
|
let originalSource = "body \{ background\: red; \}\r?\ndiv \{\r?\nfont\-size\: 5em;\r?\ncolor\: red\r?\n\}";
|
||||||
let originalSourceRE = new RegExp(originalSource);
|
let originalSourceRE = new RegExp(originalSource);
|
||||||
|
|
||||||
|
@ -51,6 +46,7 @@ function run(aEditor)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (editorTestedCount == 2) {
|
if (editorTestedCount == 2) {
|
||||||
|
gUI = null;
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,9 @@
|
||||||
|
|
||||||
// This test makes sure that the style editor does not store any
|
// This test makes sure that the style editor does not store any
|
||||||
// content CSS files in the permanent cache when opened from PB mode.
|
// content CSS files in the permanent cache when opened from PB mode.
|
||||||
|
|
||||||
|
let gUI;
|
||||||
|
|
||||||
function test() {
|
function test() {
|
||||||
waitForExplicitFinish();
|
waitForExplicitFinish();
|
||||||
let windowsToClose = [];
|
let windowsToClose = [];
|
||||||
|
@ -11,6 +14,8 @@ function test() {
|
||||||
|
|
||||||
function checkCache() {
|
function checkCache() {
|
||||||
checkDiskCacheFor(TEST_HOST);
|
checkDiskCacheFor(TEST_HOST);
|
||||||
|
|
||||||
|
gUI = null;
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,23 +23,21 @@ function test() {
|
||||||
aWindow.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
|
aWindow.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
|
||||||
aWindow.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
|
aWindow.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
|
||||||
cache.evictEntries(Ci.nsICache.STORE_ANYWHERE);
|
cache.evictEntries(Ci.nsICache.STORE_ANYWHERE);
|
||||||
launchStyleEditorChromeFromWindow(aWindow, function(aChrome) {
|
openStyleEditorInWindow(aWindow, function(panel) {
|
||||||
onEditorAdded(aChrome, aChrome.editors[0]);
|
gUI = panel.UI;
|
||||||
|
gUI.on("editor-added", onEditorAdded);
|
||||||
});
|
});
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
aWindow.gBrowser.selectedBrowser.loadURI(testURI);
|
aWindow.gBrowser.selectedBrowser.loadURI(testURI);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEditorAdded(aChrome, aEditor) {
|
function onEditorAdded(aEvent, aEditor) {
|
||||||
aChrome.removeChromeListener(this);
|
if (aEditor.sourceLoaded) {
|
||||||
|
|
||||||
if (aEditor.isLoaded) {
|
|
||||||
checkCache();
|
checkCache();
|
||||||
} else {
|
}
|
||||||
aEditor.addActionListener({
|
else {
|
||||||
onLoad: checkCache
|
aEditor.on("source-load", checkCache);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
/* 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 gEditorAddedCount = 0;
|
|
||||||
let gEditorReadOnlyCount = 0;
|
|
||||||
let gChromeListener = {
|
|
||||||
onEditorAdded: function (aChrome, aEditor) {
|
|
||||||
gEditorAddedCount++;
|
|
||||||
if (aEditor.readOnly) {
|
|
||||||
gEditorReadOnlyCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (gEditorAddedCount == aChrome.editors.length) {
|
|
||||||
// continue testing after all editors are ready
|
|
||||||
|
|
||||||
is(gEditorReadOnlyCount, 0,
|
|
||||||
"all editors are NOT read-only initially");
|
|
||||||
|
|
||||||
// all editors have been loaded, queue closing the content tab
|
|
||||||
executeSoon(function () {
|
|
||||||
gBrowser.removeCurrentTab();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onContentDetach: function (aChrome) {
|
|
||||||
// check that the UI has switched to read-only
|
|
||||||
run(aChrome);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function test()
|
|
||||||
{
|
|
||||||
waitForExplicitFinish();
|
|
||||||
|
|
||||||
gBrowser.addTab(); // because we'll close the next one
|
|
||||||
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
|
||||||
aChrome.addChromeListener(gChromeListener);
|
|
||||||
});
|
|
||||||
|
|
||||||
content.location = TESTCASE_URI;
|
|
||||||
}
|
|
||||||
|
|
||||||
function run(aChrome)
|
|
||||||
{
|
|
||||||
let document = gChromeWindow.document;
|
|
||||||
let disabledCount;
|
|
||||||
let elements;
|
|
||||||
|
|
||||||
disabledCount = 0;
|
|
||||||
elements = document.querySelectorAll("button,toolbarbutton,textbox");
|
|
||||||
for (let i = 0; i < elements.length; ++i) {
|
|
||||||
if (elements[i].hasAttribute("disabled")) {
|
|
||||||
disabledCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ok(elements.length && disabledCount == elements.length,
|
|
||||||
"all buttons, input and select elements are disabled");
|
|
||||||
|
|
||||||
disabledCount = 0;
|
|
||||||
aChrome.editors.forEach(function (aEditor) {
|
|
||||||
if (aEditor.readOnly) {
|
|
||||||
disabledCount++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
ok(aChrome.editors.length && disabledCount == aChrome.editors.length,
|
|
||||||
"all editors are read-only");
|
|
||||||
|
|
||||||
aChrome.removeChromeListener(gChromeListener);
|
|
||||||
finish();
|
|
||||||
}
|
|
|
@ -1,165 +0,0 @@
|
||||||
/* vim: set ts=2 et sw=2 tw=80: */
|
|
||||||
/* Any copyright is dedicated to the Public Domain.
|
|
||||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
|
||||||
|
|
||||||
let tempScope = {};
|
|
||||||
Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
|
|
||||||
let TargetFactory = tempScope.TargetFactory;
|
|
||||||
|
|
||||||
function test() {
|
|
||||||
|
|
||||||
// http rather than chrome to improve coverage
|
|
||||||
const TESTCASE_URI = TEST_BASE_HTTP + "simple.gz.html";
|
|
||||||
|
|
||||||
const Cc = Components.classes;
|
|
||||||
const Ci = Components.interfaces;
|
|
||||||
|
|
||||||
let toolbox;
|
|
||||||
let tempScope = {};
|
|
||||||
Components.utils.import("resource://gre/modules/FileUtils.jsm", tempScope);
|
|
||||||
let FileUtils = tempScope.FileUtils;
|
|
||||||
|
|
||||||
|
|
||||||
waitForExplicitFinish();
|
|
||||||
|
|
||||||
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
|
||||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
|
||||||
toolbox = gDevTools.getToolbox(target);
|
|
||||||
|
|
||||||
aChrome.addChromeListener({
|
|
||||||
onEditorAdded: function (aChrome, aEditor) {
|
|
||||||
if (aEditor.styleSheetIndex != 0) {
|
|
||||||
return; // we want to test against the first stylesheet
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aEditor.sourceEditor) {
|
|
||||||
run(aEditor); // already attached to input element
|
|
||||||
} else {
|
|
||||||
aEditor.addActionListener({
|
|
||||||
onAttach: run
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
toolbox.once("destroyed", function onClose() {
|
|
||||||
gChromeWindow = null;
|
|
||||||
executeSoon(function () {
|
|
||||||
waitForFocus(function () {
|
|
||||||
// wait that browser has focus again
|
|
||||||
// open StyleEditorChrome again (a new one since we closed the previous one)
|
|
||||||
launchStyleEditorChrome(function (aChrome) {
|
|
||||||
is(gChromeWindow.document.documentElement.hasAttribute("data-marker"),
|
|
||||||
false,
|
|
||||||
"opened a completely new StyleEditorChrome window");
|
|
||||||
|
|
||||||
aChrome.addChromeListener({
|
|
||||||
onEditorAdded: function (aChrome, aEditor) {
|
|
||||||
if (aEditor.styleSheetIndex != 0) {
|
|
||||||
return; // we want to test against the first stylesheet
|
|
||||||
}
|
|
||||||
|
|
||||||
if (aEditor.sourceEditor) {
|
|
||||||
testNewChrome(aEditor); // already attached to input element
|
|
||||||
} else {
|
|
||||||
aEditor.addActionListener({
|
|
||||||
onAttach: testNewChrome
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
content.location = TESTCASE_URI;
|
|
||||||
|
|
||||||
let gFilename;
|
|
||||||
|
|
||||||
function run(aEditor)
|
|
||||||
{
|
|
||||||
gFilename = FileUtils.getFile("ProfD", ["styleeditor-test.css"])
|
|
||||||
|
|
||||||
aEditor.saveToFile(gFilename, function (aFile) {
|
|
||||||
ok(aFile, "file got saved successfully");
|
|
||||||
|
|
||||||
aEditor.addActionListener({
|
|
||||||
onFlagChange: function (aEditor, aFlag) {
|
|
||||||
if (aFlag != "unsaved") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ok(aEditor.hasFlag("unsaved"),
|
|
||||||
"first stylesheet has UNSAVED flag after making a change");
|
|
||||||
|
|
||||||
// marker used to check it does not exist when we reopen
|
|
||||||
// ie. the window we opened is indeed a new one
|
|
||||||
gChromeWindow.document.documentElement.setAttribute("data-marker", "true");
|
|
||||||
toolbox.destroy();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
waitForFocus(function () {
|
|
||||||
// insert char so that this stylesheet has the UNSAVED flag
|
|
||||||
EventUtils.synthesizeKey("x", {}, gChromeWindow);
|
|
||||||
}, gChromeWindow);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function testNewChrome(aEditor)
|
|
||||||
{
|
|
||||||
ok(aEditor.savedFile,
|
|
||||||
"first stylesheet editor will save directly into the same file");
|
|
||||||
|
|
||||||
is(aEditor.getFriendlyName(), gFilename.leafName,
|
|
||||||
"first stylesheet still has the filename as it was saved");
|
|
||||||
gFilename = null;
|
|
||||||
|
|
||||||
ok(aEditor.hasFlag("unsaved"),
|
|
||||||
"first stylesheet still has UNSAVED flag at reopening");
|
|
||||||
|
|
||||||
ok(!aEditor.hasFlag("inline"),
|
|
||||||
"first stylesheet does not have INLINE flag");
|
|
||||||
|
|
||||||
ok(!aEditor.hasFlag("error"),
|
|
||||||
"editor does not have error flag initially");
|
|
||||||
let hadError = false;
|
|
||||||
|
|
||||||
let onSaveCallback = function (aFile) {
|
|
||||||
aEditor.addActionListener({
|
|
||||||
onFlagChange: function (aEditor, aFlag) {
|
|
||||||
if (!hadError && aFlag == "error") {
|
|
||||||
ok(aEditor.hasFlag("error"),
|
|
||||||
"editor has ERROR flag after attempting to save with invalid path");
|
|
||||||
hadError = true;
|
|
||||||
|
|
||||||
// save using source editor key binding (previous successful path)
|
|
||||||
waitForFocus(function () {
|
|
||||||
EventUtils.synthesizeKey("S", {accelKey: true}, gChromeWindow);
|
|
||||||
}, gChromeWindow);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hadError && aFlag == "unsaved") {
|
|
||||||
executeSoon(function () {
|
|
||||||
ok(!aEditor.hasFlag("unsaved"),
|
|
||||||
"first stylesheet has no UNSAVED flag after successful save");
|
|
||||||
ok(!aEditor.hasFlag("error"),
|
|
||||||
"ERROR flag has been removed since last operation succeeded");
|
|
||||||
finish();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
|
|
||||||
if (os == "WINNT") {
|
|
||||||
aEditor.saveToFile("C:\\I_DO_NOT_EXIST_42\\bogus.css", onSaveCallback);
|
|
||||||
} else {
|
|
||||||
aEditor.saveToFile("/I_DO_NOT_EXIST_42/bogos.css", onSaveCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,65 +4,68 @@
|
||||||
|
|
||||||
const TESTCASE_URI = TEST_BASE + "four.html";
|
const TESTCASE_URI = TEST_BASE + "four.html";
|
||||||
|
|
||||||
|
let gUI;
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
{
|
{
|
||||||
waitForExplicitFinish();
|
waitForExplicitFinish();
|
||||||
|
|
||||||
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
addTabAndOpenStyleEditor(function(panel) {
|
||||||
run(aChrome);
|
gUI = panel.UI;
|
||||||
|
gUI.on("editor-added", function(event, editor) {
|
||||||
|
if (editor == gUI.editors[3]) {
|
||||||
|
runTests();
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
content.location = TESTCASE_URI;
|
content.location = TESTCASE_URI;
|
||||||
}
|
}
|
||||||
|
|
||||||
let gChrome;
|
function runTests()
|
||||||
|
|
||||||
function run(aChrome)
|
|
||||||
{
|
{
|
||||||
gChrome = aChrome;
|
gUI.editors[0].getSourceEditor().then(onEditor0Attach);
|
||||||
aChrome.editors[0].addActionListener({onAttach: onEditor0Attach});
|
gUI.editors[2].getSourceEditor().then(onEditor2Attach);
|
||||||
aChrome.editors[2].addActionListener({onAttach: onEditor2Attach});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStylesheetNameLinkFor(aEditor)
|
function getStylesheetNameLinkFor(aEditor)
|
||||||
{
|
{
|
||||||
return gChrome.getSummaryElementForEditor(aEditor).querySelector(".stylesheet-name");
|
return aEditor.summary.querySelector(".stylesheet-name");
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEditor0Attach(aEditor)
|
function onEditor0Attach(aEditor)
|
||||||
{
|
{
|
||||||
waitForFocus(function () {
|
waitForFocus(function () {
|
||||||
let summary = gChrome.getSummaryElementForEditor(aEditor);
|
let summary = aEditor.summary;
|
||||||
EventUtils.synthesizeMouseAtCenter(summary, {}, gChromeWindow);
|
EventUtils.synthesizeMouseAtCenter(summary, {}, gPanelWindow);
|
||||||
|
|
||||||
let item = getStylesheetNameLinkFor(gChrome.editors[0]);
|
let item = getStylesheetNameLinkFor(gUI.editors[0]);
|
||||||
is(gChromeWindow.document.activeElement, item,
|
is(gPanelWindow.document.activeElement, item,
|
||||||
"editor 0 item is the active element");
|
"editor 0 item is the active element");
|
||||||
|
|
||||||
EventUtils.synthesizeKey("VK_DOWN", {}, gChromeWindow);
|
EventUtils.synthesizeKey("VK_DOWN", {}, gPanelWindow);
|
||||||
item = getStylesheetNameLinkFor(gChrome.editors[1]);
|
item = getStylesheetNameLinkFor(gUI.editors[1]);
|
||||||
is(gChromeWindow.document.activeElement, item,
|
is(gPanelWindow.document.activeElement, item,
|
||||||
"editor 1 item is the active element");
|
"editor 1 item is the active element");
|
||||||
|
|
||||||
EventUtils.synthesizeKey("VK_HOME", {}, gChromeWindow);
|
EventUtils.synthesizeKey("VK_HOME", {}, gPanelWindow);
|
||||||
item = getStylesheetNameLinkFor(gChrome.editors[0]);
|
item = getStylesheetNameLinkFor(gUI.editors[0]);
|
||||||
is(gChromeWindow.document.activeElement, item,
|
is(gPanelWindow.document.activeElement, item,
|
||||||
"fist editor item is the active element");
|
"fist editor item is the active element");
|
||||||
|
|
||||||
EventUtils.synthesizeKey("VK_END", {}, gChromeWindow);
|
EventUtils.synthesizeKey("VK_END", {}, gPanelWindow);
|
||||||
item = getStylesheetNameLinkFor(gChrome.editors[3]);
|
item = getStylesheetNameLinkFor(gUI.editors[3]);
|
||||||
is(gChromeWindow.document.activeElement, item,
|
is(gPanelWindow.document.activeElement, item,
|
||||||
"last editor item is the active element");
|
"last editor item is the active element");
|
||||||
|
|
||||||
EventUtils.synthesizeKey("VK_UP", {}, gChromeWindow);
|
EventUtils.synthesizeKey("VK_UP", {}, gPanelWindow);
|
||||||
item = getStylesheetNameLinkFor(gChrome.editors[2]);
|
item = getStylesheetNameLinkFor(gUI.editors[2]);
|
||||||
is(gChromeWindow.document.activeElement, item,
|
is(gPanelWindow.document.activeElement, item,
|
||||||
"editor 2 item is the active element");
|
"editor 2 item is the active element");
|
||||||
|
|
||||||
EventUtils.synthesizeKey("VK_RETURN", {}, gChromeWindow);
|
EventUtils.synthesizeKey("VK_RETURN", {}, gPanelWindow);
|
||||||
// this will attach and give focus editor 2
|
// this will attach and give focus editor 2
|
||||||
}, gChromeWindow);
|
}, gPanelWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onEditor2Attach(aEditor)
|
function onEditor2Attach(aEditor)
|
||||||
|
@ -70,6 +73,6 @@ function onEditor2Attach(aEditor)
|
||||||
ok(aEditor.sourceEditor.hasFocus(),
|
ok(aEditor.sourceEditor.hasFocus(),
|
||||||
"editor 2 has focus");
|
"editor 2 has focus");
|
||||||
|
|
||||||
gChrome = null;
|
gUI = null;
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,53 +4,58 @@
|
||||||
|
|
||||||
const TESTCASE_URI = TEST_BASE + "simple.html";
|
const TESTCASE_URI = TEST_BASE + "simple.html";
|
||||||
|
|
||||||
let gOriginalWidth; // these are set by run() when gChromeWindow is ready
|
let gOriginalWidth; // these are set by runTests()
|
||||||
let gOriginalHeight;
|
let gOriginalHeight;
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
{
|
{
|
||||||
waitForExplicitFinish();
|
waitForExplicitFinish();
|
||||||
|
|
||||||
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
addTabAndOpenStyleEditor(function(panel) {
|
||||||
run(aChrome);
|
let UI = panel.UI;
|
||||||
|
UI.on("editor-added", function(event, editor) {
|
||||||
|
if (editor == UI.editors[1]) {
|
||||||
|
// wait until both editors are added
|
||||||
|
runTests(UI);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
content.location = TESTCASE_URI;
|
content.location = TESTCASE_URI;
|
||||||
}
|
}
|
||||||
|
|
||||||
function run(aChrome)
|
function runTests(aUI)
|
||||||
{
|
{
|
||||||
is(aChrome.editors.length, 2,
|
is(aUI.editors.length, 2,
|
||||||
"there is 2 stylesheets initially");
|
"there is 2 stylesheets initially");
|
||||||
|
|
||||||
aChrome.editors[0].addActionListener({
|
aUI.editors[0].getSourceEditor().then(function onEditorAttached(aEditor) {
|
||||||
onAttach: function onEditorAttached(aEditor) {
|
executeSoon(function () {
|
||||||
executeSoon(function () {
|
waitForFocus(function () {
|
||||||
waitForFocus(function () {
|
// queue a resize to inverse aspect ratio
|
||||||
// queue a resize to inverse aspect ratio
|
// this will trigger a detach and reattach (to workaround bug 254144)
|
||||||
// this will trigger a detach and reattach (to workaround bug 254144)
|
let originalSourceEditor = aEditor.sourceEditor;
|
||||||
let originalSourceEditor = aEditor.sourceEditor;
|
aEditor.sourceEditor.setCaretOffset(4); // to check the caret is preserved
|
||||||
aEditor.sourceEditor.setCaretOffset(4); // to check the caret is preserved
|
|
||||||
|
|
||||||
gOriginalWidth = gChromeWindow.outerWidth;
|
gOriginalWidth = gPanelWindow.outerWidth;
|
||||||
gOriginalHeight = gChromeWindow.outerHeight;
|
gOriginalHeight = gPanelWindow.outerHeight;
|
||||||
gChromeWindow.resizeTo(120, 480);
|
gPanelWindow.resizeTo(120, 480);
|
||||||
|
|
||||||
executeSoon(function () {
|
executeSoon(function () {
|
||||||
is(aEditor.sourceEditor, originalSourceEditor,
|
is(aEditor.sourceEditor, originalSourceEditor,
|
||||||
"the editor still references the same SourceEditor instance");
|
"the editor still references the same SourceEditor instance");
|
||||||
is(aEditor.sourceEditor.getCaretOffset(), 4,
|
is(aEditor.sourceEditor.getCaretOffset(), 4,
|
||||||
"the caret position has been preserved");
|
"the caret position has been preserved");
|
||||||
|
|
||||||
// queue a resize to original aspect ratio
|
// queue a resize to original aspect ratio
|
||||||
waitForFocus(function () {
|
waitForFocus(function () {
|
||||||
gChromeWindow.resizeTo(gOriginalWidth, gOriginalHeight);
|
gPanelWindow.resizeTo(gOriginalWidth, gOriginalHeight);
|
||||||
executeSoon(function () {
|
executeSoon(function () {
|
||||||
finish();
|
finish();
|
||||||
});
|
});
|
||||||
}, gChromeWindow);
|
}, gPanelWindow);
|
||||||
});
|
});
|
||||||
}, gChromeWindow);
|
}, gPanelWindow);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ let TargetFactory = tempScope.TargetFactory;
|
||||||
Components.utils.import("resource://gre/modules/devtools/Console.jsm", tempScope);
|
Components.utils.import("resource://gre/modules/devtools/Console.jsm", tempScope);
|
||||||
let console = tempScope.console;
|
let console = tempScope.console;
|
||||||
|
|
||||||
let gChromeWindow; //StyleEditorChrome window
|
let gPanelWindow;
|
||||||
let cache = Cc["@mozilla.org/network/cache-service;1"]
|
let cache = Cc["@mozilla.org/network/cache-service;1"]
|
||||||
.getService(Ci.nsICacheService);
|
.getService(Ci.nsICacheService);
|
||||||
|
|
||||||
|
@ -23,12 +23,38 @@ Services.scriptloader.loadSubScript(testDir + "/helpers.js", this);
|
||||||
|
|
||||||
function cleanup()
|
function cleanup()
|
||||||
{
|
{
|
||||||
gChromeWindow = null;
|
gPanelWindow = null;
|
||||||
while (gBrowser.tabs.length > 1) {
|
while (gBrowser.tabs.length > 1) {
|
||||||
gBrowser.removeCurrentTab();
|
gBrowser.removeCurrentTab();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addTabAndOpenStyleEditor(callback) {
|
||||||
|
gBrowser.selectedTab = gBrowser.addTab();
|
||||||
|
gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
|
||||||
|
gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
|
||||||
|
openStyleEditorInWindow(window, callback);
|
||||||
|
}, 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;
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (aSheet) {
|
||||||
|
panel.selectStyleSheet(aSheet, aLine, aCol);
|
||||||
|
} */
|
||||||
|
|
||||||
|
callback(panel);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
function launchStyleEditorChrome(aCallback, aSheet, aLine, aCol)
|
function launchStyleEditorChrome(aCallback, aSheet, aLine, aCol)
|
||||||
{
|
{
|
||||||
launchStyleEditorChromeFromWindow(window, aCallback, aSheet, aLine, aCol);
|
launchStyleEditorChromeFromWindow(window, aCallback, aSheet, aLine, aCol);
|
||||||
|
@ -39,12 +65,12 @@ function launchStyleEditorChromeFromWindow(aWindow, aCallback, aSheet, aLine, aC
|
||||||
let target = TargetFactory.forTab(aWindow.gBrowser.selectedTab);
|
let target = TargetFactory.forTab(aWindow.gBrowser.selectedTab);
|
||||||
gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
|
gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
|
||||||
let panel = toolbox.getCurrentPanel();
|
let panel = toolbox.getCurrentPanel();
|
||||||
gChromeWindow = panel._panelWin;
|
gPanelWindow = panel._panelWin;
|
||||||
gChromeWindow.styleEditorChrome._alwaysDisableAnimations = true;
|
gPanelWindow.styleEditorChrome._alwaysDisableAnimations = true;
|
||||||
if (aSheet) {
|
if (aSheet) {
|
||||||
panel.selectStyleSheet(aSheet, aLine, aCol);
|
panel.selectStyleSheet(aSheet, aLine, aCol);
|
||||||
}
|
}
|
||||||
aCallback(gChromeWindow.styleEditorChrome);
|
aCallback(gPanelWindow.styleEditorChrome);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,6 +82,7 @@ function addTabAndLaunchStyleEditorChromeWhenLoaded(aCallback, aSheet, aLine, aC
|
||||||
launchStyleEditorChrome(aCallback, aSheet, aLine, aCol);
|
launchStyleEditorChrome(aCallback, aSheet, aLine, aCol);
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
function checkDiskCacheFor(host)
|
function checkDiskCacheFor(host)
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
<body>
|
<body>
|
||||||
Time passes:
|
Time passes:
|
||||||
<script>
|
<script>
|
||||||
for (i = 0; i < 30000; i++) {
|
for (i = 0; i < 5000; i++) {
|
||||||
document.write("<br>...");
|
document.write("<br>...");
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -84,22 +84,10 @@ function testInlineStyleSheet()
|
||||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||||
gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
|
gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
|
||||||
let panel = toolbox.getCurrentPanel();
|
let panel = toolbox.getCurrentPanel();
|
||||||
let win = panel._panelWin;
|
|
||||||
|
|
||||||
win.styleEditorChrome.addChromeListener({
|
panel.UI.on("editor-added", (event, editor) => {
|
||||||
onEditorAdded: function checkEditor(aChrome, aEditor) {
|
validateStyleEditorSheet(editor);
|
||||||
if (!aEditor.sourceEditor) {
|
})
|
||||||
aEditor.addActionListener({
|
|
||||||
onAttach: function (aEditor) {
|
|
||||||
aEditor.removeActionListener(this);
|
|
||||||
validateStyleEditorSheet(aEditor);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
validateStyleEditorSheet(aEditor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let link = getLinkByIndex(1);
|
let link = getLinkByIndex(1);
|
||||||
|
@ -111,7 +99,7 @@ function validateStyleEditorSheet(aEditor)
|
||||||
info("validating style editor stylesheet");
|
info("validating style editor stylesheet");
|
||||||
|
|
||||||
let sheet = doc.styleSheets[0];
|
let sheet = doc.styleSheets[0];
|
||||||
is(aEditor.styleSheet, sheet, "loaded stylesheet matches document stylesheet");
|
is(aEditor.styleSheet.href, sheet.href, "loaded stylesheet matches document stylesheet");
|
||||||
|
|
||||||
finishUp();
|
finishUp();
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,8 +88,10 @@ function testInlineStyleSheet()
|
||||||
info("clicking an inline stylesheet");
|
info("clicking an inline stylesheet");
|
||||||
|
|
||||||
toolbox.once("styleeditor-ready", function(id, aToolbox) {
|
toolbox.once("styleeditor-ready", function(id, aToolbox) {
|
||||||
aToolbox.panelWindow.styleEditorChrome.addChromeListener({
|
let panel = toolbox.getCurrentPanel();
|
||||||
onEditorAdded: validateStyleEditorSheet
|
|
||||||
|
panel.UI.on("editor-added", (event, editor) => {
|
||||||
|
validateStyleEditorSheet(editor);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -98,13 +100,13 @@ function testInlineStyleSheet()
|
||||||
link.click();
|
link.click();
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateStyleEditorSheet(aChrome, aEditor)
|
function validateStyleEditorSheet(aEditor)
|
||||||
{
|
{
|
||||||
info("validating style editor stylesheet");
|
info("validating style editor stylesheet");
|
||||||
|
|
||||||
let sheet = doc.styleSheets[0];
|
let sheet = doc.styleSheets[0];
|
||||||
|
|
||||||
is(aEditor.styleSheet, sheet, "loaded stylesheet matches document stylesheet");
|
is(aEditor.styleSheet.href, sheet.href, "loaded stylesheet matches document stylesheet");
|
||||||
win.close();
|
win.close();
|
||||||
|
|
||||||
finishup();
|
finishup();
|
||||||
|
|
|
@ -340,20 +340,14 @@ WebConsole.prototype = {
|
||||||
viewSourceInStyleEditor:
|
viewSourceInStyleEditor:
|
||||||
function WC_viewSourceInStyleEditor(aSourceURL, aSourceLine)
|
function WC_viewSourceInStyleEditor(aSourceURL, aSourceLine)
|
||||||
{
|
{
|
||||||
let styleSheets = {};
|
gDevTools.showToolbox(this.target, "styleeditor").then(function(toolbox) {
|
||||||
if (this.target.isLocalTab) {
|
try {
|
||||||
styleSheets = this.target.window.document.styleSheets;
|
toolbox.getCurrentPanel().selectStyleSheet(aSourceURL, aSourceLine);
|
||||||
}
|
} catch(e) {
|
||||||
for each (let style in styleSheets) {
|
// Open view source if style editor fails.
|
||||||
if (style.href == aSourceURL) {
|
this.viewSource(aSourceURL, aSourceLine);
|
||||||
gDevTools.showToolbox(this.target, "styleeditor").then(function(toolbox) {
|
|
||||||
toolbox.getCurrentPanel().selectStyleSheet(style, aSourceLine);
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
// Open view source if style editor fails.
|
|
||||||
this.viewSource(aSourceURL, aSourceLine);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test" +
|
const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test" +
|
||||||
"/test-bug-782653-css-errors.html";
|
"/test-bug-782653-css-errors.html";
|
||||||
|
|
||||||
let nodes, hud, SEC;
|
let nodes, hud, StyleEditorUI;
|
||||||
|
|
||||||
function test()
|
function test()
|
||||||
{
|
{
|
||||||
|
@ -23,7 +23,7 @@ function testViewSource(aHud)
|
||||||
hud = aHud;
|
hud = aHud;
|
||||||
|
|
||||||
registerCleanupFunction(function() {
|
registerCleanupFunction(function() {
|
||||||
nodes = hud = SEC = null;
|
nodes = hud = StyleEditorUI = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
let selector = ".webconsole-msg-cssparser .webconsole-location";
|
let selector = ".webconsole-msg-cssparser .webconsole-location";
|
||||||
|
@ -41,7 +41,16 @@ function testViewSource(aHud)
|
||||||
|
|
||||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||||
let toolbox = gDevTools.getToolbox(target);
|
let toolbox = gDevTools.getToolbox(target);
|
||||||
toolbox.once("styleeditor-selected", onStyleEditorReady);
|
toolbox.once("styleeditor-selected", (event, panel) => {
|
||||||
|
StyleEditorUI = panel.UI;
|
||||||
|
|
||||||
|
let count = 0;
|
||||||
|
StyleEditorUI.on("editor-added", function() {
|
||||||
|
if (++count == 2) {
|
||||||
|
onStyleEditorReady(panel);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
EventUtils.sendMouseEvent({ type: "click" }, nodes[0]);
|
EventUtils.sendMouseEvent({ type: "click" }, nodes[0]);
|
||||||
},
|
},
|
||||||
|
@ -49,45 +58,30 @@ function testViewSource(aHud)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function onStyleEditorReady(aEvent, aPanel)
|
function onStyleEditorReady(aPanel)
|
||||||
{
|
{
|
||||||
info(aEvent + " event fired");
|
|
||||||
|
|
||||||
SEC = aPanel.styleEditorChrome;
|
|
||||||
let win = aPanel.panelWindow;
|
let win = aPanel.panelWindow;
|
||||||
ok(win, "Style Editor Window is defined");
|
ok(win, "Style Editor Window is defined");
|
||||||
ok(SEC, "Style Editor Chrome is defined");
|
ok(StyleEditorUI, "Style Editor UI is defined");
|
||||||
|
|
||||||
function sheetForNode(aNode)
|
|
||||||
{
|
|
||||||
let href = aNode.getAttribute("title");
|
|
||||||
let sheet, i = 0;
|
|
||||||
while((sheet = content.document.styleSheets[i++])) {
|
|
||||||
if (sheet.href == href) {
|
|
||||||
return sheet;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
waitForFocus(function() {
|
waitForFocus(function() {
|
||||||
info("style editor window focused");
|
info("style editor window focused");
|
||||||
|
|
||||||
let sheet = sheetForNode(nodes[0]);
|
let href = nodes[0].getAttribute("title");
|
||||||
ok(sheet, "sheet found");
|
ok(href.contains("test-bug-782653-css-errors-1.css"), "got first stylesheet href")
|
||||||
let line = nodes[0].sourceLine;
|
let line = nodes[0].sourceLine;
|
||||||
ok(line, "found source line");
|
is(line, 8, "found source line");
|
||||||
|
|
||||||
checkStyleEditorForSheetAndLine(sheet, line - 1, function() {
|
checkStyleEditorForSheetAndLine(href, line - 1, function() {
|
||||||
info("first check done");
|
info("first check done");
|
||||||
|
|
||||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||||
let toolbox = gDevTools.getToolbox(target);
|
let toolbox = gDevTools.getToolbox(target);
|
||||||
|
|
||||||
let sheet = sheetForNode(nodes[1]);
|
let href = nodes[1].getAttribute("title");
|
||||||
ok(sheet, "sheet found");
|
ok(href.contains("test-bug-782653-css-errors-2.css"), "got second stylesheet href")
|
||||||
let line = nodes[1].sourceLine;
|
let line = nodes[1].sourceLine;
|
||||||
ok(line, "found source line");
|
is(line, 7, "found source line");
|
||||||
|
|
||||||
toolbox.selectTool("webconsole").then(function() {
|
toolbox.selectTool("webconsole").then(function() {
|
||||||
info("webconsole selected");
|
info("webconsole selected");
|
||||||
|
@ -95,7 +89,7 @@ function onStyleEditorReady(aEvent, aPanel)
|
||||||
toolbox.once("styleeditor-selected", function(aEvent) {
|
toolbox.once("styleeditor-selected", function(aEvent) {
|
||||||
info(aEvent + " event fired");
|
info(aEvent + " event fired");
|
||||||
|
|
||||||
checkStyleEditorForSheetAndLine(sheet, line - 1, function() {
|
checkStyleEditorForSheetAndLine(href, line - 1, function() {
|
||||||
info("second check done");
|
info("second check done");
|
||||||
finishTest();
|
finishTest();
|
||||||
});
|
});
|
||||||
|
@ -107,15 +101,15 @@ function onStyleEditorReady(aEvent, aPanel)
|
||||||
}, win);
|
}, win);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkStyleEditorForSheetAndLine(aStyleSheet, aLine, aCallback)
|
function checkStyleEditorForSheetAndLine(aHref, aLine, aCallback)
|
||||||
{
|
{
|
||||||
let foundEditor = null;
|
let foundEditor = null;
|
||||||
waitForSuccess({
|
waitForSuccess({
|
||||||
name: "style editor for stylesheet",
|
name: "style editor for stylesheet",
|
||||||
validatorFn: function()
|
validatorFn: function()
|
||||||
{
|
{
|
||||||
for (let editor of SEC.editors) {
|
for (let editor of StyleEditorUI.editors) {
|
||||||
if (editor.styleSheet == aStyleSheet) {
|
if (editor.styleSheet.href == aHref) {
|
||||||
foundEditor = editor;
|
foundEditor = editor;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -136,7 +130,7 @@ function performLineCheck(aEditor, aLine, aCallback)
|
||||||
{
|
{
|
||||||
is(aEditor.sourceEditor.getCaretPosition().line, aLine,
|
is(aEditor.sourceEditor.getCaretPosition().line, aLine,
|
||||||
"correct line is selected");
|
"correct line is selected");
|
||||||
is(SEC.selectedStyleSheetIndex, aEditor.styleSheetIndex,
|
is(StyleEditorUI.selectedStyleSheetIndex, aEditor.styleSheet.styleSheetIndex,
|
||||||
"correct stylesheet is selected in the editor");
|
"correct stylesheet is selected in the editor");
|
||||||
|
|
||||||
aCallback && executeSoon(aCallback);
|
aCallback && executeSoon(aCallback);
|
||||||
|
@ -150,7 +144,8 @@ function performLineCheck(aEditor, aLine, aCallback)
|
||||||
},
|
},
|
||||||
successFn: checkForCorrectState,
|
successFn: checkForCorrectState,
|
||||||
failureFn: function() {
|
failureFn: function() {
|
||||||
info("selectedStyleSheetIndex " + SEC.selectedStyleSheetIndex + " expected " + aEditor.styleSheetIndex);
|
info("selectedStyleSheetIndex " + StyleEditorUI.selectedStyleSheetIndex
|
||||||
|
+ " expected " + aEditor.styleSheet.styleSheetIndex);
|
||||||
finishTest();
|
finishTest();
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -182,7 +182,8 @@ const UnsolicitedNotifications = {
|
||||||
"tabDetached": "tabDetached",
|
"tabDetached": "tabDetached",
|
||||||
"tabNavigated": "tabNavigated",
|
"tabNavigated": "tabNavigated",
|
||||||
"pageError": "pageError",
|
"pageError": "pageError",
|
||||||
"webappsEvent": "webappsEvent"
|
"webappsEvent": "webappsEvent",
|
||||||
|
"styleSheetsAdded": "styleSheetsAdded"
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -195,6 +195,9 @@ var DebuggerServer = {
|
||||||
#endif
|
#endif
|
||||||
if ("nsIProfiler" in Ci)
|
if ("nsIProfiler" in Ci)
|
||||||
this.addActors("chrome://global/content/devtools/dbg-profiler-actors.js");
|
this.addActors("chrome://global/content/devtools/dbg-profiler-actors.js");
|
||||||
|
|
||||||
|
this.addActors("chrome://global/content/devtools/dbg-styleeditor-actors.js");
|
||||||
|
this.addTabActor(this.StyleEditorActor, "styleEditorActor");
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -9,3 +9,4 @@ toolkit.jar:
|
||||||
content/global/devtools/dbg-browser-actors.js (debugger/server/dbg-browser-actors.js)
|
content/global/devtools/dbg-browser-actors.js (debugger/server/dbg-browser-actors.js)
|
||||||
content/global/devtools/dbg-webconsole-actors.js (webconsole/dbg-webconsole-actors.js)
|
content/global/devtools/dbg-webconsole-actors.js (webconsole/dbg-webconsole-actors.js)
|
||||||
content/global/devtools/dbg-profiler-actors.js (debugger/server/dbg-profiler-actors.js)
|
content/global/devtools/dbg-profiler-actors.js (debugger/server/dbg-profiler-actors.js)
|
||||||
|
content/global/devtools/dbg-styleeditor-actors.js (styleeditor/dbg-styleeditor-actors.js)
|
||||||
|
|
|
@ -8,4 +8,5 @@ PARALLEL_DIRS += [
|
||||||
'debugger',
|
'debugger',
|
||||||
'sourcemap',
|
'sourcemap',
|
||||||
'webconsole',
|
'webconsole',
|
||||||
|
'styleeditor'
|
||||||
]
|
]
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
# 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/.
|
||||||
|
|
||||||
|
DEPTH = ../../..
|
||||||
|
topsrcdir = @top_srcdir@
|
||||||
|
srcdir = @srcdir@
|
||||||
|
VPATH = @srcdir@
|
||||||
|
|
||||||
|
include $(DEPTH)/config/autoconf.mk
|
||||||
|
|
||||||
|
#ifneq (Android,$(OS_TARGET))
|
||||||
|
# TEST_DIRS += test
|
||||||
|
#endif
|
||||||
|
|
||||||
|
include $(topsrcdir)/config/rules.mk
|
||||||
|
|
||||||
|
#libs::
|
||||||
|
# $(INSTALL) $(IFLAGS1) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools
|
|
@ -0,0 +1,745 @@
|
||||||
|
/* 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";
|
||||||
|
|
||||||
|
let Cc = Components.classes;
|
||||||
|
let Ci = Components.interfaces;
|
||||||
|
let 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/FileUtils.jsm");
|
||||||
|
|
||||||
|
let TRANSITION_CLASS = "moz-styleeditor-transitioning";
|
||||||
|
let TRANSITION_DURATION_MS = 500;
|
||||||
|
let TRANSITION_RULE = "\
|
||||||
|
:root.moz-styleeditor-transitioning, :root.moz-styleeditor-transitioning * {\
|
||||||
|
transition-duration: " + TRANSITION_DURATION_MS + "ms !important; \
|
||||||
|
transition-delay: 0ms !important;\
|
||||||
|
transition-timing-function: ease-out !important;\
|
||||||
|
transition-property: all !important;\
|
||||||
|
}";
|
||||||
|
|
||||||
|
let LOAD_ERROR = "error-load";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a StyleEditorActor. StyleEditorActor provides remote access to the
|
||||||
|
* built-in style editor module.
|
||||||
|
*/
|
||||||
|
function StyleEditorActor(aConnection, aParentActor)
|
||||||
|
{
|
||||||
|
this.conn = aConnection;
|
||||||
|
this._onDocumentLoaded = this._onDocumentLoaded.bind(this);
|
||||||
|
this._onSheetLoaded = this._onSheetLoaded.bind(this);
|
||||||
|
|
||||||
|
if (aParentActor instanceof BrowserTabActor &&
|
||||||
|
aParentActor.browser instanceof Ci.nsIDOMWindow) {
|
||||||
|
this._window = aParentActor.browser;
|
||||||
|
}
|
||||||
|
else if (aParentActor instanceof BrowserTabActor &&
|
||||||
|
aParentActor.browser instanceof Ci.nsIDOMElement) {
|
||||||
|
this._window = aParentActor.browser.contentWindow;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._window = Services.wm.getMostRecentWindow("navigator:browser");
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep a map of sheets-to-actors so we don't create two actors for one sheet
|
||||||
|
this._sheets = new Map();
|
||||||
|
|
||||||
|
this._actorPool = new ActorPool(this.conn);
|
||||||
|
this.conn.addActorPool(this._actorPool);
|
||||||
|
}
|
||||||
|
|
||||||
|
StyleEditorActor.prototype = {
|
||||||
|
/**
|
||||||
|
* Actor pool for all of the actors we send to the client.
|
||||||
|
*/
|
||||||
|
_actorPool: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The debugger server connection instance.
|
||||||
|
*/
|
||||||
|
conn: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The content window we work with.
|
||||||
|
*/
|
||||||
|
get win() this._window,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The current content document of the window we work with.
|
||||||
|
*/
|
||||||
|
get doc() this._window.document,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A window object, usually the browser window
|
||||||
|
*/
|
||||||
|
_window: null,
|
||||||
|
|
||||||
|
actorPrefix: "styleEditor",
|
||||||
|
|
||||||
|
form: function()
|
||||||
|
{
|
||||||
|
return { actor: this.actorID };
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroy the current StyleEditorActor instance.
|
||||||
|
*/
|
||||||
|
disconnect: function()
|
||||||
|
{
|
||||||
|
if (this._observer) {
|
||||||
|
this._observer.disconnect();
|
||||||
|
delete this._observer;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._sheets.clear();
|
||||||
|
|
||||||
|
this.conn.removeActorPool(this._actorPool);
|
||||||
|
this._actorPool = null;
|
||||||
|
this.conn = this._window = null;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release an actor from our actor pool.
|
||||||
|
*/
|
||||||
|
releaseActor: function(actor)
|
||||||
|
{
|
||||||
|
if (this._actorPool) {
|
||||||
|
this._actorPool.removeActor(actor.actorID);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the BaseURI for the document.
|
||||||
|
*
|
||||||
|
* @return {object} JSON message to with BaseURI
|
||||||
|
*/
|
||||||
|
onGetBaseURI: function() {
|
||||||
|
return { baseURI: this.doc.baseURIObject };
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when target navigates to a new document.
|
||||||
|
* Adds load listeners to document.
|
||||||
|
*/
|
||||||
|
onNewDocument: function() {
|
||||||
|
// delete previous document's actors
|
||||||
|
this._clearStyleSheetActors();
|
||||||
|
|
||||||
|
// Note: listening for load won't be necessary once
|
||||||
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=839103 is fixed
|
||||||
|
if (this.doc.readyState == "complete") {
|
||||||
|
this._onDocumentLoaded();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.win.addEventListener("load", this._onDocumentLoaded, false);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event handler for document loaded event.
|
||||||
|
*/
|
||||||
|
_onDocumentLoaded: function(event) {
|
||||||
|
if (event) {
|
||||||
|
this.win.removeEventListener("load", this._onDocumentLoaded, false);
|
||||||
|
}
|
||||||
|
let styleSheets = [];
|
||||||
|
|
||||||
|
if (this.doc.styleSheets.length) {
|
||||||
|
this._addStyleSheets(this.doc.styleSheets);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all the current stylesheet actors in map.
|
||||||
|
*/
|
||||||
|
_clearStyleSheetActors: function() {
|
||||||
|
for (let actor in this._sheets) {
|
||||||
|
this.releaseActor(this._sheets[actor]);
|
||||||
|
}
|
||||||
|
this._sheets.clear();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the actors of all the stylesheets in the current document.
|
||||||
|
*
|
||||||
|
* @return {object} JSON message with the stylesheet actors' forms
|
||||||
|
*/
|
||||||
|
onGetStyleSheets: function() {
|
||||||
|
let styleSheets = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < this.doc.styleSheets.length; ++i) {
|
||||||
|
let styleSheet = this.doc.styleSheets[i];
|
||||||
|
let actor = this._createStyleSheetActor(styleSheet);
|
||||||
|
styleSheets.push(actor.form());
|
||||||
|
}
|
||||||
|
|
||||||
|
return { "styleSheets": styleSheets };
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add all the stylesheets to the map and create an actor
|
||||||
|
* for each one if not already created. Send event that there
|
||||||
|
* are new stylesheets.
|
||||||
|
*
|
||||||
|
* @param {[DOMStyleSheet]} styleSheets
|
||||||
|
* Stylesheets to add
|
||||||
|
*/
|
||||||
|
_addStyleSheets: function(styleSheets)
|
||||||
|
{
|
||||||
|
let sheets = [];
|
||||||
|
for (let i = 0; i < styleSheets.length; i++) {
|
||||||
|
let styleSheet = styleSheets[i];
|
||||||
|
sheets.push(styleSheet);
|
||||||
|
|
||||||
|
// Get all sheets, including imported ones
|
||||||
|
let imports = this._getImported(styleSheet);
|
||||||
|
sheets = sheets.concat(imports);
|
||||||
|
}
|
||||||
|
|
||||||
|
let actors = sheets.map((sheet) => {
|
||||||
|
let actor = this._createStyleSheetActor(sheet);
|
||||||
|
return actor.form();
|
||||||
|
});
|
||||||
|
|
||||||
|
this._notifyStyleSheetsAdded(actors);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an event notifying that there are new style sheets
|
||||||
|
*
|
||||||
|
* @param {[object]} actors
|
||||||
|
* Forms of the new style sheet actors
|
||||||
|
*/
|
||||||
|
_notifyStyleSheetsAdded: function(actors)
|
||||||
|
{
|
||||||
|
this.conn.send({
|
||||||
|
from: this.actorID,
|
||||||
|
type: "styleSheetsAdded",
|
||||||
|
styleSheets: actors
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the stylesheets @imported from a stylesheet.
|
||||||
|
*
|
||||||
|
* @param {DOMStyleSheet} styleSheet
|
||||||
|
* Style sheet to search
|
||||||
|
* @return {array}
|
||||||
|
* All the imported stylesheets
|
||||||
|
*/
|
||||||
|
_getImported: function(styleSheet) {
|
||||||
|
let imported = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < styleSheet.cssRules.length; i++) {
|
||||||
|
let rule = styleSheet.cssRules[i];
|
||||||
|
if (rule.type == Ci.nsIDOMCSSRule.IMPORT_RULE) {
|
||||||
|
// Associated styleSheet may be null if it has already been seen due to
|
||||||
|
// duplicate @imports for the same URL.
|
||||||
|
if (!rule.styleSheet) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
imported.push(rule.styleSheet);
|
||||||
|
|
||||||
|
// recurse imports in this stylesheet as well
|
||||||
|
imported = imported.concat(this._getImported(rule.styleSheet));
|
||||||
|
}
|
||||||
|
else if (rule.type != Ci.nsIDOMCSSRule.CHARSET_RULE) {
|
||||||
|
// @import rules must precede all others except @charset
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return imported;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new actor for a style sheet, if it hasn't
|
||||||
|
* already been created, and return it.
|
||||||
|
*
|
||||||
|
* @param {DOMStyleSheet} aStyleSheet
|
||||||
|
* The style sheet to create an actor for.
|
||||||
|
* @return {StyleSheetActor}
|
||||||
|
* The actor for this style sheet
|
||||||
|
*/
|
||||||
|
_createStyleSheetActor: function(aStyleSheet)
|
||||||
|
{
|
||||||
|
if (this._sheets.has(aStyleSheet)) {
|
||||||
|
return this._sheets.get(aStyleSheet);
|
||||||
|
}
|
||||||
|
let actor = new StyleSheetActor(aStyleSheet, this);
|
||||||
|
this._actorPool.addActor(actor);
|
||||||
|
this._sheets.set(aStyleSheet, actor);
|
||||||
|
return actor;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for style sheet loading event. Add
|
||||||
|
* a new actor for the sheet and notify.
|
||||||
|
*
|
||||||
|
* @param {Event} event
|
||||||
|
*/
|
||||||
|
_onSheetLoaded: function(event) {
|
||||||
|
let style = event.target;
|
||||||
|
style.removeEventListener("load", this._onSheetLoaded, false);
|
||||||
|
|
||||||
|
let actor = this._createStyleSheetActor(style.sheet);
|
||||||
|
this._notifyStyleSheetsAdded([actor.form()]);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new style sheet in the document with the given text.
|
||||||
|
* Return an actor for it.
|
||||||
|
*
|
||||||
|
* @param {object} request
|
||||||
|
* Debugging protocol request object, with 'text property'
|
||||||
|
* @return {object}
|
||||||
|
* Object with 'styelSheet' property for form on new actor.
|
||||||
|
*/
|
||||||
|
onNewStyleSheet: function(request) {
|
||||||
|
let parent = this.doc.documentElement;
|
||||||
|
let style = this.doc.createElementNS("http://www.w3.org/1999/xhtml", "style");
|
||||||
|
style.setAttribute("type", "text/css");
|
||||||
|
|
||||||
|
if (request.text) {
|
||||||
|
style.appendChild(this.doc.createTextNode(request.text));
|
||||||
|
}
|
||||||
|
parent.appendChild(style);
|
||||||
|
|
||||||
|
let actor = this._createStyleSheetActor(style.sheet);
|
||||||
|
return { styleSheet: actor.form() };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request types this actor can handle.
|
||||||
|
*/
|
||||||
|
StyleEditorActor.prototype.requestTypes = {
|
||||||
|
"getStyleSheets": StyleEditorActor.prototype.onGetStyleSheets,
|
||||||
|
"newStyleSheet": StyleEditorActor.prototype.onNewStyleSheet,
|
||||||
|
"getBaseURI": StyleEditorActor.prototype.onGetBaseURI,
|
||||||
|
"newDocument": StyleEditorActor.prototype.onNewDocument
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function StyleSheetActor(aStyleSheet, aParentActor) {
|
||||||
|
this.styleSheet = aStyleSheet;
|
||||||
|
this.parentActor = aParentActor;
|
||||||
|
|
||||||
|
// text and index are unknown until source load
|
||||||
|
this.text = null;
|
||||||
|
this._styleSheetIndex = -1;
|
||||||
|
|
||||||
|
this._transitionRefCount = 0;
|
||||||
|
|
||||||
|
this._onSourceLoad = this._onSourceLoad.bind(this);
|
||||||
|
this._notifyError = this._notifyError.bind(this);
|
||||||
|
|
||||||
|
// if this sheet has an @import, then it's rules are loaded async
|
||||||
|
let ownerNode = this.styleSheet.ownerNode;
|
||||||
|
if (ownerNode) {
|
||||||
|
let onSheetLoaded = function(event) {
|
||||||
|
ownerNode.removeEventListener("load", onSheetLoaded, false);
|
||||||
|
this._notifyPropertyChanged("ruleCount");
|
||||||
|
}.bind(this);
|
||||||
|
|
||||||
|
ownerNode.addEventListener("load", onSheetLoaded, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyleSheetActor.prototype = {
|
||||||
|
actorPrefix: "stylesheet",
|
||||||
|
|
||||||
|
toString: function() {
|
||||||
|
return "[StyleSheetActor " + this.actorID + "]";
|
||||||
|
},
|
||||||
|
|
||||||
|
disconnect: function() {
|
||||||
|
this.parentActor.releaseActor(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Window of target
|
||||||
|
*/
|
||||||
|
get win() {
|
||||||
|
return this.parentActor._window;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Document of target.
|
||||||
|
*/
|
||||||
|
get doc() {
|
||||||
|
return this.win.document;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the index (order) of stylesheet in the document.
|
||||||
|
*
|
||||||
|
* @return number
|
||||||
|
*/
|
||||||
|
get styleSheetIndex()
|
||||||
|
{
|
||||||
|
if (this._styleSheetIndex == -1) {
|
||||||
|
for (let i = 0; i < this.doc.styleSheets.length; i++) {
|
||||||
|
if (this.doc.styleSheets[i] == this.styleSheet) {
|
||||||
|
this._styleSheetIndex = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this._styleSheetIndex;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current state of the actor
|
||||||
|
*
|
||||||
|
* @return {object}
|
||||||
|
* With properties of the underlying stylesheet, plus 'text',
|
||||||
|
* 'styleSheetIndex' and 'parentActor' if it's @imported
|
||||||
|
*/
|
||||||
|
form: function() {
|
||||||
|
let form = {
|
||||||
|
actor: this.actorID, // actorID is set when this actor is added to a pool
|
||||||
|
href: this.styleSheet.href,
|
||||||
|
disabled: this.styleSheet.disabled,
|
||||||
|
title: this.styleSheet.title,
|
||||||
|
styleSheetIndex: this.styleSheetIndex,
|
||||||
|
text: this.text
|
||||||
|
}
|
||||||
|
|
||||||
|
// get parent actor if this sheet was @imported
|
||||||
|
let parent = this.styleSheet.parentStyleSheet;
|
||||||
|
if (parent) {
|
||||||
|
form.parentActor = this.parentActor._sheets.get(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
form.ruleCount = this.styleSheet.cssRules.length;
|
||||||
|
}
|
||||||
|
catch(e) {
|
||||||
|
// stylesheet had an @import rule that wasn't loaded yet
|
||||||
|
}
|
||||||
|
|
||||||
|
return form;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle the disabled property of the style sheet
|
||||||
|
*
|
||||||
|
* @return {object}
|
||||||
|
* 'disabled' - the disabled state after toggling.
|
||||||
|
*/
|
||||||
|
onToggleDisabled: function() {
|
||||||
|
this.styleSheet.disabled = !this.styleSheet.disabled;
|
||||||
|
this._notifyPropertyChanged("disabled");
|
||||||
|
|
||||||
|
return { disabled: this.styleSheet.disabled };
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an event notifying that a property of the stylesheet
|
||||||
|
* has changed.
|
||||||
|
*
|
||||||
|
* @param {string} property
|
||||||
|
* Name of the changed property
|
||||||
|
*/
|
||||||
|
_notifyPropertyChanged: function(property) {
|
||||||
|
this.conn.send({
|
||||||
|
from: this.actorID,
|
||||||
|
type: "propertyChange-" + this.actorID,
|
||||||
|
property: property,
|
||||||
|
value: this.form()[property]
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an event notifying that an error has occured
|
||||||
|
*
|
||||||
|
* @param {string} message
|
||||||
|
* Error message
|
||||||
|
*/
|
||||||
|
_notifyError: function(message) {
|
||||||
|
this.conn.send({
|
||||||
|
from: this.actorID,
|
||||||
|
type: "error-" + this.actorID,
|
||||||
|
errorMessage: message
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler for event when the style sheet's full text has been
|
||||||
|
* loaded from its source.
|
||||||
|
*
|
||||||
|
* @param {string} source
|
||||||
|
* Text of the style sheet
|
||||||
|
* @param {[type]} charset
|
||||||
|
* Optional charset of the source
|
||||||
|
*/
|
||||||
|
_onSourceLoad: function(source, charset) {
|
||||||
|
this.text = this._decodeCSSCharset(source, charset || "");
|
||||||
|
|
||||||
|
this.conn.send({
|
||||||
|
from: this.actorID,
|
||||||
|
type: "sourceLoad-" + this.actorID,
|
||||||
|
source: this.text
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch the source of the style sheet from its URL
|
||||||
|
*/
|
||||||
|
onFetchSource: function() {
|
||||||
|
if (!this.styleSheet.href) {
|
||||||
|
// this is an inline <style> sheet
|
||||||
|
let source = this.styleSheet.ownerNode.textContent;
|
||||||
|
this._onSourceLoad(source);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
let scheme = Services.io.extractScheme(this.styleSheet.href);
|
||||||
|
switch (scheme) {
|
||||||
|
case "file":
|
||||||
|
this._styleSheetFilePath = this.styleSheet.href;
|
||||||
|
case "chrome":
|
||||||
|
case "resource":
|
||||||
|
this._loadSourceFromFile(this.styleSheet.href);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this._loadSourceFromCache(this.styleSheet.href);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a CSS source string to unicode according to the character set rules
|
||||||
|
* defined in <http://www.w3.org/TR/CSS2/syndata.html#charset>.
|
||||||
|
*
|
||||||
|
* @param string string
|
||||||
|
* Source of a CSS stylesheet, loaded from file or cache.
|
||||||
|
* @param string channelCharset
|
||||||
|
* Charset of the source string if set by the HTTP channel.
|
||||||
|
* @return string
|
||||||
|
* The CSS string, in unicode.
|
||||||
|
*/
|
||||||
|
_decodeCSSCharset: function(string, channelCharset)
|
||||||
|
{
|
||||||
|
// StyleSheet's charset can be specified from multiple sources
|
||||||
|
|
||||||
|
if (channelCharset.length > 0) {
|
||||||
|
// step 1 of syndata.html: charset given in HTTP header.
|
||||||
|
return this._convertToUnicode(string, channelCharset);
|
||||||
|
}
|
||||||
|
|
||||||
|
let sheet = this.styleSheet;
|
||||||
|
if (sheet) {
|
||||||
|
// Do we have a @charset rule in the stylesheet?
|
||||||
|
// step 2 of syndata.html (without the BOM check).
|
||||||
|
if (sheet.cssRules) {
|
||||||
|
let rules = sheet.cssRules;
|
||||||
|
if (rules.length
|
||||||
|
&& rules.item(0).type == Ci.nsIDOMCSSRule.CHARSET_RULE) {
|
||||||
|
return this._convertToUnicode(string, rules.item(0).encoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 3: charset attribute of <link> or <style> element, if it exists
|
||||||
|
if (sheet.ownerNode && sheet.ownerNode.getAttribute) {
|
||||||
|
let linkCharset = sheet.ownerNode.getAttribute("charset");
|
||||||
|
if (linkCharset != null) {
|
||||||
|
return this._convertToUnicode(string, linkCharset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 4 (1 of 2): charset of referring stylesheet.
|
||||||
|
let parentSheet = sheet.parentStyleSheet;
|
||||||
|
if (parentSheet && parentSheet.cssRules &&
|
||||||
|
parentSheet.cssRules[0].type == Ci.nsIDOMCSSRule.CHARSET_RULE) {
|
||||||
|
return this._convertToUnicode(string,
|
||||||
|
parentSheet.cssRules[0].encoding);
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 4 (2 of 2): charset of referring document.
|
||||||
|
if (sheet.ownerNode && sheet.ownerNode.ownerDocument.characterSet) {
|
||||||
|
return this._convertToUnicode(string,
|
||||||
|
sheet.ownerNode.ownerDocument.characterSet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// step 5: default to utf-8.
|
||||||
|
return this._convertToUnicode(string, "UTF-8");
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a given string, encoded in a given character set, to unicode.
|
||||||
|
*
|
||||||
|
* @param string string
|
||||||
|
* A string.
|
||||||
|
* @param string charset
|
||||||
|
* A character set.
|
||||||
|
* @return string
|
||||||
|
* A unicode string.
|
||||||
|
*/
|
||||||
|
_convertToUnicode: function(string, charset) {
|
||||||
|
// Decoding primitives.
|
||||||
|
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||||
|
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||||
|
|
||||||
|
try {
|
||||||
|
converter.charset = charset;
|
||||||
|
return converter.ConvertToUnicode(string);
|
||||||
|
} catch(e) {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load source from a file or file-like resource.
|
||||||
|
*
|
||||||
|
* @param string href
|
||||||
|
* URL for the stylesheet.
|
||||||
|
*/
|
||||||
|
_loadSourceFromFile: function(href)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
NetUtil.asyncFetch(href, (stream, status) => {
|
||||||
|
if (!Components.isSuccessCode(status)) {
|
||||||
|
this._notifyError(LOAD_ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let source = NetUtil.readInputStreamToString(stream, stream.available());
|
||||||
|
stream.close();
|
||||||
|
this._onSourceLoad(source);
|
||||||
|
});
|
||||||
|
} catch (ex) {
|
||||||
|
this._notifyError(LOAD_ERROR);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Load source from the HTTP cache.
|
||||||
|
*
|
||||||
|
* @param string href
|
||||||
|
* URL for the stylesheet.
|
||||||
|
*/
|
||||||
|
_loadSourceFromCache: function(href)
|
||||||
|
{
|
||||||
|
let channel = Services.io.newChannel(href, null, null);
|
||||||
|
let chunks = [];
|
||||||
|
let channelCharset = "";
|
||||||
|
let streamListener = { // nsIStreamListener inherits nsIRequestObserver
|
||||||
|
onStartRequest: (aRequest, aContext, aStatusCode) => {
|
||||||
|
if (!Components.isSuccessCode(aStatusCode)) {
|
||||||
|
this._notifyError(LOAD_ERROR);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onDataAvailable: (aRequest, aContext, aStream, aOffset, aCount) => {
|
||||||
|
let channel = aRequest.QueryInterface(Ci.nsIChannel);
|
||||||
|
if (!channelCharset) {
|
||||||
|
channelCharset = channel.contentCharset;
|
||||||
|
}
|
||||||
|
chunks.push(NetUtil.readInputStreamToString(aStream, aCount));
|
||||||
|
},
|
||||||
|
onStopRequest: (aRequest, aContext, aStatusCode) => {
|
||||||
|
if (!Components.isSuccessCode(aStatusCode)) {
|
||||||
|
this._notifyError(LOAD_ERROR);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let source = chunks.join("");
|
||||||
|
this._onSourceLoad(source, channelCharset);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
|
||||||
|
let loadContext = this.win.QueryInterface(Ci.nsIInterfaceRequestor)
|
||||||
|
.getInterface(Ci.nsIWebNavigation)
|
||||||
|
.QueryInterface(Ci.nsILoadContext);
|
||||||
|
channel.setPrivate(loadContext.usePrivateBrowsing);
|
||||||
|
}
|
||||||
|
channel.loadFlags = channel.LOAD_FROM_CACHE;
|
||||||
|
channel.asyncOpen(streamListener, null);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the style sheet in place with new text
|
||||||
|
*
|
||||||
|
* @param {object} request
|
||||||
|
* 'text' - new text
|
||||||
|
* 'transition' - whether to do CSS transition for change.
|
||||||
|
*/
|
||||||
|
onUpdate: function(request) {
|
||||||
|
DOMUtils.parseStyleSheet(this.styleSheet, request.text);
|
||||||
|
|
||||||
|
if (request.transition) {
|
||||||
|
this._insertTransistionRule();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._notifyStyleApplied();
|
||||||
|
}
|
||||||
|
this._notifyPropertyChanged("ruleCount");
|
||||||
|
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insert a catch-all transition rule into the document. Set a timeout
|
||||||
|
* to remove the rule after a certain time.
|
||||||
|
*/
|
||||||
|
_insertTransistionRule: function() {
|
||||||
|
// Insert the global transition rule
|
||||||
|
// Use a ref count to make sure we do not add it multiple times.. and remove
|
||||||
|
// it only when all pending StyleEditor-generated transitions ended.
|
||||||
|
if (this._transitionRefCount == 0) {
|
||||||
|
this.styleSheet.insertRule(TRANSITION_RULE, this.styleSheet.cssRules.length);
|
||||||
|
this.doc.documentElement.classList.add(TRANSITION_CLASS);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._transitionRefCount++;
|
||||||
|
|
||||||
|
// Set up clean up and commit after transition duration (+10% buffer)
|
||||||
|
// @see _onTransitionEnd
|
||||||
|
this.win.setTimeout(this._onTransitionEnd.bind(this),
|
||||||
|
Math.floor(TRANSITION_DURATION_MS * 1.1));
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This cleans up class and rule added for transition effect and then
|
||||||
|
* notifies that the style has been applied.
|
||||||
|
*/
|
||||||
|
_onTransitionEnd: function()
|
||||||
|
{
|
||||||
|
if (--this._transitionRefCount == 0) {
|
||||||
|
this.doc.documentElement.classList.remove(TRANSITION_CLASS);
|
||||||
|
this.styleSheet.deleteRule(this.styleSheet.cssRules.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._notifyStyleApplied();
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send and event notifying that the new style has been applied fully.
|
||||||
|
*/
|
||||||
|
_notifyStyleApplied: function()
|
||||||
|
{
|
||||||
|
this.conn.send({
|
||||||
|
from: this.actorID,
|
||||||
|
type: "styleApplied-" + this.actorID
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StyleSheetActor.prototype.requestTypes = {
|
||||||
|
"toggleDisabled": StyleSheetActor.prototype.onToggleDisabled,
|
||||||
|
"fetchSource": StyleSheetActor.prototype.onFetchSource,
|
||||||
|
"update": StyleSheetActor.prototype.onUpdate
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
|
||||||
|
return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
|
||||||
|
});
|
|
@ -0,0 +1,6 @@
|
||||||
|
# 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/.
|
||||||
|
|
||||||
|
#TEST_DIRS += ['tests']
|
Загрузка…
Ссылка в новой задаче