Bug 816967 - Remotable Style Editor; r=dcamp
authorHeather Arthur <fayearthur@gmail.com>
Thu, 25 Apr 2013 09:46:13 -0700
changeset 129813 a5e74dbe53276cdd7078de737681697641387f7f
parent 129812 fb8531fe086c5cf2a9b88926569ff49c8cd2e8ed
child 129814 3919cfc6dab45d4d583b31973ea0bc9fc9928c3e
push id1548
push userfayearthur@gmail.com
push dateThu, 25 Apr 2013 16:51:57 +0000
treeherderfx-team@a5e74dbe5327 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdcamp
bugs816967
milestone23.0a1
Bug 816967 - Remotable Style Editor; r=dcamp
browser/devtools/framework/ToolDefinitions.jsm
browser/devtools/styleeditor/StyleEditor.jsm
browser/devtools/styleeditor/StyleEditorChrome.jsm
browser/devtools/styleeditor/StyleEditorDebuggee.jsm
browser/devtools/styleeditor/StyleEditorPanel.jsm
browser/devtools/styleeditor/StyleEditorUI.jsm
browser/devtools/styleeditor/StyleEditorUtil.jsm
browser/devtools/styleeditor/StyleSheetEditor.jsm
browser/devtools/styleeditor/styleeditor.xul
browser/devtools/styleeditor/test/Makefile.in
browser/devtools/styleeditor/test/browser_styleeditor_bug_826982_location_changed.js
browser/devtools/styleeditor/test/browser_styleeditor_bug_851132_middle_click.js
browser/devtools/styleeditor/test/browser_styleeditor_enabled.js
browser/devtools/styleeditor/test/browser_styleeditor_filesave.js
browser/devtools/styleeditor/test/browser_styleeditor_import.js
browser/devtools/styleeditor/test/browser_styleeditor_import_rule.js
browser/devtools/styleeditor/test/browser_styleeditor_init.js
browser/devtools/styleeditor/test/browser_styleeditor_loading.js
browser/devtools/styleeditor/test/browser_styleeditor_new.js
browser/devtools/styleeditor/test/browser_styleeditor_passedinsheet.js
browser/devtools/styleeditor/test/browser_styleeditor_pretty.js
browser/devtools/styleeditor/test/browser_styleeditor_private_perwindowpb.js
browser/devtools/styleeditor/test/browser_styleeditor_readonly.js
browser/devtools/styleeditor/test/browser_styleeditor_reopen.js
browser/devtools/styleeditor/test/browser_styleeditor_sv_keynav.js
browser/devtools/styleeditor/test/browser_styleeditor_sv_resize.js
browser/devtools/styleeditor/test/head.js
browser/devtools/styleeditor/test/longload.html
browser/devtools/styleinspector/test/browser_computedview_734259_style_editor_link.js
browser/devtools/styleinspector/test/browser_ruleview_734259_style_editor_link.js
browser/devtools/webconsole/HUDService.jsm
browser/devtools/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js
toolkit/devtools/debugger/dbg-client.jsm
toolkit/devtools/debugger/server/dbg-server.js
toolkit/devtools/jar.mn
toolkit/devtools/moz.build
toolkit/devtools/styleeditor/Makefile.in
toolkit/devtools/styleeditor/dbg-styleeditor-actors.js
toolkit/devtools/styleeditor/moz.build
--- a/browser/devtools/framework/ToolDefinitions.jsm
+++ b/browser/devtools/framework/ToolDefinitions.jsm
@@ -138,17 +138,17 @@ let styleEditorDefinition = {
   accesskey: l10n("open.accesskey", styleEditorStrings),
   modifiers: "shift",
   icon: "chrome://browser/skin/devtools/tool-styleeditor.png",
   url: "chrome://browser/content/styleeditor.xul",
   label: l10n("ToolboxStyleEditor.label", styleEditorStrings),
   tooltip: l10n("ToolboxStyleEditor.tooltip", styleEditorStrings),
 
   isTargetSupported: function(target) {
-    return !target.isRemote;
+    return true;
   },
 
   build: function(iframeWindow, toolbox) {
     let panel = new StyleEditorPanel(iframeWindow, toolbox);
     return panel.open();
   }
 };
 
