Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 26 Apr 2013 13:06:35 -0400
changeset 141029 83e88a8350d338ae569473d9c52d6da06d6be214
parent 141028 5d00e25645df0ec114e480e794fe3f9903f112cc (current diff)
parent 140882 d360244c69ab7874374baceeccdb7c611816645c (diff)
child 141030 7c244b521e54c431ee9b7fbdc74f19f2336af15e
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone23.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound.
browser/components/sessionstore/src/SessionStore.jsm
browser/devtools/styleeditor/StyleEditor.jsm
browser/devtools/styleeditor/StyleEditorChrome.jsm
browser/devtools/styleeditor/test/browser_styleeditor_bug_826982_location_changed.js
browser/devtools/styleeditor/test/browser_styleeditor_passedinsheet.js
browser/devtools/styleeditor/test/browser_styleeditor_readonly.js
browser/devtools/styleeditor/test/browser_styleeditor_reopen.js
--- a/browser/components/sessionstore/src/SessionStore.jsm
+++ b/browser/components/sessionstore/src/SessionStore.jsm
@@ -286,26 +286,22 @@ let SessionStoreInternal = {
   _statesToRestore: {},
 
   // counts the number of crashes since the last clean start
   _recentCrashes: 0,
 
   // whether the last window was closed and should be restored
   _restoreLastWindow: false,
 
-  // tabs to restore in order
-  _tabsToRestore: { priority: [], visible: [], hidden: [] },
+  // number of tabs currently restoring
   _tabsRestoringCount: 0,
 
-  // overrides MAX_CONCURRENT_TAB_RESTORES and _restoreHiddenTabs when true
+  // overrides MAX_CONCURRENT_TAB_RESTORES and restore_hidden_tabs when true
   _restoreOnDemand: false,
 
-  // whether to restore hidden tabs or not, pref controlled.
-  _restoreHiddenTabs: null,
-
   // whether to restore app tabs on demand or not, pref controlled.
   _restorePinnedTabsOnDemand: null,
 
   // The state from the previous session (after restoring pinned tabs). This
   // state is persisted and passed through to the next session during an app
   // restart to make the third party add-on warning not trash the deferred
   // session
   _lastSessionState: null,
@@ -378,20 +374,16 @@ let SessionStoreInternal = {
     // this pref is only read at startup, so no need to observe it
     this._sessionhistory_max_entries =
       this._prefBranch.getIntPref("sessionhistory.max_entries");
 
     this._restoreOnDemand =
       this._prefBranch.getBoolPref("sessionstore.restore_on_demand");
     this._prefBranch.addObserver("sessionstore.restore_on_demand", this, true);
 
-    this._restoreHiddenTabs =
-      this._prefBranch.getBoolPref("sessionstore.restore_hidden_tabs");
-    this._prefBranch.addObserver("sessionstore.restore_hidden_tabs", this, true);
-
     this._restorePinnedTabsOnDemand =
       this._prefBranch.getBoolPref("sessionstore.restore_pinned_tabs_on_demand");
     this._prefBranch.addObserver("sessionstore.restore_pinned_tabs_on_demand", this, true);
 
     gSessionStartup.onceInitialized.then(
       this.initSession.bind(this)
     );
   },
@@ -565,20 +557,18 @@ let SessionStoreInternal = {
    * Called on application shutdown, after notifications:
    * quit-application-granted, quit-application
    */
   _uninit: function ssi_uninit() {
     // save all data for session resuming
     if (this._sessionInitialized)
       this.saveState(true);
 
-    // clear out _tabsToRestore in case it's still holding refs
-    this._tabsToRestore.priority = null;
-    this._tabsToRestore.visible = null;
-    this._tabsToRestore.hidden = null;
+    // clear out priority queue in case it's still holding refs
+    TabRestoreQueue.reset();
 
     // Make sure to break our cycle with the save timer
     if (this._saveTimer) {
       this._saveTimer.cancel();
       this._saveTimer = null;
     }
   },
 
@@ -1204,20 +1194,16 @@ let SessionStoreInternal = {
         if (!this._resume_from_crash)
           _SessionFile.wipe();
         this.saveState(true);
         break;
       case "sessionstore.restore_on_demand":
         this._restoreOnDemand =
           this._prefBranch.getBoolPref("sessionstore.restore_on_demand");
         break;
-      case "sessionstore.restore_hidden_tabs":
-        this._restoreHiddenTabs =
-          this._prefBranch.getBoolPref("sessionstore.restore_hidden_tabs");
-        break;
       case "sessionstore.restore_pinned_tabs_on_demand":
         this._restorePinnedTabsOnDemand =
           this._prefBranch.getBoolPref("sessionstore.restore_pinned_tabs_on_demand");
         break;
     }
   },
 
   /**
@@ -1386,40 +1372,36 @@ let SessionStoreInternal = {
         this.restoreTab(tab);
 
       // attempt to update the current URL we send in a crash report
       this._updateCrashReportURL(aWindow);
     }
   },
 
   onTabShow: function ssi_onTabShow(aWindow, aTab) {
-    // If the tab hasn't been restored yet, move it into the right _tabsToRestore bucket
+    // If the tab hasn't been restored yet, move it into the right bucket
     if (aTab.linkedBrowser.__SS_restoreState &&
         aTab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
-      this._tabsToRestore.hidden.splice(this._tabsToRestore.hidden.indexOf(aTab), this._tabsToRestore.hidden.length);
-      // Just put it at the end of the list of visible tabs;
-      this._tabsToRestore.visible.push(aTab);
+      TabRestoreQueue.hiddenToVisible(aTab);
 
       // let's kick off tab restoration again to ensure this tab gets restored
       // with "restore_hidden_tabs" == false (now that it has become visible)
       this.restoreNextTab();
     }
 
     // Default delay of 2 seconds gives enough time to catch multiple TabShow
     // events due to changing groups in Panorama.
     this.saveStateDelayed(aWindow);
   },
 
   onTabHide: function ssi_onTabHide(aWindow, aTab) {
-    // If the tab hasn't been restored yet, move it into the right _tabsToRestore bucket
+    // If the tab hasn't been restored yet, move it into the right bucket
     if (aTab.linkedBrowser.__SS_restoreState &&
         aTab.linkedBrowser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE) {
-      this._tabsToRestore.visible.splice(this._tabsToRestore.visible.indexOf(aTab), this._tabsToRestore.visible.length);
-      // Just put it at the end of the list of hidden tabs;
-      this._tabsToRestore.hidden.push(aTab);
+      TabRestoreQueue.visibleToHidden(aTab);
     }
 
     // Default delay of 2 seconds gives enough time to catch multiple TabHide
     // events due to changing groups in Panorama.
     this.saveStateDelayed(aWindow);
   },
 
   /* ........ nsISessionStore API .............. */
@@ -1435,17 +1417,17 @@ let SessionStoreInternal = {
       var state = JSON.parse(aState);
     }
     catch (ex) { /* invalid state object - don't restore anything */ }
     if (!state || !state.windows)
       throw (Components.returnCode = Cr.NS_ERROR_INVALID_ARG);
 
     this._browserSetState = true;
 
-    // Make sure _tabsToRestore is emptied out
+    // Make sure the priority queue is emptied out
     this._resetRestoringState();
 
     var window = this._getMostRecentBrowserWindow();
     if (!window) {
       this._restoreCount = 1;
       this._openWindowWithState(state);
       return;
     }
@@ -3148,23 +3130,17 @@ let SessionStoreInternal = {
     }, 0);
 
     // This could cause us to ignore MAX_CONCURRENT_TAB_RESTORES a bit, but
     // it ensures each window will have its selected tab loaded.
     if (aRestoreImmediately || aWindow.gBrowser.selectedBrowser == browser) {
       this.restoreTab(tab);
     }
     else {
-      // Put the tab into the right bucket
-      if (tabData.pinned)
-        this._tabsToRestore.priority.push(tab);
-      else if (tabData.hidden)
-        this._tabsToRestore.hidden.push(tab);
-      else
-        this._tabsToRestore.visible.push(tab);
+      TabRestoreQueue.add(tab);
       this.restoreNextTab();
     }
   },
 
   /**
    * Restores the specified tab. If the tab can't be restored (eg, no history or
    * calling gotoIndex fails), then state changes will be rolled back.
    * This method will check if gTabsProgressListener is attached to the tab's
@@ -3189,18 +3165,18 @@ let SessionStoreInternal = {
     // There are cases within where we haven't actually started a load. In that
     // that case we'll reset state changes we made and return false to the caller
     // can handle appropriately.
     let didStartLoad = false;
 
     // Make sure that the tabs progress listener is attached to this window
     this._ensureTabsProgressListener(window);
 
-    // Make sure that this tab is removed from _tabsToRestore
-    this._removeTabFromTabsToRestore(aTab);
+    // Make sure that this tab is removed from the priority queue.
+    TabRestoreQueue.remove(aTab);
 
     // Increase our internal count.
     this._tabsRestoringCount++;
 
     // Set this tab's state to restoring
     browser.__SS_restoreState = TAB_STATE_RESTORING;
     browser.removeAttribute("pending");
     aTab.removeAttribute("pending");
@@ -3276,34 +3252,22 @@ let SessionStoreInternal = {
    */
   restoreNextTab: function ssi_restoreNextTab() {
     // If we call in here while quitting, we don't actually want to do anything
     if (this._loadState == STATE_QUITTING)
       return;
 
     // If it's not possible to restore anything, then just bail out.
     if ((this._restoreOnDemand &&
-        (this._restorePinnedTabsOnDemand || !this._tabsToRestore.priority.length)) ||
+        (this._restorePinnedTabsOnDemand || !TabRestoreQueue.hasPriorityTabs)) ||
         this._tabsRestoringCount >= MAX_CONCURRENT_TAB_RESTORES)
       return;
 
