Bug 583041 - Style Editor integration; part 3; r=rcampbell,dao,l10n
authorCedric Vivier <cedricv@neonux.com>
Wed, 26 Oct 2011 17:02:08 +0800
changeset 80302 2513f713a23e53d90f4ac674b8b3c2feb304963f
parent 80301 69cd4124a49a44d338dd10938d57448c88cd3405
child 80303 4119bee2221dea719ddaf61fd09103902c41a5e9
push id330
push userrcampbell@mozilla.com
push dateThu, 17 Nov 2011 22:33:35 +0000
treeherderfx-team@1cac8411fdc6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrcampbell, dao, l10n
bugs583041
milestone11.0a1
Bug 583041 - Style Editor integration; part 3; r=rcampbell,dao,l10n
browser/app/profile/firefox.js
browser/base/content/browser-appmenu.inc
browser/base/content/browser-menubar.inc
browser/base/content/browser-sets.inc
browser/base/content/browser.js
browser/devtools/Makefile.in
browser/devtools/jar.mn
browser/devtools/styleeditor/Makefile.in
browser/devtools/styleeditor/SplitView.jsm
browser/devtools/styleeditor/StyleEditor.jsm
browser/devtools/styleeditor/StyleEditorChrome.jsm
browser/devtools/styleeditor/StyleEditorUtil.jsm
browser/devtools/styleeditor/splitview.css
browser/devtools/styleeditor/styleeditor.css
browser/devtools/styleeditor/styleeditor.xul
browser/locales/en-US/chrome/browser/browser.dtd
browser/locales/en-US/chrome/browser/devtools/styleeditor.dtd
browser/locales/en-US/chrome/browser/devtools/styleeditor.properties
browser/locales/jar.mn
browser/themes/gnomestripe/devtools/eye-toggle.png
browser/themes/gnomestripe/devtools/splitview.css
browser/themes/gnomestripe/devtools/styleeditor.css
browser/themes/gnomestripe/jar.mn
browser/themes/pinstripe/devtools/eye-toggle.png
browser/themes/pinstripe/devtools/splitview.css
browser/themes/pinstripe/devtools/styleeditor.css
browser/themes/pinstripe/jar.mn
browser/themes/winstripe/devtools/eye-toggle.png
browser/themes/winstripe/devtools/splitview.css
browser/themes/winstripe/devtools/styleeditor.css
browser/themes/winstripe/jar.mn
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1013,16 +1013,19 @@ pref("devtools.inspector.htmlHeight", 11
 pref("devtools.styleinspector.enabled", true);
 
 // Enable the rules view
 pref("devtools.ruleview.enabled", true);
 
 // Enable the Scratchpad tool.
 pref("devtools.scratchpad.enabled", true);
 
+// Enable the Style Editor.
+pref("devtools.styleeditor.enabled", true);
+
 // Enable tools for Chrome development.
 pref("devtools.chrome.enabled", false);
 
 // Disable the GCLI enhanced command line.
 pref("devtools.gcli.enable", false);
 
 // The last Web Console height. This is initially 0 which means that the Web
 // Console will use the default height next time it shows.
--- a/browser/base/content/browser-appmenu.inc
+++ b/browser/base/content/browser-appmenu.inc
@@ -187,16 +187,21 @@
                     type="checkbox"
                     command="Tools:Inspect"
                     key="key_inspect"/>
           <menuitem id="appmenu_scratchpad"
                     hidden="true"
                     label="&scratchpad.label;"
                     key="key_scratchpad"
                     command="Tools:Scratchpad"/>
+          <menuitem id="appmenu_styleeditor"
+                    hidden="true"
+                    label="&styleeditor.label;"
+                    key="key_styleeditor"
+                    command="Tools:StyleEditor"/>
           <menuitem id="appmenu_pageSource"
                     label="&viewPageSourceCmd.label;"
                     command="View:PageSource"
                     key="key_viewSource"/>
           <menuitem id="appmenu_errorConsole"
                     hidden="true"
                     label="&errorConsoleCmd.label;"
                     key="key_errorConsole"
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -545,16 +545,22 @@
                             key="key_inspect"
                             command="Tools:Inspect"/>
                   <menuitem id="menu_scratchpad"
                             hidden="true"
                             label="&scratchpad.label;"
                             accesskey="&scratchpad.accesskey;"
                             key="key_scratchpad"
                             command="Tools:Scratchpad"/>
+                  <menuitem id="menu_styleeditor"
+                            hidden="true"
+                            label="&styleeditor.label;"
+                            accesskey="&styleeditor.accesskey;"
+                            key="key_styleeditor"
+                            command="Tools:StyleEditor"/>
                   <menuitem id="menu_pageSource"
                             accesskey="&pageSourceCmd.accesskey;"
                             label="&pageSourceCmd.label;"
                             key="key_viewSource"
                             command="View:PageSource"/>
                   <menuitem id="javascriptConsole"
                             hidden="true"
                             label="&errorConsoleCmd.label;"
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -121,16 +121,17 @@
     <command id="cmd_fullZoomReset"   oncommand="FullZoom.reset()"/>
     <command id="cmd_fullZoomToggle"  oncommand="ZoomManager.toggleZoom();"/>
     <command id="Browser:OpenLocation" oncommand="openLocation();"/>
 
     <command id="Tools:Search" oncommand="BrowserSearch.webSearch();"/>
     <command id="Tools:Downloads" oncommand="BrowserDownloadsUI();"/>
     <command id="Tools:Inspect" oncommand="InspectorUI.toggleInspectorUI();" disabled="true"/>
     <command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();" disabled="true"/>
+    <command id="Tools:StyleEditor" oncommand="StyleEditor.openChrome();" disabled="true"/>
     <command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
     <command id="Tools:Sanitize"
      oncommand="Cc['@mozilla.org/browser/browserglue;1'].getService(Ci.nsIBrowserGlue).sanitize(window);"/>
     <command id="Tools:PrivateBrowsing" oncommand="gPrivateBrowsingUI.toggleMode();"/>
     <command id="History:UndoCloseTab" oncommand="undoCloseTab();"/>
     <command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
     <command id="Browser:ToggleAddonBar" oncommand="toggleAddonBar();"/>
   </commandset>
@@ -239,16 +240,18 @@
     <key id="key_openDownloads" key="&downloads.commandkey;" command="Tools:Downloads" modifiers="accel"/>
 #endif
     <key id="key_openAddons" key="&addons.commandkey;" command="Tools:Addons" modifiers="accel,shift"/>
     <key id="key_errorConsole" key="&errorConsoleCmd.commandkey;" oncommand="toJavaScriptConsole();" modifiers="accel,shift" disabled="true"/>
     <key id="key_webConsole" key="&webConsoleCmd.commandkey;" oncommand="HUDConsoleUI.toggleHUD();" modifiers="accel,shift"/>
     <key id="key_inspect" key="&inspectMenu.commandkey;" command="Tools:Inspect" modifiers="accel,shift"/>
     <key id="key_scratchpad" keycode="&scratchpad.keycode;" modifiers="shift"
          keytext="&scratchpad.keytext;" command="Tools:Scratchpad"/>
+    <key id="key_styleeditor" keycode="&styleeditor.keycode;" modifiers="shift"
+         keytext="&styleeditor.keytext;" command="Tools:StyleEditor"/>
     <key id="openFileKb" key="&openFileCmd.commandkey;" command="Browser:OpenFile"  modifiers="accel"/>
     <key id="key_savePage" key="&savePageCmd.commandkey;" command="Browser:SavePage" modifiers="accel"/>
     <key id="printKb" key="&printCmd.commandkey;" command="cmd_print"  modifiers="accel"/>
     <key id="key_close" key="&closeCmd.key;" command="cmd_close" modifiers="accel"/>
     <key id="key_closeWindow" key="&closeCmd.key;" command="cmd_closeWindow" modifiers="accel,shift"/>
     <key id="key_undo"
          key="&undoCmd.key;"
          modifiers="accel"/>
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1716,16 +1716,26 @@ function delayedStartup(isLoadingBlank, 
   if (scratchpadEnabled) {
     document.getElementById("menu_scratchpad").hidden = false;
     document.getElementById("Tools:Scratchpad").removeAttribute("disabled");
 #ifdef MENUBAR_CAN_AUTOHIDE
     document.getElementById("appmenu_scratchpad").hidden = false;
 #endif
   }
 
+  // Enable Style Editor?
+  let styleEditorEnabled = gPrefService.getBoolPref(StyleEditor.prefEnabledName);
+  if (styleEditorEnabled) {
+    document.getElementById("menu_styleeditor").hidden = false;
+    document.getElementById("Tools:StyleEditor").removeAttribute("disabled");
+#ifdef MENUBAR_CAN_AUTOHIDE
+    document.getElementById("appmenu_styleeditor").hidden = false;
+#endif
+  }
+
 #ifdef MENUBAR_CAN_AUTOHIDE
   // If the user (or the locale) hasn't enabled the top-level "Character
   // Encoding" menu via the "browser.menu.showCharacterEncoding" preference,
   // hide it.
   if ("true" != gPrefService.getComplexValue("browser.menu.showCharacterEncoding",
                                              Ci.nsIPrefLocalizedString).data)
     document.getElementById("appmenu_charsetMenu").hidden = true;
 #endif
@@ -8965,16 +8975,44 @@ var Scratchpad = {
 };
 
 XPCOMUtils.defineLazyGetter(Scratchpad, "ScratchpadManager", function() {
   let tmp = {};
   Cu.import("resource:///modules/devtools/scratchpad-manager.jsm", tmp);
   return tmp.ScratchpadManager;
 });
 
+var StyleEditor = {
+  prefEnabledName: "devtools.styleeditor.enabled",
+  openChrome: function SE_openChrome()
+  {
+    const CHROME_URL = "chrome://browser/content/styleeditor.xul";
+    const CHROME_WINDOW_TYPE = "Tools:StyleEditor";
+    const CHROME_WINDOW_FLAGS = "chrome,centerscreen,resizable,dialog=no";
+
+    // focus currently open Style Editor window for this document, if any
+    let contentWindow = gBrowser.selectedBrowser.contentWindow;
+    let contentWindowID = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).
+      getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
+    let enumerator = Services.wm.getEnumerator(CHROME_WINDOW_TYPE);
+    while (enumerator.hasMoreElements()) {
+      var win = enumerator.getNext();
+      if (win.styleEditorChrome.contentWindowID == contentWindowID) {
+        win.focus();
+        return win;
+      }
+    }
+
+    let chromeWindow = Services.ww.openWindow(null, CHROME_URL, "_blank",
+                                              CHROME_WINDOW_FLAGS,
+                                              contentWindow);
+    chromeWindow.focus();
+    return chromeWindow;
+  }
+};
 
 XPCOMUtils.defineLazyGetter(window, "gShowPageResizers", function () {
 #ifdef XP_WIN
   // Only show resizers on Windows 2000 and XP
   let sysInfo = Components.classes["@mozilla.org/system-info;1"]
                           .getService(Components.interfaces.nsIPropertyBag2);
   return parseFloat(sysInfo.getProperty("version")) < 6;
 #else
--- a/browser/devtools/Makefile.in
+++ b/browser/devtools/Makefile.in
@@ -45,14 +45,15 @@ VPATH   = @srcdir@
 include $(DEPTH)/config/autoconf.mk
 
 include $(topsrcdir)/config/config.mk
 
 DIRS = \
   highlighter \
   webconsole \
   sourceeditor \
+  styleeditor \
   styleinspector \
   scratchpad \
   shared \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -1,11 +1,15 @@
 browser.jar:
 *   content/browser/inspector.html                (highlighter/inspector.html)
     content/browser/NetworkPanel.xhtml            (webconsole/NetworkPanel.xhtml)
 *   content/browser/scratchpad.xul                (scratchpad/scratchpad.xul)
 *   content/browser/scratchpad.js                 (scratchpad/scratchpad.js)
+*   content/browser/styleeditor.xul               (styleeditor/styleeditor.xul)
+    content/browser/splitview.css                 (styleeditor/splitview.css)
+    content/browser/styleeditor.css               (styleeditor/styleeditor.css)
     content/browser/devtools/csshtmltree.xul      (styleinspector/csshtmltree.xul)
     content/browser/devtools/cssruleview.xul      (styleinspector/cssruleview.xul)
     content/browser/devtools/styleinspector.css   (styleinspector/styleinspector.css)
     content/browser/orion.js                      (sourceeditor/orion/orion.js)
     content/browser/orion.css                     (sourceeditor/orion/orion.css)
     content/browser/orion-mozilla.css             (sourceeditor/orion/mozilla.css)
+
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/Makefile.in
@@ -0,0 +1,54 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is Style Editor code.
+#
+# The Initial Developer of the Original Code is Mozilla Foundation.
+#
+# Portions created by the Initial Developer are Copyright (C) 2010
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#    Cedric Vivier <cedricv@neonux.com>  (Original author)
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+DEPTH		= ../../..
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+ifdef ENABLE_TESTS
+  ifneq (mobile,$(MOZ_BUILD_APP))
+  	  DIRS += test
+  endif
+endif
+
+include $(topsrcdir)/config/rules.mk
+
+libs::
+	$(NSINSTALL) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/SplitView.jsm
@@ -0,0 +1,488 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Style Editor code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Cedric Vivier <cedricv@neonux.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["SplitView"];
+
+/* this must be kept in sync with CSS (ie. splitview.css) */
+const LANDSCAPE_MEDIA_QUERY = "(min-aspect-ratio: 5/3)";
+
+const BINDING_USERDATA = "splitview-binding";
+
+
+/**
+ * SplitView constructor
+ *
+ * Initialize the split view UI on an existing DOM element.
+ *
+ * A split view contains items, each of those having one summary and one details
+ * elements.
+ * It is adaptive as it behaves similarly to a richlistbox when there the aspect
+ * ratio is narrow or as a pair listbox-box otherwise.
+ *
+ * @param DOMElement aRoot
+ * @see appendItem
+ */
+function SplitView(aRoot)
+{
+  this._root = aRoot;
+  this._controller = aRoot.querySelector(".splitview-controller");
+  this._nav = aRoot.querySelector(".splitview-nav");
+  this._side = aRoot.querySelector(".splitview-side-details");
+  this._activeSummary = null
+
+  this._mql = aRoot.ownerDocument.defaultView.matchMedia(LANDSCAPE_MEDIA_QUERY);
+
+  this._filter = aRoot.querySelector(".splitview-filter");
+  if (this._filter) {
+    this._setupFilterBox();
+  }
+
+  // items list focus and search-on-type handling
+  this._nav.addEventListener("keydown", function onKeyCatchAll(aEvent) {
+    function getFocusedItemWithin(nav) {
+      let node = nav.ownerDocument.activeElement;
+      while (node && node.parentNode != nav) {
+        node = node.parentNode;
+      }
+      return node;
+    }
+
+    // do not steal focus from inside iframes or textboxes
+    if (aEvent.target.ownerDocument != this._nav.ownerDocument ||
+        aEvent.target.tagName == "input" ||
+        aEvent.target.tagName == "textbox" ||
+        aEvent.target.tagName == "textarea" ||
+        aEvent.target.classList.contains("textbox")) {
+      return false;
+    }
+
+    // handle keyboard navigation within the items list
+    let newFocusOrdinal;
+    if (aEvent.keyCode == aEvent.DOM_VK_PAGE_UP ||
+        aEvent.keyCode == aEvent.DOM_VK_HOME) {
+      newFocusOrdinal = 0;
+    } else if (aEvent.keyCode == aEvent.DOM_VK_PAGE_DOWN ||
+               aEvent.keyCode == aEvent.DOM_VK_END) {
+      newFocusOrdinal = this._nav.childNodes.length - 1;
+    } else if (aEvent.keyCode == aEvent.DOM_VK_UP) {
+      newFocusOrdinal = getFocusedItemWithin(this._nav).getAttribute("data-ordinal");
+      newFocusOrdinal--;
+    } else if (aEvent.keyCode == aEvent.DOM_VK_DOWN) {
+      newFocusOrdinal = getFocusedItemWithin(this._nav).getAttribute("data-ordinal");
+      newFocusOrdinal++;
+    }
+    if (newFocusOrdinal !== undefined) {
+      aEvent.stopPropagation();
+      let el = this.getSummaryElementByOrdinal(newFocusOrdinal);
+      if (el) {
+        el.focus();
+      }
+      return false;
+    }
+
+    // search-on-type when any non-whitespace character is pressed while list
+    // has the focus
+    if (this._filter &&
+        !/\s/.test(String.fromCharCode(aEvent.which))) {
+      this._filter.focus();
+    }
+  }.bind(this), false);
+}
+
+SplitView.prototype = {
+  /**
+    * Retrieve whether the UI currently has a landscape orientation.
+    *
+    * @return boolean
+    */
+  get isLandscape() this._mql.matches,
+
+  /**
+    * Retrieve the root element.
+    *
+    * @return DOMElement
+    */
+  get rootElement() this._root,
+
+  /**
+    * Retrieve the active item's summary element or null if there is none.
+    *
+    * @return DOMElement
+    */
+  get activeSummary() this._activeSummary,
+
+  /**
+    * Set the active item's summary element.
+    *
+    * @param DOMElement aSummary
+    */
+  set activeSummary(aSummary)
+  {
+    if (aSummary == this._activeSummary) {
+      return;
+    }
+
+    if (this._activeSummary) {
+      let binding = this._activeSummary.getUserData(BINDING_USERDATA);
+      this._activeSummary.classList.remove("splitview-active");
+      binding._details.classList.remove("splitview-active");
+
+      if (binding.onHide) {
+        binding.onHide(this._activeSummary, binding._details, binding.data);
+      }
+    }
+
+    if (!aSummary) {
+      return;
+    }
+
+    let binding = aSummary.getUserData(BINDING_USERDATA);
+    aSummary.classList.add("splitview-active");
+    binding._details.classList.add("splitview-active");
+
+    this._activeSummary = aSummary;
+
+    if (binding.onShow) {
+      binding.onShow(aSummary, binding._details, binding.data);
+    }
+    aSummary.scrollIntoView();
+  },
+
+  /**
+    * Retrieve the active item's details element or null if there is none.
+    * @return DOMElement
+    */
+  get activeDetails()
+  {
+    let summary = this.activeSummary;
+    return summary ? summary.getUserData(BINDING_USERDATA)._details : null;
+  },
+
+  /**
+   * Retrieve the summary element for a given ordinal.
+   *
+   * @param number aOrdinal
+   * @return DOMElement
+   *         Summary element with given ordinal or null if not found.
+   * @see appendItem
+   */
+  getSummaryElementByOrdinal: function SEC_getSummaryElementByOrdinal(aOrdinal)
+  {
+    return this._nav.querySelector("* > li[data-ordinal='" + aOrdinal + "']");
+  },
+
+  /**
+   * Append an item to the split view.
+   *
+   * @param DOMElement aSummary
+   *        The summary element for the item.
+   * @param DOMElement aDetails
+   *        The details element for the item.
+   * @param object aOptions
+   *     Optional object that defines custom behavior and data for the item.
+   *     All properties are optional :
+   *     - function(DOMElement summary, DOMElement details, object data) onCreate
+   *         Called when the item has been added.
+   *     - function(summary, details, data) onShow
+   *         Called when the item is shown/active.
+   *     - function(summary, details, data) onHide
+   *         Called when the item is hidden/inactive.
+   *     - function(summary, details, data) onDestroy
+   *         Called when the item has been removed.
+   *     - function(summary, details, data, query) onFilterBy
+   *         Called when the user performs a filtering search.
+   *         If the function returns false, the item does not match query
+   *         string and will be hidden.
+   *     - object data
+   *         Object to pass to the callbacks above.
+   *     - boolean disableAnimations
+   *         If true there is no animation or scrolling when this item is
+   *         appended. Set this when batch appending (eg. initial population).
+   *     - number ordinal
+   *         Items with a lower ordinal are displayed before those with a
+   *         higher ordinal.
+   */
+  appendItem: function ASV_appendItem(aSummary, aDetails, aOptions)
+  {
+    let binding = aOptions || {};
+
+    binding._summary = aSummary;
+    binding._details = aDetails;
+    aSummary.setUserData(BINDING_USERDATA, binding, null);
+
+    if (!binding.disableAnimations) {
+      aSummary.classList.add("splitview-slide");
+      aSummary.classList.add("splitview-flash");
+    }
+    this._nav.appendChild(aSummary);
+
+    aSummary.addEventListener("click", function onSummaryClick(aEvent) {
+      aEvent.stopPropagation();
+      this.activeSummary = aSummary;
+    }.bind(this), false);
+
+    this._side.appendChild(aDetails);
+
+    if (binding.onCreate) {
+      // queue onCreate handler
+      this._root.ownerDocument.defaultView.setTimeout(function () {
+        binding.onCreate(aSummary, aDetails, binding.data);
+      }, 0);
+    }
+
+    if (!binding.disableAnimations) {
+      scheduleAnimation(aSummary, "splitview-slide", "splitview-flash");
+      aSummary.scrollIntoView();
+    }
+  },
+
+  /**
+   * Append an item to the split view according to two template elements
+   * (one for the item's summary and the other for the item's details).
+   *
+   * @param string aName
+   *        Name of the template elements to instantiate.
+   *        Requires two (hidden) DOM elements with id "splitview-tpl-summary-"
+   *        and "splitview-tpl-details-" suffixed with aName.
+   * @param object aOptions
+   *        Optional object that defines custom behavior and data for the item.
+   *        See appendItem for full description.
+   * @return object{summary:,details:}
+   *         Object with the new DOM elements created for summary and details.
+   * @see appendItem
+   */
+  appendTemplatedItem: function ASV_appendTemplatedItem(aName, aOptions)
+  {
+    aOptions = aOptions || {};
+    let summary = this._root.querySelector("#splitview-tpl-summary-" + aName);
+    let details = this._root.querySelector("#splitview-tpl-details-" + aName);
+
+    summary = summary.cloneNode(true);
+    summary.id = "";
+    if (aOptions.ordinal !== undefined) { // can be zero
+      summary.style.MozBoxOrdinalGroup = aOptions.ordinal;
+      summary.setAttribute("data-ordinal", aOptions.ordinal);
+    }
+    details = details.cloneNode(true);
+    details.id = "";
+
+    this.appendItem(summary, details, aOptions);
+    return {summary: summary, details: details};
+  },
+
+  /**
+    * Remove an item from the split view.
+    *
+    * @param DOMElement aSummary
+    *        Summary element of the item to remove.
+    */
+  removeItem: function ASV_removeItem(aSummary)
+  {
+    if (aSummary == this._activeSummary) {
+      this.activeSummary = null;
+    }
+
+    let binding = aSummary.getUserData(BINDING_USERDATA);
+    aSummary.parentNode.removeChild(aSummary);
+    binding._details.parentNode.removeChild(binding._details);
+
+    if (binding.onDestroy) {
+      binding.onDestroy(aSummary, binding._details, binding.data);
+    }
+  },
+
+  /**
+   * Remove all items from the split view.
+   */
+  removeAll: function ASV_removeAll()
+  {
+    while (this._nav.hasChildNodes()) {
+      this.removeItem(this._nav.firstChild);
+    }
+  },
+
+  /**
+    * Filter items by given string.
+    * Matching is performed on every item by calling onFilterBy when defined
+    * and then by searching aQuery in the summary element's text item.
+    * Non-matching item is hidden.
+    *
+    * If no item matches, 'splitview-all-filtered' class is set on the filter
+    * input element and the splitview-nav element.
+    *
+    * @param string aQuery
+    *        The query string. Use null to reset (no filter).
+    * @return number
+    *         The number of filtered (non-matching) item.
+    */
+  filterItemsBy: function ASV_filterItemsBy(aQuery)
+  {
+    if (!this._nav.hasChildNodes()) {
+      return 0;
+    }
+    if (aQuery) {
+      aQuery = aQuery.trim();
+    }
+    if (!aQuery) {
+      for (let i = 0; i < this._nav.childNodes.length; ++i) {
+        this._nav.childNodes[i].classList.remove("splitview-filtered");
+      }
+      this._filter.classList.remove("splitview-all-filtered");
+      this._nav.classList.remove("splitview-all-filtered");
+      return 0;
+    }
+
+    let count = 0;
+    let filteredCount = 0;
+    for (let i = 0; i < this._nav.childNodes.length; ++i) {
+      let summary = this._nav.childNodes[i];
+
+      let matches = false;
+      let binding = summary.getUserData(BINDING_USERDATA);
+      if (binding.onFilterBy) {
+        matches = binding.onFilterBy(summary, binding._details, binding.data, aQuery);
+      }
+      if (!matches) { // try text content
+        let content = summary.textContent.toUpperCase();
+        matches = (content.indexOf(aQuery.toUpperCase()) > -1);
+      }
+
+      count++;
+      if (!matches) {
+        summary.classList.add("splitview-filtered");
+        filteredCount++;
+      } else {
+        summary.classList.remove("splitview-filtered");
+      }
+    }
+
+    if (count > 0 && filteredCount == count) {
+      this._filter.classList.add("splitview-all-filtered");
+      this._nav.classList.add("splitview-all-filtered");
+    } else {
+      this._filter.classList.remove("splitview-all-filtered");
+      this._nav.classList.remove("splitview-all-filtered");
+    }
+    return filteredCount;
+  },
+
+  /**
+   * Set the item's CSS class name.
+   * This sets the class on both the summary and details elements, retaining
+   * any SplitView-specific classes.
+   *
+   * @param DOMElement aSummary
+   *        Summary element of the item to set.
+   * @param string aClassName
+   *        One or more space-separated CSS classes.
+   */
+  setItemClassName: function ASV_setItemClassName(aSummary, aClassName)
+  {
+    let binding = aSummary.getUserData(BINDING_USERDATA);
+    let viewSpecific;
+
+    viewSpecific = aSummary.className.match(/(splitview\-[\w-]+)/g);
+    viewSpecific = viewSpecific ? viewSpecific.join(" ") : "";
+    aSummary.className = viewSpecific + " " + aClassName;
+
+    viewSpecific = binding._details.className.match(/(splitview\-[\w-]+)/g);
+    viewSpecific = viewSpecific ? viewSpecific.join(" ") : "";
+    binding._details.className = viewSpecific + " " + aClassName;
+  },
+
+  /**
+   * Set up filter search box.
+   */
+  _setupFilterBox: function ASV__setupFilterBox()
+  {
+    let clearFilter = function clearFilter(aEvent) {
+      this._filter.value = "";
+      this.filterItemsBy("");
+      return false;
+    }.bind(this);
+
+    this._filter.addEventListener("command", function onFilterInput(aEvent) {
+      this.filterItemsBy(this._filter.value);
+    }.bind(this), false);
+
+    this._filter.addEventListener("keyup", function onFilterKeyUp(aEvent) {
+      if (aEvent.keyCode == aEvent.DOM_VK_ESCAPE) {
+        clearFilter();
+      }
+      if (aEvent.keyCode == aEvent.DOM_VK_ENTER ||
+          aEvent.keyCode == aEvent.DOM_VK_RETURN) {
+        // autofocus matching item if there is only one
+        let matches = this._nav.querySelectorAll("* > li:not(.splitview-filtered)");
+        if (matches.length == 1) {
+          this.activeSummary = matches[0];
+        }
+      }
+    }.bind(this), false);
+
+    let clearButtons = this._root.querySelectorAll(".splitview-filter-clearButton");
+    for (let i = 0; i < clearButtons.length; ++i) {
+      clearButtons[i].addEventListener("click", clearFilter, false);
+    }
+  }
+};
+
+//
+// private helpers
+
+/**
+ * Schedule one or multiple CSS animation(s) on an element.
+ *
+ * @param DOMElement aElement
+ * @param string ...
+ *        One or multiple animation class name(s).
+ */
+function scheduleAnimation(aElement)
+{
+  let classes = Array.prototype.slice.call(arguments, 1);
+  for each (let klass in classes) {
+    aElement.classList.add(klass);
+  }
+
+  let window = aElement.ownerDocument.defaultView;
+  window.mozRequestAnimationFrame(function triggerAnimation() {
+    for each (let klass in classes) {
+      aElement.classList.remove(klass);
+    }
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/StyleEditor.jsm
@@ -0,0 +1,1093 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Style Editor code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Cedric Vivier <cedricv@neonux.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = ["StyleEditor", "StyleEditorFlags"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+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-";
+
+
+/**
+ * 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
+ */
+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
+  };
+
+  this._styleSheet = aStyleSheet;
+  this._styleSheetIndex = -1; // unknown for now, will be set after load
+
+  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 is to proxy the focus event to underlying SourceEditor
+  this._onInputElementFocusBinding = this._onInputElementFocus.bind(this);
+  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;
+  },
+
+  /**
+   * Retrieve the index (order) of stylesheet in the document.
+   *
+   * @return number
+   */
+  get styleSheetIndex()
+  {
+    let document = this.contentDocument;
+    if (this._styleSheetIndex == -1) {
+      for (let i = 0; i < document.styleSheets.length; ++i) {
+        if (document.styleSheets[i] == this.styleSheet) {
+          this._styleSheetIndex = i;
+          break;
+        }
+      }
+    }
+    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
+        };
+        this._sourceEditor.destroy();
+        this._sourceEditor = null;
+      }
+
+      this.window.removeEventListener("unload",
+                                      this._onWindowUnloadBinding, false);
+      this._inputElement.removeEventListener("focus",
+        this._onInputElementFocusBinding, true);
+      this._triggerAction("Detach");
+    }
+
+    this._inputElement = aElement;
+    if (!aElement) {
+      return;
+    }
+
+    // attach to new input element
+    this.window.addEventListener("unload", this._onWindowUnloadBinding, false);
+    this._focusOnSourceEditorReady = false;
+    aElement.addEventListener("focus", this._onInputElementFocusBinding, true);
+
+    this._sourceEditor = null; // set it only when ready (safe to use)
+
+    let sourceEditor = new SourceEditor();
+    let config = {
+      placeholderText: this._state.text, //! this is initialText (bug 680371)
+      showLineNumbers: true,
+      mode: SourceEditor.MODES.CSS,
+      readOnly: this._state.readOnly,
+      keys: this._getKeyBindings()
+    };
+
+    sourceEditor.init(aElement, config, function onSourceEditorReady() {
+      sourceEditor.setSelection(this._state.selection.start,
+                                this._state.selection.end);
+
+      if (this._focusOnSourceEditorReady) {
+        sourceEditor.focus();
+      }
+
+      sourceEditor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
+                                    function onTextChanged(aEvent) {
+        this.updateStyleSheet();
+      }.bind(this));
+
+      this._sourceEditor = sourceEditor;
+      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)
+  {
+    aFile = this._showFilePicker(aFile, false, aParentWindow);
+    if (!aFile) {
+      return;
+    }
+    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));
+  },
+
+  /**
+    * 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._flags.push(StyleEditorFlags.NEW);
+      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;
+    }
+    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
+   *
+   *   onCommit:               Called when changes have been 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.
+   *        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)
+  {
+    aFile = this._showFilePicker(aFile, true);
+    if (!aFile) {
+      if (aCallback) {
+        aCallback(null);
+      }
+      return;
+    }
+
+    if (this._sourceEditor) {
+      this._state.text = this._sourceEditor.getText();
+    }
+
+    let ostream = FileUtils.openSafeFileOutputStream(aFile);
+    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 = aFile;
+      this._persistExpando();
+
+      if (aCallback) {
+        aCallback(aFile);
+      }
+      this.clearFlag(StyleEditorFlags.UNSAVED);
+      this.clearFlag(StyleEditorFlags.ERROR);
+    }.bind(this));
+  },
+
+  /**
+   * 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();
+    }
+    let source = this._state.text;
+    let oldNode = this.styleSheet.ownerNode;
+    let oldIndex = this.styleSheetIndex;
+
+    let newNode = this.contentDocument.createElement("style");
+    newNode.setAttribute("type", "text/css");
+    newNode.appendChild(this.contentDocument.createTextNode(source));
+    oldNode.parentNode.replaceChild(newNode, oldNode);
+
+    this._styleSheet = this.contentDocument.styleSheets[oldIndex];
+    this._persistExpando();
+
+    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.
+   * @return nsIFile
+   *         The selected file or null if the user did not pick one.
+   */
+  _showFilePicker: function SE__showFilePicker(aFile, aSave, aParentWindow)
+  {
+    if (typeof(aFile) == "string") {
+      try {
+        let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+        file.initWithPath(aFile);
+        return file;
+      } catch (ex) {
+        this._signalError(aSave ? SAVE_ERROR : LOAD_ERROR);
+        return null;
+      }
+    }
+    if (aFile) {
+      return aFile;
+    }
+
+    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";
+
+    fp.init(window, _(key + ".title"), mode);
+    fp.appendFilters(_(key + ".filter"), "*.css");
+    fp.appendFilters(fp.filterAll);
+
+    let rv = fp.show();
+    return (rv == fp.returnCancel) ? null : fp.file;
+  },
+
+  /**
+   * 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":
+      case "chrome":
+      case "resource":
+        this._loadSourceFromFile(this.styleSheet.href);
+        break;
+      default:
+        this._loadSourceFromCache(this.styleSheet.href);
+        break;
+    }
+  },
+
+  /**
+   * 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)
+  {
+    try {
+      let cacheService = Cc["@mozilla.org/network/cache-service;1"]
+                           .getService(Ci.nsICacheService);
+      let session = cacheService.createSession("HTTP", Ci.nsICache.STORE_ANYWHERE, true);
+      session.doomEntriesIfExpired = false;
+      session.asyncOpenCacheEntry(aHref, Ci.nsICache.ACCESS_READ, {
+        onCacheEntryAvailable: this._onCacheEntryAvailable.bind(this)
+      });
+    } catch (ex) {
+      this._signalError(LOAD_ERROR);
+    }
+  },
+
+   /**
+    * The nsICacheListener.onCacheEntryAvailable method implementation used when
+    * the style sheet source is loaded from the browser cache.
+    *
+    * @param nsICacheEntryDescriptor aEntry
+    * @param nsCacheAccessMode aMode
+    * @param integer aStatus
+    */
+  _onCacheEntryAvailable: function SE__onCacheEntryAvailable(aEntry, aMode, aStatus)
+  {
+    if (!Components.isSuccessCode(aStatus)) {
+      return this._signalError(LOAD_ERROR);
+    }
+
+    let stream = aEntry.openInputStream(0);
+    let chunks = [];
+    let streamListener = { // nsIStreamListener inherits nsIRequestObserver
+      onStartRequest: function (aRequest, aContext, aStatusCode) {
+      },
+      onDataAvailable: function (aRequest, aContext, aStream, aOffset, aCount) {
+        chunks.push(NetUtil.readInputStreamToString(aStream, aCount));
+      },
+      onStopRequest: function (aRequest, aContext, aStatusCode) {
+        this._onSourceLoad(chunks.join(""));
+      }.bind(this),
+    };
+
+    let head = aEntry.getMetaDataElement("response-head");
+    if (/^Content-Encoding:\s*gzip/mi.test(head)) {
+      let converter = Cc["@mozilla.org/streamconv;1?from=gzip&to=uncompressed"]
+                        .createInstance(Ci.nsIStreamConverter);
+      converter.asyncConvertData("gzip", "uncompressed", streamListener, null);
+      streamListener = converter; // proxy original listener via converter
+    }
+
+    try {
+      streamListener.onStartRequest(null, null);
+      while (stream.available()) {
+        streamListener.onDataAvailable(null, null, stream, 0, stream.available());
+      }
+      streamListener.onStopRequest(null, null, 0);
+    } catch (ex) {
+      this._signalError(LOAD_ERROR);
+    } finally {
+      try {
+        stream.close();
+      } catch (ex) {
+        // swallow (some stream implementations can auto-close at eos)
+      }
+      aEntry.close();
+    }
+  },
+
+  /**
+   * Called when source has been loaded.
+   *
+   * @param string aSourceText
+   */
+  _onSourceLoad: function SE__onSourceLoad(aSourceText)
+  {
+    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.createElement("style");
+    style.setAttribute("type", "text/css");
+    if (aText) {
+      style.appendChild(document.createTextNode(aText));
+    }
+    parent.appendChild(style);
+
+    this._styleSheet = document.styleSheets[document.styleSheets.length - 1];
+    this._flags.push(aText ? StyleEditorFlags.IMPORTED : StyleEditorFlags.NEW);
+    if (aText) {
+      this._onSourceLoad(aText);
+    }
+  },
+
+  /**
+   * 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);
+    }
+
+    // trigger all listeners that have this action handler
+    for (let i = 0; i < this._actionListeners.length; ++i) {
+      let listener = this._actionListeners[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 event handler to automatically proxy inputElement's focus event to
+    * SourceEditor whenever it is ready.
+    * SourceEditor should probably have a command buffer so that timing issues
+    * related to iframe implementation details are handled by itself rather than
+    * by all its users.
+    */
+  _onInputElementFocus: function SE__onInputElementFocus(aEvent)
+  {
+    if (this._sourceEditor) {
+      this._sourceEditor.focus();
+    } else {
+      this._focusOnSourceEditorReady = true;
+    }
+  },
+
+  /**
+    * 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 () {
+    let bindings = [];
+
+    bindings.push({
+      action: "StyleEditor.save",
+      code: _("saveStyleSheet.commandkey"),
+      accel: true,
+      callback: function save() {
+        this.saveToFile(this._savedFile);
+      }.bind(this)
+    });
+    bindings.push({
+      action: "StyleEditor.saveAs",
+      code: _("saveStyleSheet.commandkey"),
+      accel: true,
+      shift: true,
+      callback: function saveAs() {
+        this.saveToFile();
+      }.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
+ */
+let 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);
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/StyleEditorChrome.jsm
@@ -0,0 +1,488 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Style Editor code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Cedric Vivier <cedricv@neonux.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const 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");
+
+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.
+ */
+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
+
+  this._contentWindow = null;
+  this._isContentAttached = false;
+
+  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();
+
+    // attach to the content window
+    this.contentWindow = aContentWindow;
+    this._contentWindowID = null;
+  }.bind(this);
+
+  if (this._document.readyState == "complete") {
+    initializeUI();
+  } else {
+    this._window.addEventListener("load", initializeUI, false);
+  }
+}
+
+StyleEditorChrome.prototype = {
+  /**
+   * 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._populateChrome();
+      return;
+    } else {
+      let onContentReady = function () {
+        aContentWindow.removeEventListener("load", onContentReady, false);
+        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 whether the content has been attached and StyleEditor instances
+    * exist for all of its stylesheets.
+    *
+    * @return boolean
+    * @see addChromeListener
+    */
+  get isContentAttached() this._isContentAttached,
+
+  /**
+   * 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;
+  },
+
+  /**
+   * Add a listener for StyleEditorChrome events.
+   *
+   * The listener implements IStyleEditorChromeListener := {
+   *   onContentAttach:        Called when a content window has been attached.
+   *                           All editors are instantiated, though they might
+   *                           not be loaded yet.
+   *                           Arguments: (StyleEditorChrome aChrome)
+   *                           @see contentWindow
+   *                           @see StyleEditor.isLoaded
+   *                           @see StyleEditor.addActionListener
+   *
+   *   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);
+    }
+
+    // trigger all listeners that have this named handler
+    for (let i = 0; i < this._listeners.length; ++i) {
+      let listener = this._listeners[i];
+      let handler = listener["on" + aName];
+      if (handler) {
+        handler.apply(listener, aArgs);
+      }
+    }
+  },
+
+  /**
+   * 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 = new StyleEditor(this.contentDocument);
+      this._editors.push(editor);
+      editor.addActionListener(this);
+      editor.load();
+    }.bind(this));
+
+    wire(this._view.rootElement, ".style-editor-importButton", function onImportButton() {
+      let editor = new StyleEditor(this.contentDocument);
+      this._editors.push(editor);
+      editor.addActionListener(this);
+      editor.importFromFile(this._mockImportFile || null, this._window);
+    }.bind(this));
+  },
+
+  /**
+   * Reset the chrome UI to an empty state.
+   */
+  _resetChrome: function SEC__resetChrome()
+  {
+    this._editors.forEach(function (aEditor) {
+      aEditor.removeActionListener(this);
+    }.bind(this));
+    this._editors = [];
+
+    this._view.removeAll();
+  },
+
+  /**
+   * Populate the chrome UI according to the content document.
+   *
+   * @see StyleEditor._setupShadowStyleSheet
+   */
+  _populateChrome: function SEC__populateChrome()
+  {
+    this._resetChrome();
+
+    this._document.title = _("chromeWindowTitle",
+          this.contentDocument.title || this.contentDocument.location.href);
+
+    let document = this.contentDocument;
+    for (let i = 0; i < document.styleSheets.length; ++i) {
+      let styleSheet = document.styleSheets[i];
+
+      let editor = new StyleEditor(document, styleSheet);
+      editor.addActionListener(this);
+      this._editors.push(editor);
+    }
+
+    this._triggerChromeListeners("ContentAttach");
+
+    // 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);
+  },
+
+  /**
+   * 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,input,select");
+    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);
+
+    text(summary, ".stylesheet-name", 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-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 the first or new stylesheet
+        if (editor.styleSheetIndex == 0 ||
+            editor.hasFlag(StyleEditorFlags.NEW)) {
+          this._view.activeSummary = aSummary;
+        }
+
+        this._triggerChromeListeners("EditorAdded", [editor]);
+      }.bind(this),
+      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.inputElement.focus();
+      }
+    });
+  },
+
+  /**
+   * 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/StyleEditorUtil.jsm
@@ -0,0 +1,196 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Style Editor code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Cedric Vivier <cedricv@neonux.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+"use strict";
+
+const EXPORTED_SYMBOLS = [
+  "_",
+  "assert",
+  "attr",
+  "getCurrentBrowserTabContentWindow",
+  "log",
+  "text",
+  "wire"
+];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+const PROPERTIES_URL = "chrome://browser/locale/devtools/styleeditor.properties";
+
+const console = Services.console;
+const gStringBundle = Services.strings.createBundle(PROPERTIES_URL);
+
+
+/**
+ * Returns a localized string with the given key name from the string bundle.
+ *
+ * @param aName
+ * @param ...rest
+ *        Optional arguments to format in the string.
+ * @return string
+ */
+function _(aName)
+{
+
+  if (arguments.length == 1) {
+    return gStringBundle.GetStringFromName(aName);
+  }
+  let rest = Array.prototype.slice.call(arguments, 1);
+  return gStringBundle.formatStringFromName(aName, rest, rest.length);
+}
+
+/**
+ * Assert an expression is true or throw if false.
+ *
+ * @param aExpression
+ * @param aMessage
+ *        Optional message.
+ * @return aExpression
+ */
+function assert(aExpression, aMessage)
+{
+  if (!!!(aExpression)) {
+    let msg = aMessage ? "ASSERTION FAILURE:" + aMessage : "ASSERTION FAILURE";
+    log(msg);
+    throw new Error(msg);
+  }
+  return aExpression;
+}
+
+/**
+ * Retrieve or set the text content of an element.
+ *
+ * @param DOMElement aRoot
+ *        The element to use for querySelector.
+ * @param string aSelector
+ *        Selector string for the element to get/set the text content.
+ * @param string aText
+ *        Optional text to set.
+ * @return string
+ *         Text content of matching element or null if there were no element
+ *         matching aSelector.
+ */
+function text(aRoot, aSelector, aText)
+{
+  let element = aRoot.querySelector(aSelector);
+  if (!element) {
+    return null;
+  }
+
+  if (aText === undefined) {
+    return element.textContent;
+  }
+  element.textContent = aText;
+  return aText;
+}
+
+/**
+ * Iterates _own_ properties of an object.
+ *
+ * @param aObject
+ *        The object to iterate.
+ * @param function aCallback(aKey, aValue)
+ */
+function forEach(aObject, aCallback)
+{
+  for (let key in aObject) {
+    if (aObject.hasOwnProperty(key)) {
+      aCallback(key, aObject[key]);
+    }
+  }
+}
+
+/**
+ * Log a message to the console.
+ * 
+ * @param ...rest
+ *        One or multiple arguments to log.
+ *        If multiple arguments are given, they will be joined by " " in the log.
+ */
+function log()
+{
+  console.logStringMessage(Array.prototype.slice.call(arguments).join(" "));
+}
+
+/**
+ * Wire up element(s) matching selector with attributes, event listeners, etc.
+ *
+ * @param DOMElement aRoot
+ *        The element to use for querySelectorAll.
+ *        Can be null if aSelector is a DOMElement.
+ * @param string|DOMElement aSelectorOrElement
+ *        Selector string or DOMElement for the element(s) to wire up.
+ * @param object aDescriptor
+ *        An object describing how to wire matching selector, supported properties
+ *        are "events", "attributes" and "userData" taking objects themselves.
+ *        Each key of properties above represents the name of the event, attribute
+ *        or userData, with the value being a function used as an event handler,
+ *        string to use as attribute value, or object to use as named userData
+ *        respectively.
+ *        If aDescriptor is a function, the argument is equivalent to :
+ *        {events: {'click': aDescriptor}}
+ */
+function wire(aRoot, aSelectorOrElement, aDescriptor)
+{
+  let matches;
+  if (typeof(aSelectorOrElement) == "string") { // selector
+    matches = aRoot.querySelectorAll(aSelectorOrElement);
+    if (!matches.length) {
+      return;
+    }
+  } else {
+    matches = [aSelectorOrElement]; // element
+  }
+
+  if (typeof(aDescriptor) == "function") {
+    aDescriptor = {events: {click: aDescriptor}};
+  }
+
+  for (let i = 0; i < matches.length; ++i) {
+    let element = matches[i];
+    forEach(aDescriptor.events, function (aName, aHandler) {
+      element.addEventListener(aName, aHandler, false);
+    });
+    forEach(aDescriptor.attributes, element.setAttribute);
+    forEach(aDescriptor.userData, element.setUserData);
+  }
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/splitview.css
@@ -0,0 +1,96 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Style Editor code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Cedric Vivier <cedricv@neonux.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+box,
+.splitview-nav {
+  -moz-box-flex: 1;
+  -moz-box-orient: vertical;
+}
+
+.splitview-main {
+  -moz-box-flex: 0;
+}
+
+.splitview-controller {
+  min-height: 8em;
+  max-height: 14em;
+}
+
+.splitview-nav {
+  display: -moz-box;
+}
+
+/* only the active details pane is shown */
+.splitview-side-details > * {
+  display: none;
+}
+.splitview-side-details > .splitview-active {
+  display: -moz-box;
+}
+
+/* this is to keep in sync with SplitView.jsm's LANDSCAPE_MEDIA_QUERY */
+@media (min-aspect-ratio: 5/3) {
+  .splitview-root {
+    -moz-box-orient: horizontal;
+  }
+  .splitview-controller {
+    -moz-box-flex: 0;
+    max-height: none;
+  }
+  .splitview-details {
+    display: none;
+  }
+  .splitview-details.splitview-active {
+    display: -moz-box;
+  }
+}
+
+/* filtered items are hidden */
+ol.splitview-nav > li.splitview-filtered {
+  display: none;
+}
+
+/* "empty list" and "all filtered" placeholders are hidden */
+.splitview-nav:empty,
+.splitview-nav.splitview-all-filtered,
+.splitview-nav + .splitview-nav.placeholder {
+  display: none;
+}
+.splitview-nav.splitview-all-filtered ~ .splitview-nav.placeholder.all-filtered,
+.splitview-nav:empty ~ .splitview-nav.placeholder.empty {
+  display: -moz-box;
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/styleeditor.css
@@ -0,0 +1,43 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Style Editor code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Cedric Vivier <cedricv@neonux.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+.splitview-nav > li hgroup > .stylesheet-error-message {
+  display: none;
+}
+.splitview-nav > li.error hgroup > .stylesheet-error-message {
+  display: block;
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/styleeditor.xul
@@ -0,0 +1,130 @@
+<?xml version="1.0"?>
+<!-- ***** BEGIN LICENSE BLOCK *****
+   - Version: MPL 1.1/GPL 2.0/LGPL 2.1
+   -
+   - The contents of this file are subject to the Mozilla Public License Version
+   - 1.1 (the "License"); you may not use this file except in compliance with
+   - the License. You may obtain a copy of the License at
+   - http://www.mozilla.org/MPL/
+   -
+   - Software distributed under the License is distributed on an "AS IS" basis,
+   - WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+   - for the specific language governing rights and limitations under the
+   - License.
+   -
+   - The Original Code is Style Editor code.
+   -
+   - The Initial Developer of the Original Code is The Mozilla Foundation.
+   - Portions created by the Initial Developer are Copyright (C) 2011
+   - the Initial Developer. All Rights Reserved.
+   -
+   - Contributor(s):
+   -   Cedric Vivier <cedricv@neonux.com> (original author)
+   -
+   - Alternatively, the contents of this file may be used under the terms of
+   - either the GNU General Public License Version 2 or later (the "GPL"), or
+   - the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+   - in which case the provisions of the GPL or the LGPL are applicable instead
+   - of those above. If you wish to allow use of your version of this file only
+   - under the terms of either the GPL or the LGPL, and not to allow others to
+   - use your version of this file under the terms of the MPL, indicate your
+   - decision by deleting the provisions above and replace them with the notice
+   - and other provisions required by the GPL or the LGPL. If you do not delete
+   - the provisions above, a recipient may use your version of this file under
+   - the terms of any one of the MPL, the GPL or the LGPL.
+   -
+   - ***** END LICENSE BLOCK ***** -->
+<!DOCTYPE window [
+<!ENTITY % styleEditorDTD SYSTEM "chrome://browser/locale/devtools/styleeditor.dtd" >
+ %styleEditorDTD;
+]>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/splitview.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/devtools/splitview.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/styleeditor.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/devtools/styleeditor.css" type="text/css"?>
+<xul:window xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        xmlns="http://www.w3.org/1999/xhtml"
+        id="style-editor-chrome-window"
+        title="&window.title;"
+        windowtype="Tools:StyleEditor"
+        width="800" height="280"
+        persist="screenX screenY width height sizemode">
+<xul:script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
+
+<xul:box id="style-editor-chrome" class="splitview-root">
+  <xul:box class="splitview-side-details"></xul:box>
+  <xul:box class="splitview-controller">
+    <xul:box class="splitview-main">
+      <xul:box class="toolbar">
+        <xul:button class="style-editor-newButton"
+                    accesskey="&newButton.accesskey;"
+                    tooltiptext="&newButton.tooltip;"
+                    label="&newButton.label;"></xul:button>
+        <xul:button class="style-editor-importButton"
+                    accesskey="&importButton.accesskey;"
+                    tooltiptext="&importButton.tooltip;"
+                    label="&importButton.label;"></xul:button>
+        <xul:box class="spacer" flex="1"></xul:box>
+        <xul:textbox class="splitview-filter"
+                     type="search"
+                     tooltiptext="&searchInput.tooltip;"
+                     placeholder="&searchInput.placeholder;"/>
+      </xul:box>
+    </xul:box>
+    <xul:box class="splitview-nav-container">
+      <ol class="splitview-nav" tabindex="0"></ol>
+      <div class="splitview-nav placeholder empty">
+        <p><strong>&noStyleSheet.label;</strong></p>
+        <p>&noStyleSheet-tip-start.label;
+          <a href="#"
+             class="style-editor-newButton">&noStyleSheet-tip-action.label;</a>
+          &noStyleSheet-tip-end.label;</p>
+      </div>
+      <div class="splitview-nav placeholder all-filtered">
+        <p><strong>&searchNoResults.label;</strong></p>
+        <p>
+          <a href="#"
+             class="splitview-filter-clearButton">&searchClearButton.label;</a>
+        </p>
+      </div>
+    </xul:box> <!-- .splitview-nav-container -->
+  </xul:box>   <!-- .splitview-controller -->
+
+  <div id="splitview-templates" hidden="true">
+    <li id="splitview-tpl-summary-stylesheet" tabindex="0">
+      <a class="stylesheet-enabled" tabindex="0" href="#"
+         title="&visibilityToggle.tooltip;"
+         accesskey="&saveButton.accesskey;"></a>
+      <hgroup class="stylesheet-info">
+        <h1><a class="stylesheet-name" href="#"></a></h1>
+        <h2 class="stylesheet-title"></h2>
+        <h3 class="stylesheet-error-message"></h3>
+      </hgroup>
+      <div class="stylesheet-more">
+        <hgroup class="stylesheet-stats">
+          <h3 class="stylesheet-rule-count"></h3>
+        </hgroup>
+        <hgroup class="stylesheet-actions">
+          <h1><a class="stylesheet-saveButton" href="#"
+                 title="&saveButton.tooltip;"
+                 accesskey="&saveButton.accesskey;">&saveButton.label;</a></h1>
+        </hgroup>
+      </div>
+    </li>
+
+    <xul:box id="splitview-tpl-details-stylesheet" class="splitview-details">
+      <div class="stylesheet-editor-input textbox" tabindex="0"
+           data-placeholder="&editorTextbox.placeholder;"></div>
+    </xul:box>
+  </div> <!-- #splitview-templates -->
+</xul:box>   <!-- .splitview-root -->
+
+<xul:script type="application/javascript"><![CDATA[
+Components.utils.import("resource:///modules/devtools/StyleEditorChrome.jsm");
+let chromeRoot = document.getElementById("style-editor-chrome");
+let contentWindow = window.arguments[0];
+let chrome = new StyleEditorChrome(chromeRoot, contentWindow);
+window.styleEditorChrome = chrome;
+]]></xul:script>
+</xul:window>
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -215,16 +215,23 @@ can reach it easily. -->
 
 <!ENTITY inspectPanelTitle.label        "HTML">
 <!ENTITY inspectButton.label            "Inspect">
 <!ENTITY inspectButton.accesskey        "I">
 <!ENTITY inspectCloseButton.tooltiptext "Close Inspector">
 <!ENTITY inspectStyleButton.label     "Style">
 <!ENTITY inspectStyleButton.accesskey "S">
 
+<!-- LOCALIZATION NOTE (styleeditor.label): This menu item label appears
+  -  in the Tools menu. -->
+<!ENTITY styleeditor.label            "Style Editor">
+<!ENTITY styleeditor.accesskey        "y">
+<!ENTITY styleeditor.keycode          "VK_F7">
+<!ENTITY styleeditor.keytext          "F7">
+
 <!ENTITY getMoreDevtoolsCmd.label        "Get More Tools">
 <!ENTITY getMoreDevtoolsCmd.accesskey    "M">
 
 <!ENTITY fileMenu.label         "File"> 
 <!ENTITY fileMenu.accesskey       "F">
 <!ENTITY newNavigatorCmd.label        "New Window">
 <!ENTITY newNavigatorCmd.key        "N">
 <!ENTITY newNavigatorCmd.accesskey      "N">
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/devtools/styleeditor.dtd
@@ -0,0 +1,62 @@
+<!-- LOCALIZATION NOTE : FILE This file contains the Style Editor window strings -->
+<!-- LOCALIZATION NOTE : FILE Do not translate commandkeys -->
+<!-- LOCALIZATION NOTE : The correct localization of this file might be to keep
+     it in English, or another language commonly spoken among web developers.
+     You want to make that choice consistent across the developer tools.
+     A good criteria is the language in which you'd find the best documentation
+     on web development on the web. -->
+
+<!-- LOCALIZATION NOTE (window.title): This is the default title for Style Editor
+     main window. NB: the property chromeWindowTitle in styleeditor.properties
+     is used to dynamically update the Style Editor window title with the title
+     of the web page it is editing. -->
+<!ENTITY window.title               "Style Editor">
+
+<!ENTITY newButton.label            "New">
+<!ENTITY newButton.tooltip          "Create and append a new style sheet to the document">
+<!ENTITY newButton.accesskey        "N">
+<!ENTITY newButton.commandkey       "n">
+
+<!ENTITY importButton.label         "Import…">
+<!ENTITY importButton.tooltip       "Import and append an existing style sheet to the document">
+<!ENTITY importButton.accesskey     "I">
+<!ENTITY importButton.commandkey    "i">
+
+<!ENTITY searchInput.tooltip        "Filter style sheets by name">
+<!ENTITY searchInput.placeholder    "Find style sheet">
+
+<!-- LOCALIZATION NOTE  (searchNoResults): This is shown when searching a term
+     that is not found in any stylesheet or stylesheet name. -->
+<!ENTITY searchNoResults.label      "No matching style sheet has been found.">
+
+<!-- LOCALIZATION NOTE  (searchClearButton): This button clears the search input
+     box and is visible only when a search term has been typed. -->
+<!ENTITY searchClearButton.label    "Clear">
+
+<!ENTITY visibilityToggle.tooltip   "Toggle style sheet visibility">
+<!ENTITY visibilityToggle.accesskey "V">
+
+<!ENTITY saveButton.label           "Save">
+<!ENTITY saveButton.tooltip         "Save this style sheet to a file">
+<!ENTITY saveButton.accesskey       "S">
+<!ENTITY saveButton.commandkey      "s">
+
+<!-- LOCALICATION NOTE  (scoped.label): This is shown in a stylesheets list item
+     when the stylesheet uses the scoped attribute on the <style> element. -->
+<!ENTITY scoped.label               "Scoped.">
+
+<!ENTITY editorTextbox.placeholder  "Type CSS here.">
+
+<!-- LOCALICATION NOTE  (noStyleSheet.label): This is shown when a page has no
+     stylesheet. -->
+<!ENTITY noStyleSheet.label         "This page has no style sheet.">
+
+<!-- LOCALICATION NOTE  (noStyleSheet-tip-start.label): This is the start of a
+     tip sentence shown when there is no stylesheet. It suggests to create a new
+     stylesheet and provides an action link to do so. -->
+<!ENTITY noStyleSheet-tip-start.label  "Perhaps you'd like to ">
+<!-- LOCALICATION NOTE  (noStyleSheet-tip-action.label): This is text for the
+     link that triggers creation of a new stylesheet. -->
+<!ENTITY noStyleSheet-tip-action.label "append a new style sheet">
+<!-- LOCALICATION NOTE  (noStyleSheet-tip-end.label): End of the tip sentence -->
+<!ENTITY noStyleSheet-tip-end.label    "?">
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/devtools/styleeditor.properties
@@ -0,0 +1,53 @@
+# LOCALIZATION NOTE These strings are used inside the Style Editor.
+# LOCALIZATION NOTE The correct localization of this file might be to keep it
+# in English, or another language commonly spoken among web developers.
+# You want to make that choice consistent across the developer tools.
+# A good criteria is the language in which you'd find the best documentation
+# on web development on the web.
+
+# LOCALIZATION NOTE  (chromeWindowTitle): This is the title of the Style Editor
+# 'chrome' window. That is, the main window with the stylesheets list.
+# The argument is either the content document's title or its href if no title
+# is available.
+chromeWindowTitle=Style Editor [%S]
+
+# LOCALIZATION NOTE  (inlineStyleSheet): This is the name used for an style sheet
+# that is declared inline in the <style> element. Shown in the stylesheets list.
+# the argument is the index (order) of the containing <style> element in the
+# document.
+inlineStyleSheet=<inline style sheet #%S>
+
+# LOCALIZATION NOTE  (newStyleSheet): This is the default name for a new
+# user-created style sheet.
+newStyleSheet=New style sheet #%S
+
+# LOCALIZATION NOTE  (ruleCount.label): Semi-colon list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# This is shown in the style sheets list.
+# #1 rule.
+# example: 111 rules.
+ruleCount.label=#1 rule.;#1 rules.
+
+# LOCALIZATION NOTE  (error-load.label): This is shown when loading fails.
+error-load=Style sheet could not be loaded.
+
+# LOCALIZATION NOTE  (error-save.label): This is shown when saving fails.
+error-save=Style sheet could not be saved.
+
+# LOCALIZATION NOTE  (importStyleSheet.title): This is the file picker title,
+# when you import a style sheet into the Style Editor.
+importStyleSheet.title=Import style sheet
+
+# LOCALIZATION NOTE  (importStyleSheet.title): This is the *.css filter title
+importStyleSheet.filter=CSS files
+
+# LOCALIZATION NOTE  (saveStyleSheet.title): This is the file picker title,
+# when you save a style sheet from the Style Editor.
+saveStyleSheet.title=Save style sheet
+
+# LOCALIZATION NOTE  (saveStyleSheet.title): This is the *.css filter title
+saveStyleSheet.filter=CSS files
+
+# LOCALIZATION NOTE  (saveStyleSheet.commandkey): This the key to use in
+# conjunction with accel (Command on Mac or Ctrl on other platforms) to Save
+saveStyleSheet.commandkey=S
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -16,16 +16,18 @@
     locale/browser/baseMenuOverlay.dtd             (%chrome/browser/baseMenuOverlay.dtd)
     locale/browser/browser.properties              (%chrome/browser/browser.properties)
     locale/browser/devtools/gcli.properties           (%chrome/browser/devtools/gcli.properties)
     locale/browser/devtools/gclicommands.properties   (%chrome/browser/devtools/gclicommands.properties)
     locale/browser/devtools/webconsole.properties     (%chrome/browser/devtools/webconsole.properties)
     locale/browser/devtools/inspector.properties      (%chrome/browser/devtools/inspector.properties)
     locale/browser/devtools/scratchpad.properties     (%chrome/browser/devtools/scratchpad.properties)
     locale/browser/devtools/scratchpad.dtd            (%chrome/browser/devtools/scratchpad.dtd)
+    locale/browser/devtools/styleeditor.properties    (%chrome/browser/devtools/styleeditor.properties)
+    locale/browser/devtools/styleeditor.dtd           (%chrome/browser/devtools/styleeditor.dtd)
     locale/browser/devtools/styleinspector.properties (%chrome/browser/devtools/styleinspector.properties)
     locale/browser/devtools/styleinspector.dtd        (%chrome/browser/devtools/styleinspector.dtd)
     locale/browser/devtools/webConsole.dtd            (%chrome/browser/devtools/webConsole.dtd)
     locale/browser/openLocation.dtd                (%chrome/browser/openLocation.dtd)
     locale/browser/openLocation.properties         (%chrome/browser/openLocation.properties)
 *   locale/browser/pageInfo.dtd                    (%chrome/browser/pageInfo.dtd)
     locale/browser/pageInfo.properties             (%chrome/browser/pageInfo.properties)
     locale/browser/quitDialog.properties           (%chrome/browser/quitDialog.properties)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4ad3c219c6493fdfbe3152b206a9052288dee505
GIT binary patch
literal 784
zc$@(c1MmEaP)<h;3K|Lk000e1NJLTq000sI000aK1^@s6ZxZdi00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipq)
z69Y1a+YeFz00NLnL_t(2&#jVANK|1I#n1P>H}hnkqo@lJnzD3;q(aN6O^d)#p`rYf
zWtIqqNP*CzRl61yQj16uG!zUiMQsoVsv%kwCYDJCQmG_nng2`2H_jX1_uUpfgra2^
z&gOFX!9C{!|7qCYi;7caiG~p=0kBh&^bwE>0t3U6!uTi{n~vA@&i0PZg@1xV!2oY)
zYAzQ-))L6As>*oA_VoIKBY9&0*wbgveV->MYb{G;5+EI_T7RMXTyws^SZ;zs!GLnN
zvAIr4k;fS`9Zu(T)!EZmG^cX~fNA@jW-ON2+V=3e6^m^VL~)9$bYHAKe<R;tER|3&
zz#Hy0UnL<9E1VesuxD3R{f_kXiTe*ax4q~exHkCat^d>b#At3#_V@Mm^@Z8F`9qwu
zbs^;L;gJ#j&aJxFO4x{87M3WmAr|92T3U3h5y0BZ{(+kul*Sp{rWwmQus^qZtH<+W
zX!u<rK$0_N3L*CnkBqofr^69%3b6*j0ze=l08s!;Rh6i)L<9g-Rf*asMnSY;`x$3U
zs*0M-D=SX6xOCST5hQ>ONyK}4`@8_)^Jcbbnl__p+N{r;*#-bTy?tIu!~tv&LF&5x
zv9jV+3kwAUiE9xft-GhU#xM*I013uc%gahGr6ecE0Z6oqU0GUQj&^iCx(%WP09@Dg
zsS~Bgo8n#iEDHq#3IO+7#7KSoBvdp%zqn5lIT>S%ZnwL!>P-0u0BCLR$O?zUHAIxc
z8M9JSlHdD}7IwwE^f>_G&H4f80Fu7W%%r^<7(DoMWz}QGOpg?lgx_JT#%bDALW28S
zZcg@)H#6f4fMozizQ0)f@sI#C04`e&00cnV7H)@a0f^W#^ZmsF0Db`r+#yvr<!LDZ
O0000<MNUMnLSTYptX97O
new file mode 100644
--- /dev/null
+++ b/browser/themes/gnomestripe/devtools/splitview.css
@@ -0,0 +1,137 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Style Editor code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Cedric Vivier <cedricv@neonux.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+.splitview-root {
+  margin-top: 4px;
+}
+
+.splitview-filter {
+  -moz-box-flex: 1;
+  -moz-margin-start: 1ex;
+  max-width: 25ex;
+}
+
+.splitview-nav-container {
+  -moz-box-pack: center;
+  margin: 4px;
+  border: 1px inset WindowFrame;
+  border-radius: 4px;
+  background-color: -moz-default-background-color;
+  color: -moz-default-color;
+}
+
+ol.splitview-nav {
+  overflow-x: hidden;
+  overflow-y: auto;
+  list-style-image: none;
+  list-style: none;
+  padding: 0;
+  margin: 0;
+}
+
+ol.splitview-nav > li {
+  display: -moz-box;
+  -moz-box-orient: vertical;
+  outline: 0;
+  border-bottom: 1px solid ThreeDShadow;
+  padding: 2px;
+  padding-top: 4px;
+  vertical-align: bottom;
+  color: black;
+  -moz-transition-property: background-color, max-height;
+  -moz-transition-duration: 1s;
+  max-height: 2048px; /* for slide transition */
+}
+ol.splitview-nav > li.splitview-active {
+  background-color: #eaf2fe;   /* same as orion.css's .line_caret */
+}
+ol.splitview-nav > li:hover,
+ol.splitview-nav > li:focus {
+  background-color: #f0f5fd;   /* slightly lighter .line_caret */
+}
+
+ol.splitview-nav > li.splitview-flash {
+  background-color: #faf0e1;   /* complement of .line_caret */
+}
+ol.splitview-nav > li.splitview-slide {
+  max-height: 0;
+  overflow: hidden;
+}
+
+.splitview-side-details {
+  margin: 2px;
+}
+
+.splitview-nav.splitview-all-filtered ~ .splitview-nav.placeholder.all-filtered,
+.splitview-nav:empty ~ .splitview-nav.placeholder.empty {
+  -moz-box-flex: 0;
+  text-align: center;
+}
+
+.splitview-main .toolbar,
+.splitview-main .toolbar > * {
+  display: -moz-box;
+}
+.splitview-main .toolbar {
+  -moz-box-flex: 1;
+  -moz-box-orient: horizontal;
+  -moz-padding-start: 3px;
+}
+
+a {
+  color: -moz-hyperlinktext;
+  text-decoration: underline;
+}
+
+button {
+  margin: 0;
+}
+
+/* limited width mode (hide search unless it has focus [search-on-type]) */
+@media (max-width: 250px) {
+  .splitview-filter {
+    max-width: none;
+    position: fixed;
+    margin: 0;
+    bottom: -4em;
+    -moz-transition-property: bottom;
+    -moz-transition-duration: 0.2s;
+  }
+  .splitview-filter[focused="true"] {
+    bottom: 0;
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/themes/gnomestripe/devtools/styleeditor.css
@@ -0,0 +1,177 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Style Editor code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Cedric Vivier <cedricv@neonux.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+ol.splitview-nav:focus {
+  outline: 0; /* focus ring is on the stylesheet name */
+}
+
+.splitview-nav > li:-moz-locale-dir(ltr),
+.splitview-nav > li hgroup:-moz-locale-dir(ltr) {
+  float: left;
+}
+.splitview-nav > li:-moz-locale-dir(rtl),
+.splitview-nav > li hgroup:-moz-locale-dir(rtl) {
+  float: right;
+}
+.splitview-nav > li > hgroup.stylesheet-info {
+  max-width: 66%;
+}
+.splitview-nav > li > hgroup .stylesheet-name {
+  display: inline-block;
+  width: 100%;
+  max-width: 34ex;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.splitview-nav > li.unsaved > hgroup .stylesheet-name {
+  font-style: italic;
+}
+.splitview-nav > li.unsaved > hgroup .stylesheet-name:before:-moz-locale-dir(ltr),
+.splitview-nav > li.unsaved > hgroup .stylesheet-name:after:-moz-locale-dir(rtl) {
+  font-style: italic;
+  content: "* ";
+}
+
+.splitview-nav > li > .stylesheet-enabled {
+  float: left;
+  width: 20px;
+  height: 20px;
+  -moz-margin-end: 6px;
+  border: 1px solid transparent;
+  background-image: url(eye-toggle.png);
+  background-position: center center;
+  background-repeat: no-repeat;
+}
+.splitview-nav > li > .stylesheet-enabled:-moz-locale-dir(rtl) {
+  float: right;
+}
+.splitview-nav > li.disabled > .stylesheet-enabled {
+  background-image: none;
+}
+.splitview-nav > li > .stylesheet-enabled:focus,
+.splitview-nav > li:hover > .stylesheet-enabled {
+  outline: 0;
+  border: 1px inset WindowFrame;
+  -moz-margin-end: 6px;
+}
+
+.splitview-nav > li hgroup .stylesheet-title {
+  color: GrayText;
+  font-size: 0.8em;
+}
+.splitview-nav > li.error hgroup > .stylesheet-error-message {
+  color: red;
+  font-size: 0.8em;
+}
+
+.splitview-nav > li > .stylesheet-more {
+  position: relative;
+  right: 0;
+}
+.splitview-nav > li > .stylesheet-more:-moz-locale-dir(rtl) {
+  left: 0;
+}
+
+.splitview-nav > li hgroup.stylesheet-stats,
+.splitview-nav > li hgroup.stylesheet-actions {
+  position: absolute;
+  z-index: 2;
+  -moz-transition-property: left, right;
+  -moz-transition-duration: 0.2s;
+  -moz-transition-delay: 0.2s;
+  -moz-transition-timing-function: ease-in-out;
+}
+
+.splitview-nav > li hgroup.stylesheet-stats {
+  z-index: 1;
+  -moz-transition-delay: 0.4s;
+  color: GrayText;
+  padding-left: 6px; /* Fitts */
+  padding-right: 6px;
+}
+.splitview-nav > li hgroup.stylesheet-actions a {
+  color: ButtonText;
+  padding-left: 6px; /* Fitts */
+  padding-right: 6px;
+}
+
+.splitview-nav > li hgroup.stylesheet-actions:-moz-locale-dir(ltr),
+.splitview-nav > li:hover hgroup.stylesheet-stats:-moz-locale-dir(ltr),
+.splitview-nav > li:focus hgroup.stylesheet-stats:-moz-locale-dir(ltr),
+.splitview-nav > li.splitview-active hgroup.stylesheet-stats:-moz-locale-dir(ltr) {
+  right: -50ex;
+}
+.splitview-nav > li hgroup.stylesheet-actions:-moz-locale-dir(rtl),
+.splitview-nav > li:hover hgroup.stylesheet-stats:-moz-locale-dir(rtl),
+.splitview-nav > li:focus hgroup.stylesheet-stats:-moz-locale-dir(rtl),
+.splitview-nav > li.splitview-active hgroup.stylesheet-stats:-moz-locale-dir(rtl) {
+  left: -50ex;
+}
+.splitview-nav > li hgroup.stylesheet-stats:-moz-locale-dir(ltr),
+.splitview-nav > li:hover hgroup.stylesheet-actions:-moz-locale-dir(ltr),
+.splitview-nav > li:focus hgroup.stylesheet-actions:-moz-locale-dir(ltr),
+.splitview-nav > li.splitview-active hgroup.stylesheet-actions:-moz-locale-dir(ltr) {
+  right: 0;
+}
+.splitview-nav > li hgroup.stylesheet-stats:-moz-locale-dir(rtl),
+.splitview-nav > li:hover hgroup.stylesheet-actions:-moz-locale-dir(rtl),
+.splitview-nav > li:focus hgroup.stylesheet-actions:-moz-locale-dir(rtl),
+.splitview-nav > li.splitview-active hgroup.stylesheet-actions:-moz-locale-dir(rtl) {
+  left: 0;
+}
+
+.stylesheet-editor-input {
+  display: -moz-box;
+  -moz-box-flex: 1;
+  overflow: hidden;
+  min-height: 8em;
+  margin: 3px;
+  margin-top: 0;
+  border: 1px inset WindowFrame;
+  border-radius: 4px;
+  background-color: -moz-default-background-color;
+}
+
+h1,
+h2,
+h3 {
+  font-size: inherit;
+  font-weight: normal;
+  margin: 0;
+  padding: 0;
+}
--- a/browser/themes/gnomestripe/jar.mn
+++ b/browser/themes/gnomestripe/jar.mn
@@ -106,17 +106,19 @@ browser.jar:
   skin/classic/browser/devtools/breadcrumbs/rtl-middle-pressed.png           (devtools/breadcrumbs/rtl-middle-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-middle-selected-pressed.png  (devtools/breadcrumbs/rtl-middle-selected-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-middle-selected.png          (devtools/breadcrumbs/rtl-middle-selected.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-middle.png                   (devtools/breadcrumbs/rtl-middle.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-start-pressed.png            (devtools/breadcrumbs/rtl-start-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-start-selected-pressed.png   (devtools/breadcrumbs/rtl-start-selected-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-start.png                    (devtools/breadcrumbs/rtl-start.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-start-selected.png           (devtools/breadcrumbs/rtl-start-selected.png)
-
+  skin/classic/browser/devtools/splitview.css         (devtools/splitview.css)
+  skin/classic/browser/devtools/styleeditor.css       (devtools/styleeditor.css)
+  skin/classic/browser/devtools/eye-toggle.png        (devtools/eye-toggle.png)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-16-throbber.png
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-24-throbber.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-128.png
   skin/classic/browser/sync-desktopIcon.png
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4ad3c219c6493fdfbe3152b206a9052288dee505
GIT binary patch
literal 784
zc$@(c1MmEaP)<h;3K|Lk000e1NJLTq000sI000aK1^@s6ZxZdi00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipq)
z69Y1a+YeFz00NLnL_t(2&#jVANK|1I#n1P>H}hnkqo@lJnzD3;q(aN6O^d)#p`rYf
zWtIqqNP*CzRl61yQj16uG!zUiMQsoVsv%kwCYDJCQmG_nng2`2H_jX1_uUpfgra2^
z&gOFX!9C{!|7qCYi;7caiG~p=0kBh&^bwE>0t3U6!uTi{n~vA@&i0PZg@1xV!2oY)
zYAzQ-))L6As>*oA_VoIKBY9&0*wbgveV->MYb{G;5+EI_T7RMXTyws^SZ;zs!GLnN
zvAIr4k;fS`9Zu(T)!EZmG^cX~fNA@jW-ON2+V=3e6^m^VL~)9$bYHAKe<R;tER|3&
zz#Hy0UnL<9E1VesuxD3R{f_kXiTe*ax4q~exHkCat^d>b#At3#_V@Mm^@Z8F`9qwu
zbs^;L;gJ#j&aJxFO4x{87M3WmAr|92T3U3h5y0BZ{(+kul*Sp{rWwmQus^qZtH<+W
zX!u<rK$0_N3L*CnkBqofr^69%3b6*j0ze=l08s!;Rh6i)L<9g-Rf*asMnSY;`x$3U
zs*0M-D=SX6xOCST5hQ>ONyK}4`@8_)^Jcbbnl__p+N{r;*#-bTy?tIu!~tv&LF&5x
zv9jV+3kwAUiE9xft-GhU#xM*I013uc%gahGr6ecE0Z6oqU0GUQj&^iCx(%WP09@Dg
zsS~Bgo8n#iEDHq#3IO+7#7KSoBvdp%zqn5lIT>S%ZnwL!>P-0u0BCLR$O?zUHAIxc
z8M9JSlHdD}7IwwE^f>_G&H4f80Fu7W%%r^<7(DoMWz}QGOpg?lgx_JT#%bDALW28S
zZcg@)H#6f4fMozizQ0)f@sI#C04`e&00cnV7H)@a0f^W#^ZmsF0Db`r+#yvr<!LDZ
O0000<MNUMnLSTYptX97O
new file mode 100644
--- /dev/null
+++ b/browser/themes/pinstripe/devtools/splitview.css
@@ -0,0 +1,137 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Style Editor code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Cedric Vivier <cedricv@neonux.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+.splitview-root {
+  margin-top: 4px;
+}
+
+.splitview-filter {
+  -moz-box-flex: 1;
+  -moz-margin-start: 1ex;
+  max-width: 25ex;
+}
+
+.splitview-nav-container {
+  -moz-box-pack: center;
+  margin: 4px;
+  border: 1px inset WindowFrame;
+  border-radius: 4px;
+  background-color: -moz-default-background-color;
+  color: -moz-default-color;
+}
+
+ol.splitview-nav {
+  overflow-x: hidden;
+  overflow-y: auto;
+  list-style-image: none;
+  list-style: none;
+  padding: 0;
+  margin: 0;
+}
+
+ol.splitview-nav > li {
+  display: -moz-box;
+  -moz-box-orient: vertical;
+  outline: 0;
+  border-bottom: 1px solid ThreeDShadow;
+  padding: 2px;
+  padding-top: 4px;
+  vertical-align: bottom;
+  color: black;
+  -moz-transition-property: background-color, max-height;
+  -moz-transition-duration: 1s;
+  max-height: 2048px; /* for slide transition */
+}
+ol.splitview-nav > li.splitview-active {
+  background-color: #eaf2fe;   /* same as orion.css's .line_caret */
+}
+ol.splitview-nav > li:hover,
+ol.splitview-nav > li:focus {
+  background-color: #f0f5fd;   /* slightly lighter .line_caret */
+}
+
+ol.splitview-nav > li.splitview-flash {
+  background-color: #faf0e1;   /* complement of .line_caret */
+}
+ol.splitview-nav > li.splitview-slide {
+  max-height: 0;
+  overflow: hidden;
+}
+
+.splitview-side-details {
+  margin: 2px;
+}
+
+.splitview-nav.splitview-all-filtered ~ .splitview-nav.placeholder.all-filtered,
+.splitview-nav:empty ~ .splitview-nav.placeholder.empty {
+  -moz-box-flex: 0;
+  text-align: center;
+}
+
+.splitview-main .toolbar,
+.splitview-main .toolbar > * {
+  display: -moz-box;
+}
+.splitview-main .toolbar {
+  -moz-box-flex: 1;
+  -moz-box-orient: horizontal;
+  -moz-padding-start: 3px;
+}
+
+a {
+  color: -moz-hyperlinktext;
+  text-decoration: underline;
+}
+
+button {
+  margin: 0;
+}
+
+/* limited width mode (hide search unless it has focus [search-on-type]) */
+@media (max-width: 250px) {
+  .splitview-filter {
+    max-width: none;
+    position: fixed;
+    margin: 0;
+    bottom: -4em;
+    -moz-transition-property: bottom;
+    -moz-transition-duration: 0.2s;
+  }
+  .splitview-filter[focused="true"] {
+    bottom: 0;
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/themes/pinstripe/devtools/styleeditor.css
@@ -0,0 +1,177 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Style Editor code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Cedric Vivier <cedricv@neonux.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+ol.splitview-nav:focus {
+  outline: 0; /* focus ring is on the stylesheet name */
+}
+
+.splitview-nav > li:-moz-locale-dir(ltr),
+.splitview-nav > li hgroup:-moz-locale-dir(ltr) {
+  float: left;
+}
+.splitview-nav > li:-moz-locale-dir(rtl),
+.splitview-nav > li hgroup:-moz-locale-dir(rtl) {
+  float: right;
+}
+.splitview-nav > li > hgroup.stylesheet-info {
+  max-width: 66%;
+}
+.splitview-nav > li > hgroup .stylesheet-name {
+  display: inline-block;
+  width: 100%;
+  max-width: 34ex;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.splitview-nav > li.unsaved > hgroup .stylesheet-name {
+  font-style: italic;
+}
+.splitview-nav > li.unsaved > hgroup .stylesheet-name:before:-moz-locale-dir(ltr),
+.splitview-nav > li.unsaved > hgroup .stylesheet-name:after:-moz-locale-dir(rtl) {
+  font-style: italic;
+  content: "* ";
+}
+
+.splitview-nav > li > .stylesheet-enabled {
+  float: left;
+  width: 20px;
+  height: 20px;
+  -moz-margin-end: 6px;
+  border: 1px solid transparent;
+  background-image: url(eye-toggle.png);
+  background-position: center center;
+  background-repeat: no-repeat;
+}
+.splitview-nav > li > .stylesheet-enabled:-moz-locale-dir(rtl) {
+  float: right;
+}
+.splitview-nav > li.disabled > .stylesheet-enabled {
+  background-image: none;
+}
+.splitview-nav > li > .stylesheet-enabled:focus,
+.splitview-nav > li:hover > .stylesheet-enabled {
+  outline: 0;
+  border: 1px inset WindowFrame;
+  -moz-margin-end: 6px;
+}
+
+.splitview-nav > li hgroup .stylesheet-title {
+  color: GrayText;
+  font-size: 0.8em;
+}
+.splitview-nav > li.error hgroup > .stylesheet-error-message {
+  color: red;
+  font-size: 0.8em;
+}
+
+.splitview-nav > li > .stylesheet-more {
+  position: relative;
+  right: 0;
+}
+.splitview-nav > li > .stylesheet-more:-moz-locale-dir(rtl) {
+  left: 0;
+}
+
+.splitview-nav > li hgroup.stylesheet-stats,
+.splitview-nav > li hgroup.stylesheet-actions {
+  position: absolute;
+  z-index: 2;
+  -moz-transition-property: left, right;
+  -moz-transition-duration: 0.2s;
+  -moz-transition-delay: 0.2s;
+  -moz-transition-timing-function: ease-in-out;
+}
+
+.splitview-nav > li hgroup.stylesheet-stats {
+  z-index: 1;
+  -moz-transition-delay: 0.4s;
+  color: GrayText;
+  padding-left: 6px; /* Fitts */
+  padding-right: 6px;
+}
+.splitview-nav > li hgroup.stylesheet-actions a {
+  color: ButtonText;
+  padding-left: 6px; /* Fitts */
+  padding-right: 6px;
+}
+
+.splitview-nav > li hgroup.stylesheet-actions:-moz-locale-dir(ltr),
+.splitview-nav > li:hover hgroup.stylesheet-stats:-moz-locale-dir(ltr),
+.splitview-nav > li:focus hgroup.stylesheet-stats:-moz-locale-dir(ltr),
+.splitview-nav > li.splitview-active hgroup.stylesheet-stats:-moz-locale-dir(ltr) {
+  right: -50ex;
+}
+.splitview-nav > li hgroup.stylesheet-actions:-moz-locale-dir(rtl),
+.splitview-nav > li:hover hgroup.stylesheet-stats:-moz-locale-dir(rtl),
+.splitview-nav > li:focus hgroup.stylesheet-stats:-moz-locale-dir(rtl),
+.splitview-nav > li.splitview-active hgroup.stylesheet-stats:-moz-locale-dir(rtl) {
+  left: -50ex;
+}
+.splitview-nav > li hgroup.stylesheet-stats:-moz-locale-dir(ltr),
+.splitview-nav > li:hover hgroup.stylesheet-actions:-moz-locale-dir(ltr),
+.splitview-nav > li:focus hgroup.stylesheet-actions:-moz-locale-dir(ltr),
+.splitview-nav > li.splitview-active hgroup.stylesheet-actions:-moz-locale-dir(ltr) {
+  right: 0;
+}
+.splitview-nav > li hgroup.stylesheet-stats:-moz-locale-dir(rtl),
+.splitview-nav > li:hover hgroup.stylesheet-actions:-moz-locale-dir(rtl),
+.splitview-nav > li:focus hgroup.stylesheet-actions:-moz-locale-dir(rtl),
+.splitview-nav > li.splitview-active hgroup.stylesheet-actions:-moz-locale-dir(rtl) {
+  left: 0;
+}
+
+.stylesheet-editor-input {
+  display: -moz-box;
+  -moz-box-flex: 1;
+  overflow: hidden;
+  min-height: 8em;
+  margin: 3px;
+  margin-top: 0;
+  border: 1px inset WindowFrame;
+  border-radius: 4px;
+  background-color: -moz-default-background-color;
+}
+
+h1,
+h2,
+h3 {
+  font-size: inherit;
+  font-weight: normal;
+  margin: 0;
+  padding: 0;
+}
--- a/browser/themes/pinstripe/jar.mn
+++ b/browser/themes/pinstripe/jar.mn
@@ -146,17 +146,19 @@ browser.jar:
   skin/classic/browser/devtools/breadcrumbs/rtl-middle-pressed.png           (devtools/breadcrumbs/rtl-middle-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-middle-selected-pressed.png  (devtools/breadcrumbs/rtl-middle-selected-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-middle-selected.png          (devtools/breadcrumbs/rtl-middle-selected.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-middle.png                   (devtools/breadcrumbs/rtl-middle.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-start-pressed.png            (devtools/breadcrumbs/rtl-start-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-start-selected-pressed.png   (devtools/breadcrumbs/rtl-start-selected-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-start.png                    (devtools/breadcrumbs/rtl-start.png)
   skin/classic/browser/devtools/breadcrumbs/rtl-start-selected.png           (devtools/breadcrumbs/rtl-start-selected.png)
-
+  skin/classic/browser/devtools/splitview.css               (devtools/splitview.css)
+  skin/classic/browser/devtools/styleeditor.css             (devtools/styleeditor.css)
+  skin/classic/browser/devtools/eye-toggle.png              (devtools/eye-toggle.png)
 #ifdef MOZ_SERVICES_SYNC
   skin/classic/browser/sync-throbber.png
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-128.png
   skin/classic/browser/sync-desktopIcon.png
   skin/classic/browser/sync-mobileIcon.png
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4ad3c219c6493fdfbe3152b206a9052288dee505
GIT binary patch
literal 784
zc$@(c1MmEaP)<h;3K|Lk000e1NJLTq000sI000aK1^@s6ZxZdi00001b5ch_0Itp)
z=>Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipq)
z69Y1a+YeFz00NLnL_t(2&#jVANK|1I#n1P>H}hnkqo@lJnzD3;q(aN6O^d)#p`rYf
zWtIqqNP*CzRl61yQj16uG!zUiMQsoVsv%kwCYDJCQmG_nng2`2H_jX1_uUpfgra2^
z&gOFX!9C{!|7qCYi;7caiG~p=0kBh&^bwE>0t3U6!uTi{n~vA@&i0PZg@1xV!2oY)
zYAzQ-))L6As>*oA_VoIKBY9&0*wbgveV->MYb{G;5+EI_T7RMXTyws^SZ;zs!GLnN
zvAIr4k;fS`9Zu(T)!EZmG^cX~fNA@jW-ON2+V=3e6^m^VL~)9$bYHAKe<R;tER|3&
zz#Hy0UnL<9E1VesuxD3R{f_kXiTe*ax4q~exHkCat^d>b#At3#_V@Mm^@Z8F`9qwu
zbs^;L;gJ#j&aJxFO4x{87M3WmAr|92T3U3h5y0BZ{(+kul*Sp{rWwmQus^qZtH<+W
zX!u<rK$0_N3L*CnkBqofr^69%3b6*j0ze=l08s!;Rh6i)L<9g-Rf*asMnSY;`x$3U
zs*0M-D=SX6xOCST5hQ>ONyK}4`@8_)^Jcbbnl__p+N{r;*#-bTy?tIu!~tv&LF&5x
zv9jV+3kwAUiE9xft-GhU#xM*I013uc%gahGr6ecE0Z6oqU0GUQj&^iCx(%WP09@Dg
zsS~Bgo8n#iEDHq#3IO+7#7KSoBvdp%zqn5lIT>S%ZnwL!>P-0u0BCLR$O?zUHAIxc
z8M9JSlHdD}7IwwE^f>_G&H4f80Fu7W%%r^<7(DoMWz}QGOpg?lgx_JT#%bDALW28S
zZcg@)H#6f4fMozizQ0)f@sI#C04`e&00cnV7H)@a0f^W#^ZmsF0Db`r+#yvr<!LDZ
O0000<MNUMnLSTYptX97O
new file mode 100644
--- /dev/null
+++ b/browser/themes/winstripe/devtools/splitview.css
@@ -0,0 +1,137 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Style Editor code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Cedric Vivier <cedricv@neonux.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+.splitview-root {
+  margin-top: 4px;
+}
+
+.splitview-filter {
+  -moz-box-flex: 1;
+  -moz-margin-start: 1ex;
+  max-width: 25ex;
+}
+
+.splitview-nav-container {
+  -moz-box-pack: center;
+  margin: 4px;
+  border: 1px inset WindowFrame;
+  border-radius: 4px;
+  background-color: -moz-default-background-color;
+  color: -moz-default-color;
+}
+
+ol.splitview-nav {
+  overflow-x: hidden;
+  overflow-y: auto;
+  list-style-image: none;
+  list-style: none;
+  padding: 0;
+  margin: 0;
+}
+
+ol.splitview-nav > li {
+  display: -moz-box;
+  -moz-box-orient: vertical;
+  outline: 0;
+  border-bottom: 1px solid ThreeDShadow;
+  padding: 2px;
+  padding-top: 4px;
+  vertical-align: bottom;
+  color: black;
+  -moz-transition-property: background-color, max-height;
+  -moz-transition-duration: 1s;
+  max-height: 2048px; /* for slide transition */
+}
+ol.splitview-nav > li.splitview-active {
+  background-color: #eaf2fe;   /* same as orion.css's .line_caret */
+}
+ol.splitview-nav > li:hover,
+ol.splitview-nav > li:focus {
+  background-color: #f0f5fd;   /* slightly lighter .line_caret */
+}
+
+ol.splitview-nav > li.splitview-flash {
+  background-color: #faf0e1;   /* complement of .line_caret */
+}
+ol.splitview-nav > li.splitview-slide {
+  max-height: 0;
+  overflow: hidden;
+}
+
+.splitview-side-details {
+  margin: 2px;
+}
+
+.splitview-nav.splitview-all-filtered ~ .splitview-nav.placeholder.all-filtered,
+.splitview-nav:empty ~ .splitview-nav.placeholder.empty {
+  -moz-box-flex: 0;
+  text-align: center;
+}
+
+.splitview-main .toolbar,
+.splitview-main .toolbar > * {
+  display: -moz-box;
+}
+.splitview-main .toolbar {
+  -moz-box-flex: 1;
+  -moz-box-orient: horizontal;
+  -moz-padding-start: 3px;
+}
+
+a {
+  color: -moz-hyperlinktext;
+  text-decoration: underline;
+}
+
+button {
+  margin: 0;
+}
+
+/* limited width mode (hide search unless it has focus [search-on-type]) */
+@media (max-width: 250px) {
+  .splitview-filter {
+    max-width: none;
+    position: fixed;
+    margin: 0;
+    bottom: -4em;
+    -moz-transition-property: bottom;
+    -moz-transition-duration: 0.2s;
+  }
+  .splitview-filter[focused="true"] {
+    bottom: 0;
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/themes/winstripe/devtools/styleeditor.css
@@ -0,0 +1,177 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Style Editor code.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Cedric Vivier <cedricv@neonux.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+ol.splitview-nav:focus {
+  outline: 0; /* focus ring is on the stylesheet name */
+}
+
+.splitview-nav > li:-moz-locale-dir(ltr),
+.splitview-nav > li hgroup:-moz-locale-dir(ltr) {
+  float: left;
+}
+.splitview-nav > li:-moz-locale-dir(rtl),
+.splitview-nav > li hgroup:-moz-locale-dir(rtl) {
+  float: right;
+}
+.splitview-nav > li > hgroup.stylesheet-info {
+  max-width: 66%;
+}
+.splitview-nav > li > hgroup .stylesheet-name {
+  display: inline-block;
+  width: 100%;
+  max-width: 34ex;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.splitview-nav > li.unsaved > hgroup .stylesheet-name {
+  font-style: italic;
+}
+.splitview-nav > li.unsaved > hgroup .stylesheet-name:before:-moz-locale-dir(ltr),
+.splitview-nav > li.unsaved > hgroup .stylesheet-name:after:-moz-locale-dir(rtl) {
+  font-style: italic;
+  content: "* ";
+}
+
+.splitview-nav > li > .stylesheet-enabled {
+  float: left;
+  width: 20px;
+  height: 20px;
+  -moz-margin-end: 6px;
+  border: 1px solid transparent;
+  background-image: url(eye-toggle.png);
+  background-position: center center;
+  background-repeat: no-repeat;
+}
+.splitview-nav > li > .stylesheet-enabled:-moz-locale-dir(rtl) {
+  float: right;
+}
+.splitview-nav > li.disabled > .stylesheet-enabled {
+  background-image: none;
+}
+.splitview-nav > li > .stylesheet-enabled:focus,
+.splitview-nav > li:hover > .stylesheet-enabled {
+  outline: 0;
+  border: 1px inset WindowFrame;
+  -moz-margin-end: 6px;
+}
+
+.splitview-nav > li hgroup .stylesheet-title {
+  color: GrayText;
+  font-size: 0.8em;
+}
+.splitview-nav > li.error hgroup > .stylesheet-error-message {
+  color: red;
+  font-size: 0.8em;
+}
+
+.splitview-nav > li > .stylesheet-more {
+  position: relative;
+  right: 0;
+}
+.splitview-nav > li > .stylesheet-more:-moz-locale-dir(rtl) {
+  left: 0;
+}
+
+.splitview-nav > li hgroup.stylesheet-stats,
+.splitview-nav > li hgroup.stylesheet-actions {
+  position: absolute;
+  z-index: 2;
+  -moz-transition-property: left, right;
+  -moz-transition-duration: 0.2s;
+  -moz-transition-delay: 0.2s;
+  -moz-transition-timing-function: ease-in-out;
+}
+
+.splitview-nav > li hgroup.stylesheet-stats {
+  z-index: 1;
+  -moz-transition-delay: 0.4s;
+  color: GrayText;
+  padding-left: 6px; /* Fitts */
+  padding-right: 6px;
+}
+.splitview-nav > li hgroup.stylesheet-actions a {
+  color: ButtonText;
+  padding-left: 6px; /* Fitts */
+  padding-right: 6px;
+}
+
+.splitview-nav > li hgroup.stylesheet-actions:-moz-locale-dir(ltr),
+.splitview-nav > li:hover hgroup.stylesheet-stats:-moz-locale-dir(ltr),
+.splitview-nav > li:focus hgroup.stylesheet-stats:-moz-locale-dir(ltr),
+.splitview-nav > li.splitview-active hgroup.stylesheet-stats:-moz-locale-dir(ltr) {
+  right: -50ex;
+}
+.splitview-nav > li hgroup.stylesheet-actions:-moz-locale-dir(rtl),
+.splitview-nav > li:hover hgroup.stylesheet-stats:-moz-locale-dir(rtl),
+.splitview-nav > li:focus hgroup.stylesheet-stats:-moz-locale-dir(rtl),
+.splitview-nav > li.splitview-active hgroup.stylesheet-stats:-moz-locale-dir(rtl) {
+  left: -50ex;
+}
+.splitview-nav > li hgroup.stylesheet-stats:-moz-locale-dir(ltr),
+.splitview-nav > li:hover hgroup.stylesheet-actions:-moz-locale-dir(ltr),
+.splitview-nav > li:focus hgroup.stylesheet-actions:-moz-locale-dir(ltr),
+.splitview-nav > li.splitview-active hgroup.stylesheet-actions:-moz-locale-dir(ltr) {
+  right: 0;
+}
+.splitview-nav > li hgroup.stylesheet-stats:-moz-locale-dir(rtl),
+.splitview-nav > li:hover hgroup.stylesheet-actions:-moz-locale-dir(rtl),
+.splitview-nav > li:focus hgroup.stylesheet-actions:-moz-locale-dir(rtl),
+.splitview-nav > li.splitview-active hgroup.stylesheet-actions:-moz-locale-dir(rtl) {
+  left: 0;
+}
+
+.stylesheet-editor-input {
+  display: -moz-box;
+  -moz-box-flex: 1;
+  overflow: hidden;
+  min-height: 8em;
+  margin: 3px;
+  margin-top: 0;
+  border: 1px inset WindowFrame;
+  border-radius: 4px;
+  background-color: -moz-default-background-color;
+}
+
+h1,
+h2,
+h3 {
+  font-size: inherit;
+  font-weight: normal;
+  margin: 0;
+  padding: 0;
+}
--- a/browser/themes/winstripe/jar.mn
+++ b/browser/themes/winstripe/jar.mn
@@ -130,17 +130,19 @@ browser.jar:
         skin/classic/browser/devtools/breadcrumbs/rtl-middle-pressed.png           (devtools/breadcrumbs/rtl-middle-pressed.png)
         skin/classic/browser/devtools/breadcrumbs/rtl-middle-selected-pressed.png  (devtools/breadcrumbs/rtl-middle-selected-pressed.png)
         skin/classic/browser/devtools/breadcrumbs/rtl-middle-selected.png          (devtools/breadcrumbs/rtl-middle-selected.png)
         skin/classic/browser/devtools/breadcrumbs/rtl-middle.png                   (devtools/breadcrumbs/rtl-middle.png)
         skin/classic/browser/devtools/breadcrumbs/rtl-start-pressed.png            (devtools/breadcrumbs/rtl-start-pressed.png)
         skin/classic/browser/devtools/breadcrumbs/rtl-start-selected-pressed.png   (devtools/breadcrumbs/rtl-start-selected-pressed.png)
         skin/classic/browser/devtools/breadcrumbs/rtl-start.png                    (devtools/breadcrumbs/rtl-start.png)
         skin/classic/browser/devtools/breadcrumbs/rtl-start-selected.png           (devtools/breadcrumbs/rtl-start-selected.png)
-
+        skin/classic/browser/devtools/splitview.css                 (devtools/splitview.css)
+        skin/classic/browser/devtools/styleeditor.css               (devtools/styleeditor.css)
+        skin/classic/browser/devtools/eye-toggle.png                (devtools/eye-toggle.png)
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/browser/sync-throbber.png
         skin/classic/browser/sync-16.png
         skin/classic/browser/sync-32.png
         skin/classic/browser/sync-128.png
         skin/classic/browser/sync-bg.png
         skin/classic/browser/sync-desktopIcon.png
         skin/classic/browser/sync-mobileIcon.png
@@ -282,17 +284,19 @@ browser.jar:
         skin/classic/aero/browser/devtools/breadcrumbs/rtl-middle-pressed.png           (devtools/breadcrumbs/rtl-middle-pressed.png)
         skin/classic/aero/browser/devtools/breadcrumbs/rtl-middle-selected-pressed.png  (devtools/breadcrumbs/rtl-middle-selected-pressed.png)
         skin/classic/aero/browser/devtools/breadcrumbs/rtl-middle-selected.png          (devtools/breadcrumbs/rtl-middle-selected.png)
         skin/classic/aero/browser/devtools/breadcrumbs/rtl-middle.png                   (devtools/breadcrumbs/rtl-middle.png)
         skin/classic/aero/browser/devtools/breadcrumbs/rtl-start-pressed.png            (devtools/breadcrumbs/rtl-start-pressed.png)
         skin/classic/aero/browser/devtools/breadcrumbs/rtl-start-selected-pressed.png   (devtools/breadcrumbs/rtl-start-selected-pressed.png)
         skin/classic/aero/browser/devtools/breadcrumbs/rtl-start.png                    (devtools/breadcrumbs/rtl-start.png)
         skin/classic/aero/browser/devtools/breadcrumbs/rtl-start-selected.png           (devtools/breadcrumbs/rtl-start-selected.png)
-
+        skin/classic/aero/browser/devtools/splitview.css             (devtools/splitview.css)
+        skin/classic/aero/browser/devtools/styleeditor.css           (devtools/styleeditor.css)
+        skin/classic/aero/browser/devtools/eye-toggle.png            (devtools/eye-toggle.png)
 #ifdef MOZ_SERVICES_SYNC
         skin/classic/aero/browser/sync-throbber.png
         skin/classic/aero/browser/sync-16.png
         skin/classic/aero/browser/sync-32.png
         skin/classic/aero/browser/sync-128.png
         skin/classic/aero/browser/sync-bg.png
         skin/classic/aero/browser/sync-desktopIcon.png
         skin/classic/aero/browser/sync-mobileIcon.png