deleted file mode 100644
--- a/browser/devtools/styleeditor/StyleEditor.jsm
+++ /dev/null
@@ -1,1329 +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 = ["StyleEditor", "StyleEditorFlags"];
-
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-
-const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"]
-                   .getService(Ci.inIDOMUtils);
-
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/NetUtil.jsm");
-Cu.import("resource://gre/modules/FileUtils.jsm");
-Cu.import("resource:///modules/devtools/StyleEditorUtil.jsm");
-Cu.import("resource:///modules/source-editor.jsm");
-
-const LOAD_ERROR = "error-load";
-const SAVE_ERROR = "error-save";
-
-// max update frequency in ms (avoid potential typing lag and/or flicker)
-// @see StyleEditor.updateStylesheet
-const UPDATE_STYLESHEET_THROTTLE_DELAY = 500;
-
-// @see StyleEditor._persistExpando
-const STYLESHEET_EXPANDO = "-moz-styleeditor-stylesheet-";
-
-const TRANSITIONS_PREF = "devtools.styleeditor.transitions";
-
-const TRANSITION_CLASS = "moz-styleeditor-transitioning";
-const TRANSITION_DURATION_MS = 500;
-const 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;\
-}";
-
-/**
- * Style Editor module-global preferences
- */
-const TRANSITIONS_ENABLED = Services.prefs.getBoolPref(TRANSITIONS_PREF);
-
-
-/**
- * StyleEditor constructor.
- *
- * The StyleEditor is initialized 'headless', it does not display source
- * or receive input. Setting inputElement attaches a DOMElement to handle this.
- *
- * An editor can be created stand-alone or created by StyleEditorChrome to
- * manage all the style sheets of a document, including @import'ed sheets.
- *
- * @param DOMDocument aDocument
- *        The content document where changes will be applied to.
- * @param DOMStyleSheet aStyleSheet
- *        Optional. The DOMStyleSheet to edit.
- *        If not set, a new empty style sheet will be appended to the document.
- * @see inputElement
- * @see StyleEditorChrome
- */
-this.StyleEditor = function StyleEditor(aDocument, aStyleSheet)
-{
-  assert(aDocument, "Argument 'aDocument' is required.");
-
-  this._document = aDocument; // @see contentDocument
-  this._inputElement = null;  // @see inputElement
-  this._sourceEditor = null;  // @see sourceEditor
-
-  this._state = {             // state to handle inputElement attach/detach
-    text: "",                 // seamlessly
-    selection: {start: 0, end: 0},
-    readOnly: false,
-    topIndex: 0,              // the first visible line
-  };
-
-  this._styleSheet = aStyleSheet;
-  this._styleSheetIndex = -1; // unknown for now, will be set after load
-  this._styleSheetFilePath = null; // original file path for the style sheet
-
-  this._loaded = false;
-
-  this._flags = [];           // @see flags
-  this._savedFile = null;     // @see savedFile
-
-  this._errorMessage = null;  // @see errorMessage
-
-  // listeners for significant editor actions. @see addActionListener
-  this._actionListeners = [];
-
-  // this is to perform pending updates before editor closing
-  this._onWindowUnloadBinding = this._onWindowUnload.bind(this);
-
-  this._transitionRefCount = 0;
-
-  this._focusOnSourceEditorReady = false;
-}
-
-StyleEditor.prototype = {
-  /**
-   * Retrieve the content document this editor will apply changes to.
-   *
-   * @return DOMDocument
-   */
-  get contentDocument() this._document,
-
-  /**
-   * Retrieve the stylesheet this editor is attached to.
-   *
-   * @return DOMStyleSheet
-   */
-  get styleSheet()
-  {
-    assert(this._styleSheet, "StyleSheet must be loaded first.");
-    return this._styleSheet;
-  },
-
-  /**
-   * Recursively traverse imported stylesheets to find the index
-   *
-   * @param number aIndex
-   *        The index of the current sheet in the document.
-   * @param CSSStyleSheet aSheet
-   *        A stylesheet we're going to browse to look for all imported sheets.
-   */
-  _getImportedStyleSheetIndex: function SE__getImportedStyleSheetIndex(aIndex, aSheet)
-  {
-    let index = aIndex;
-    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;
-        }
-
-        if (rule.styleSheet == this.styleSheet) {
-          this._styleSheetIndex = index;
-          return index;
-        }
-        index++;
-        index = this._getImportedStyleSheetIndex(index, rule.styleSheet);
-
-        if (this._styleSheetIndex != -1) {
-          return index;
-        }
-      } else if (rule.type != Ci.nsIDOMCSSRule.CHARSET_RULE) {
-        // @import rules must precede all others except @charset
-        return index;
-      }
-    }
-    return index;
-  },
-
-  /**
-   * Retrieve the index (order) of stylesheet in the document.
-   *
-   * @return number
-   */
-  get styleSheetIndex()
-  {
-    let document = this.contentDocument;
-    if (this._styleSheetIndex == -1) {
-      let index = 0;
-      let sheetIndex = 0;
-      while (sheetIndex <= document.styleSheets.length) {
-        let sheet = document.styleSheets[sheetIndex];
-        if (sheet == this.styleSheet) {
-          this._styleSheetIndex = index;
-          break;
-        }
-        index++;
-        index = this._getImportedStyleSheetIndex(index, sheet);
-        if (this._styleSheetIndex != -1) {
-          break;
-        }
-        sheetIndex++;
-      }
-    }
-    return this._styleSheetIndex;
-  },
-
-  /**
-   * Retrieve the input element that handles display and input for this editor.
-   * Can be null if the editor is detached/headless, which means that this
-   * StyleEditor is not attached to an input element.
-   *
-   * @return DOMElement
-   */
-  get inputElement() this._inputElement,
-
-  /**
-   * Set the input element that handles display and input for this editor.
-   * This detaches the previous input element if previously set.
-   *
-   * @param DOMElement aElement
-   */
-  set inputElement(aElement)
-  {
-    if (aElement == this._inputElement) {
-      return; // no change
-    }
-
-    if (this._inputElement) {
-      // detach from current input element
-      if (this._sourceEditor) {
-        // save existing state first (for seamless reattach)
-        this._state = {
-          text: this._sourceEditor.getText(),
-          selection: this._sourceEditor.getSelection(),
-          readOnly: this._sourceEditor.readOnly,
-          topIndex: this._sourceEditor.getTopIndex(),
-        };
-        this._sourceEditor.destroy();
-        this._sourceEditor = null;
-      }
-
-      this.window.removeEventListener("unload",
-                                      this._onWindowUnloadBinding, false);
-      this._triggerAction("Detach");
-    }
-
-    this._inputElement = aElement;
-    if (!aElement) {
-      return;
-    }
-
-    // attach to new input element
-    this.window.addEventListener("unload", this._onWindowUnloadBinding, false);
-    this._focusOnSourceEditorReady = false;
-
-    this._sourceEditor = null; // set it only when ready (safe to use)
-
-    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(aElement, config, function onSourceEditorReady() {
-      setupBracketCompletion(sourceEditor);
-
-      sourceEditor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
-                                    function onTextChanged(aEvent) {
-        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._triggerAction("Attach");
-    }.bind(this));
-  },
-
-  /**
-   * Retrieve the underlying SourceEditor instance for this StyleEditor.
-   * Can be null if not ready or Style Editor is detached/headless.
-   *
-   * @return SourceEditor
-   */
-  get sourceEditor() this._sourceEditor,
-
-  /**
-   * Setter for the read-only state of the editor.
-   *
-   * @param boolean aValue
-   *        Tells if you want the editor to be read-only or not.
-   */
-  set readOnly(aValue)
-  {
-    this._state.readOnly = aValue;
-    if (this._sourceEditor) {
-      this._sourceEditor.readOnly = aValue;
-    }
-  },
-
-  /**
-   * Getter for the read-only state of the editor.
-   *
-   * @return boolean
-   */
-  get readOnly()
-  {
-    return this._state.readOnly;
-  },
-
-  /**
-   * Retrieve the window that contains the editor.
-   * Can be null if the editor is detached/headless.
-   *
-   * @return DOMWindow
-   */
-  get window()
-  {
-    if (!this.inputElement) {
-      return null;
-    }
-    return this.inputElement.ownerDocument.defaultView;
-  },
-
-  /**
-   * Retrieve the last file this editor has been saved to or null if none.
-   *
-   * @return nsIFile
-   */
-  get savedFile() this._savedFile,
-
-  /**
-   * Import style sheet from file and load it into the editor asynchronously.
-   * "Load" action triggers when complete.
-   *
-   * @param mixed aFile
-   *        Optional nsIFile or filename string.
-   *        If not set a file picker will be shown.
-   * @param nsIWindow aParentWindow
-   *        Optional parent window for the file picker.
-   */
-  importFromFile: function SE_importFromFile(aFile, aParentWindow)
-  {
-    let callback = function(aFile) {
-      if (aFile) {
-        this._savedFile = aFile; // remember filename for next save if any
-
-        NetUtil.asyncFetch(aFile, function onAsyncFetch(aStream, aStatus) {
-          if (!Components.isSuccessCode(aStatus)) {
-            return this._signalError(LOAD_ERROR);
-          }
-          let source = NetUtil.readInputStreamToString(aStream, aStream.available());
-          aStream.close();
-
-          this._appendNewStyleSheet(source);
-          this.clearFlag(StyleEditorFlags.ERROR);
-        }.bind(this));
-      }
-    }.bind(this);
-
-    this._showFilePicker(aFile, false, aParentWindow, callback);
-  },
-
-  /**
-    * Retrieve localized error message of last error condition, or null if none.
-    * This is set when the editor has flag StyleEditorFlags.ERROR.
-    *
-    * @see addActionListener
-    */
-  get errorMessage() this._errorMessage,
-
-  /**
-   * Tell whether the stylesheet has been loaded and ready for modifications.
-   *
-   * @return boolean
-   */
-  get isLoaded() this._loaded,
-
-  /**
-   * Load style sheet source into the editor, asynchronously.
-   * "Load" handler triggers when complete.
-   *
-   * @see addActionListener
-   */
-  load: function SE_load()
-  {
-    if (!this._styleSheet) {
-      this._appendNewStyleSheet();
-    }
-    this._loadSource();
-  },
-
-  /**
-   * Get a user-friendly name for the style sheet.
-   *
-   * @return string
-   */
-  getFriendlyName: function SE_getFriendlyName()
-  {
-    if (this.savedFile) { // reuse the saved filename if any
-      return this.savedFile.leafName;
-    }
-
-    if (this.hasFlag(StyleEditorFlags.NEW)) {
-      let index = this.styleSheetIndex + 1; // 0-indexing only works for devs
-      return _("newStyleSheet", index);
-    }
-
-    if (this.hasFlag(StyleEditorFlags.INLINE)) {
-      let index = this.styleSheetIndex + 1; // 0-indexing only works for devs
-      return _("inlineStyleSheet", index);
-    }
-
-    if (!this._friendlyName) {
-      let sheetURI = this.styleSheet.href;
-      let contentURI = this.contentDocument.baseURIObject;
-      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;
-  },
-
-  /**
-   * Add a listener for significant StyleEditor actions.
-   *
-   * The listener implements IStyleEditorActionListener := {
-   *   onLoad:                 Called when the style sheet has been loaded and
-   *                           parsed.
-   *                           Arguments: (StyleEditor editor)
-   *                           @see load
-   *
-   *   onFlagChange:           Called when a flag has been set or cleared.
-   *                           Arguments: (StyleEditor editor, string flagName)
-   *                           @see setFlag
-   *
-   *   onAttach:               Called when an input element has been attached.
-   *                           Arguments: (StyleEditor editor)
-   *                           @see inputElement
-   *
-   *   onDetach:               Called when input element has been detached.
-   *                           Arguments: (StyleEditor editor)
-   *                           @see inputElement
-   *
-   *   onUpdate:               Called when changes are being applied to the live
-   *                           DOM style sheet but might not be complete from
-   *                           a WYSIWYG perspective (eg. transitioned update).
-   *                           Arguments: (StyleEditor editor)
-   *
-   *   onCommit:               Called when changes have been completely committed
-   *                           /applied to the live DOM style sheet.
-   *                           Arguments: (StyleEditor editor)
-   * }
-   *
-   * All listener methods are optional.
-   *
-   * @param IStyleEditorActionListener aListener
-   * @see removeActionListener
-   */
-  addActionListener: function SE_addActionListener(aListener)
-  {
-    this._actionListeners.push(aListener);
-  },
-
-  /**
-   * Remove a listener for editor actions from the current list of listeners.
-   *
-   * @param IStyleEditorActionListener aListener
-   * @see addActionListener
-   */
-  removeActionListener: function SE_removeActionListener(aListener)
-  {
-    let index = this._actionListeners.indexOf(aListener);
-    if (index != -1) {
-      this._actionListeners.splice(index, 1);
-    }
-  },
-
-  /**
-   * Editor UI flags.
-   *
-   * These are 1-bit indicators that can be used for UI feedback/indicators or
-   * extensions to track the editor status.
-   * Since they are simple strings, they promote loose coupling and can simply
-   * map to CSS class names, which allows to 'expose' indicators declaratively
-   * via CSS (including possibly complex combinations).
-   *
-   * Flag changes can be tracked via onFlagChange (@see addActionListener).
-   *
-   * @see StyleEditorFlags
-   */
-
-  /**
-   * Retrieve a space-separated string of all UI flags set on this editor.
-   *
-   * @return string
-   * @see setFlag
-   * @see clearFlag
-   */
-  get flags() this._flags.join(" "),
-
-  /**
-   * Set a flag.
-   *
-   * @param string aName
-   *        Name of the flag to set. One of StyleEditorFlags members.
-   * @return boolean
-   *         True if the flag has been set, false if flag is already set.
-   * @see StyleEditorFlags
-   */
-  setFlag: function SE_setFlag(aName)
-  {
-    let prop = aName.toUpperCase();
-    assert(StyleEditorFlags[prop], "Unknown flag: " + prop);
-
-    if (this.hasFlag(aName)) {
-      return false;
-    }
-    this._flags.push(aName);
-    this._triggerAction("FlagChange", [aName]);
-    return true;
-  },
-
-  /**
-   * Clear a flag.
-   *
-   * @param string aName
-   *        Name of the flag to clear.
-   * @return boolean
-   *         True if the flag has been cleared, false if already clear.
-   */
-  clearFlag: function SE_clearFlag(aName)
-  {
-    let index = this._flags.indexOf(aName);
-    if (index == -1) {
-      return false;
-    }
-    this._flags.splice(index, 1);
-    this._triggerAction("FlagChange", [aName]);
-    return true;
-  },
-
-  /**
-   * Toggle a flag, according to a condition.
-   *
-   * @param aCondition
-   *        If true the flag is set, otherwise cleared.
-   * @param string aName
-   *        Name of the flag to toggle.
-   * @return boolean
-   *        True if the flag has been set or cleared, ie. the flag got switched.
-   */
-  toggleFlag: function SE_toggleFlag(aCondition, aName)
-  {
-    return (aCondition) ? this.setFlag(aName) : this.clearFlag(aName);
-  },
-
-  /**
-   * Check if given flag is set.
-   *
-   * @param string aName
-   *        Name of the flag to check presence for.
-   * @return boolean
-   *         True if the flag is set, false otherwise.
-   */
-  hasFlag: function SE_hasFlag(aName) (this._flags.indexOf(aName) != -1),
-
-  /**
-   * Enable or disable style sheet.
-   *
-   * @param boolean aEnabled
-   */
-  enableStyleSheet: function SE_enableStyleSheet(aEnabled)
-  {
-    this.styleSheet.disabled = !aEnabled;
-    this.toggleFlag(this.styleSheet.disabled, StyleEditorFlags.DISABLED);
-
-    if (this._updateTask) {
-      this._updateStyleSheet(); // perform cancelled update
-    }
-  },
-
-  /**
-   * 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 aFile
-   *        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) aCallback
-   *        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 SE_saveToFile(aFile, aCallback)
-  {
-    let callback = function(aReturnFile) {
-      if (!aReturnFile) {
-        if (aCallback) {
-          aCallback(null);
-        }
-        return;
-      }
-
-      if (this._sourceEditor) {
-        this._state.text = this._sourceEditor.getText();
-      }
-
-      let ostream = FileUtils.openSafeFileOutputStream(aReturnFile);
-      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 SE_onStreamCopied(status) {
-        if (!Components.isSuccessCode(status)) {
-          if (aCallback) {
-            aCallback(null);
-          }
-          this._signalError(SAVE_ERROR);
-          return;
-        }
-        FileUtils.closeSafeFileOutputStream(ostream);
-
-        // remember filename for next save if any
-        this._friendlyName = null;
-        this._savedFile = aReturnFile;
-        this._persistExpando();
-
-        if (aCallback) {
-          aCallback(aReturnFile);
-        }
-        this.clearFlag(StyleEditorFlags.UNSAVED);
-        this.clearFlag(StyleEditorFlags.ERROR);
-      }.bind(this));
-    }.bind(this);
-
-    this._showFilePicker(aFile || this._styleSheetFilePath, true, null, callback);
-  },
-
-  /**
-   * Queue a throttled task to update the live style sheet.
-   *
-   * @param boolean aImmediate
-   *        Optional. If true the update is performed immediately.
-   */
-  updateStyleSheet: function SE_updateStyleSheet(aImmediate)
-  {
-    let window = this.window;
-
-    if (this._updateTask) {
-      // cancel previous queued task not executed within throttle delay
-      window.clearTimeout(this._updateTask);
-    }
-
-    if (aImmediate) {
-      this._updateStyleSheet();
-    } else {
-      this._updateTask = window.setTimeout(this._updateStyleSheet.bind(this),
-                                           UPDATE_STYLESHEET_THROTTLE_DELAY);
-    }
-  },
-
-  /**
-   * Update live style sheet according to modifications.
-   */
-  _updateStyleSheet: function SE__updateStyleSheet()
-  {
-    this.setFlag(StyleEditorFlags.UNSAVED);
-
-    if (this.styleSheet.disabled) {
-      return;
-    }
-
-    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();
-    }
-    DOMUtils.parseStyleSheet(this.styleSheet, this._state.text);
-    this._persistExpando();
-
-    if (!TRANSITIONS_ENABLED) {
-      this._triggerAction("Update");
-      this._triggerAction("Commit");
-      return;
-    }
-
-    let content = this.contentDocument;
-
-    // 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) {
-      this.styleSheet.insertRule(TRANSITION_RULE, this.styleSheet.cssRules.length);
-      content.documentElement.classList.add(TRANSITION_CLASS);
-    }
-
-    this._transitionRefCount++;
-
-    // Set up clean up and commit after transition duration (+10% buffer)
-    // @see _onTransitionEnd
-    content.defaultView.setTimeout(this._onTransitionEnd.bind(this),
-                                   Math.floor(TRANSITION_DURATION_MS * 1.1));
-
-    this._triggerAction("Update");
-  },
-
-  /**
-    * This cleans up class and rule added for transition effect and then trigger
-    * Commit as the changes have been completed.
-    */
-  _onTransitionEnd: function SE__onTransitionEnd()
-  {
-    if (--this._transitionRefCount == 0) {
-      this.contentDocument.documentElement.classList.remove(TRANSITION_CLASS);
-      this.styleSheet.deleteRule(this.styleSheet.cssRules.length - 1);
-    }
-
-    this._triggerAction("Commit");
-  },
-
-  /**
-   * Show file picker and return the file user selected.
-   *
-   * @param mixed aFile
-   *        Optional nsIFile or string representing the filename to auto-select.
-   * @param boolean aSave
-   *        If true, the user is selecting a filename to save.
-   * @param nsIWindow aParentWindow
-   *        Optional parent window. If null the parent window of the file picker
-   *        will be the window of the attached input element.
-   * @param aCallback
-   *        The callback method, which will be called passing in the selected
-   *        file or null if the user did not pick one.
-   */
-  _showFilePicker: function SE__showFilePicker(aFile, aSave, aParentWindow, aCallback)
-  {
-    if (typeof(aFile) == "string") {
-      try {
-        if (Services.io.extractScheme(aFile) == "file") {
-          let uri = Services.io.newURI(aFile, null, null);
-          let file = uri.QueryInterface(Ci.nsIFileURL).file;
-          aCallback(file);
-          return;
-        }
-      } catch (ex) {
-      }
-      try {
-        let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
-        file.initWithPath(aFile);
-        aCallback(file);
-        return;
-      } catch (ex) {
-        this._signalError(aSave ? SAVE_ERROR : LOAD_ERROR);
-        aCallback(null);
-        return;
-      }
-    }
-    if (aFile) {
-      aCallback(aFile);
-      return;
-    }
-
-    let window = aParentWindow
-                 ? aParentWindow
-                 : this.inputElement.ownerDocument.defaultView;
-    let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
-    let mode = aSave ? fp.modeSave : fp.modeOpen;
-    let key = aSave ? "saveStyleSheet" : "importStyleSheet";
-    let fpCallback = function fpCallback_done(aResult) {
-      if (aResult == Ci.nsIFilePicker.returnCancel) {
-        aCallback(null);
-      } else {
-        aCallback(fp.file);
-      }
-    };
-
-    fp.init(window, _(key + ".title"), mode);
-    fp.appendFilters(_(key + ".filter"), "*.css");
-    fp.appendFilters(fp.filterAll);
-    fp.open(fpCallback);
-    return;
-  },
-
-  /**
-   * Retrieve the style sheet source from the cache or from a local file.
-   */
-  _loadSource: function SE__loadSource()
-  {
-    if (!this.styleSheet.href) {
-      // this is an inline <style> sheet
-      this._flags.push(StyleEditorFlags.INLINE);
-      this._onSourceLoad(this.styleSheet.ownerNode.textContent);
-      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;
-    }
-  },
-
-  /**
-   * 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 aString
-   *        Source of a CSS stylesheet, loaded from file or cache.
-   * @param string aChannelCharset
-   *        Charset of the source string if set by the HTTP channel.
-   * @return string
-   *         The CSS string, in unicode.
-   */
-  _decodeCSSCharset: function SE__decodeCSSCharset(aString, aChannelCharset)
-  {
-    // StyleSheet's charset can be specified from multiple sources
-
-    if (aChannelCharset.length > 0) {
-      // step 1 of syndata.html: charset given in HTTP header.
-      return this._convertToUnicode(aString, aChannelCharset);
-    }
-
-    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(aString, 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(aString, 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(aString,
-            parentSheet.cssRules[0].encoding);
-      }
-
-      // step 4 (2 of 2): charset of referring document.
-      if (sheet.ownerNode && sheet.ownerNode.ownerDocument.characterSet) {
-        return this._convertToUnicode(aString,
-            sheet.ownerNode.ownerDocument.characterSet);
-      }
-    }
-
-    // step 5: default to utf-8.
-    return this._convertToUnicode(aString, "UTF-8");
-  },
-
-  /**
-   * Convert a given string, encoded in a given character set, to unicode.
-   * @param string aString
-   *        A string.
-   * @param string aCharset
-   *        A character set.
-   * @return string
-   *         A unicode string.
-   */
-  _convertToUnicode: function SE__convertToUnicode(aString, aCharset) {
-    // Decoding primitives.
-    let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
-        .createInstance(Ci.nsIScriptableUnicodeConverter);
-
-    try {
-      converter.charset = aCharset;
-      return converter.ConvertToUnicode(aString);
-    } catch(e) {
-      return aString;
-    }
-  },
-
-  /**
-   * Load source from a file or file-like resource.
-   *
-   * @param string aHref
-   *        URL for the stylesheet.
-   */
-  _loadSourceFromFile: function SE__loadSourceFromFile(aHref)
-  {
-    try {
-      NetUtil.asyncFetch(aHref, function onFetch(aStream, aStatus) {
-        if (!Components.isSuccessCode(aStatus)) {
-          return this._signalError(LOAD_ERROR);
-        }
-        let source = NetUtil.readInputStreamToString(aStream, aStream.available());
-        aStream.close();
-        this._onSourceLoad(source);
-      }.bind(this));
-    } catch (ex) {
-      this._signalError(LOAD_ERROR);
-    }
-  },
-
-  /**
-   * Load source from the HTTP cache.
-   *
-   * @param string aHref
-   *        URL for the stylesheet.
-   */
-  _loadSourceFromCache: function SE__loadSourceFromCache(aHref)
-  {
-    let channel = Services.io.newChannel(aHref, null, null);
-    let chunks = [];
-    let channelCharset = "";
-    let streamListener = { // nsIStreamListener inherits nsIRequestObserver
-      onStartRequest: function (aRequest, aContext, aStatusCode) {
-        if (!Components.isSuccessCode(aStatusCode)) {
-          return this._signalError(LOAD_ERROR);
-        }
-      }.bind(this),
-      onDataAvailable: function (aRequest, aContext, aStream, aOffset, aCount) {
-        let channel = aRequest.QueryInterface(Ci.nsIChannel);
-        if (!channelCharset) {
-          channelCharset = channel.contentCharset;
-        }
-        chunks.push(NetUtil.readInputStreamToString(aStream, aCount));
-      },
-      onStopRequest: function (aRequest, aContext, aStatusCode) {
-        if (!Components.isSuccessCode(aStatusCode)) {
-          return this._signalError(LOAD_ERROR);
-        }
-
-        this._onSourceLoad(chunks.join(""), channelCharset);
-      }.bind(this)
-    };
-
-    if (channel instanceof Ci.nsIPrivateBrowsingChannel) {
-      let contentWin = this.contentDocument.defaultView;
-      let loadContext = contentWin.QueryInterface(Ci.nsIInterfaceRequestor)
-                          .getInterface(Ci.nsIWebNavigation)
-                          .QueryInterface(Ci.nsILoadContext);
-      channel.setPrivate(loadContext.usePrivateBrowsing);
-    }
-    channel.loadFlags = channel.LOAD_FROM_CACHE;
-    channel.asyncOpen(streamListener, null);
-  },
-
-  /**
-   * Called when source has been loaded.
-   *
-   * @param string aSourceText
-   * @param string aCharset
-   *        Optional. The character set to use. The default is to detect the
-   *        character set following the standard (see
-   *        <http://www.w3.org/TR/CSS2/syndata.html#charset>).
-   */
-  _onSourceLoad: function SE__onSourceLoad(aSourceText, aCharset)
-  {
-    aSourceText = this._decodeCSSCharset(aSourceText, aCharset || "");
-    this._restoreExpando();
-    this._state.text = prettifyCSS(aSourceText);
-    this._loaded = true;
-    this._triggerAction("Load");
-  },
-
-  /**
-   * Create a new style sheet and append it to the content document.
-   *
-   * @param string aText
-   *        Optional CSS text.
-   */
-  _appendNewStyleSheet: function SE__appendNewStyleSheet(aText)
-  {
-    let document = this.contentDocument;
-    let parent = document.documentElement;
-    let style = document.createElementNS("http://www.w3.org/1999/xhtml", "style");
-    style.setAttribute("type", "text/css");
-    if (aText) {
-      style.appendChild(document.createTextNode(aText));
-    }
-    parent.appendChild(style);
-
-    this._styleSheet = document.styleSheets[document.styleSheets.length - 1];
-    if (aText) {
-      this._onSourceLoad(aText);
-      this._flags.push(StyleEditorFlags.IMPORTED);
-    } else {
-      this._flags.push(StyleEditorFlags.NEW);
-      this._flags.push(StyleEditorFlags.UNSAVED);
-    }
-  },
-
-  /**
-   * Signal an error to the user.
-   *
-   * @param string aErrorCode
-   *        String name for the localized error property in the string bundle.
-   * @param ...rest
-   *        Optional arguments to pass for message formatting.
-   * @see StyleEditorUtil._
-   */
-  _signalError: function SE__signalError(aErrorCode)
-  {
-    this._errorMessage = _.apply(null, arguments);
-    this.setFlag(StyleEditorFlags.ERROR);
-  },
-
-  /**
-   * Trigger named action handler in listeners.
-   *
-   * @param string aName
-   *        Name of the action to trigger.
-   * @param Array aArgs
-   *        Optional array of arguments to pass to the listener(s).
-   * @see addActionListener
-   */
-  _triggerAction: function SE__triggerAction(aName, aArgs)
-  {
-    // insert the origin editor 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._actionListeners.concat();
-
-    // trigger all listeners that have this action handler
-    for (let i = 0; i < listeners.length; i++) {
-      let listener = listeners[i];
-      let actionHandler = listener["on" + aName];
-      if (actionHandler) {
-        actionHandler.apply(listener, aArgs);
-      }
-    }
-
-    // when a flag got changed, user-facing state need to be persisted
-    if (aName == "FlagChange") {
-      this._persistExpando();
-    }
-  },
-
-  /**
-    * Unload event handler to perform any pending update before closing
-    */
-  _onWindowUnload: function SE__onWindowUnload(aEvent)
-  {
-    if (this._updateTask) {
-      this.updateStyleSheet(true);
-    }
-  },
-
-  /**
-   * Focus the Style Editor input.
-   */
-  focus: function SE_focus()
-  {
-    if (this._sourceEditor) {
-      this._sourceEditor.focus();
-    } else {
-      this._focusOnSourceEditorReady = true;
-    }
-  },
-
-  /**
-   * Event handler for when the editor is shown. Call this after the editor is
-   * shown.
-   */
-  onShow: function SE_onShow()
-  {
-    if (this._sourceEditor) {
-      this._sourceEditor.setTopIndex(this._state.topIndex);
-    }
-    this.focus();
-  },
-
-  /**
-   * Event handler for when the editor is hidden. Call this before the editor is
-   * hidden.
-   */
-  onHide: function SE_onHide()
-  {
-    if (this._sourceEditor) {
-      this._state.topIndex = this._sourceEditor.getTopIndex();
-    }
-  },
-
-  /**
-    * Persist StyleEditor extra data to the attached DOM stylesheet expando.
-    * The expando on the DOM stylesheet is used to restore user-facing state
-    * when the StyleEditor is closed and then reopened again.
-    *
-    * @see styleSheet
-    */
-  _persistExpando: function SE__persistExpando()
-  {
-    if (!this._styleSheet) {
-      return; // not loaded
-    }
-    let name = STYLESHEET_EXPANDO + this.styleSheetIndex;
-    let expando = this.contentDocument.getUserData(name);
-    if (!expando) {
-      expando = {};
-      this.contentDocument.setUserData(name, expando, null);
-    }
-    expando._flags = this._flags;
-    expando._savedFile = this._savedFile;
-  },
-
-  /**
-    * Restore the attached DOM stylesheet expando into this editor state.
-    *
-    * @see styleSheet
-    */
-  _restoreExpando: function SE__restoreExpando()
-  {
-    if (!this._styleSheet) {
-      return; // not loaded
-    }
-    let name = STYLESHEET_EXPANDO + this.styleSheetIndex;
-    let expando = this.contentDocument.getUserData(name);
-    if (expando) {
-      this._flags = expando._flags;
-      this._savedFile = expando._savedFile;
-    }
-  },
-
-  /**
-    * Retrieve custom key bindings objects as expected by SourceEditor.
-    * SourceEditor action names are not displayed to the user.
-    *
-    * @return Array
-    */
-  _getKeyBindings: function SE__getKeyBindings()
-  {
-    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;
-  }
-};
-
-/**
- * List of StyleEditor UI flags.
- * A Style Editor add-on using its own flag needs to add it to this object.
- *
- * @see StyleEditor.setFlag
- */
-this.StyleEditorFlags = {
-  DISABLED:      "disabled",
-  ERROR:         "error",
-  IMPORTED:      "imported",
-  INLINE:        "inline",
-  MODIFIED:      "modified",
-  NEW:           "new",
-  UNSAVED:       "unsaved"
-};
-
-
-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";
-
-/**
- * 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 aText
- *        The CSS source to prettify.
- * @return string
- *         Prettified CSS source
- */
-function prettifyCSS(aText)
-{
-  // remove initial and terminating HTML comments and surrounding whitespace
-  aText = aText.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 < aText.length; i++) {
-    let c = aText[i];
-    let shouldIndent = false;
-
-    switch (c) {
-      case "}":
-        if (i - partStart > 1) {
-          // there's more than just } on the line, add line
-          parts.push(indent + aText.substring(partStart, i));
-          partStart = i;
-        }
-        indent = repeat(TAB_CHARS, --indentLevel);
-        /* fallthrough */
-      case ";":
-      case "{":
-        shouldIndent = true;
-        break;
-    }
-
-    if (shouldIndent) {
-      let la = aText[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 + aText.substring(partStart, i + 1));
-        if (c == "}") {
-          parts.push(""); // for extra line separator
-        }
-        partStart = i + 1;
-      } else {
-        return aText; // assume it is not minified, early exit
-      }
-    }
-
-    if (c == "{") {
-      indent = repeat(TAB_CHARS, ++indentLevel);
-    }
-  }
-  return parts.join(LINE_SEPARATOR);
-}
-
-/**
-  * Return string that repeats aText for aCount times.
-  *
-  * @param string aText
-  * @param number aCount
-  * @return string
-  */
-function repeat(aText, aCount)
-{
-  return (new Array(aCount + 1)).join(aText);
-}
-
-/**
- * Set up bracket completion on a given SourceEditor.
- * This automatically closes the following CSS brackets: "{", "(", "["
- *
- * @param SourceEditor aSourceEditor
- */
-function setupBracketCompletion(aSourceEditor)
-{
-  let editorElement = aSourceEditor.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(aEvent) {
-    let pair = pairs[aEvent.charCode];
-    if (!pair || aEvent.ctrlKey || aEvent.metaKey ||
-        aEvent.accelKey || aEvent.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
-    aSourceEditor.setCaretOffset(aSourceEditor.getCaretOffset() - 1);
-  }, false);
-}
deleted file mode 100644
--- a/browser/devtools/styleeditor/StyleEditorChrome.jsm
+++ /dev/null
@@ -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);
-  },
-};
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/StyleEditorDebuggee.jsm
@@ -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);
+  }
+}
--- a/browser/devtools/styleeditor/StyleEditorPanel.jsm
+++ b/browser/devtools/styleeditor/StyleEditorPanel.jsm
@@ -7,202 +7,122 @@
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 this.EXPORTED_SYMBOLS = ["StyleEditorPanel"];
 
 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");
 
 this.StyleEditorPanel = function StyleEditorPanel(panelWin, toolbox) {
   EventEmitter.decorate(this);
 
   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.
-   */
-  get target() this._target,
-
-  /**
-   * Panel window getter.
-   */
-  get panelWindow() this._panelWin,
-
-  /**
-   * StyleEditorChrome instance getter.
-   */
-  get styleEditorChrome() this._panelWin.styleEditorChrome,
-
-  /**
-   * Set the page to target.
+   * 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
    */
-  setPage: function StyleEditor_setPage(contentWindow) {
-    if (this._panelWin.styleEditorChrome) {
-      this._panelWin.styleEditorChrome.contentWindow = contentWindow;
-      this.selectStyleSheet(null, null, null);
-    } else {
-      let chromeRoot = this._panelDoc.getElementById("style-editor-chrome");
-      let chrome = new StyleEditorChrome(chromeRoot, contentWindow);
-      let promise = chrome.open();
-
-      this._panelWin.styleEditorChrome = chrome;
-      this.selectStyleSheet(null, null, null);
-      return promise;
-    }
-  },
-
-  /**
-   * Navigated to a new page.
-   */
-  newPage: function StyleEditor_newPage(event, payload) {
-    let window = payload._navPayload || payload;
-    this.reset();
-    this.setPage(window);
-  },
-
-  /**
-   * Before navigating to a new page or reloading the page.
-   */
-  beforeNavigate: function StyleEditor_beforeNavigate(event, payload) {
-    let request = payload._navPayload || payload;
-    if (this.styleEditorChrome.isDirty) {
-      this.preventNavigate(request);
+  _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);
     }
   },
 
   /**
-   * Show a notificiation about losing unsaved changes.
+   * 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
    */