-    // Look in priority, then visible, then hidden
-    let nextTabArray;
-    if (this._tabsToRestore.priority.length) {
-      nextTabArray = this._tabsToRestore.priority
-    }
-    else if (this._tabsToRestore.visible.length) {
-      nextTabArray = this._tabsToRestore.visible;
-    }
-    else if (this._restoreHiddenTabs && this._tabsToRestore.hidden.length) {
-      nextTabArray = this._tabsToRestore.hidden;
-    }
-
-    if (nextTabArray) {
-      let tab = nextTabArray.shift();
+    let tab = TabRestoreQueue.shift();
+    if (tab) {
       let didStartLoad = this.restoreTab(tab);
       // If we don't start a load in the restored tab (eg, no entries) then we
       // want to attempt to restore the next tab.
       if (!didStartLoad)
         this.restoreNextTab();
     }
   },
 
@@ -4396,17 +4360,17 @@ let SessionStoreInternal = {
       delete this._closedWindows[i]._shouldRestore;
     }
   },
 
   /**
    * Reset state to prepare for a new session state to be restored.
    */
   _resetRestoringState: function ssi_initRestoringState() {
-    this._tabsToRestore = { priority: [], visible: [], hidden: [] };
+    TabRestoreQueue.reset();
     this._tabsRestoringCount = 0;
   },
 
   /**
    * Reset the restoring state for a particular tab. This will be called when
    * removing a tab or when a tab needs to be reset (it's being overwritten).
    *
    * @param aTab
@@ -4439,39 +4403,21 @@ let SessionStoreInternal = {
     else if (previousState == TAB_STATE_NEEDS_RESTORE) {
       // Make sure the session history listener is removed. This is normally
       // done in restoreTab, but this tab is being removed before that gets called.
       this._removeSHistoryListener(aTab);
 
       // Make sure that the tab is removed from the list of tabs to restore.
       // Again, this is normally done in restoreTab, but that isn't being called
       // for this tab.
-      this._removeTabFromTabsToRestore(aTab);
+      TabRestoreQueue.remove(aTab);
     }
   },
 
   /**
-   * Remove the tab from this._tabsToRestore[priority/visible/hidden]
-   *
-   * @param aTab
-   */
-  _removeTabFromTabsToRestore: function ssi_removeTabFromTabsToRestore(aTab) {
-    // We'll always check priority first since we don't have an indicator if
-    // a tab will be there or not.
-    let arr = this._tabsToRestore.priority;
-    let index = arr.indexOf(aTab);
-    if (index == -1) {
-      arr = this._tabsToRestore[aTab.hidden ? "hidden" : "visible"];
-      index = arr.indexOf(aTab);
-    }
-    if (index > -1)
-      arr.splice(index, 1);
-  },
-
-  /**
    * Add the tabs progress listener to the window if it isn't already
    *
    * @param aWindow
    *        The window to add our progress listener to
    */
   _ensureTabsProgressListener: function ssi_ensureTabsProgressListener(aWindow) {
     let tabbrowser = aWindow.gBrowser;
     if (tabbrowser.mTabsProgressListeners.indexOf(gRestoreTabsProgressListener) == -1)
@@ -4502,16 +4448,122 @@ let SessionStoreInternal = {
     if (browser.__SS_shistoryListener) {
       browser.webNavigation.sessionHistory.
                             removeSHistoryListener(browser.__SS_shistoryListener);
       delete browser.__SS_shistoryListener;
     }
   }
 };
 
