зеркало из 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),
|
||||
|
||||
isTargetSupported: function(target) {
|
||||
return !target.isRemote;
|
||||
return true;
|
||||
},
|
||||
|
||||
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/commonjs/sdk/core/promise.js");
|
||||
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",
|
||||
"resource:///modules/devtools/StyleEditorChrome.jsm");
|
||||
|
@ -21,183 +25,99 @@ this.StyleEditorPanel = function StyleEditorPanel(panelWin, toolbox) {
|
|||
|
||||
this._toolbox = toolbox;
|
||||
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._panelDoc = panelWin.document;
|
||||
|
||||
this.destroy = this.destroy.bind(this);
|
||||
this._showError = this._showError.bind(this);
|
||||
}
|
||||
|
||||
StyleEditorPanel.prototype = {
|
||||
get target() this._toolbox.target,
|
||||
|
||||
get panelWindow() this._panelWin,
|
||||
|
||||
/**
|
||||
* open is effectively an asynchronous constructor
|
||||
*/
|
||||
open: function StyleEditor_open() {
|
||||
let contentWin = this._toolbox.target.window;
|
||||
open: function() {
|
||||
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;
|
||||
deferred.resolve(this);
|
||||
}.bind(this));
|
||||
})
|
||||
|
||||
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,
|
||||
|
||||
/**
|
||||
* Panel window getter.
|
||||
*/
|
||||
get panelWindow() this._panelWin,
|
||||
|
||||
/**
|
||||
* 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;
|
||||
_showError: function(event, errorCode) {
|
||||
let message = _(errorCode);
|
||||
let notificationBox = this._toolbox.getNotificationBox();
|
||||
let notification = notificationBox.getNotificationWithValue("styleeditor-error");
|
||||
if (!notification) {
|
||||
notificationBox.appendNotification(message,
|
||||
"styleeditor-error", "", notificationBox.PRIORITY_CRITICAL_LOW);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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) {
|
||||
this._panelWin.styleEditorChrome.selectStyleSheet(stylesheet, line, col);
|
||||
selectStyleSheet: function(href, 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) {
|
||||
this._destroyed = true;
|
||||
|
||||
this._target.off("will-navigate", this.beforeNavigate);
|
||||
this._target.off("navigate", this.newPage);
|
||||
this._target.off("close", this.destroy);
|
||||
this._target = null;
|
||||
this._toolbox = null;
|
||||
this._panelWin = null;
|
||||
this._panelDoc = null;
|
||||
|
||||
this._debuggee.destroy();
|
||||
this.UI.destroy();
|
||||
}
|
||||
|
||||
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 = [
|
||||
"_",
|
||||
"assert",
|
||||
"attr", // XXXkhuey unused?
|
||||
"getCurrentBrowserTabContentWindow", // XXXkhuey unused?
|
||||
"log",
|
||||
"text",
|
||||
"wire"
|
||||
"wire",
|
||||
"showFilePicker"
|
||||
];
|
||||
|
||||
const Cc = Components.classes;
|
||||
|
@ -108,7 +107,7 @@ function forEach(aObject, aCallback)
|
|||
|
||||
/**
|
||||
* Log a message to the console.
|
||||
*
|
||||
*
|
||||
* @param ...rest
|
||||
* One or multiple arguments to log.
|
||||
* If multiple arguments are given, they will be joined by " " in the log.
|
||||
|
@ -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"
|
||||
accesskey="&newButton.accesskey;"
|
||||
tooltiptext="&newButton.tooltip;"
|
||||
label="&newButton.label;"
|
||||
disabled="true"/>
|
||||
label="&newButton.label;"/>
|
||||
<xul:toolbarbutton class="style-editor-importButton devtools-toolbarbutton"
|
||||
accesskey="&importButton.accesskey;"
|
||||
tooltiptext="&importButton.tooltip;"
|
||||
label="&importButton.label;"
|
||||
disabled="true"/>
|
||||
label="&importButton.label;"/>
|
||||
</xul:toolbar>
|
||||
</xul:box>
|
||||
<xul:box id="splitview-resizer-target" class="splitview-nav-container"
|
||||
|
|
|
@ -21,14 +21,10 @@ _BROWSER_TEST_FILES = \
|
|||
browser_styleeditor_init.js \
|
||||
browser_styleeditor_loading.js \
|
||||
browser_styleeditor_new.js \
|
||||
browser_styleeditor_passedinsheet.js \
|
||||
browser_styleeditor_pretty.js \
|
||||
browser_styleeditor_private_perwindowpb.js \
|
||||
browser_styleeditor_readonly.js \
|
||||
browser_styleeditor_reopen.js \
|
||||
browser_styleeditor_sv_keynav.js \
|
||||
browser_styleeditor_sv_resize.js \
|
||||
browser_styleeditor_bug_826982_location_changed.js \
|
||||
browser_styleeditor_bug_851132_middle_click.js \
|
||||
head.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";
|
||||
|
||||
let gUI;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
||||
run(aChrome);
|
||||
let count = 0;
|
||||
addTabAndOpenStyleEditor(function(panel) {
|
||||
gUI = panel.UI;
|
||||
gUI.on("editor-added", function(event, editor) {
|
||||
count++;
|
||||
if (count == 2) {
|
||||
runTests();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}
|
||||
|
||||
let gSEChrome, timeoutID;
|
||||
let timeoutID;
|
||||
|
||||
function run(aChrome) {
|
||||
gSEChrome = aChrome;
|
||||
function runTests() {
|
||||
gBrowser.tabContainer.addEventListener("TabOpen", onTabAdded, false);
|
||||
aChrome.editors[0].addActionListener({onAttach: onEditor0Attach});
|
||||
aChrome.editors[1].addActionListener({onAttach: onEditor1Attach});
|
||||
gUI.editors[0].getSourceEditor().then(onEditor0Attach);
|
||||
gUI.editors[1].getSourceEditor().then(onEditor1Attach);
|
||||
}
|
||||
|
||||
function getStylesheetNameLinkFor(aEditor) {
|
||||
return gSEChrome.getSummaryElementForEditor(aEditor).querySelector(".stylesheet-name");
|
||||
return aEditor.summary.querySelector(".stylesheet-name");
|
||||
}
|
||||
|
||||
function onEditor0Attach(aEditor) {
|
||||
waitForFocus(function () {
|
||||
// left mouse click should focus editor 1
|
||||
EventUtils.synthesizeMouseAtCenter(
|
||||
getStylesheetNameLinkFor(gSEChrome.editors[1]),
|
||||
getStylesheetNameLinkFor(gUI.editors[1]),
|
||||
{button: 0},
|
||||
gChromeWindow);
|
||||
}, gChromeWindow);
|
||||
gPanelWindow);
|
||||
}, gPanelWindow);
|
||||
}
|
||||
|
||||
function onEditor1Attach(aEditor) {
|
||||
|
@ -42,9 +50,9 @@ function onEditor1Attach(aEditor) {
|
|||
|
||||
// right mouse click should not open a new tab
|
||||
EventUtils.synthesizeMouseAtCenter(
|
||||
getStylesheetNameLinkFor(gSEChrome.editors[2]),
|
||||
getStylesheetNameLinkFor(gUI.editors[2]),
|
||||
{button: 1},
|
||||
gChromeWindow);
|
||||
gPanelWindow);
|
||||
|
||||
setTimeout(finish, 0);
|
||||
}
|
||||
|
@ -56,5 +64,5 @@ function onTabAdded() {
|
|||
|
||||
registerCleanupFunction(function () {
|
||||
gBrowser.tabContainer.removeEventListener("TabOpen", onTabAdded, false);
|
||||
gSEChrome = null;
|
||||
gUI = null;
|
||||
});
|
||||
|
|
|
@ -5,97 +5,71 @@
|
|||
// https rather than chrome to improve coverage
|
||||
const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
|
||||
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
|
||||
let count = 0;
|
||||
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
||||
aChrome.addChromeListener({
|
||||
onEditorAdded: function (aChrome, aEditor) {
|
||||
count++;
|
||||
if (count == 2) {
|
||||
// we test against first stylesheet after all are ready
|
||||
let editor = aChrome.editors[0];
|
||||
if (!editor.sourceEditor) {
|
||||
editor.addActionListener({
|
||||
onAttach: function (aEditor) {
|
||||
run(aChrome, aEditor);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
run(aChrome, editor);
|
||||
}
|
||||
}
|
||||
addTabAndOpenStyleEditor(function(panel) {
|
||||
let UI = panel.UI;
|
||||
UI.on("editor-added", function(event, editor) {
|
||||
count++;
|
||||
if (count == 2) {
|
||||
// we test against first stylesheet after all are ready
|
||||
let editor = UI.editors[0];
|
||||
editor.getSourceEditor().then(runTests.bind(this, UI, editor));
|
||||
}
|
||||
});
|
||||
})
|
||||
});
|
||||
|
||||
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],
|
||||
"stylesheet with index 0 is the first stylesheet listed in the UI");
|
||||
let summary = editor.summary;
|
||||
let enabledToggle = summary.querySelector(".stylesheet-enabled");
|
||||
ok(enabledToggle, "enabled toggle button exists");
|
||||
|
||||
let firstStyleSheetEditor = aEditor;
|
||||
let firstStyleSheetUI = aChrome.getSummaryElementForEditor(aEditor);
|
||||
let enabledToggle = firstStyleSheetUI.querySelector(".stylesheet-enabled");
|
||||
|
||||
is(firstStyleSheetEditor.contentDocument.styleSheets[0].disabled, false,
|
||||
is(editor.styleSheet.disabled, false,
|
||||
"first stylesheet is initially enabled");
|
||||
is(firstStyleSheetEditor.hasFlag("disabled"), false,
|
||||
"first stylesheet is initially enabled, it does not have DISABLED flag");
|
||||
is(firstStyleSheetUI.classList.contains("disabled"), false,
|
||||
|
||||
is(summary.classList.contains("disabled"), false,
|
||||
"first stylesheet is initially enabled, UI does not have DISABLED class");
|
||||
|
||||
let disabledToggleCount = 0;
|
||||
firstStyleSheetEditor.addActionListener({
|
||||
onFlagChange: function (aEditor, aFlagName) {
|
||||
if (aFlagName != "disabled") {
|
||||
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();
|
||||
editor.on("property-change", function(event, property) {
|
||||
if (property != "disabled") {
|
||||
return;
|
||||
}
|
||||
disabledToggleCount++;
|
||||
|
||||
if (disabledToggleCount == 1) {
|
||||
is(editor.styleSheet.disabled, true, "first stylesheet is now disabled");
|
||||
is(summary.classList.contains("disabled"), true,
|
||||
"first stylesheet is now disabled, UI has DISABLED class");
|
||||
|
||||
// now toggle it back to enabled
|
||||
waitForFocus(function () {
|
||||
EventUtils.synthesizeMouseAtCenter(enabledToggle, {}, gPanelWindow);
|
||||
}, gPanelWindow);
|
||||
return;
|
||||
}
|
||||
|
||||
// disabledToggleCount == 2
|
||||
is(editor.styleSheet.disabled, false, "first stylesheet is now enabled again");
|
||||
is(summary.classList.contains("disabled"), false,
|
||||
"first stylesheet is now enabled again, UI does not have DISABLED class");
|
||||
|
||||
finish();
|
||||
});
|
||||
|
||||
waitForFocus(function () {
|
||||
EventUtils.synthesizeMouseAtCenter(enabledToggle, {}, gChromeWindow);
|
||||
}, gChromeWindow);
|
||||
EventUtils.synthesizeMouseAtCenter(enabledToggle, {}, gPanelWindow);
|
||||
}, gPanelWindow);
|
||||
}
|
||||
|
|
|
@ -21,39 +21,28 @@ function test()
|
|||
|
||||
copy(TESTCASE_URI_HTML, "simple.html", function(htmlFile) {
|
||||
copy(TESTCASE_URI_CSS, "simple.css", function(cssFile) {
|
||||
|
||||
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
||||
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
|
||||
});
|
||||
}
|
||||
addTabAndOpenStyleEditor(function(panel) {
|
||||
let UI = panel.UI;
|
||||
UI.on("editor-added", function(event, editor) {
|
||||
if (editor.styleSheet.styleSheetIndex != 0) {
|
||||
return; // we want to test against the first stylesheet
|
||||
}
|
||||
});
|
||||
let editor = UI.editors[0];
|
||||
editor.getSourceEditor().then(runTests.bind(this, editor));
|
||||
})
|
||||
});
|
||||
|
||||
let uri = Services.io.newFileURI(htmlFile);
|
||||
let filePath = uri.resolve("");
|
||||
|
||||
content.location = filePath;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function run(aEditor)
|
||||
function runTests(editor)
|
||||
{
|
||||
aEditor.saveToFile(null, function (aFile) {
|
||||
ok(aFile, "file should get saved directly when using a file:// URI");
|
||||
|
||||
gChromeWindow.close();
|
||||
editor.saveToFile(null, function (file) {
|
||||
ok(file, "file should get saved directly when using a file:// URI");
|
||||
finish();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -13,27 +13,21 @@ const FILENAME = "styleeditor-import-test.css";
|
|||
const SOURCE = "body{background:red;}";
|
||||
|
||||
|
||||
let gUI;
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
|
||||
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
||||
aChrome.addChromeListener({
|
||||
onEditorAdded: testEditorAdded
|
||||
});
|
||||
run(aChrome);
|
||||
addTabAndOpenStyleEditor(function(panel) {
|
||||
gUI = panel.UI;
|
||||
gUI.on("editor-added", testEditorAdded);
|
||||
});
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}
|
||||
|
||||
function run(aChrome)
|
||||
{
|
||||
is(aChrome.editors.length, 2,
|
||||
"there is 2 stylesheets initially");
|
||||
}
|
||||
|
||||
function testImport(aChrome, aEditor)
|
||||
function testImport()
|
||||
{
|
||||
// create file to import first
|
||||
let file = FileUtils.getFile("ProfD", [FILENAME]);
|
||||
|
@ -46,42 +40,37 @@ function testImport(aChrome, aEditor)
|
|||
FileUtils.closeSafeFileOutputStream(ostream);
|
||||
|
||||
// click the import button now that the file to import is ready
|
||||
aChrome._mockImportFile = file;
|
||||
gUI._mockImportFile = file;
|
||||
|
||||
waitForFocus(function () {
|
||||
let document = gChromeWindow.document
|
||||
let document = gPanelWindow.document
|
||||
let importButton = document.querySelector(".style-editor-importButton");
|
||||
EventUtils.synthesizeMouseAtCenter(importButton, {}, gChromeWindow);
|
||||
}, gChromeWindow);
|
||||
ok(importButton, "import button exists");
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(importButton, {}, gPanelWindow);
|
||||
}, gPanelWindow);
|
||||
});
|
||||
}
|
||||
|
||||
let gAddedCount = 0;
|
||||
function testEditorAdded(aChrome, aEditor)
|
||||
function testEditorAdded(aEvent, aEditor)
|
||||
{
|
||||
if (++gAddedCount == 2) {
|
||||
// test import after the 2 initial stylesheets have been loaded
|
||||
if (!aChrome.editors[0].sourceEditor) {
|
||||
aChrome.editors[0].addActionListener({
|
||||
onAttach: function () {
|
||||
testImport(aChrome);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
testImport(aChrome);
|
||||
}
|
||||
gUI.editors[0].getSourceEditor().then(function() {
|
||||
testImport();
|
||||
});
|
||||
}
|
||||
|
||||
if (!aEditor.hasFlag("imported")) {
|
||||
if (!aEditor.savedFile) {
|
||||
return;
|
||||
}
|
||||
|
||||
ok(!aEditor.hasFlag("inline"),
|
||||
"imported stylesheet does not have INLINE flag");
|
||||
ok(aEditor.savedFile,
|
||||
is(aEditor.savedFile.leafName, FILENAME,
|
||||
"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");
|
||||
|
||||
gUI = null;
|
||||
finish();
|
||||
}
|
||||
|
|
|
@ -5,30 +5,39 @@
|
|||
// http rather than chrome to improve coverage
|
||||
const TESTCASE_URI = TEST_BASE_HTTP + "import.html";
|
||||
|
||||
let gUI;
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
|
||||
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
||||
run(aChrome);
|
||||
addTabAndOpenStyleEditor(function(panel) {
|
||||
gUI = panel.UI;
|
||||
gUI.on("editor-added", onEditorAdded);
|
||||
});
|
||||
|
||||
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");
|
||||
|
||||
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");
|
||||
|
||||
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");
|
||||
|
||||
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");
|
||||
|
||||
gUI = null;
|
||||
finish();
|
||||
}
|
||||
|
|
|
@ -4,71 +4,52 @@
|
|||
|
||||
const TESTCASE_URI = TEST_BASE + "simple.html";
|
||||
|
||||
let gUI;
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
|
||||
launchStyleEditorChrome(function(aChrome) {
|
||||
aChrome.addChromeListener({
|
||||
onEditorAdded: testEditorAdded
|
||||
});
|
||||
run(aChrome);
|
||||
addTabAndOpenStyleEditor(function(panel) {
|
||||
gUI = panel.UI;
|
||||
gUI.on("editor-added", testEditorAdded);
|
||||
});
|
||||
|
||||
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;
|
||||
function testEditorAdded(aChrome, aEditor)
|
||||
function testEditorAdded(aEvent, aEditor)
|
||||
{
|
||||
if (aEditor.styleSheetIndex == 0) {
|
||||
if (aEditor.styleSheet.styleSheetIndex == 0) {
|
||||
gEditorAddedCount++;
|
||||
testFirstStyleSheetEditor(aChrome, aEditor);
|
||||
testFirstStyleSheetEditor(aEditor);
|
||||
}
|
||||
if (aEditor.styleSheetIndex == 1) {
|
||||
if (aEditor.styleSheet.styleSheetIndex == 1) {
|
||||
gEditorAddedCount++;
|
||||
testSecondStyleSheetEditor(aChrome, aEditor);
|
||||
testSecondStyleSheetEditor(aEditor);
|
||||
}
|
||||
|
||||
if (gEditorAddedCount == 2) {
|
||||
gUI = null;
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
||||
function testFirstStyleSheetEditor(aChrome, aEditor)
|
||||
function testFirstStyleSheetEditor(aEditor)
|
||||
{
|
||||
// Note: the html <link> contains charset="UTF-8".
|
||||
ok(aEditor._state.text.indexOf("\u263a") >= 0,
|
||||
"stylesheet is unicode-aware.");
|
||||
|
||||
//testing TESTCASE's simple.css stylesheet
|
||||
is(aEditor.styleSheetIndex, 0,
|
||||
is(aEditor.styleSheet.styleSheetIndex, 0,
|
||||
"first stylesheet is at index 0");
|
||||
|
||||
is(aEditor, aChrome.editors[0],
|
||||
is(aEditor, gUI.editors[0],
|
||||
"first stylesheet corresponds to StyleEditorChrome.editors[0]");
|
||||
|
||||
ok(!aEditor.hasFlag("inline"),
|
||||
"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 summary = aEditor.summary;
|
||||
|
||||
let name = summary.querySelector(".stylesheet-name > label").getAttribute("value");
|
||||
is(name, "simple.css",
|
||||
|
@ -82,21 +63,16 @@ function testFirstStyleSheetEditor(aChrome, aEditor)
|
|||
"first stylesheet UI is focused/active");
|
||||
}
|
||||
|
||||
function testSecondStyleSheetEditor(aChrome, aEditor)
|
||||
function testSecondStyleSheetEditor(aEditor)
|
||||
{
|
||||
//testing TESTCASE's inline stylesheet
|
||||
is(aEditor.styleSheetIndex, 1,
|
||||
is(aEditor.styleSheet.styleSheetIndex, 1,
|
||||
"second stylesheet is at index 1");
|
||||
|
||||
is(aEditor, aChrome.editors[1],
|
||||
is(aEditor, gUI.editors[1],
|
||||
"second stylesheet corresponds to StyleEditorChrome.editors[1]");
|
||||
|
||||
ok(aEditor.hasFlag("inline"),
|
||||
"second stylesheet has INLINE flag");
|
||||
|
||||
let summary = aChrome.getSummaryElementForEditor(aEditor);
|
||||
ok(summary.classList.contains("inline"),
|
||||
"second stylesheet UI has INLINE class");
|
||||
let summary = aEditor.summary;
|
||||
|
||||
let name = summary.querySelector(".stylesheet-name > label").getAttribute("value");
|
||||
ok(/^<.*>$/.test(name),
|
||||
|
|
|
@ -9,29 +9,31 @@ function test()
|
|||
{
|
||||
waitForExplicitFinish();
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
|
||||
// launch Style Editor right when the tab is created (before load)
|
||||
// this checks that the Style Editor still launches correctly when it is opened
|
||||
// *while* the page is still loading. The Style Editor should not signal that
|
||||
// it is loaded until the accompanying content page is loaded.
|
||||
launchStyleEditorChrome(function (aChrome) {
|
||||
|
||||
addTabAndOpenStyleEditor(function(panel) {
|
||||
panel.UI.on("editor-added", testEditorAdded);
|
||||
|
||||
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 TRANSITION_CLASS = "moz-styleeditor-transitioning";
|
||||
const TESTCASE_CSS_SOURCE = "body{background-color:red;";
|
||||
let TRANSITION_CLASS = "moz-styleeditor-transitioning";
|
||||
let TESTCASE_CSS_SOURCE = "body{background-color:red;";
|
||||
|
||||
let gUI;
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
|
||||
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
||||
aChrome.addChromeListener({
|
||||
onEditorAdded: testEditorAdded
|
||||
});
|
||||
run(aChrome);
|
||||
addTabAndOpenStyleEditor(function(panel) {
|
||||
gUI = panel.UI;
|
||||
gUI.on("editor-added", testEditorAdded);
|
||||
});
|
||||
|
||||
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 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;
|
||||
|
||||
|
||||
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)
|
||||
function testEditorAdded(aEvent, aEditor)
|
||||
{
|
||||
gAddedCount++;
|
||||
if (gAddedCount == 2) {
|
||||
waitForFocus(function () { // create a new style sheet
|
||||
let newButton = gChromeWindow.document.querySelector(".style-editor-newButton");
|
||||
EventUtils.synthesizeMouseAtCenter(newButton, {}, gChromeWindow);
|
||||
}, gChromeWindow);
|
||||
waitForFocus(function () {// create a new style sheet
|
||||
let newButton = gPanelWindow.document.querySelector(".style-editor-newButton");
|
||||
ok(newButton, "'new' button exists");
|
||||
|
||||
EventUtils.synthesizeMouseAtCenter(newButton, {}, gPanelWindow);
|
||||
}, gPanelWindow);
|
||||
}
|
||||
if (gAddedCount != 3) {
|
||||
if (gAddedCount < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
ok(!gNewEditor, "creating a new stylesheet triggers one EditorAdded 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");
|
||||
|
||||
let listener = {
|
||||
onAttach: function (aEditor) {
|
||||
waitForFocus(function () {
|
||||
gOriginalStyleSheet = aEditor.styleSheet;
|
||||
gOriginalOwnerNode = aEditor.styleSheet.ownerNode;
|
||||
gOriginalHref = aEditor.styleSheet.href;
|
||||
aEditor.getSourceEditor().then(testEditor);
|
||||
|
||||
ok(aEditor.isLoaded,
|
||||
"new editor is loaded when attached");
|
||||
ok(aEditor.hasFlag("new"),
|
||||
"new editor has NEW flag");
|
||||
ok(aEditor.hasFlag("unsaved"),
|
||||
"new editor has UNSAVED flag");
|
||||
aEditor.styleSheet.once("style-applied", function() {
|
||||
// when changes have been completely applied to live stylesheet after transisiton
|
||||
let summary = aEditor.summary;
|
||||
let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
|
||||
is(parseInt(ruleCount), 1,
|
||||
"new editor shows 1 rule after modification");
|
||||
|
||||
ok(aEditor.inputElement,
|
||||
"new editor has an input element attached");
|
||||
ok(!content.document.documentElement.classList.contains(TRANSITION_CLASS),
|
||||
"StyleEditor's transition class has been removed from content");
|
||||
});
|
||||
}
|
||||
|
||||
ok(aEditor.sourceEditor.hasFocus(),
|
||||
"new editor has focus");
|
||||
function testEditor(aEditor) {
|
||||
waitForFocus(function () {
|
||||
gOriginalHref = aEditor.styleSheet.href;
|
||||
|
||||
let summary = aChrome.getSummaryElementForEditor(aEditor);
|
||||
let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
|
||||
is(parseInt(ruleCount), 0,
|
||||
"new editor initially shows 0 rules");
|
||||
let summary = aEditor.summary;
|
||||
|
||||
let computedStyle = content.getComputedStyle(content.document.body, null);
|
||||
is(computedStyle.backgroundColor, "rgb(255, 255, 255)",
|
||||
"content's background color is initially white");
|
||||
ok(aEditor.sourceLoaded,
|
||||
"new editor is loaded when attached");
|
||||
ok(aEditor.isNew,
|
||||
"new editor has isNew flag");
|
||||
|
||||
EventUtils.synthesizeKey("[", {accelKey: true}, gChromeWindow);
|
||||
is(aEditor.sourceEditor.getText(), "",
|
||||
"Nothing happened as it is a known shortcut in source editor");
|
||||
ok(aEditor.sourceEditor.hasFocus(),
|
||||
"new editor has focus");
|
||||
|
||||
EventUtils.synthesizeKey("]", {accelKey: true}, gChromeWindow);
|
||||
is(aEditor.sourceEditor.getText(), "",
|
||||
"Nothing happened as it is a known shortcut in source editor");
|
||||
let summary = aEditor.summary;
|
||||
let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
|
||||
is(parseInt(ruleCount), 0,
|
||||
"new editor initially shows 0 rules");
|
||||
|
||||
for each (let c in TESTCASE_CSS_SOURCE) {
|
||||
EventUtils.synthesizeKey(c, {}, gChromeWindow);
|
||||
}
|
||||
let computedStyle = content.getComputedStyle(content.document.body, null);
|
||||
is(computedStyle.backgroundColor, "rgb(255, 255, 255)",
|
||||
"content's background color is initially white");
|
||||
|
||||
is(aEditor.sourceEditor.getText(), TESTCASE_CSS_SOURCE + "}",
|
||||
"rule bracket has been auto-closed");
|
||||
EventUtils.synthesizeKey("[", {accelKey: true}, gPanelWindow);
|
||||
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
|
||||
content.addEventListener("transitionend", function () {
|
||||
gTransitionEndCount++;
|
||||
EventUtils.synthesizeKey("]", {accelKey: true}, gPanelWindow);
|
||||
is(aEditor.sourceEditor.getText(), "",
|
||||
"Nothing happened as it is a known shortcut in source editor");
|
||||
|
||||
let computedStyle = content.getComputedStyle(content.document.body, null);
|
||||
is(computedStyle.backgroundColor, "rgb(255, 0, 0)",
|
||||
"content's background color has been updated to red");
|
||||
for each (let c in TESTCASE_CSS_SOURCE) {
|
||||
EventUtils.synthesizeKey(c, {}, gPanelWindow);
|
||||
}
|
||||
|
||||
executeSoon(finishOnTransitionEndAndCommit);
|
||||
}, false);
|
||||
}, gChromeWindow) ;
|
||||
},
|
||||
is(aEditor.sourceEditor.getText(), TESTCASE_CSS_SOURCE + "}",
|
||||
"rule bracket has been auto-closed");
|
||||
|
||||
onUpdate: function (aEditor) {
|
||||
gUpdateCount++;
|
||||
ok(aEditor.unsaved,
|
||||
"new editor has unsaved flag");
|
||||
|
||||
ok(content.document.documentElement.classList.contains(TRANSITION_CLASS),
|
||||
"StyleEditor's transition class has been added to content");
|
||||
},
|
||||
// we know that the testcase above will start a CSS transition
|
||||
content.addEventListener("transitionend", onTransitionEnd, false);
|
||||
}, gPanelWindow) ;
|
||||
}
|
||||
|
||||
onCommit: function (aEditor) {
|
||||
gCommitCount++;
|
||||
function onTransitionEnd() {
|
||||
content.removeEventListener("transitionend", onTransitionEnd, false);
|
||||
|
||||
ok(aEditor.hasFlag("new"),
|
||||
"new editor still has NEW flag");
|
||||
ok(aEditor.hasFlag("unsaved"),
|
||||
"new editor has UNSAVED flag after modification");
|
||||
let computedStyle = content.getComputedStyle(content.document.body, null);
|
||||
is(computedStyle.backgroundColor, "rgb(255, 0, 0)",
|
||||
"content's background color has been updated to red");
|
||||
|
||||
let summary = aChrome.getSummaryElementForEditor(aEditor);
|
||||
let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
|
||||
is(parseInt(ruleCount), 1,
|
||||
"new editor shows 1 rule after modification");
|
||||
if (gNewEditor) {
|
||||
is(gNewEditor.styleSheet.href, gOriginalHref,
|
||||
"style sheet href did not change");
|
||||
|
||||
ok(!content.document.documentElement.classList.contains(TRANSITION_CLASS),
|
||||
"StyleEditor's transition class has been removed from content");
|
||||
|
||||
aEditor.removeActionListener(listener);
|
||||
|
||||
executeSoon(finishOnTransitionEndAndCommit);
|
||||
}
|
||||
};
|
||||
|
||||
aEditor.addActionListener(listener);
|
||||
if (aEditor.sourceEditor) {
|
||||
listener.onAttach(aEditor);
|
||||
gNewEditor = null;
|
||||
gUI = null;
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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";
|
||||
|
||||
let gUI;
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
|
||||
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
||||
aChrome.addChromeListener({
|
||||
onEditorAdded: function (aChrome, aEditor) {
|
||||
if (aEditor.sourceEditor) {
|
||||
run(aEditor); // already attached to input element
|
||||
} else {
|
||||
aEditor.addActionListener({
|
||||
onAttach: run
|
||||
});
|
||||
}
|
||||
}
|
||||
addTabAndOpenStyleEditor(function(panel) {
|
||||
gUI = panel.UI;
|
||||
gUI.on("editor-added", function(event, editor) {
|
||||
editor.getSourceEditor().then(function() {
|
||||
testEditor(editor);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -27,21 +23,20 @@ function test()
|
|||
}
|
||||
|
||||
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 prettifiedSourceRE = new RegExp(prettifiedSource);
|
||||
|
||||
ok(prettifiedSourceRE.test(aEditor.sourceEditor.getText()),
|
||||
"minified source has been prettified automatically");
|
||||
editorTestedCount++;
|
||||
let chrome = gChromeWindow.styleEditorChrome;
|
||||
let summary = chrome.getSummaryElementForEditor(chrome.editors[1]);
|
||||
EventUtils.synthesizeMouseAtCenter(summary, {}, gChromeWindow);
|
||||
let summary = gUI.editors[1].summary;
|
||||
EventUtils.synthesizeMouseAtCenter(summary, {}, gPanelWindow);
|
||||
}
|
||||
|
||||
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 originalSourceRE = new RegExp(originalSource);
|
||||
|
||||
|
@ -51,6 +46,7 @@ function run(aEditor)
|
|||
}
|
||||
|
||||
if (editorTestedCount == 2) {
|
||||
gUI = null;
|
||||
finish();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
// This test makes sure that the style editor does not store any
|
||||
// content CSS files in the permanent cache when opened from PB mode.
|
||||
|
||||
let gUI;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
let windowsToClose = [];
|
||||
|
@ -11,6 +14,8 @@ function test() {
|
|||
|
||||
function checkCache() {
|
||||
checkDiskCacheFor(TEST_HOST);
|
||||
|
||||
gUI = null;
|
||||
finish();
|
||||
}
|
||||
|
||||
|
@ -18,23 +23,21 @@ function test() {
|
|||
aWindow.gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
|
||||
aWindow.gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
|
||||
cache.evictEntries(Ci.nsICache.STORE_ANYWHERE);
|
||||
launchStyleEditorChromeFromWindow(aWindow, function(aChrome) {
|
||||
onEditorAdded(aChrome, aChrome.editors[0]);
|
||||
openStyleEditorInWindow(aWindow, function(panel) {
|
||||
gUI = panel.UI;
|
||||
gUI.on("editor-added", onEditorAdded);
|
||||
});
|
||||
}, true);
|
||||
|
||||
aWindow.gBrowser.selectedBrowser.loadURI(testURI);
|
||||
}
|
||||
|
||||
function onEditorAdded(aChrome, aEditor) {
|
||||
aChrome.removeChromeListener(this);
|
||||
|
||||
if (aEditor.isLoaded) {
|
||||
function onEditorAdded(aEvent, aEditor) {
|
||||
if (aEditor.sourceLoaded) {
|
||||
checkCache();
|
||||
} else {
|
||||
aEditor.addActionListener({
|
||||
onLoad: checkCache
|
||||
});
|
||||
}
|
||||
else {
|
||||
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";
|
||||
|
||||
let gUI;
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
|
||||
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
||||
run(aChrome);
|
||||
addTabAndOpenStyleEditor(function(panel) {
|
||||
gUI = panel.UI;
|
||||
gUI.on("editor-added", function(event, editor) {
|
||||
if (editor == gUI.editors[3]) {
|
||||
runTests();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
content.location = TESTCASE_URI;
|
||||
}
|
||||
|
||||
let gChrome;
|
||||
|
||||
function run(aChrome)
|
||||
function runTests()
|
||||
{
|
||||
gChrome = aChrome;
|
||||
aChrome.editors[0].addActionListener({onAttach: onEditor0Attach});
|
||||
aChrome.editors[2].addActionListener({onAttach: onEditor2Attach});
|
||||
gUI.editors[0].getSourceEditor().then(onEditor0Attach);
|
||||
gUI.editors[2].getSourceEditor().then(onEditor2Attach);
|
||||
}
|
||||
|
||||
function getStylesheetNameLinkFor(aEditor)
|
||||
{
|
||||
return gChrome.getSummaryElementForEditor(aEditor).querySelector(".stylesheet-name");
|
||||
return aEditor.summary.querySelector(".stylesheet-name");
|
||||
}
|
||||
|
||||
function onEditor0Attach(aEditor)
|
||||
{
|
||||
waitForFocus(function () {
|
||||
let summary = gChrome.getSummaryElementForEditor(aEditor);
|
||||
EventUtils.synthesizeMouseAtCenter(summary, {}, gChromeWindow);
|
||||
let summary = aEditor.summary;
|
||||
EventUtils.synthesizeMouseAtCenter(summary, {}, gPanelWindow);
|
||||
|
||||
let item = getStylesheetNameLinkFor(gChrome.editors[0]);
|
||||
is(gChromeWindow.document.activeElement, item,
|
||||
let item = getStylesheetNameLinkFor(gUI.editors[0]);
|
||||
is(gPanelWindow.document.activeElement, item,
|
||||
"editor 0 item is the active element");
|
||||
|
||||
EventUtils.synthesizeKey("VK_DOWN", {}, gChromeWindow);
|
||||
item = getStylesheetNameLinkFor(gChrome.editors[1]);
|
||||
is(gChromeWindow.document.activeElement, item,
|
||||
EventUtils.synthesizeKey("VK_DOWN", {}, gPanelWindow);
|
||||
item = getStylesheetNameLinkFor(gUI.editors[1]);
|
||||
is(gPanelWindow.document.activeElement, item,
|
||||
"editor 1 item is the active element");
|
||||
|
||||
EventUtils.synthesizeKey("VK_HOME", {}, gChromeWindow);
|
||||
item = getStylesheetNameLinkFor(gChrome.editors[0]);
|
||||
is(gChromeWindow.document.activeElement, item,
|
||||
EventUtils.synthesizeKey("VK_HOME", {}, gPanelWindow);
|
||||
item = getStylesheetNameLinkFor(gUI.editors[0]);
|
||||
is(gPanelWindow.document.activeElement, item,
|
||||
"fist editor item is the active element");
|
||||
|
||||
EventUtils.synthesizeKey("VK_END", {}, gChromeWindow);
|
||||
item = getStylesheetNameLinkFor(gChrome.editors[3]);
|
||||
is(gChromeWindow.document.activeElement, item,
|
||||
EventUtils.synthesizeKey("VK_END", {}, gPanelWindow);
|
||||
item = getStylesheetNameLinkFor(gUI.editors[3]);
|
||||
is(gPanelWindow.document.activeElement, item,
|
||||
"last editor item is the active element");
|
||||
|
||||
EventUtils.synthesizeKey("VK_UP", {}, gChromeWindow);
|
||||
item = getStylesheetNameLinkFor(gChrome.editors[2]);
|
||||
is(gChromeWindow.document.activeElement, item,
|
||||
EventUtils.synthesizeKey("VK_UP", {}, gPanelWindow);
|
||||
item = getStylesheetNameLinkFor(gUI.editors[2]);
|
||||
is(gPanelWindow.document.activeElement, item,
|
||||
"editor 2 item is the active element");
|
||||
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, gChromeWindow);
|
||||
EventUtils.synthesizeKey("VK_RETURN", {}, gPanelWindow);
|
||||
// this will attach and give focus editor 2
|
||||
}, gChromeWindow);
|
||||
}, gPanelWindow);
|
||||
}
|
||||
|
||||
function onEditor2Attach(aEditor)
|
||||
|
@ -70,6 +73,6 @@ function onEditor2Attach(aEditor)
|
|||
ok(aEditor.sourceEditor.hasFocus(),
|
||||
"editor 2 has focus");
|
||||
|
||||
gChrome = null;
|
||||
gUI = null;
|
||||
finish();
|
||||
}
|
||||
|
|
|
@ -4,53 +4,58 @@
|
|||
|
||||
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;
|
||||
|
||||
function test()
|
||||
{
|
||||
waitForExplicitFinish();
|
||||
|
||||
addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
|
||||
run(aChrome);
|
||||
addTabAndOpenStyleEditor(function(panel) {
|
||||
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;
|
||||
}
|
||||
|
||||
function run(aChrome)
|
||||
function runTests(aUI)
|
||||
{
|
||||
is(aChrome.editors.length, 2,
|
||||
is(aUI.editors.length, 2,
|
||||
"there is 2 stylesheets initially");
|
||||
|
||||
aChrome.editors[0].addActionListener({
|
||||
onAttach: function onEditorAttached(aEditor) {
|
||||
executeSoon(function () {
|
||||
waitForFocus(function () {
|
||||
// queue a resize to inverse aspect ratio
|
||||
// this will trigger a detach and reattach (to workaround bug 254144)
|
||||
let originalSourceEditor = aEditor.sourceEditor;
|
||||
aEditor.sourceEditor.setCaretOffset(4); // to check the caret is preserved
|
||||
aUI.editors[0].getSourceEditor().then(function onEditorAttached(aEditor) {
|
||||
executeSoon(function () {
|
||||
waitForFocus(function () {
|
||||
// queue a resize to inverse aspect ratio
|
||||
// this will trigger a detach and reattach (to workaround bug 254144)
|
||||
let originalSourceEditor = aEditor.sourceEditor;
|
||||
aEditor.sourceEditor.setCaretOffset(4); // to check the caret is preserved
|
||||
|
||||
gOriginalWidth = gChromeWindow.outerWidth;
|
||||
gOriginalHeight = gChromeWindow.outerHeight;
|
||||
gChromeWindow.resizeTo(120, 480);
|
||||
gOriginalWidth = gPanelWindow.outerWidth;
|
||||
gOriginalHeight = gPanelWindow.outerHeight;
|
||||
gPanelWindow.resizeTo(120, 480);
|
||||
|
||||
executeSoon(function () {
|
||||
is(aEditor.sourceEditor, originalSourceEditor,
|
||||
"the editor still references the same SourceEditor instance");
|
||||
is(aEditor.sourceEditor.getCaretOffset(), 4,
|
||||
"the caret position has been preserved");
|
||||
executeSoon(function () {
|
||||
is(aEditor.sourceEditor, originalSourceEditor,
|
||||
"the editor still references the same SourceEditor instance");
|
||||
is(aEditor.sourceEditor.getCaretOffset(), 4,
|
||||
"the caret position has been preserved");
|
||||
|
||||
// queue a resize to original aspect ratio
|
||||
waitForFocus(function () {
|
||||
gChromeWindow.resizeTo(gOriginalWidth, gOriginalHeight);
|
||||
executeSoon(function () {
|
||||
finish();
|
||||
});
|
||||
}, gChromeWindow);
|
||||
});
|
||||
}, gChromeWindow);
|
||||
});
|
||||
}
|
||||
// queue a resize to original aspect ratio
|
||||
waitForFocus(function () {
|
||||
gPanelWindow.resizeTo(gOriginalWidth, gOriginalHeight);
|
||||
executeSoon(function () {
|
||||
finish();
|
||||
});
|
||||
}, gPanelWindow);
|
||||
});
|
||||
}, gPanelWindow);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ let TargetFactory = tempScope.TargetFactory;
|
|||
Components.utils.import("resource://gre/modules/devtools/Console.jsm", tempScope);
|
||||
let console = tempScope.console;
|
||||
|
||||
let gChromeWindow; //StyleEditorChrome window
|
||||
let gPanelWindow;
|
||||
let cache = Cc["@mozilla.org/network/cache-service;1"]
|
||||
.getService(Ci.nsICacheService);
|
||||
|
||||
|
@ -23,12 +23,38 @@ Services.scriptloader.loadSubScript(testDir + "/helpers.js", this);
|
|||
|
||||
function cleanup()
|
||||
{
|
||||
gChromeWindow = null;
|
||||
gPanelWindow = null;
|
||||
while (gBrowser.tabs.length > 1) {
|
||||
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)
|
||||
{
|
||||
launchStyleEditorChromeFromWindow(window, aCallback, aSheet, aLine, aCol);
|
||||
|
@ -39,12 +65,12 @@ function launchStyleEditorChromeFromWindow(aWindow, aCallback, aSheet, aLine, aC
|
|||
let target = TargetFactory.forTab(aWindow.gBrowser.selectedTab);
|
||||
gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
|
||||
let panel = toolbox.getCurrentPanel();
|
||||
gChromeWindow = panel._panelWin;
|
||||
gChromeWindow.styleEditorChrome._alwaysDisableAnimations = true;
|
||||
gPanelWindow = panel._panelWin;
|
||||
gPanelWindow.styleEditorChrome._alwaysDisableAnimations = true;
|
||||
if (aSheet) {
|
||||
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);
|
||||
}, true);
|
||||
}
|
||||
*/
|
||||
|
||||
function checkDiskCacheFor(host)
|
||||
{
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<body>
|
||||
Time passes:
|
||||
<script>
|
||||
for (i = 0; i < 30000; i++) {
|
||||
for (i = 0; i < 5000; i++) {
|
||||
document.write("<br>...");
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -84,22 +84,10 @@ function testInlineStyleSheet()
|
|||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
|
||||
let panel = toolbox.getCurrentPanel();
|
||||
let win = panel._panelWin;
|
||||
|
||||
win.styleEditorChrome.addChromeListener({
|
||||
onEditorAdded: function checkEditor(aChrome, aEditor) {
|
||||
if (!aEditor.sourceEditor) {
|
||||
aEditor.addActionListener({
|
||||
onAttach: function (aEditor) {
|
||||
aEditor.removeActionListener(this);
|
||||
validateStyleEditorSheet(aEditor);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
validateStyleEditorSheet(aEditor);
|
||||
}
|
||||
}
|
||||
});
|
||||
panel.UI.on("editor-added", (event, editor) => {
|
||||
validateStyleEditorSheet(editor);
|
||||
})
|
||||
});
|
||||
|
||||
let link = getLinkByIndex(1);
|
||||
|
@ -111,7 +99,7 @@ function validateStyleEditorSheet(aEditor)
|
|||
info("validating style editor stylesheet");
|
||||
|
||||
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();
|
||||
}
|
||||
|
|
|
@ -88,8 +88,10 @@ function testInlineStyleSheet()
|
|||
info("clicking an inline stylesheet");
|
||||
|
||||
toolbox.once("styleeditor-ready", function(id, aToolbox) {
|
||||
aToolbox.panelWindow.styleEditorChrome.addChromeListener({
|
||||
onEditorAdded: validateStyleEditorSheet
|
||||
let panel = toolbox.getCurrentPanel();
|
||||
|
||||
panel.UI.on("editor-added", (event, editor) => {
|
||||
validateStyleEditorSheet(editor);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -98,13 +100,13 @@ function testInlineStyleSheet()
|
|||
link.click();
|
||||
}
|
||||
|
||||
function validateStyleEditorSheet(aChrome, aEditor)
|
||||
function validateStyleEditorSheet(aEditor)
|
||||
{
|
||||
info("validating style editor stylesheet");
|
||||
|
||||
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();
|
||||
|
||||
finishup();
|
||||
|
|
|
@ -340,20 +340,14 @@ WebConsole.prototype = {
|
|||
viewSourceInStyleEditor:
|
||||
function WC_viewSourceInStyleEditor(aSourceURL, aSourceLine)
|
||||
{
|
||||
let styleSheets = {};
|
||||
if (this.target.isLocalTab) {
|
||||
styleSheets = this.target.window.document.styleSheets;
|
||||
}
|
||||
for each (let style in styleSheets) {
|
||||
if (style.href == aSourceURL) {
|
||||
gDevTools.showToolbox(this.target, "styleeditor").then(function(toolbox) {
|
||||
toolbox.getCurrentPanel().selectStyleSheet(style, aSourceLine);
|
||||
});
|
||||
return;
|
||||
gDevTools.showToolbox(this.target, "styleeditor").then(function(toolbox) {
|
||||
try {
|
||||
toolbox.getCurrentPanel().selectStyleSheet(aSourceURL, aSourceLine);
|
||||
} catch(e) {
|
||||
// Open view source if style editor fails.
|
||||
this.viewSource(aSourceURL, aSourceLine);
|
||||
}
|
||||
}
|
||||
// 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" +
|
||||
"/test-bug-782653-css-errors.html";
|
||||
|
||||
let nodes, hud, SEC;
|
||||
let nodes, hud, StyleEditorUI;
|
||||
|
||||
function test()
|
||||
{
|
||||
|
@ -23,7 +23,7 @@ function testViewSource(aHud)
|
|||
hud = aHud;
|
||||
|
||||
registerCleanupFunction(function() {
|
||||
nodes = hud = SEC = null;
|
||||
nodes = hud = StyleEditorUI = null;
|
||||
});
|
||||
|
||||
let selector = ".webconsole-msg-cssparser .webconsole-location";
|
||||
|
@ -41,7 +41,16 @@ function testViewSource(aHud)
|
|||
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
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]);
|
||||
},
|
||||
|
@ -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;
|
||||
ok(win, "Style Editor Window is defined");
|
||||
ok(SEC, "Style Editor Chrome 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;
|
||||
}
|
||||
ok(StyleEditorUI, "Style Editor UI is defined");
|
||||
|
||||
waitForFocus(function() {
|
||||
info("style editor window focused");
|
||||
|
||||
let sheet = sheetForNode(nodes[0]);
|
||||
ok(sheet, "sheet found");
|
||||
let href = nodes[0].getAttribute("title");
|
||||
ok(href.contains("test-bug-782653-css-errors-1.css"), "got first stylesheet href")
|
||||
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");
|
||||
|
||||
let target = TargetFactory.forTab(gBrowser.selectedTab);
|
||||
let toolbox = gDevTools.getToolbox(target);
|
||||
|
||||
let sheet = sheetForNode(nodes[1]);
|
||||
ok(sheet, "sheet found");
|
||||
let href = nodes[1].getAttribute("title");
|
||||
ok(href.contains("test-bug-782653-css-errors-2.css"), "got second stylesheet href")
|
||||
let line = nodes[1].sourceLine;
|
||||
ok(line, "found source line");
|
||||
is(line, 7, "found source line");
|
||||
|
||||
toolbox.selectTool("webconsole").then(function() {
|
||||
info("webconsole selected");
|
||||
|
@ -95,7 +89,7 @@ function onStyleEditorReady(aEvent, aPanel)
|
|||
toolbox.once("styleeditor-selected", function(aEvent) {
|
||||
info(aEvent + " event fired");
|
||||
|
||||
checkStyleEditorForSheetAndLine(sheet, line - 1, function() {
|
||||
checkStyleEditorForSheetAndLine(href, line - 1, function() {
|
||||
info("second check done");
|
||||
finishTest();
|
||||
});
|
||||
|
@ -107,15 +101,15 @@ function onStyleEditorReady(aEvent, aPanel)
|
|||
}, win);
|
||||
}
|
||||
|
||||
function checkStyleEditorForSheetAndLine(aStyleSheet, aLine, aCallback)
|
||||
function checkStyleEditorForSheetAndLine(aHref, aLine, aCallback)
|
||||
{
|
||||
let foundEditor = null;
|
||||
waitForSuccess({
|
||||
name: "style editor for stylesheet",
|
||||
validatorFn: function()
|
||||
{
|
||||
for (let editor of SEC.editors) {
|
||||
if (editor.styleSheet == aStyleSheet) {
|
||||
for (let editor of StyleEditorUI.editors) {
|
||||
if (editor.styleSheet.href == aHref) {
|
||||
foundEditor = editor;
|
||||
return true;
|
||||
}
|
||||
|
@ -136,7 +130,7 @@ function performLineCheck(aEditor, aLine, aCallback)
|
|||
{
|
||||
is(aEditor.sourceEditor.getCaretPosition().line, aLine,
|
||||
"correct line is selected");
|
||||
is(SEC.selectedStyleSheetIndex, aEditor.styleSheetIndex,
|
||||
is(StyleEditorUI.selectedStyleSheetIndex, aEditor.styleSheet.styleSheetIndex,
|
||||
"correct stylesheet is selected in the editor");
|
||||
|
||||
aCallback && executeSoon(aCallback);
|
||||
|
@ -150,7 +144,8 @@ function performLineCheck(aEditor, aLine, aCallback)
|
|||
},
|
||||
successFn: checkForCorrectState,
|
||||
failureFn: function() {
|
||||
info("selectedStyleSheetIndex " + SEC.selectedStyleSheetIndex + " expected " + aEditor.styleSheetIndex);
|
||||
info("selectedStyleSheetIndex " + StyleEditorUI.selectedStyleSheetIndex
|
||||
+ " expected " + aEditor.styleSheet.styleSheetIndex);
|
||||
finishTest();
|
||||
},
|
||||
});
|
||||
|
|
|
@ -182,7 +182,8 @@ const UnsolicitedNotifications = {
|
|||
"tabDetached": "tabDetached",
|
||||
"tabNavigated": "tabNavigated",
|
||||
"pageError": "pageError",
|
||||
"webappsEvent": "webappsEvent"
|
||||
"webappsEvent": "webappsEvent",
|
||||
"styleSheetsAdded": "styleSheetsAdded"
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -195,6 +195,9 @@ var DebuggerServer = {
|
|||
#endif
|
||||
if ("nsIProfiler" in Ci)
|
||||
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-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-styleeditor-actors.js (styleeditor/dbg-styleeditor-actors.js)
|
||||
|
|
|
@ -8,4 +8,5 @@ PARALLEL_DIRS += [
|
|||
'debugger',
|
||||
'sourcemap',
|
||||
'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']
|
Загрузка…
Ссылка в новой задаче