-  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);
+  selectStyleSheet: function(href, line, col) {
+    if (!this._debuggee || !this.UI) {
+      return;
     }
-
-    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();
+    let stylesheet = this._debuggee.styleSheetFromHref(href);
+    this.UI.selectStyleSheet(href, line, col);
   },
 
   /**
-   * Select a stylesheet.
+   * Destroy the style editor.
    */
-  selectStyleSheet: function StyleEditor_selectStyleSheet(stylesheet, line, col) {
-    this._panelWin.styleEditorChrome.selectStyleSheet(stylesheet, line, col);
-  },
-
-  /**
-   * Destroy StyleEditor
-   */
-  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);
   },
 }
 
 XPCOMUtils.defineLazyGetter(StyleEditorPanel.prototype, "strings",
   function () {
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/StyleEditorUI.jsm
@@ -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);
+  }
+}
--- a/browser/devtools/styleeditor/StyleEditorUtil.jsm
+++ b/browser/devtools/styleeditor/StyleEditorUtil.jsm
@@ -3,21 +3,20 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = [
   "_",
   "assert",
-  "attr", // XXXkhuey unused?
-  "getCurrentBrowserTabContentWindow", // XXXkhuey unused?
   "log",
   "text",
-  "wire"
+  "wire",
+  "showFilePicker"
 ];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/Services.jsm");
 