+/**
+ * Priority queue that keeps track of a list of tabs to restore and returns
+ * the tab we should restore next, based on priority rules. We decide between
+ * pinned, visible and hidden tabs in that and FIFO order. Hidden tabs are only
+ * restored with restore_hidden_tabs=true.
+ */
+let TabRestoreQueue = {
+  // The separate buckets used to store tabs.
+  tabs: {priority: [], visible: [], hidden: []},
+
+  // Returns whether we have any high priority tabs in the queue.
+  get hasPriorityTabs() !!this.tabs.priority.length,
+
+  // Lazy getter that returns whether we should restore hidden tabs.
+  get restoreHiddenTabs() {
+    let updateValue = () => {
+      let value = Services.prefs.getBoolPref(PREF);
+      let definition = {value: value, configurable: true};
+      Object.defineProperty(this, "restoreHiddenTabs", definition);
+    }
+
+    const PREF = "browser.sessionstore.restore_hidden_tabs";
+    Services.prefs.addObserver(PREF, updateValue, false);
+    updateValue();
+  },
+
+  // Resets the queue and removes all tabs.
+  reset: function () {
+    this.tabs = {priority: [], visible: [], hidden: []};
+  },
+
+  // Adds a tab to the queue and determines its priority bucket.
+  add: function (tab) {
+    let {priority, hidden, visible} = this.tabs;
+
+    if (tab.pinned) {
+      priority.push(tab);
+    } else if (tab.hidden) {
+      hidden.push(tab);
+    } else {
+      visible.push(tab);
+    }
+  },
+
+  // Removes a given tab from the queue, if it's in there.
+  remove: function (tab) {
+    let {priority, hidden, visible} = this.tabs;
+
+    // We'll always check priority first since we don't
+    // have an indicator if a tab will be there or not.
+    let set = priority;
+    let index = set.indexOf(tab);
+
+    if (index == -1) {
+      set = tab.hidden ? hidden : visible;
+      index = set.indexOf(tab);
+    }
+
+    if (index > -1) {
+      set.splice(index, 1);
+    }
+  },
+
+  // Returns and removes the tab with the highest priority.
+  shift: function () {
+    let set;
+    let {priority, hidden, visible} = this.tabs;
+
+    if (priority.length) {
+      set = priority;
+    } else if (visible.length) {
+      set = visible;
+    } else if (this.restoreHiddenTabs && hidden.length) {
+      set = hidden;
+    }
+
+    return set && set.shift();
+  },
+
+  // Moves a given tab from the 'hidden' to the 'visible' bucket.
+  hiddenToVisible: function (tab) {
+    let {hidden, visible} = this.tabs;
+    let index = hidden.indexOf(tab);
+
+    if (index > -1) {
+      hidden.splice(index, 1);
+      visible.push(tab);
+    } else {
+      throw new Error("restore queue: hidden tab not found");
+    }
+  },
+
+  // Moves a given tab from the 'visible' to the 'hidden' bucket.
+  visibleToHidden: function (tab) {
+    let {visible, hidden} = this.tabs;
+    let index = visible.indexOf(tab);
+
+    if (index > -1) {
+      visible.splice(index, 1);
+      hidden.push(tab);
+    } else {
+      throw new Error("restore queue: visible tab not found");
+    }
+  }
+};
+
 // This is used to help meter the number of restoring tabs. This is the control
 // point for telling the next tab to restore. It gets attached to each gBrowser
 // via gBrowser.addTabsProgressListener
 let gRestoreTabsProgressListener = {
   onStateChange: function(aBrowser, aWebProgress, aRequest, aStateFlags, aStatus) {
     // Ignore state changes on browsers that we've already restored and state
     // changes that aren't applicable.
     if (aBrowser.__SS_restoreState &&
--- 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,83 @@ 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");
     let line = nodes[0].sourceLine;
     ok(line, "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");
       let line = nodes[1].sourceLine;
       ok(line, "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 +123,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']