@@ -103,17 +102,17 @@ function forEach(aObject, aCallback)
     if (aObject.hasOwnProperty(key)) {
       aCallback(key, aObject[key]);
     }
   }
 }
 
 /**
  * Log a message to the console.
- * 
+ *
  * @param ...rest
  *        One or multiple arguments to log.
  *        If multiple arguments are given, they will be joined by " " in the log.
  */
 this.log = function log()
 {
   console.logStringMessage(Array.prototype.slice.call(arguments).join(" "));
 }
@@ -157,8 +156,68 @@ this.wire = function wire(aRoot, aSelect
     forEach(aDescriptor.events, function (aName, aHandler) {
       element.addEventListener(aName, aHandler, false);
     });
     forEach(aDescriptor.attributes, element.setAttribute);
     forEach(aDescriptor.userData, element.setUserData);
   }
 }
 
+/**
+ * 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;
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/StyleSheetEditor.jsm
@@ -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);
+}
+
--- a/browser/devtools/styleeditor/styleeditor.xul
+++ b/browser/devtools/styleeditor/styleeditor.xul
@@ -46,23 +46,21 @@
 
   <xul:box id="style-editor-chrome" class="splitview-root loading">
     <xul:box class="splitview-controller">
       <xul:box class="splitview-main">
         <xul:toolbar class="devtools-toolbar">
           <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"
               persist="width height">
         <ol class="splitview-nav" tabindex="0"></ol>
         <div class="splitview-nav placeholder empty">
           <p><strong>&noStyleSheet.label;</strong></p>
           <p>&noStyleSheet-tip-start.label;
--- a/browser/devtools/styleeditor/test/Makefile.in
+++ b/browser/devtools/styleeditor/test/Makefile.in
@@ -16,24 +16,20 @@ include $(topsrcdir)/config/rules.mk
                  browser_styleeditor_filesave.js \
                  browser_styleeditor_cmd_edit.js \
                  browser_styleeditor_cmd_edit.html \
                  browser_styleeditor_import.js \
                  browser_styleeditor_import_rule.js \
                  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 \
                  four.html \
                  head.js \
                  helpers.js \
                  import.css \
                  import.html \
deleted file mode 100644
--- a/browser/devtools/styleeditor/test/browser_styleeditor_bug_826982_location_changed.js
+++ /dev/null
@@ -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>";
-}
--- a/browser/devtools/styleeditor/test/browser_styleeditor_bug_851132_middle_click.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_bug_851132_middle_click.js
@@ -1,60 +1,68 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 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) {
   ok(aEditor.sourceEditor.hasFocus(),
      "left mouse click has given editor 1 focus");
 
   // 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);
 }
 
 function onTabAdded() {
   ok(false, "middle mouse click has opened a new tab");
   finish();
 }
 
 registerCleanupFunction(function () {
   gBrowser.tabContainer.removeEventListener("TabOpen", onTabAdded, false);
-  gSEChrome = null;
+  gUI = null;
 });
--- a/browser/devtools/styleeditor/test/browser_styleeditor_enabled.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_enabled.js
@@ -1,101 +1,75 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // https rather than chrome to improve coverage
 const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
 
-
 function test()
 {
   waitForExplicitFinish();
 
   let count = 0;
-  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(editor.styleSheet.disabled, false,
+     "first stylesheet is initially enabled");
 
-  is(firstStyleSheetEditor.contentDocument.styleSheets[0].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++;
+  editor.on("property-change", function(event, property) {
+    if (property != "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");
+    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, {}, gChromeWindow);
-        }, gChromeWindow);
-        return;
-      }
+      // now toggle it back to enabled
+      waitForFocus(function () {
+        EventUtils.synthesizeMouseAtCenter(enabledToggle, {}, gPanelWindow);
+      }, gPanelWindow);
+      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");
+    // 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();
-    }
+    finish();
   });
 
   waitForFocus(function () {
-    EventUtils.synthesizeMouseAtCenter(enabledToggle, {}, gChromeWindow);
-  }, gChromeWindow);
+    EventUtils.synthesizeMouseAtCenter(enabledToggle, {}, gPanelWindow);
+  }, gPanelWindow);
 }
--- a/browser/devtools/styleeditor/test/browser_styleeditor_filesave.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_filesave.js
@@ -16,49 +16,38 @@ let NetUtil = tempScope.NetUtil;
 
 
 function test()
 {
   waitForExplicitFinish();
 
   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();
   });
 }
 
 function copy(aSrcChromeURL, aDestFileName, aCallback)
 {
   let destFile = FileUtils.getFile("ProfD", [aDestFileName]);
   write(read(aSrcChromeURL), destFile, aCallback);
--- a/browser/devtools/styleeditor/test/browser_styleeditor_import.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_import.js
@@ -8,80 +8,69 @@ const TESTCASE_URI = TEST_BASE_HTTP + "s
 let tempScope = {};
 Components.utils.import("resource://gre/modules/FileUtils.jsm", tempScope);
 let FileUtils = tempScope.FileUtils;
 
 const FILENAME = "styleeditor-import-test.css";
 const SOURCE = "body{background:red;}";
 
 
+let gUI;
+
 function test()
 {
   waitForExplicitFinish();
 
-  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]);
   let ostream = FileUtils.openSafeFileOutputStream(file);
   let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                     .createInstance(Ci.nsIScriptableUnicodeConverter);
   converter.charset = "UTF-8";
   let istream = converter.convertToInputStream(SOURCE);
   NetUtil.asyncCopy(istream, ostream, function (status) {
     FileUtils.closeSafeFileOutputStream(ostream);
 
     // click the import button now that the file to import is ready
-    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();
 }
--- a/browser/devtools/styleeditor/test/browser_styleeditor_import_rule.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_import_rule.js
@@ -1,34 +1,43 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // http rather than chrome to improve coverage
 const TESTCASE_URI = TEST_BASE_HTTP + "import.html";
 
+let gUI;
+
 function test()
 {
   waitForExplicitFinish();
 
-  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();
 }
--- a/browser/devtools/styleeditor/test/browser_styleeditor_init.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_init.js
@@ -1,107 +1,83 @@
 /* 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 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)
+let gEditorAddedCount = 0;
+function testEditorAdded(aEvent, aEditor)
 {
-  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)
-{
-  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",
      "first stylesheet's name is `simple.css`");
 
   let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
   is(parseInt(ruleCount), 1,
      "first stylesheet UI shows rule count as 1");
 
   ok(summary.classList.contains("splitview-active"),
      "first stylesheet UI is focused/active");
 }
 
-function testSecondStyleSheetEditor(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),
      "second stylesheet's name is surrounded by `<>`");
 
   let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
   is(parseInt(ruleCount), 3,
      "second stylesheet UI shows rule count as 3");
--- a/browser/devtools/styleeditor/test/browser_styleeditor_loading.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_loading.js
@@ -4,34 +4,36 @@
 
 const TESTCASE_URI = TEST_BASE + "longload.html";
 
 
 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) {
-    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");
+  addTabAndOpenStyleEditor(function(panel) {
+    panel.UI.on("editor-added", testEditorAdded);
 
-      button = gChromeWindow.document.querySelector(".style-editor-importButton");
-      ok(!button.hasAttribute("disabled"),
-         "import button is enabled");
-
-      finish();
+    content.location = TESTCASE_URI;
   });
 }
+
+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();
+}
--- a/browser/devtools/styleeditor/test/browser_styleeditor_new.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_new.js
@@ -1,169 +1,124 @@
 /* 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 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);
+
+  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.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");
+    ok(!content.document.documentElement.classList.contains(TRANSITION_CLASS),
+       "StyleEditor's transition class has been removed from content");
+  });
+}
 
-        ok(aEditor.inputElement,
-           "new editor has an input element attached");
-
-        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);
-        }
-
-        is(aEditor.sourceEditor.getText(), TESTCASE_CSS_SOURCE + "}",
-           "rule bracket has been auto-closed");
+  let computedStyle = content.getComputedStyle(content.document.body, null);
+  is(computedStyle.backgroundColor, "rgb(255, 255, 255)",
+     "content's background color is initially white");
 
-        // 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");
+  EventUtils.synthesizeKey("]", {accelKey: true}, gPanelWindow);
+  is(aEditor.sourceEditor.getText(), "",
+     "Nothing happened as it is a known shortcut in source editor");
 
-          executeSoon(finishOnTransitionEndAndCommit);
-        }, false);
-      }, gChromeWindow) ;
-    },
+  for each (let c in TESTCASE_CSS_SOURCE) {
+    EventUtils.synthesizeKey(c, {}, gPanelWindow);
+  }
 
-    onUpdate: function (aEditor) {
-      gUpdateCount++;
+  is(aEditor.sourceEditor.getText(), TESTCASE_CSS_SOURCE + "}",
+     "rule bracket has been auto-closed");
 
-      ok(content.document.documentElement.classList.contains(TRANSITION_CLASS),
-         "StyleEditor's transition class has been added to content");
-    },
-
-    onCommit: function (aEditor) {
-      gCommitCount++;
+  ok(aEditor.unsaved,
+     "new editor has unsaved flag");
 
-      ok(aEditor.hasFlag("new"),
-         "new editor still has NEW flag");
-      ok(aEditor.hasFlag("unsaved"),
-         "new editor has UNSAVED flag after modification");
+  // we know that the testcase above will start a CSS transition
+  content.addEventListener("transitionend", onTransitionEnd, false);
+}, gPanelWindow) ;
+}
 
-      let summary = aChrome.getSummaryElementForEditor(aEditor);
-      let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
-      is(parseInt(ruleCount), 1,
-         "new editor shows 1 rule after modification");
+function onTransitionEnd() {
+  content.removeEventListener("transitionend", onTransitionEnd, false);
 
-      ok(!content.document.documentElement.classList.contains(TRANSITION_CLASS),
-         "StyleEditor's transition class has been removed from content");
-
-      aEditor.removeActionListener(listener);
+  let computedStyle = content.getComputedStyle(content.document.body, null);
+  is(computedStyle.backgroundColor, "rgb(255, 0, 0)",
+     "content's background color has been updated to red");
 
-      executeSoon(finishOnTransitionEndAndCommit);
-    }
-  };
+  if (gNewEditor) {
+    is(gNewEditor.styleSheet.href, gOriginalHref,
+       "style sheet href did not change");
 
-  aEditor.addActionListener(listener);
-  if (aEditor.sourceEditor) {
-    listener.onAttach(aEditor);
+    gNewEditor = null;
+    gUI = null;
+    finish();
   }
 }
deleted file mode 100644
--- a/browser/devtools/styleeditor/test/browser_styleeditor_passedinsheet.js
+++ /dev/null
@@ -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();
-  }
-}
--- a/browser/devtools/styleeditor/test/browser_styleeditor_pretty.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_pretty.js
@@ -1,56 +1,52 @@
 /* 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 + "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);
+      });
     });
   });
 
   content.location = TESTCASE_URI;
 }
 
 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);
 
     ok(originalSourceRE.test(aEditor.sourceEditor.getText()),
        "non-minified source has been left untouched");
     editorTestedCount++;
   }
 
   if (editorTestedCount == 2) {
+    gUI = null;
     finish();
   }
 }
--- a/browser/devtools/styleeditor/test/browser_styleeditor_private_perwindowpb.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_private_perwindowpb.js
@@ -1,45 +1,48 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // This test makes sure that the style editor does not store any
 // content CSS files in the permanent cache when opened from PB mode.
+
+let gUI;
+
 function test() {
   waitForExplicitFinish();
   let windowsToClose = [];
   let testURI = 'http://' + TEST_HOST + '/browser/browser/devtools/styleeditor/test/test_private.html';
 
   function checkCache() {
     checkDiskCacheFor(TEST_HOST);
+
+    gUI = null;
     finish();
   }
 
   function doTest(aWindow) {
     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);
     }
   }
 
   function testOnWindow(options, callback) {
     let win = OpenBrowserWindow(options);
     win.addEventListener("load", function onLoad() {
       win.removeEventListener("load", onLoad, false);
       windowsToClose.push(win);
deleted file mode 100644
--- a/browser/devtools/styleeditor/test/browser_styleeditor_readonly.js
+++ /dev/null
@@ -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();
-}
deleted file mode 100644
--- a/browser/devtools/styleeditor/test/browser_styleeditor_reopen.js
+++ /dev/null
@@ -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);
-    }
-  }
-}
--- a/browser/devtools/styleeditor/test/browser_styleeditor_sv_keynav.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_sv_keynav.js
@@ -1,75 +1,78 @@
 /* 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 + "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)
 {
   ok(aEditor.sourceEditor.hasFocus(),
      "editor 2 has focus");
 
-  gChrome = null;
+  gUI = null;
   finish();
 }
--- a/browser/devtools/styleeditor/test/browser_styleeditor_sv_resize.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_sv_resize.js
@@ -1,56 +1,61 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TESTCASE_URI = TEST_BASE + "simple.html";
 
-let gOriginalWidth; // these are set by 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);
+    });
   });
 }
--- a/browser/devtools/styleeditor/test/head.js
+++ b/browser/devtools/styleeditor/test/head.js
@@ -7,60 +7,87 @@ const TEST_BASE_HTTPS = "https://example
 const TEST_HOST = 'mochi.test:8888';
 
 let tempScope = {};
 Cu.import("resource:///modules/devtools/Target.jsm", tempScope);
 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);
 
 
 // Import the GCLI test helper
 let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 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);
 }
 
 function launchStyleEditorChromeFromWindow(aWindow, aCallback, aSheet, aLine, aCol)
 {
   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);
   });
 }
 
 function addTabAndLaunchStyleEditorChromeWhenLoaded(aCallback, aSheet, aLine, aCol)
 {
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
     gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
     launchStyleEditorChrome(aCallback, aSheet, aLine, aCol);
   }, true);
 }
+*/
 
 function checkDiskCacheFor(host)
 {
   let foundPrivateData = false;
 
   let visitor = {
     visitDevice: function(deviceID, deviceInfo) {
       if (deviceID == "disk")
--- a/browser/devtools/styleeditor/test/longload.html
+++ b/browser/devtools/styleeditor/test/longload.html
@@ -15,14 +15,14 @@
   div > span {
      text-decoration: underline;
   }
   </style>
 </head>
 <body>
   Time passes:
   <script>
-    for (i = 0; i < 30000; i++) {
+    for (i = 0; i < 5000; i++) {
       document.write("<br>...");
     }
   </script>
 </body>
 </html>
--- a/browser/devtools/styleinspector/test/browser_computedview_734259_style_editor_link.js
+++ b/browser/devtools/styleinspector/test/browser_computedview_734259_style_editor_link.js
@@ -79,44 +79,32 @@ function testInlineStyle()
 
 function testInlineStyleSheet()
 {
   info("clicking an inline stylesheet");
 
   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);
   link.click();
 }
 
 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();
 }
 
 function expandProperty(aIndex, aCallback)
 {
   let contentDoc = computedView.styleDocument;
   let expando = contentDoc.querySelectorAll(".expandable")[aIndex];
--- a/browser/devtools/styleinspector/test/browser_ruleview_734259_style_editor_link.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_734259_style_editor_link.js
@@ -83,33 +83,35 @@ function testInlineStyle()
   });
 }
 
 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);
     });
   });
 
   let link = getLinkByIndex(1);
   link.scrollIntoView();
   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();
 }
 
 function getLinkByIndex(aIndex)
 {
   let contentDoc = ruleView().doc;
--- a/browser/devtools/webconsole/HUDService.jsm
+++ b/browser/devtools/webconsole/HUDService.jsm
@@ -335,30 +335,24 @@ WebConsole.prototype = {
    * @param integer aSourceLine
    *        The line number which you want to place the caret.
    * TODO: This function breaks the client-server boundaries.
    *       To be fixed in bug 793259.
    */
   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);
+    });
   },
 
   /**
    * Tries to open a JavaScript file related to the web page for the web console
    * instance in the Script Debugger. If the file is not found, it is opened in
    * source view instead.
    *
    * @param string aSourceURL
--- a/browser/devtools/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_bug_782653_CSS_links_in_Style_Editor.js
@@ -2,33 +2,33 @@
 /* ***** BEGIN LICENSE BLOCK *****
  * Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  * ***** END LICENSE BLOCK ***** */
 
 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()
 {
   addTab(TEST_URI);
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
     openConsole(null, testViewSource);
   }, true);
 }
 
 function testViewSource(aHud)
 {
   hud = aHud;
 
   registerCleanupFunction(function() {
-    nodes = hud = SEC = null;
+    nodes = hud = StyleEditorUI = null;
   });
 
   let selector = ".webconsole-msg-cssparser .webconsole-location";
 
   waitForSuccess({
     name: "find the location node",
     validatorFn: function()
     {
@@ -36,91 +36,85 @@ function testViewSource(aHud)
     },
     successFn: function()
     {
       nodes = hud.outputNode.querySelectorAll(selector);
       is(nodes.length, 2, "correct number of css messages");
 
       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]);
     },
     failureFn: finishTest,
   });
 }
 
-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");
 
         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();
           });
         });
 
         EventUtils.sendMouseEvent({ type: "click" }, nodes[1]);
       });
     });
   }, 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;
         }
       }
       return false;
     },
     successFn: function()
     {
@@ -131,27 +125,28 @@ function checkStyleEditorForSheetAndLine
 }
 
 function performLineCheck(aEditor, aLine, aCallback)
 {
   function checkForCorrectState()
   {
     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);
   }
 
   waitForSuccess({
     name: "source editor load",
     validatorFn: function()
     {
       return aEditor.sourceEditor;
     },
     successFn: checkForCorrectState,
     failureFn: function() {
-      info("selectedStyleSheetIndex " + SEC.selectedStyleSheetIndex + " expected " + aEditor.styleSheetIndex);
+      info("selectedStyleSheetIndex " + StyleEditorUI.selectedStyleSheetIndex
+           + " expected " + aEditor.styleSheet.styleSheetIndex);
       finishTest();
     },
   });
 }
--- a/toolkit/devtools/debugger/dbg-client.jsm
+++ b/toolkit/devtools/debugger/dbg-client.jsm
@@ -177,17 +177,18 @@ const UnsolicitedNotifications = {
   "networkEvent": "networkEvent",
   "networkEventUpdate": "networkEventUpdate",
   "newGlobal": "newGlobal",
   "newScript": "newScript",
   "newSource": "newSource",
   "tabDetached": "tabDetached",
   "tabNavigated": "tabNavigated",
   "pageError": "pageError",
-  "webappsEvent": "webappsEvent"
+  "webappsEvent": "webappsEvent",
+  "styleSheetsAdded": "styleSheetsAdded"
 };
 
 /**
  * Set of pause types that are sent by the server and not as an immediate
  * response to a client request.
  */
 const UnsolicitedPauses = {
   "resumeLimit": "resumeLimit",
--- a/toolkit/devtools/debugger/server/dbg-server.js
+++ b/toolkit/devtools/debugger/server/dbg-server.js
@@ -190,16 +190,19 @@ var DebuggerServer = {
     this.addActors("chrome://global/content/devtools/dbg-browser-actors.js");
 #ifndef MOZ_WIDGET_GONK
     this.addActors("chrome://global/content/devtools/dbg-webconsole-actors.js");
     this.addTabActor(this.WebConsoleActor, "consoleActor");
     this.addGlobalActor(this.WebConsoleActor, "consoleActor");
 #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");
   },
 
   /**
    * Listens on the given port for remote debugger connections.
    *
    * @param aPort int
    *        The port to listen on.
    */
--- a/toolkit/devtools/jar.mn
+++ b/toolkit/devtools/jar.mn
@@ -4,8 +4,9 @@
 
 toolkit.jar:
   content/global/devtools/dbg-transport.js        (debugger/dbg-transport.js)
 * content/global/devtools/dbg-server.js           (debugger/server/dbg-server.js)
   content/global/devtools/dbg-script-actors.js    (debugger/server/dbg-script-actors.js)
   content/global/devtools/dbg-browser-actors.js   (debugger/server/dbg-browser-actors.js)
   content/global/devtools/dbg-webconsole-actors.js (webconsole/dbg-webconsole-actors.js)
   content/global/devtools/dbg-profiler-actors.js  (debugger/server/dbg-profiler-actors.js)
+  content/global/devtools/dbg-styleeditor-actors.js  (styleeditor/dbg-styleeditor-actors.js)
--- a/toolkit/devtools/moz.build
+++ b/toolkit/devtools/moz.build
@@ -3,9 +3,10 @@
 # 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/.
 
 PARALLEL_DIRS += [
     'debugger',
     'sourcemap',
     'webconsole',
+    'styleeditor'
 ]
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/styleeditor/Makefile.in
@@ -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
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/styleeditor/dbg-styleeditor-actors.js
@@ -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);
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/styleeditor/moz.build
@@ -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']