merge with mozilla-central 2ab636cf01f3
authorDoug Turner <dougt@dougt.org>
Fri, 18 Nov 2011 10:38:37 -0800
changeset 81902 b209ff13993adc1dc85bd0272a6c572d581332a6
parent 81901 02f6f69e8e2fc20e1cdb4d51c0c30000a4093f6c (current diff)
parent 80448 2ab636cf01f325ff6e2c99bf75386b9f78b90ae6 (diff)
child 81903 0695a3352ef8a1f22399c5660cf23c27ddc84cf8
push idunknown
push userunknown
push dateunknown
milestone11.0a1
merge with mozilla-central 2ab636cf01f3
browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.htm
browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.js
content/base/src/nsFrameLoader.h
toolkit/library/libxul-config.mk
widget/src/android/AndroidBridge.cpp
widget/src/android/AndroidBridge.h
widget/src/android/AndroidJavaWrappers.cpp
widget/src/android/AndroidJavaWrappers.h
widget/src/android/nsIAndroidBridge.idl
widget/src/android/nsWindow.cpp
xpcom/base/nsConsoleService.cpp
--- a/accessible/src/base/NotificationController.cpp
+++ b/accessible/src/base/NotificationController.cpp
@@ -587,29 +587,29 @@ NotificationController::CoalesceSelChang
   }
 
   // Unpack the packed selection change event because we've got one
   // more selection add/remove.
   if (aThisEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION) {
     if (aThisEvent->mPackedEvent) {
       aThisEvent->mPackedEvent->mEventType =
         aThisEvent->mPackedEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ?
-          static_cast<PRUint32>(nsIAccessibleEvent::EVENT_SELECTION_ADD) :
-          static_cast<PRUint32>(nsIAccessibleEvent::EVENT_SELECTION_REMOVE);
+          nsIAccessibleEvent::EVENT_SELECTION_ADD :
+          nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
 
       aThisEvent->mPackedEvent->mEventRule =
         AccEvent::eCoalesceSelectionChange;
 
       aThisEvent->mPackedEvent = nsnull;
     }
 
     aThisEvent->mEventType =
       aThisEvent->mSelChangeType == AccSelChangeEvent::eSelectionAdd ?
-        static_cast<PRUint32>(nsIAccessibleEvent::EVENT_SELECTION_ADD) :
-        static_cast<PRUint32>(nsIAccessibleEvent::EVENT_SELECTION_REMOVE);
+        nsIAccessibleEvent::EVENT_SELECTION_ADD :
+        nsIAccessibleEvent::EVENT_SELECTION_REMOVE;
 
     return;
   }
 
   // Convert into selection add since control has single selection but other
   // selection events for this control are queued.
   if (aTailEvent->mEventType == nsIAccessibleEvent::EVENT_SELECTION)
     aTailEvent->mEventType = nsIAccessibleEvent::EVENT_SELECTION_ADD;
--- a/accessible/src/base/nsARIAMap.cpp
+++ b/accessible/src/base/nsARIAMap.cpp
@@ -435,26 +435,28 @@ nsRoleMapEntry nsARIAMap::gWAIRoleMap[] 
   },
   {
     "separator",
     nsIAccessibleRole::ROLE_SEPARATOR,
     kUseMapRole,
     eNoValue,
     eNoAction,
     eNoLiveAttr,
-    kNoReqStates
+    kNoReqStates,
+    eARIAOrientation
   },
   {
     "slider",
     nsIAccessibleRole::ROLE_SLIDER,
     kUseMapRole,
     eHasValueMinMax,
     eNoAction,
     eNoLiveAttr,
     kNoReqStates,
+    eARIAOrientation,
     eARIAReadonly
   },
   {
     "spinbutton",
     nsIAccessibleRole::ROLE_SPINBUTTON,
     kUseMapRole,
     eHasValueMinMax,
     eNoAction,
@@ -644,18 +646,18 @@ nsStateMapEntry nsARIAMap::gWAIStateMap[
                   0, states::MULTI_LINE, states::SINGLE_LINE, true),
 
   // eARIAMultiSelectable
   nsStateMapEntry(&nsGkAtoms::aria_multiselectable, kBoolType,
                   0, states::MULTISELECTABLE | states::EXTSELECTABLE),
 
   // eARIAOrientation
   nsStateMapEntry(&nsGkAtoms::aria_orientation, eUseFirstState,
-                  "vertical", states::VERTICAL,
-                  "horizontal", states::HORIZONTAL),
+                  "horizontal", states::HORIZONTAL,
+                  "vertical", states::VERTICAL),
 
   // eARIAPressed
   nsStateMapEntry(&nsGkAtoms::aria_pressed, kMixedType,
                   states::CHECKABLE, states::PRESSED),
 
   // eARIAReadonly
   nsStateMapEntry(&nsGkAtoms::aria_readonly, kBoolType,
                   0, states::READONLY),
--- a/accessible/src/msaa/ia2AccessibleRelation.cpp
+++ b/accessible/src/msaa/ia2AccessibleRelation.cpp
@@ -186,17 +186,17 @@ ia2AccessibleRelation::get_nTargets(long
 
 STDMETHODIMP
 ia2AccessibleRelation::get_target(long aTargetIndex, IUnknown **aTarget)
 {
 __try {
   if (aTargetIndex < 0 || aTargetIndex >= mTargets.Length() || !aTarget)
     return E_INVALIDARG;
 
-  mTargets[aTargetIndex]->QueryInterface((const nsID&) IID_IUnknown, (void**) aTarget);
+  mTargets[aTargetIndex]->QueryNativeInterface(IID_IUnknown, (void**) aTarget);
   return S_OK;
 
 } __except(nsAccessNodeWrap::FilterA11yExceptions(::GetExceptionCode(), GetExceptionInformation())) { }
   return E_FAIL;
 }
 
 STDMETHODIMP
 ia2AccessibleRelation::get_targets(long aMaxTargets, IUnknown **aTargets,
--- a/accessible/tests/mochitest/states/test_aria.html
+++ b/accessible/tests/mochitest/states/test_aria.html
@@ -119,20 +119,26 @@
       testStates("aria_link_anchor", STATE_SELECTABLE);
 
       // some weak landmarks
       testStates("aria_main_link", STATE_LINKED);
       testStates("aria_navigation_link", STATE_LINKED);
       testStates("aria_main_anchor", STATE_SELECTABLE);
       testStates("aria_navigation_anchor", STATE_SELECTABLE);
 
-      // scrollbar
-      testStates("aria_scrollbar", 0, EXT_STATE_VERTICAL);
+      // aria-orientation (applied to scrollbar, separator, slider)
+      testStates("aria_scrollbar", 0, EXT_STATE_HORIZONTAL);
       testStates("aria_hscrollbar", 0, EXT_STATE_HORIZONTAL);
       testStates("aria_vscrollbar", 0, EXT_STATE_VERTICAL);
+      testStates("aria_separator", 0, EXT_STATE_HORIZONTAL);
+      testStates("aria_hseparator", 0, EXT_STATE_HORIZONTAL);
+      testStates("aria_vseparator", 0, EXT_STATE_VERTICAL);
+      testStates("aria_slider", 0, EXT_STATE_HORIZONTAL);
+      testStates("aria_hslider", 0, EXT_STATE_HORIZONTAL);
+      testStates("aria_vslider", 0, EXT_STATE_VERTICAL);
 
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 
@@ -155,16 +161,21 @@
      title="Unify ARIA state attributes mapping rules">
     Mozilla Bug 499653
   </a>
   <a target="_blank"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=681674"
      title="aria-autocomplete not supported on standard form text input controls">
     Mozilla Bug 681674
   </a>
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=681674"
+     title="aria-orientation should be applied to separator and slider roles">
+    Mozilla Bug 681674
+  </a>
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <div id="textbox_autocomplete_inline" role="textbox" aria-autocomplete="inline"></div>
   <div id="textbox_autocomplete_list" role="textbox" aria-autocomplete="list"></div>
   <div id="textbox_autocomplete_both" role="textbox" aria-autocomplete="both"></div>
@@ -222,14 +233,20 @@
   <a id="aria_main_link" role="main" href="foo">main</a>
   <a id="aria_navigation_link" role="navigation" href="foo">nav</a>
   
   <!-- landmarks: anchors -->
   <a id="aria_application_anchor" role="application" name="app_anchor">app</a>
   <a id="aria_main_anchor" role="main" name="main_anchor">main</a>
   <a id="aria_navigation_anchor" role="navigation" name="nav_anchor">nav</a>
 
-  <!-- scrollbar -->
-  <div id="aria_scrollbar" role="scrollbar">scrollbar</a>
-  <div id="aria_hscrollbar" role="scrollbar" aria-orientation="horizontal">horizontal scrollbar</a>
-  <div id="aria_vscrollbar" role="scrollbar" aria-orientation="vertical">vertical scrollbar</a>
+  <!-- aria-orientation -->
+  <div id="aria_scrollbar" role="scrollbar">scrollbar</div>
+  <div id="aria_hscrollbar" role="scrollbar" aria-orientation="horizontal">horizontal scrollbar</div>
+  <div id="aria_vscrollbar" role="scrollbar" aria-orientation="vertical">vertical scrollbar</div>
+  <div id="aria_separator" role="separator">separator</div>
+  <div id="aria_hseparator" role="separator" aria-orientation="horizontal">horizontal separator</div>
+  <div id="aria_vseparator" role="separator" aria-orientation="vertical">vertical separator</div>
+  <div id="aria_slider" role="slider">slider</div>
+  <div id="aria_hslider" role="slider" aria-orientation="horizontal">horizontal slider</div>
+  <div id="aria_vslider" role="slider" aria-orientation="vertical">vertical slider</div>
 </body>
 </html>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -49,19 +49,18 @@
 #endif
 
 pref("browser.chromeURL","chrome://browser/content/");
 pref("browser.hiddenWindowChromeURL", "chrome://browser/content/hiddenWindow.xul");
 
 // Enables some extra Extension System Logging (can reduce performance)
 pref("extensions.logging.enabled", false);
 
-// Enables strict compatibility. To be toggled in bug 698653, to make addons
-// compatibile by default.
-pref("extensions.strictCompatibility", true);
+// Disables strict compatibility, making addons compatible-by-default.
+pref("extensions.strictCompatibility", false);
 
 // Specifies a minimum maxVersion an addon needs to say it's compatible with
 // for it to be compatible by default.
 pref("extensions.minCompatibleAppVersion", "4.0");
 
 // Preferences for AMO integration
 pref("extensions.getAddons.cache.enabled", true);
 pref("extensions.getAddons.maxResults", 15);
@@ -1014,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
@@ -8964,16 +8974,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/base/content/content.js
+++ b/browser/base/content/content.js
@@ -31,19 +31,19 @@
 # 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 *****
 
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
+let Cc = Components.classes;
+let Ci = Components.interfaces;
+let Cu = Components.utils;
 
 // Bug 671101 - directly using webNavigation in this context
 // causes docshells to leak
 __defineGetter__("webNavigation", function () {
   return docShell.QueryInterface(Ci.nsIWebNavigation);
 });
 
 addMessageListener("WebNavigation:LoadURI", function (message) {
--- a/browser/components/places/tests/browser/browser_drag_bookmarks_on_toolbar.js
+++ b/browser/components/places/tests/browser/browser_drag_bookmarks_on_toolbar.js
@@ -255,20 +255,20 @@ var gTests = [
         });
     }
   },
 ];
 
 function nextTest() {
   if (gTests.length) {
     var test = gTests.shift();
-    info("Start of test: " + test.desc);
-    test.run();
-
-    waitForFocus(nextTest);
+    waitForFocus(function() {
+      info("Start of test: " + test.desc);
+      test.run();
+    });
   }
   else {
     // Collapse the personal toolbar if needed.
     if (wasCollapsed)
       setToolbarVisibility(toolbar, false);
     finish();
   }
 }
@@ -278,11 +278,11 @@ let wasCollapsed = toolbar.collapsed;
 
 function test() {
   waitForExplicitFinish();
 
   // Uncollapse the personal toolbar if needed.
   if (wasCollapsed)
     setToolbarVisibility(toolbar, true);
 
-  waitForFocus(nextTest);
+  nextTest();
 }
 
--- a/browser/components/tabview/content.js
+++ b/browser/components/tabview/content.js
@@ -31,17 +31,17 @@
  * 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 Cu = Components.utils;
+let Cu = Components.utils;
 
 Cu.import("resource:///modules/tabview/utils.jsm");
 
 // Bug 671101 - directly using webProgress in this context
 // causes docShells to leak
 __defineGetter__("webProgress", function () {
   let ifaceReq = docShell.QueryInterface(Ci.nsIInterfaceRequestor);
   return ifaceReq.getInterface(Ci.nsIWebProgress);
--- 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)
+
--- a/browser/devtools/scratchpad/scratchpad.js
+++ b/browser/devtools/scratchpad/scratchpad.js
@@ -486,20 +486,22 @@ var Scratchpad = {
 
     return propPanel;
   },
 
   // Menu Operations
 
   /**
    * Open a new Scratchpad window.
+   *
+   * @return nsIWindow
    */
   openScratchpad: function SP_openScratchpad()
   {
-    ScratchpadManager.openScratchpad();
+    return ScratchpadManager.openScratchpad();
   },
 
   /**
    * Export the textbox content to a file.
    *
    * @param nsILocalFile aFile
    *        The file where you want to save the textbox content.
    * @param boolean aNoConfirmation
@@ -763,16 +765,18 @@ var Scratchpad = {
     this.editor.setCaretOffset(this.editor.getCharCount());
     
     if (this.filename && !this.saved) {
       this.onTextChanged();
     }
     else if (this.filename && this.saved) {
       this.onTextSaved();
     }
+
+    this._triggerObservers("Ready");
   },
 
   /**
    * Insert text at the current caret location.
    *
    * @param string aText
    *        The text you want to insert.
    */
@@ -872,16 +876,78 @@ var Scratchpad = {
     }
 
     this.resetContext();
     this.editor.removeEventListener(SourceEditor.EVENTS.CONTEXT_MENU,
                                     this.onContextMenu);
     this.editor.destroy();
     this.editor = null;
   },
+
+  _observers: [],
+
+  /**
+   * Add an observer for Scratchpad events.
+   *
+   * The observer implements IScratchpadObserver := {
+   *   onReady:      Called when the Scratchpad and its SourceEditor are ready.
+   *                 Arguments: (Scratchpad aScratchpad)
+   * }
+   *
+   * All observer handlers are optional.
+   *
+   * @param IScratchpadObserver aObserver
+   * @see removeObserver
+   */
+  addObserver: function SP_addObserver(aObserver)
+  {
+    this._observers.push(aObserver);
+  },
+
+  /**
+   * Remove an observer for Scratchpad events.
+   *
+   * @param IScratchpadObserver aObserver
+   * @see addObserver
+   */
+  removeObserver: function SP_removeObserver(aObserver)
+  {
+    let index = this._observers.indexOf(aObserver);
+    if (index != -1) {
+      this._observers.splice(index, 1);
+    }
+  },
+
+  /**
+   * Trigger named handlers in Scratchpad observers.
+   *
+   * @param string aName
+   *        Name of the handler to trigger.
+   * @param Array aArgs
+   *        Optional array of arguments to pass to the observer(s).
+   * @see addObserver
+   */
+  _triggerObservers: function SP_triggerObservers(aName, aArgs)
+  {
+    // insert this Scratchpad instance as the first argument
+    if (!aArgs) {
+      aArgs = [this];
+    } else {
+      aArgs.unshift(this);
+    }
+
+    // trigger all observers that implement this named handler
+    for (let i = 0; i < this._observers.length; ++i) {
+      let observer = this._observers[i];
+      let handler = observer["on" + aName];
+      if (handler) {
+        handler.apply(observer, aArgs);
+      }
+    }
+  }
 };
 
 XPCOMUtils.defineLazyGetter(Scratchpad, "strings", function () {
   return Services.strings.createBundle(SCRATCHPAD_L10N);
 });
 
 addEventListener("DOMContentLoaded", Scratchpad.onLoad.bind(Scratchpad), false);
 addEventListener("unload", Scratchpad.onUnload.bind(Scratchpad), false);
--- a/browser/devtools/scratchpad/test/Makefile.in
+++ b/browser/devtools/scratchpad/test/Makefile.in
@@ -53,11 +53,12 @@ include $(topsrcdir)/config/rules.mk
 		browser_scratchpad_ui.js \
 		browser_scratchpad_bug_646070_chrome_context_pref.js \
 		browser_scratchpad_bug_660560_tab.js \
 		browser_scratchpad_open.js \
 		browser_scratchpad_restore.js \
 		browser_scratchpad_bug_679467_falsy.js \
 		browser_scratchpad_bug_699130_edit_ui_updates.js \
 		browser_scratchpad_bug_669612_unsaved.js \
+		head.js \
 
 libs:: $(_BROWSER_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_646070_chrome_context_pref.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_646070_chrome_context_pref.js
@@ -1,15 +1,12 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-// Reference to the Scratchpad chrome window object.
-let gScratchpadWindow;
-
 let gOldPref;
 let DEVTOOLS_CHROME_ENABLED = "devtools.chrome.enabled";
 
 function test()
 {
   waitForExplicitFinish();
 
   gOldPref = Services.prefs.getBoolPref(DEVTOOLS_CHROME_ENABLED);
@@ -50,13 +47,10 @@ function runTests()
   let chromeContextCommand = gScratchpadWindow.document.
                             getElementById("sp-cmd-browserContext");
   ok(chromeContextCommand, "Chrome context command element exists");
   ok(!chromeContextCommand.hasAttribute("disabled"),
      "Chrome context command is disabled");
 
   Services.prefs.setBoolPref(DEVTOOLS_CHROME_ENABLED, gOldPref);
 
-  gScratchpadWindow.close();
-  gScratchpadWindow = null;
-  gBrowser.removeCurrentTab();
   finish();
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_660560_tab.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_660560_tab.js
@@ -1,41 +1,47 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-// Reference to the Scratchpad chrome window object.
-let gScratchpadWindow;
+var ScratchpadManager = Scratchpad.ScratchpadManager;
 
 function test()
 {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function() {
-    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
+  gBrowser.selectedBrowser.addEventListener("load", function onTabLoad() {
+    gBrowser.selectedBrowser.removeEventListener("load", onTabLoad, true);
 
     ok(window.Scratchpad, "Scratchpad variable exists");
 
     Services.prefs.setIntPref("devtools.editor.tabsize", 5);
 
     gScratchpadWindow = Scratchpad.openScratchpad();
-    gScratchpadWindow.addEventListener("load", runTests, false);
+    gScratchpadWindow.addEventListener("load", function onScratchpadLoad() {
+      gScratchpadWindow.removeEventListener("load", onScratchpadLoad, false);
+
+      gScratchpadWindow.Scratchpad.addObserver({
+        onReady: runTests
+      });
+    }, false);
   }, true);
 
   content.location = "data:text/html,Scratchpad test for the Tab key, bug 660560";
 }
 
 function runTests()
 {
-  gScratchpadWindow.removeEventListener("load", arguments.callee, false);
-
   let sp = gScratchpadWindow.Scratchpad;
   ok(sp, "Scratchpad object exists in new window");
 
+  is(this.onReady, runTests, "the handler runs in the context of the observer");
+  sp.removeObserver(this);
+
   ok(sp.editor.hasFocus(), "the editor has focus");
 
   sp.setText("window.foo;");
   sp.editor.setCaretOffset(0);
 
   EventUtils.synthesizeKey("VK_TAB", {}, gScratchpadWindow);
 
   is(sp.getText(), "     window.foo;", "Tab key added 5 spaces");
@@ -58,36 +64,37 @@ function runTests()
   is(sp.getText(), "     w    omgindow.foo;", "insertTextAtCaret() works");
 
   is(sp.editor.getCaretOffset(), 13, "caret location is correct after update");
 
   gScratchpadWindow.close();
 
   Services.prefs.setIntPref("devtools.editor.tabsize", 6);
   Services.prefs.setBoolPref("devtools.editor.expandtab", false);
+
   gScratchpadWindow = Scratchpad.openScratchpad();
-  gScratchpadWindow.addEventListener("load", runTests2, false);
+  gScratchpadWindow.addEventListener("load", function onScratchpadLoad() {
+    gScratchpadWindow.removeEventListener("load", onScratchpadLoad, false);
+    gScratchpadWindow.Scratchpad.addObserver({
+      onReady: runTests2
+    });
+  }, false);
 }
 
 function runTests2()
 {
-  gScratchpadWindow.removeEventListener("load", arguments.callee, false);
-
   let sp = gScratchpadWindow.Scratchpad;
+  sp.removeObserver(this);
 
   sp.setText("window.foo;");
   sp.editor.setCaretOffset(0);
 
   EventUtils.synthesizeKey("VK_TAB", {}, gScratchpadWindow);
 
   is(sp.getText(), "\twindow.foo;", "Tab key added the tab character");
 
   is(sp.editor.getCaretOffset(), 1, "caret location is correct");
 
   Services.prefs.clearUserPref("devtools.editor.tabsize");
   Services.prefs.clearUserPref("devtools.editor.expandtab");
 
-  gScratchpadWindow.close();
-  gScratchpadWindow = null;
-
-  gBrowser.removeCurrentTab();
   finish();
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_669612_unsaved.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_669612_unsaved.js
@@ -27,114 +27,144 @@ function test()
 
   content.location = "data:text/html,<p>test star* UI for unsaved file changes";
 }
 
 function testListeners()
 {
   let win = ScratchpadManager.openScratchpad();
 
-  win.addEventListener("load", function() {
-    let scratchpad = win.Scratchpad;
+  win.addEventListener("load", function onScratchpadLoad() {
+    win.removeEventListener("load", onScratchpadLoad, false);
 
-    scratchpad.setText("new text");
-    ok(!isStar(win), "no star if scratchpad isn't from a file");
+    win.Scratchpad.addObserver({
+      onReady: function (aScratchpad) {
+        aScratchpad.removeObserver(this);
 
-    scratchpad.onTextSaved(); 
-    ok(!isStar(win), "no star before changing text");
+        aScratchpad.setText("new text");
+        ok(!isStar(win), "no star if scratchpad isn't from a file");
+
+        aScratchpad.onTextSaved();
+        ok(!isStar(win), "no star before changing text");
 
-    scratchpad.setText("new text2");
-    ok(isStar(win), "shows star if scratchpad text changes");
+        aScratchpad.setText("new text2");
+        ok(isStar(win), "shows star if scratchpad text changes");
+
+        aScratchpad.onTextSaved();
+        ok(!isStar(win), "no star if scratchpad was just saved");
 
-    scratchpad.onTextSaved();
-    ok(!isStar(win), "no star if scratchpad was just saved");
-    
-    scratchpad.undo();
-    ok(isStar(win), "star if scratchpad undo");
+        aScratchpad.undo();
+        ok(isStar(win), "star if scratchpad undo");
 
-    win.close();
-    done();
-  });
+        win.close();
+        done();
+      }
+    });
+  }, false);
 }
 
 function testErrorStatus()
 {
   let win = ScratchpadManager.openScratchpad();
 
-  win.addEventListener("load", function() {
-    let scratchpad = win.Scratchpad;
+  win.addEventListener("load", function onScratchpadLoad() {
+    win.removeEventListener("load", onScratchpadLoad, false);
+
+    win.Scratchpad.addObserver({
+      onReady: function (aScratchpad) {
+        aScratchpad.removeObserver(this);
 
-    scratchpad.onTextSaved(Components.results.NS_ERROR_FAILURE);
-    scratchpad.setText("new text");
-    ok(!isStar(win), "no star if file save failed");
+        aScratchpad.onTextSaved(Components.results.NS_ERROR_FAILURE);
+        aScratchpad.setText("new text");
+        ok(!isStar(win), "no star if file save failed");
 
-    win.close();
-    done();
-  });
+        win.close();
+        done();
+      }
+    });
+  }, false);
 }
 
 
 function testRestoreNotFromFile()
 {
   let session = [{
     text: "test1",
     executionContext: 1
   }];
 
   let [win] = ScratchpadManager.restoreSession(session);
-  win.addEventListener("load", function() {
-    let scratchpad = win.Scratchpad;
+  win.addEventListener("load", function onScratchpadLoad() {
+    win.removeEventListener("load", onScratchpadLoad, false);
+
+    win.Scratchpad.addObserver({
+      onReady: function (aScratchpad) {
+        aScratchpad.removeObserver(this);
 
-    scratchpad.setText("new text");
-    ok(!isStar(win), "no star if restored scratchpad isn't from a file");
-    
-    win.close();
-    done();
-  });
+        aScratchpad.setText("new text");
+        ok(!isStar(win), "no star if restored scratchpad isn't from a file");
+
+        win.close();
+        done();
+      }
+    });
+  }, false);
 }
 
 function testRestoreFromFileSaved()
 {
   let session = [{
     filename: "test.js",
     text: "test1",
     executionContext: 1,
     saved: true
   }];
 
   let [win] = ScratchpadManager.restoreSession(session);
-  win.addEventListener("load", function() {
-    let scratchpad = win.Scratchpad;
+  win.addEventListener("load", function onScratchpadLoad() {
+    win.removeEventListener("load", onScratchpadLoad, false);
 
-    ok(!isStar(win), "no star before changing text in scratchpad restored from file");
+    win.Scratchpad.addObserver({
+      onReady: function (aScratchpad) {
+        aScratchpad.removeObserver(this);
+
+        ok(!isStar(win), "no star before changing text in scratchpad restored from file");
 
-    scratchpad.setText("new text");
-    ok(isStar(win), "star when text changed from scratchpad restored from file");
+        aScratchpad.setText("new text");
+        ok(isStar(win), "star when text changed from scratchpad restored from file");
 
-    win.close();
-    done();
-  });
+        win.close();
+        done();
+      }
+    });
+  }, false);
 }
 
 function testRestoreFromFileUnsaved()
 {
   let session = [{
     filename: "test.js",
     text: "test1",
     executionContext: 1,
     saved: false
   }];
 
   let [win] = ScratchpadManager.restoreSession(session);
-  win.addEventListener("load", function() {
-    let scratchpad = win.Scratchpad;
+  win.addEventListener("load", function onScratchpadLoad() {
+    win.removeEventListener("load", onScratchpadLoad, false);
+
+    win.Scratchpad.addObserver({
+      onReady: function (aScratchpad) {
+        aScratchpad.removeObserver(this);
 
-    ok(isStar(win), "star with scratchpad restored with unsaved text");
+        ok(isStar(win), "star with scratchpad restored with unsaved text");
 
-    win.close();
-    done();
-  });
+        win.close();
+        done();
+      }
+    });
+  }, false);
 }
 
 function isStar(win)
 {
   return win.document.title.match(/^\*[^\*]/);
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_679467_falsy.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_679467_falsy.js
@@ -25,19 +25,16 @@ function testFalsy(sp)
   gScratchpadWindow.removeEventListener("load", testFalsy, false);
 
   let sp = gScratchpadWindow.Scratchpad;
   verifyFalsies(sp);
   
   sp.setBrowserContext();
   verifyFalsies(sp);
 
-  gScratchpadWindow.close();
-  gScratchpadWindow = null;
-  gBrowser.removeCurrentTab();
   finish();
 }
 
 function verifyFalsies(sp)
 {
   sp.setText("undefined");
   sp.display();
   is(sp.selectedText, "/*\nundefined\n*/", "'undefined' is displayed");
--- a/browser/devtools/scratchpad/test/browser_scratchpad_bug_699130_edit_ui_updates.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_bug_699130_edit_ui_updates.js
@@ -1,39 +1,35 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 Cu.import("resource:///modules/source-editor.jsm");
 
-// Reference to the Scratchpad chrome window object.
-let gScratchpadWindow;
-
 function test()
 {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
     gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
 
     gScratchpadWindow = Scratchpad.openScratchpad();
-    gScratchpadWindow.addEventListener("load", function onScratchpadLoad() {
-      gScratchpadWindow.removeEventListener("load", onScratchpadLoad, false);
-      waitForFocus(runTests, gScratchpadWindow);
-    }, false);
+    gScratchpadWindow.addEventListener("load", runTests, false);
   }, true);
 
   content.location = "data:text/html,test Edit menu updates Scratchpad - bug 699130";
 }
 
 function runTests()
 {
+  gScratchpadWindow.removeEventListener("load", runTests, false);
+
   let sp = gScratchpadWindow.Scratchpad;
   let doc = gScratchpadWindow.document;
   let winUtils = gScratchpadWindow.QueryInterface(Ci.nsIInterfaceRequestor).
                  getInterface(Ci.nsIDOMWindowUtils);
   let OS = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
 
   info("will test the Edit menu");
 
@@ -121,33 +117,40 @@ function runTests()
 
   let showAfterSelect = function() {
     ok(!cutItem.hasAttribute("disabled"), "cut menuitem is enabled after select");
     closeMenu(hideAfterSelect);
   };
 
   let hideAfterSelect = function() {
     sp.editor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED, onCut);
-    EventUtils.synthesizeKey("x", {accelKey: true}, gScratchpadWindow);
+    waitForFocus(function () {
+      let selectedText = sp.editor.getSelectedText();
+      ok(selectedText.length > 0, "non-empty selected text will be cut");
+
+      EventUtils.synthesizeKey("x", {accelKey: true}, gScratchpadWindow);
+    }, gScratchpadWindow);
   };
 
   let onCut = function() {
     sp.editor.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED, onCut);
     openMenu(12, 12, showAfterCut);
   };
 
   let showAfterCut = function() {
     ok(cutItem.hasAttribute("disabled"), "cut menuitem is disabled after cut");
     ok(!pasteItem.hasAttribute("disabled"), "paste menuitem is enabled after cut");
     closeMenu(hideAfterCut);
   };
 
   let hideAfterCut = function() {
     sp.editor.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED, onPaste);
-    EventUtils.synthesizeKey("v", {accelKey: true}, gScratchpadWindow);
+    waitForFocus(function () {
+      EventUtils.synthesizeKey("v", {accelKey: true}, gScratchpadWindow);
+    }, gScratchpadWindow);
   };
 
   let onPaste = function() {
     sp.editor.removeEventListener(SourceEditor.EVENTS.TEXT_CHANGED, onPaste);
     openMenu(13, 13, showAfterPaste);
   };
 
   let showAfterPaste = function() {
@@ -156,17 +159,17 @@ function runTests()
     closeMenu(hideAfterPaste);
   };
 
   let hideAfterPaste = function() {
     if (pass == 0) {
       pass++;
       testContextMenu();
     } else {
-      finishTest();
+      finish();
     }
   };
 
   let testContextMenu = function() {
     info("will test the context menu");
 
     editMenu = null;
     isContextMenu = true;
@@ -177,17 +180,10 @@ function runTests()
     ok(cutItem, "the Cut menuitem");
     pasteItem = doc.getElementById("menu_paste");
     ok(pasteItem, "the Paste menuitem");
 
     sp.setText("bug 699130: hello world! (context menu)");
     openMenu(10, 10, firstShow);
   };
 
-  let finishTest = function() {
-    gScratchpadWindow.close();
-    gScratchpadWindow = null;
-    gBrowser.removeCurrentTab();
-    finish();
-  };
-
   openMenu(10, 10, firstShow);
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_contexts.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_contexts.js
@@ -118,13 +118,10 @@ function runTests()
   sp.setContentContext();
 
   is(sp.executionContext, gScratchpadWindow.SCRATCHPAD_CONTEXT_CONTENT,
      "executionContext is content");
 
   is(sp.run()[2], "undefined",
      "global variable no longer exists after changing the context");
 
-  gScratchpadWindow.close();
-  gScratchpadWindow = null;
-  gBrowser.removeCurrentTab();
   finish();
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_execute_print.js
@@ -1,15 +1,12 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-// Reference to the Scratchpad chrome window object.
-let gScratchpadWindow;
-
 function test()
 {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
 
@@ -124,13 +121,10 @@ function runTests()
   sp.setText("foo1");
   sp.setText("foo2");
   is(sp.getText(), "foo2", "editor content updated");
   sp.undo();
   is(sp.getText(), "foo1", "undo() works");
   sp.redo();
   is(sp.getText(), "foo2", "redo() works");
 
-  gScratchpadWindow.close();
-  gScratchpadWindow = null;
-  gBrowser.removeCurrentTab();
   finish();
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_files.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_files.js
@@ -133,13 +133,10 @@ function fileRead(aInputStream, aStatus)
     NetUtil.readInputStreamToString(aInputStream, aInputStream.available());;
 
   is(updatedContent, gFileContent, "file properly updated");
 
   // Done!
   gFile.remove(false);
   gFile = null;
   gScratchpad = null;
-  gScratchpadWindow.close();
-  gScratchpadWindow = null;
-  gBrowser.removeCurrentTab();
   finish();
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_initialization.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_initialization.js
@@ -45,13 +45,10 @@ function runTests()
      "Error console command is disabled");
 
   let chromeContextCommand = gScratchpadWindow.document.
                             getElementById("sp-cmd-browserContext");
   ok(chromeContextCommand, "Chrome context command element exists");
   is(chromeContextCommand.getAttribute("disabled"), "true",
      "Chrome context command is disabled");
 
-  gScratchpadWindow.close();
-  gScratchpadWindow = null;
-  gBrowser.removeCurrentTab();
   finish();
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_inspect.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_inspect.js
@@ -50,15 +50,12 @@ function runTests()
         break;
       }
     }
     ok(found, "found the document.title property");
 
     executeSoon(function() {
       propPanel.hidePopup();
 
-      gScratchpadWindow.close();
-      gScratchpadWindow = null;
-      gBrowser.removeCurrentTab();
       finish();
     });
   }, false);
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_open.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_open.js
@@ -23,47 +23,51 @@ function test()
   testOpenWithState();
   testOpenInvalidState();
 }
 
 function testOpen()
 {
   let win = ScratchpadManager.openScratchpad();
 
-  win.addEventListener("load", function() {
+  win.addEventListener("load", function onScratchpadLoad() {
+    win.removeEventListener("load", onScratchpadLoad, false);
+
     is(win.Scratchpad.filename, undefined, "Default filename is undefined");
     is(win.Scratchpad.getText(),
        win.Scratchpad.strings.GetStringFromName("scratchpadIntro"),
        "Default text is loaded")
     is(win.Scratchpad.executionContext, win.SCRATCHPAD_CONTEXT_CONTENT,
       "Default execution context is content");
 
     win.close();
     done();
-  });
+  }, false);
 }
 
 function testOpenWithState()
 {
   let state = {
     filename: "testfile",
     executionContext: 2,
     text: "test text"
   };
 
   let win = ScratchpadManager.openScratchpad(state);
 
-  win.addEventListener("load", function() {
+  win.addEventListener("load", function onScratchpadLoad() {
+    win.removeEventListener("load", onScratchpadLoad, false);
+
     is(win.Scratchpad.filename, state.filename, "Filename loaded from state");
     is(win.Scratchpad.executionContext, state.executionContext, "Execution context loaded from state");
     is(win.Scratchpad.getText(), state.text, "Content loaded from state");
 
     win.close();
     done();
-  });
+  }, false);
 }
 
 function testOpenInvalidState()
 {
   let state = 7;
 
   let win = ScratchpadManager.openScratchpad(state);
   ok(!win, "no scratchpad opened if state is not an object");
--- a/browser/devtools/scratchpad/test/browser_scratchpad_restore.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_restore.js
@@ -44,19 +44,20 @@ function testRestore()
       text: "text3",
       executionContext: 1
     }
   ];
 
   asyncMap(states, function(state, done) {
     // Open some scratchpad windows
     let win = ScratchpadManager.openScratchpad(state);
-    win.addEventListener("load", function() {
+    win.addEventListener("load", function onScratchpadLoad() {
+      removeEventListener("load", onScratchpadLoad, false);
       done(win);
-    })
+    }, false)
   }, function(wins) {
     // Then save the windows to session store
     ScratchpadManager.saveOpenWindows();
 
     // Then get their states
     let session = ScratchpadManager.getSessionState();
 
     // Then close them
@@ -68,21 +69,22 @@ function testRestore()
     ScratchpadManager.saveOpenWindows();
 
     // Then restore them
     let restoredWins = ScratchpadManager.restoreSession(session);
 
     is(restoredWins.length, 3, "Three scratchad windows restored");
 
     asyncMap(restoredWins, function(restoredWin, done) {
-      restoredWin.addEventListener("load", function() {
+      restoredWin.addEventListener("load", function onScratchpadLoad() {
+        restoredWin.removeEventListener("load", onScratchpadLoad, false);
         let state = restoredWin.Scratchpad.getState();
         restoredWin.close();
         done(state);
-      });
+      }, false);
     }, function(restoredStates) {
       // Then make sure they were restored with the right states
       ok(statesMatch(restoredStates, states),
         "All scratchpad window states restored correctly");
 
       // Yay, we're done!
       finish();
     });
--- a/browser/devtools/scratchpad/test/browser_scratchpad_tab_switch.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_tab_switch.js
@@ -94,17 +94,13 @@ function runTests2() {
 
 function runTests3() {
   gBrowser.selectedBrowser.removeEventListener("load", runTests3, true);
   // Check that the sandbox is not cached.
 
   sp.setText("typeof foosbug653108;");
   is(sp.run()[2], "undefined", "global variable does not exist");
 
-  gScratchpadWindow.close();
-  gScratchpadWindow = null;
   tab1 = null;
   tab2 = null;
   sp = null;
-  gBrowser.removeCurrentTab();
-  gBrowser.removeCurrentTab();
   finish();
 }
--- a/browser/devtools/scratchpad/test/browser_scratchpad_ui.js
+++ b/browser/devtools/scratchpad/test/browser_scratchpad_ui.js
@@ -70,13 +70,10 @@ function runTests()
     ok(lastMethodCalled == methodName,
        "method " + methodName + " invoked by the associated menuitem");
 
     sp[methodName] = oldMethod;
   }
 
   delete sp.__noSuchMethod__;
 
-  gScratchpadWindow.close();
-  gScratchpadWindow = null;
-  gBrowser.removeCurrentTab();
   finish();
 }
new file mode 100644
--- /dev/null
+++ b/browser/devtools/scratchpad/test/head.js
@@ -0,0 +1,20 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let gScratchpadWindow; // Reference to the Scratchpad chrome window object
+
+function cleanup()
+{
+  if (gScratchpadWindow) {
+    gScratchpadWindow.close();
+    gScratchpadWindow = null;
+  }
+  while (gBrowser.tabs.length > 1) {
+    gBrowser.removeCurrentTab();
+  }
+}
+
+registerCleanupFunction(cleanup);
--- a/browser/devtools/sourceeditor/orion/README
+++ b/browser/devtools/sourceeditor/orion/README
@@ -20,16 +20,18 @@ Orion version: git clone from 2011-10-26
     https://github.com/mihaisucan/orion.client/tree/bug-362107
       see https://bugs.eclipse.org/bugs/show_bug.cgi?id=362107
   + patch for Eclipse Bug 362428 - _getXToOffset() throws:
     https://github.com/mihaisucan/orion.client/tree/bug-362428
       see https://bugs.eclipse.org/bugs/show_bug.cgi?id=362428
   + patch for Eclipse Bug 362835 - Pasted HTML shows twice:
     https://github.com/mihaisucan/orion.client/tree/bug-362835
       see https://bugs.eclipse.org/bugs/show_bug.cgi?id=362835
+  + patch for Eclipse Bug 363508 - Selection is broken after TextView hide/unhide
+    see https://bugs.eclipse.org/bugs/show_bug.cgi?id=363508
 
 # License
 
 The following files are licensed according to the contents in the LICENSE
 file:
   orion.js
   orion.css
 
--- a/browser/devtools/sourceeditor/orion/orion.js
+++ b/browser/devtools/sourceeditor/orion/orion.js
@@ -1947,17 +1947,17 @@ if (typeof window !== "undefined" && typ
  * All rights reserved. This program and the accompanying materials are made 
  * available under the terms of the Eclipse Public License v1.0 
  * (http://www.eclipse.org/legal/epl-v10.html), and the Eclipse Distribution 
  * License v1.0 (http://www.eclipse.org/org/documents/edl-v10.html). 
  * 
  * Contributors: 
  *		Felipe Heidrich (IBM Corporation) - initial API and implementation
  *		Silenio Quarti (IBM Corporation) - initial API and implementation
- *		Mihai Sucan (Mozilla Foundation) - fix for Bug#334583 Bug#348471 Bug#349485 Bug#350595 Bug#360726 Bug#361180 Bug#358623 Bug#362286 Bug#362107 Bug#362428 Bug#362835
+ *		Mihai Sucan (Mozilla Foundation) - fix for Bug#334583 Bug#348471 Bug#349485 Bug#350595 Bug#360726 Bug#361180 Bug#358623 Bug#362286 Bug#362107 Bug#362428 Bug#362835 Bug#363508
  ******************************************************************************/
 
 /*global window document navigator setTimeout clearTimeout XMLHttpRequest define */
 
 /**
  * @namespace The global container for Orion APIs.
  */ 
 var orion = orion || {};
@@ -3859,16 +3859,18 @@ orion.textview.TextView = (function() {
 				}
 
 				this._isMouseDown = true;
 				this._handleMouse(e);
 				if (isOpera || isChrome) {
 					if (!this._hasFocus) {
 						this.focus();
 					}
+				}
+				if (e.preventDefault) {
 					e.preventDefault();
 				}
 			}
 		},
 		_handleMouseMove: function (e) {
 			if (!e) { e = window.event; }
 			this._setLinksVisible(!this._isMouseDown && (isMac ? e.metaKey : e.ctrlKey));
 			if (!this._isMouseDown) {
--- a/browser/devtools/sourceeditor/source-editor-orion.jsm
+++ b/browser/devtools/sourceeditor/source-editor-orion.jsm
@@ -217,17 +217,27 @@ SourceEditor.prototype = {
       this._view.setKeyBinding(binding, aKey.action);
 
       if (aKey.callback) {
         this._view.setAction(aKey.action, aKey.callback);
       }
     }, this);
 
     if (aCallback) {
-      aCallback(this);
+      let iframe = this._view._frame;
+      let document = iframe.contentDocument;
+      if (!document || document.readyState != "complete") {
+        let onIframeLoad = function () {
+          iframe.contentWindow.removeEventListener("load", onIframeLoad, false);
+          aCallback(this);
+        }.bind(this);
+        iframe.contentWindow.addEventListener("load", onIframeLoad, false);
+      } else {
+        aCallback(this);
+      }
     }
   },
 
   /**
    * Initialize the custom Orion editor features.
    * @private
    */
   _initEditorFeatures: function SE__initEditorFeatures()
@@ -543,16 +553,38 @@ SourceEditor.prototype = {
    * Focus the editor.
    */
   focus: function SE_focus()
   {
     this._view.focus();
   },
 
   /**
+   * Get the first visible line number.
+   *
+   * @return number
+   *         The line number, counting from 0.
+   */
+  getTopIndex: function SE_getTopIndex()
+  {
+    return this._view.getTopIndex();
+  },
+
+  /**
+   * Set the first visible line number.
+   *
+   * @param number aTopIndex
+   *         The line number, counting from 0.
+   */
+  setTopIndex: function SE_setTopIndex(aTopIndex)
+  {
+    this._view.setTopIndex(aTopIndex);
+  },
+
+  /**
    * Check if the editor has focus.
    *
    * @return boolean
    *         True if the editor is focused, false otherwise.
    */
   hasFocus: function SE_hasFocus()
   {
     return this._iframe.ownerDocument.activeElement === this._iframe;
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,489 @@
+/* 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);
+
+      if (binding.onHide) {
+        binding.onHide(this._activeSummary, binding._details, binding.data);
+      }
+
+      this._activeSummary.classList.remove("splitview-active");
+      binding._details.classList.remove("splitview-active");
+    }
+
+    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,1135 @@
+/* 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,
+    topIndex: 0,              // the first visible line
+  };
+
+  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._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,
+          topIndex: this._sourceEditor.getTopIndex(),
+        };
+        this._sourceEditor.destroy();
+        this._sourceEditor = null;
+      }
+
+      this.window.removeEventListener("unload",
+                                      this._onWindowUnloadBinding, false);
+      this._triggerAction("Detach");
+    }
+
+    this._inputElement = aElement;
+    if (!aElement) {
+      return;
+    }
+
+    // attach to new input element
+    this.window.addEventListener("unload", this._onWindowUnloadBinding, false);
+    this._focusOnSourceEditorReady = false;
+
+    this._sourceEditor = null; // set it only when ready (safe to use)
+
+    let sourceEditor = new SourceEditor();
+    let config = {
+      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.addEventListener(SourceEditor.EVENTS.TEXT_CHANGED,
+                                    function onTextChanged(aEvent) {
+        this.updateStyleSheet();
+      }.bind(this));
+
+      this._sourceEditor = sourceEditor;
+
+      if (this._focusOnSourceEditorReady) {
+        this._focusOnSourceEditorReady = false;
+        sourceEditor.focus();
+      }
+
+      sourceEditor.setTopIndex(this._state.topIndex);
+      sourceEditor.setSelection(this._state.selection.start,
+                                this._state.selection.end);
+
+      this._triggerAction("Attach");
+    }.bind(this));
+  },
+
+  /**
+   * Retrieve the underlying SourceEditor instance for this StyleEditor.
+   * Can be null if not ready or Style Editor is detached/headless.
+   *
+   * @return SourceEditor
+   */
+  get sourceEditor() this._sourceEditor,
+
+  /**
+   * Setter for the read-only state of the editor.
+   *
+   * @param boolean aValue
+   *        Tells if you want the editor to be read-only or not.
+   */
+  set readOnly(aValue)
+  {
+    this._state.readOnly = aValue;
+    if (this._sourceEditor) {
+      this._sourceEditor.readOnly = aValue;
+    }
+  },
+
+  /**
+   * Getter for the read-only state of the editor.
+   *
+   * @return boolean
+   */
+  get readOnly()
+  {
+    return this._state.readOnly;
+  },
+
+  /**
+   * Retrieve the window that contains the editor.
+   * Can be null if the editor is detached/headless.
+   *
+   * @return DOMWindow
+   */
+  get window()
+  {
+    if (!this.inputElement) {
+      return null;
+    }
+    return this.inputElement.ownerDocument.defaultView;
+  },
+
+  /**
+   * Retrieve the last file this editor has been saved to or null if none.
+   *
+   * @return nsIFile
+   */
+  get savedFile() this._savedFile,
+
+  /**
+   * Import style sheet from file and load it into the editor asynchronously.
+   * "Load" action triggers when complete.
+   *
+   * @param mixed aFile
+   *        Optional nsIFile or filename string.
+   *        If not set a file picker will be shown.
+   * @param nsIWindow aParentWindow
+   *        Optional parent window for the file picker.
+   */
+  importFromFile: function SE_importFromFile(aFile, aParentWindow)
+  {
+    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 the Style Editor input.
+   */
+  focus: function SE_focus()
+  {
+    if (this._sourceEditor) {
+      this._sourceEditor.focus();
+    } else {
+      this._focusOnSourceEditorReady = true;
+    }
+  },
+
+  /**
+   * Event handler for when the editor is shown. Call this after the editor is
+   * shown.
+   */
+  onShow: function SE_onShow()
+  {
+    if (this._sourceEditor) {
+      this._sourceEditor.setTopIndex(this._state.topIndex);
+    }
+    this.focus();
+  },
+
+  /**
+   * Event handler for when the editor is hidden. Call this before the editor is
+   * hidden.
+   */
+  onHide: function SE_onHide()
+  {
+    if (this._sourceEditor) {
+      this._state.topIndex = this._sourceEditor.getTopIndex();
+    }
+  },
+
+  /**
+    * Persist StyleEditor extra data to the attached DOM stylesheet expando.
+    * The expando on the DOM stylesheet is used to restore user-facing state
+    * when the StyleEditor is closed and then reopened again.
+    *
+    * @see styleSheet
+    */
+  _persistExpando: function SE__persistExpando()
+  {
+    if (!this._styleSheet) {
+      return; // not loaded
+    }
+    let name = STYLESHEET_EXPANDO + this.styleSheetIndex;
+    let expando = this.contentDocument.getUserData(name);
+    if (!expando) {
+      expando = {};
+      this.contentDocument.setUserData(name, expando, null);
+    }
+    expando._flags = this._flags;
+    expando._savedFile = this._savedFile;
+  },
+
+  /**
+    * Restore the attached DOM stylesheet expando into this editor state.
+    *
+    * @see styleSheet
+    */
+  _restoreExpando: function SE__restoreExpando()
+  {
+    if (!this._styleSheet) {
+      return; // not loaded
+    }
+    let name = STYLESHEET_EXPANDO + this.styleSheetIndex;
+    let expando = this.contentDocument.getUserData(name);
+    if (expando) {
+      this._flags = expando._flags;
+      this._savedFile = expando._savedFile;
+    }
+  },
+
+  /**
+    * Retrieve custom key bindings objects as expected by SourceEditor.
+    * SourceEditor action names are not displayed to the user.
+    *
+    * @return Array
+    */
+  _getKeyBindings: function SE__getKeyBindings()
+  {
+    let bindings = [];
+
+    bindings.push({
+      action: "StyleEditor.save",
+      code: _("saveStyleSheet.commandkey"),
+      accel: true,
+      callback: function save() {
+        this.saveToFile(this._savedFile);
+      }.bind(this)
+    });
+
+    bindings.push({
+      action: "StyleEditor.saveAs",
+      code: _("saveStyleSheet.commandkey"),
+      accel: true,
+      shift: true,
+      callback: function saveAs() {
+        this.saveToFile();
+      }.bind(this)
+    });
+
+    bindings.push({
+      action: "undo",
+      code: _("undo.commandkey"),
+      accel: true,
+      callback: function undo() {
+        this._sourceEditor.undo();
+      }.bind(this)
+    });
+
+    bindings.push({
+      action: "redo",
+      code: _("redo.commandkey"),
+      accel: true,
+      shift: true,
+      callback: function redo() {
+        this._sourceEditor.redo();
+      }.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,491 @@
+/* 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),
+      onHide: function ASV_onItemShow(aSummary, aDetails, aData) {
+        aData.editor.onHide();
+      },
+      onShow: function ASV_onItemShow(aSummary, aDetails, aData) {
+        let editor = aData.editor;
+        if (!editor.inputElement) {
+          // attach editor to input element the first time it is shown
+          editor.inputElement = aDetails.querySelector(".stylesheet-editor-input");
+        }
+        editor.onShow();
+      }
+    });
+  },
+
+  /**
+   * Called when an editor flag changed.
+   *
+   * @param StyleEditor aEditor
+   * @param string aFlagName
+   * @see StyleEditor.flags
+   */
+  onFlagChange: function SEAL_onFlagChange(aEditor, aFlagName)
+  {
+    this._updateSummaryForEditor(aEditor);
+  },
+
+  /**
+   * Called when when changes have been committed/applied to the live DOM
+   * stylesheet.
+   *
+   * @param StyleEditor aEditor
+   */
+  onCommit: function SEAL_onCommit(aEditor)
+  {
+    this._updateSummaryForEditor(aEditor);
+  },
+};
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/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"
+           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>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/Makefile.in
@@ -0,0 +1,72 @@
+# ***** 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) 2007
+# 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 of 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@
+relativesrcdir  = browser/devtools/styleeditor/test
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/rules.mk
+
+_BROWSER_TEST_FILES = \
+                 browser_styleeditor_enabled.js \
+                 browser_styleeditor_import.js \
+                 browser_styleeditor_init.js \
+                 browser_styleeditor_loading.js \
+                 browser_styleeditor_new.js \
+                 browser_styleeditor_pretty.js \
+                 browser_styleeditor_readonly.js \
+                 browser_styleeditor_reopen.js \
+                 browser_styleeditor_sv_filter.js \
+                 browser_styleeditor_sv_keynav.js \
+                 browser_styleeditor_sv_resize.js \
+                 four.html \
+                 head.js \
+                 media.html \
+                 media-small.css \
+                 minified.html \
+                 simple.css \
+                 simple.css.gz \
+                 simple.css.gz^headers^ \
+                 simple.gz.html \
+                 simple.html \
+                 $(NULL)
+
+libs::	$(_BROWSER_TEST_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_enabled.js
@@ -0,0 +1,101 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// https rather than chrome to improve coverage
+const TESTCASE_URI = TEST_BASE_HTTPS + "simple.html";
+
+
+function test()
+{
+  waitForExplicitFinish();
+
+  let count = 0;
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    aChrome.addChromeListener({
+      onEditorAdded: function (aChrome, aEditor) {
+        count++;
+        if (count == 2) {
+          // we test against first stylesheet after all are ready
+          let editor = aChrome.editors[0];
+          if (!editor.sourceEditor) {
+            editor.addActionListener({
+              onAttach: function (aEditor) {
+                run(aChrome, aEditor);
+              }
+            });
+          } else {
+            run(aChrome, editor);
+          }
+        }
+      }
+    });
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+function run(aChrome, aEditor)
+{
+  testEnabledToggle(aChrome, aEditor);
+}
+
+function testEnabledToggle(aChrome, aEditor)
+{
+  is(aEditor, aChrome.editors[0],
+     "stylesheet with index 0 is the first stylesheet listed in the UI");
+
+  let firstStyleSheetEditor = aEditor;
+  let firstStyleSheetUI = aChrome.getSummaryElementForEditor(aEditor);
+  let enabledToggle = firstStyleSheetUI.querySelector(".stylesheet-enabled");
+
+  is(firstStyleSheetEditor.contentDocument.styleSheets[0].disabled, false,
+     "first stylesheet is initially enabled");
+  is(firstStyleSheetEditor.hasFlag("disabled"), false,
+     "first stylesheet is initially enabled, it does not have DISABLED flag");
+  is(firstStyleSheetUI.classList.contains("disabled"), false,
+     "first stylesheet is initially enabled, UI does not have DISABLED class");
+
+  let disabledToggleCount = 0;
+  firstStyleSheetEditor.addActionListener({
+    onFlagChange: function (aEditor, aFlagName) {
+      if (aFlagName != "disabled") {
+        return;
+      }
+      disabledToggleCount++;
+
+      if (disabledToggleCount == 1) {
+        is(firstStyleSheetEditor, aEditor,
+           "FlagChange handler triggered for DISABLED flag on the first editor");
+        is(firstStyleSheetEditor.styleSheet.disabled, true,
+           "first stylesheet is now disabled");
+        is(firstStyleSheetEditor.hasFlag("disabled"), true,
+           "first stylesheet is now disabled, it has DISABLED flag");
+        is(firstStyleSheetUI.classList.contains("disabled"), true,
+           "first stylesheet is now disabled, UI has DISABLED class");
+
+        // now toggle it back to enabled
+        waitForFocus(function () {
+          EventUtils.synthesizeMouseAtCenter(enabledToggle, {}, gChromeWindow);
+        }, gChromeWindow);
+        return;
+      }
+
+      // disabledToggleCount == 2
+      is(firstStyleSheetEditor, aEditor,
+         "FlagChange handler triggered for DISABLED flag on the first editor (2)");
+      is(firstStyleSheetEditor.styleSheet.disabled, false,
+         "first stylesheet is now enabled again");
+      is(firstStyleSheetEditor.hasFlag("disabled"), false,
+         "first stylesheet is now enabled again, it does not have DISABLED flag");
+      is(firstStyleSheetUI.classList.contains("disabled"), false,
+         "first stylesheet is now enabled again, UI does not have DISABLED class");
+
+      finish();
+    }
+  });
+
+  waitForFocus(function () {
+    EventUtils.synthesizeMouseAtCenter(enabledToggle, {}, gChromeWindow);
+  }, gChromeWindow);
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_import.js
@@ -0,0 +1,87 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// http rather than chrome to improve coverage
+const TESTCASE_URI = TEST_BASE_HTTP + "simple.html";
+
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
+const FILENAME = "styleeditor-import-test.css";
+const SOURCE = "body{background:red;}";
+
+
+function test()
+{
+  waitForExplicitFinish();
+
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    aChrome.addChromeListener({
+      onContentAttach: run,
+      onEditorAdded: testEditorAdded
+    });
+    if (aChrome.isContentAttached) {
+      run(aChrome);
+    }
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+function run(aChrome)
+{
+  is(aChrome.editors.length, 2,
+     "there is 2 stylesheets initially");
+}
+
+function testImport(aChrome, aEditor)
+{
+  // create file to import first
+  let file = FileUtils.getFile("ProfD", [FILENAME]);
+  let ostream = FileUtils.openSafeFileOutputStream(file);
+  let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+                    .createInstance(Ci.nsIScriptableUnicodeConverter);
+  converter.charset = "UTF-8";
+  let istream = converter.convertToInputStream(SOURCE);
+  NetUtil.asyncCopy(istream, ostream, function (status) {
+    FileUtils.closeSafeFileOutputStream(ostream);
+
+    // click the import button now that the file to import is ready
+    aChrome._mockImportFile = file;
+
+    waitForFocus(function () {
+      let document = gChromeWindow.document
+      let importButton = document.querySelector(".style-editor-importButton");
+      EventUtils.synthesizeMouseAtCenter(importButton, {}, gChromeWindow);
+    }, gChromeWindow);
+  });
+}
+
+let gAddedCount = 0;
+function testEditorAdded(aChrome, aEditor)
+{
+  if (++gAddedCount == 2) {
+    // test import after the 2 initial stylesheets have been loaded
+    if (!aChrome.editors[0].sourceEditor) {
+      aChrome.editors[0].addActionListener({
+        onAttach: function () {
+          testImport(aChrome);
+        }
+      });
+    } else {
+      testImport(aChrome);
+    }
+  }
+
+  if (!aEditor.hasFlag("imported")) {
+    return;
+  }
+
+  ok(!aEditor.hasFlag("inline"),
+     "imported stylesheet does not have INLINE flag");
+  ok(aEditor.savedFile,
+     "imported stylesheet will be saved directly into the same file");
+  is(aEditor.getFriendlyName(), FILENAME,
+     "imported stylesheet has the same name as the filename");
+
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_init.js
@@ -0,0 +1,126 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE + "simple.html";
+
+
+function test()
+{
+  waitForExplicitFinish();
+
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    aChrome.addChromeListener({
+      onContentAttach: run,
+      onEditorAdded: testEditorAdded
+    });
+    if (aChrome.isContentAttached) {
+      run(aChrome);
+    }
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+let gContentAttachHandled = false;
+function run(aChrome)
+{
+  gContentAttachHandled = true;
+  is(aChrome.contentWindow.document.readyState, "complete",
+     "content document is complete");
+
+  let SEC = gChromeWindow.styleEditorChrome;
+  is(SEC, aChrome, "StyleEditorChrome object exists as new window property");
+
+  ok(gChromeWindow.document.title.indexOf("simple testcase") >= 0,
+     "the Style Editor window title contains the document's title");
+
+  // check editors are instantiated
+  is(SEC.editors.length, 2,
+     "there is two StyleEditor instances managed");
+  ok(SEC.editors[0].styleSheetIndex < SEC.editors[1].styleSheetIndex,
+     "editors are ordered by styleSheetIndex");
+
+  // check StyleEditorChrome is a singleton wrt to the same DOMWindow
+  let chromeWindow = StyleEditor.openChrome();
+  is(chromeWindow, gChromeWindow,
+     "attempt to edit the same document returns the same Style Editor window");
+}
+
+let gEditorAddedCount = 0;
+function testEditorAdded(aChrome, aEditor)
+{
+  if (!gEditorAddedCount) {
+    is(gContentAttachHandled, true,
+       "ContentAttach event triggered before EditorAdded");
+  }
+
+  if (aEditor.styleSheetIndex == 0) {
+    gEditorAddedCount++;
+    testFirstStyleSheetEditor(aChrome, aEditor);
+  }
+  if (aEditor.styleSheetIndex == 1) {
+    gEditorAddedCount++;
+    testSecondStyleSheetEditor(aChrome, aEditor);
+  }
+
+  if (gEditorAddedCount == 2) {
+    finish();
+  }
+}
+
+function testFirstStyleSheetEditor(aChrome, aEditor)
+{
+  //testing TESTCASE's simple.css stylesheet
+  is(aEditor.styleSheetIndex, 0,
+     "first stylesheet is at index 0");
+
+  is(aEditor, aChrome.editors[0],
+     "first stylesheet corresponds to StyleEditorChrome.editors[0]");
+
+  ok(!aEditor.hasFlag("inline"),
+     "first stylesheet does not have INLINE flag");
+
+  let summary = aChrome.getSummaryElementForEditor(aEditor);
+  ok(!summary.classList.contains("inline"),
+     "first stylesheet UI does not have INLINE class");
+
+  let name = summary.querySelector(".stylesheet-name").textContent;
+  is(name, "simple.css",
+     "first stylesheet's name is `simple.css`");
+
+  let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
+  is(parseInt(ruleCount), 1,
+     "first stylesheet UI shows rule count as 1");
+
+  ok(summary.classList.contains("splitview-active"),
+     "first stylesheet UI is focused/active");
+}
+
+function testSecondStyleSheetEditor(aChrome, aEditor)
+{
+  //testing TESTCASE's inline stylesheet
+  is(aEditor.styleSheetIndex, 1,
+     "second stylesheet is at index 1");
+
+  is(aEditor, aChrome.editors[1],
+     "second stylesheet corresponds to StyleEditorChrome.editors[1]");
+
+  ok(aEditor.hasFlag("inline"),
+     "second stylesheet has INLINE flag");
+
+  let summary = aChrome.getSummaryElementForEditor(aEditor);
+  ok(summary.classList.contains("inline"),
+     "second stylesheet UI has INLINE class");
+
+  let name = summary.querySelector(".stylesheet-name").textContent;
+  ok(/^<.*>$/.test(name),
+     "second stylesheet's name is surrounded by `<>`");
+
+  let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
+  is(parseInt(ruleCount), 3,
+     "second stylesheet UI shows rule count as 3");
+
+  ok(!summary.classList.contains("splitview-active"),
+     "second stylesheet UI is NOT focused/active");
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_loading.js
@@ -0,0 +1,39 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE + "simple.html";
+
+
+function test()
+{
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+
+  // launch Style Editor right when the tab is created (before load)
+  // this checks that the Style Editor still launches correctly when it is opened
+  // *while* the page is still loading
+  launchStyleEditorChrome(function (aChrome) {
+    isnot(gBrowser.selectedBrowser.contentWindow.document.readyState, "complete",
+          "content document is still loading");
+
+    if (!aChrome.isContentAttached) {
+      aChrome.addChromeListener({
+        onContentAttach: run
+      });
+    } else {
+      run(aChrome);
+    }
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+function run(aChrome)
+{
+  is(aChrome.contentWindow.document.readyState, "complete",
+     "content document is complete");
+
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_new.js
@@ -0,0 +1,117 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE + "simple.html";
+
+
+function test()
+{
+  waitForExplicitFinish();
+
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    aChrome.addChromeListener({
+      onContentAttach: run,
+      onEditorAdded: testEditorAdded
+    });
+    if (aChrome.isContentAttached) {
+      run(aChrome);
+    }
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+function run(aChrome)
+{
+  is(aChrome.editors.length, 2,
+     "there is 2 stylesheets initially");
+}
+
+let gAddedCount = 0;  // to add new stylesheet after the 2 initial stylesheets
+let gNewEditor;       // to make sure only one new stylesheet got created
+let gCommitCount = 0; // to make sure only one Commit event is triggered
+
+function testEditorAdded(aChrome, aEditor)
+{
+  gAddedCount++;
+  if (gAddedCount == 2) {
+    waitForFocus(function () { // create a new style sheet
+      let newButton = gChromeWindow.document.querySelector(".style-editor-newButton");
+      EventUtils.synthesizeMouseAtCenter(newButton, {}, gChromeWindow);
+    }, gChromeWindow);
+  }
+  if (gAddedCount != 3) {
+    return;
+  }
+
+  ok(!gNewEditor, "creating a new stylesheet triggers one EditorAdded event");
+  gNewEditor = aEditor; // above test will fail if we get a duplicate event
+
+  is(aChrome.editors.length, 3,
+     "creating a new stylesheet added a new StyleEditor instance");
+
+  let listener = {
+    onAttach: function (aEditor) {
+      waitForFocus(function () {
+        ok(aEditor.isLoaded,
+           "new editor is loaded when attached");
+        ok(aEditor.hasFlag("new"),
+           "new editor has NEW flag");
+        ok(!aEditor.hasFlag("unsaved"),
+           "new editor does not have UNSAVED flag");
+
+        ok(aEditor.inputElement,
+           "new editor has an input element attached");
+
+        ok(aEditor.sourceEditor.hasFocus(),
+           "new editor has focus");
+
+        let summary = aChrome.getSummaryElementForEditor(aEditor);
+        let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
+        is(parseInt(ruleCount), 0,
+           "new editor initially shows 0 rules");
+
+        let computedStyle = content.getComputedStyle(content.document.body, null);
+        is(computedStyle.backgroundColor, "rgb(255, 255, 255)",
+           "content's background color is initially white");
+
+        for each (let c in "body{background-color:red;}") {
+          EventUtils.synthesizeKey(c, {}, gChromeWindow);
+        }
+      }, gChromeWindow) ;
+    },
+
+    onCommit: function (aEditor) {
+      gCommitCount++;
+
+      ok(aEditor.hasFlag("new"),
+         "new editor still has NEW flag");
+      ok(aEditor.hasFlag("unsaved"),
+         "new editor has UNSAVED flag after modification");
+
+      let summary = aChrome.getSummaryElementForEditor(aEditor);
+      let ruleCount = summary.querySelector(".stylesheet-rule-count").textContent;
+      is(parseInt(ruleCount), 1,
+         "new editor shows 1 rule after modification");
+
+      let computedStyle = content.getComputedStyle(content.document.body, null);
+      is(computedStyle.backgroundColor, "rgb(255, 0, 0)",
+         "content's background color has been updated to red");
+
+      executeSoon(function () {
+        is(gCommitCount, 1, "received only one Commit event (throttle)");
+
+        aEditor.removeActionListener(listener);
+
+        gNewEditor = null;
+        finish();
+      });
+    }
+  };
+
+  aEditor.addActionListener(listener);
+  if (aEditor.sourceEditor) {
+    listener.onAttach(aEditor);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_pretty.js
@@ -0,0 +1,56 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE + "minified.html";
+
+
+function test()
+{
+  waitForExplicitFinish();
+
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    aChrome.addChromeListener({
+      onEditorAdded: function (aChrome, aEditor) {
+        if (aEditor.sourceEditor) {
+          run(aEditor); // already attached to input element
+        } else {
+          aEditor.addActionListener({
+            onAttach: run
+          });
+        }
+      }
+    });
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+let editorTestedCount = 0;
+function run(aEditor)
+{
+  if (aEditor.styleSheetIndex == 0) {
+    let prettifiedSource = "body\{\r?\n\tbackground\:white;\r?\n\}\r?\n\r?\ndiv\{\r?\n\tfont\-size\:4em;\r?\n\tcolor\:red\r?\n\}\r?\n";
+    let prettifiedSourceRE = new RegExp(prettifiedSource);
+
+    ok(prettifiedSourceRE.test(aEditor.sourceEditor.getText()),
+       "minified source has been prettified automatically");
+    editorTestedCount++;
+    let chrome = gChromeWindow.styleEditorChrome;
+    let summary = chrome.getSummaryElementForEditor(chrome.editors[1]);
+    EventUtils.synthesizeMouseAtCenter(summary, {}, gChromeWindow);
+  }
+
+  if (aEditor.styleSheetIndex == 1) {
+    let originalSource = "body \{ background\: red; \}\r?\ndiv \{\r?\nfont\-size\: 5em;\r?\ncolor\: red\r?\n\}";
+    let originalSourceRE = new RegExp(originalSource);
+
+    ok(originalSourceRE.test(aEditor.sourceEditor.getText()),
+       "non-minified source has been left untouched");
+    editorTestedCount++;
+  }
+
+  if (editorTestedCount == 2) {
+    finish();
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_readonly.js
@@ -0,0 +1,74 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE + "simple.html";
+
+
+let gEditorAddedCount = 0;
+let gEditorReadOnlyCount = 0;
+let gChromeListener = {
+  onEditorAdded: function (aChrome, aEditor) {
+    gEditorAddedCount++;
+    if (aEditor.readOnly) {
+      gEditorReadOnlyCount++;
+    }
+
+    if (gEditorAddedCount == aChrome.editors.length) {
+      // continue testing after all editors are ready
+
+      is(gEditorReadOnlyCount, 0,
+         "all editors are NOT read-only initially");
+
+      // all editors have been loaded, queue closing the content tab
+      executeSoon(function () {
+        gBrowser.removeCurrentTab();
+      });
+    }
+  },
+  onContentDetach: function (aChrome) {
+    // check that the UI has switched to read-only
+    run(aChrome);
+  }
+};
+
+function test()
+{
+  waitForExplicitFinish();
+
+  gBrowser.addTab(); // because we'll close the next one
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    aChrome.addChromeListener(gChromeListener);
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+function run(aChrome)
+{
+  let document = gChromeWindow.document;
+  let disabledCount;
+  let elements;
+
+  disabledCount = 0;
+  elements = document.querySelectorAll("button,input,select");
+  for (let i = 0; i < elements.length; ++i) {
+    if (elements[i].hasAttribute("disabled")) {
+      disabledCount++;
+    }
+  }
+  ok(elements.length && disabledCount == elements.length,
+     "all buttons, input and select elements are disabled");
+
+  disabledCount = 0;
+  aChrome.editors.forEach(function (aEditor) {
+    if (aEditor.readOnly) {
+      disabledCount++;
+    }
+  });
+  ok(aChrome.editors.length && disabledCount == aChrome.editors.length,
+     "all editors are read-only");
+
+  aChrome.removeChromeListener(gChromeListener);
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_reopen.js
@@ -0,0 +1,155 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// http rather than chrome to improve coverage
+const TESTCASE_URI = TEST_BASE_HTTP + "simple.gz.html";
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
+
+
+function test()
+{
+  waitForExplicitFinish();
+
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    aChrome.addChromeListener({
+      onEditorAdded: function (aChrome, aEditor) {
+        if (aEditor.styleSheetIndex != 0) {
+          return; // we want to test against the first stylesheet
+        }
+
+        if (aEditor.sourceEditor) {
+          run(aEditor); // already attached to input element
+        } else {
+          aEditor.addActionListener({
+            onAttach: run
+          });
+        }
+      }
+    });
+
+    gChromeWindow.addEventListener("unload", function onClose() {
+      gChromeWindow.removeEventListener("unload", onClose, true);
+      gChromeWindow = null;
+      executeSoon(function () {
+        waitForFocus(function () {
+          // wait that browser has focus again
+          // open StyleEditorChrome again (a new one since we closed the previous one)
+          launchStyleEditorChrome(function (aChrome) {
+            is(gChromeWindow.document.documentElement.hasAttribute("data-marker"),
+               false,
+               "opened a completely new StyleEditorChrome window");
+
+            aChrome.addChromeListener({
+              onEditorAdded: function (aChrome, aEditor) {
+                if (aEditor.styleSheetIndex != 0) {
+                  return; // we want to test against the first stylesheet
+                }
+
+                if (aEditor.sourceEditor) {
+                  testNewChrome(aEditor); // already attached to input element
+                } else {
+                  aEditor.addActionListener({
+                    onAttach: testNewChrome
+                  });
+                }
+              }
+            });
+          });
+        });
+      });
+    }, true);
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+let gFilename;
+
+function run(aEditor)
+{
+  gFilename = FileUtils.getFile("ProfD", ["styleeditor-test.css"])
+
+  aEditor.saveToFile(gFilename, function (aFile) {
+    ok(aFile, "file got saved successfully");
+
+    aEditor.addActionListener({
+      onFlagChange: function (aEditor, aFlag) {
+        if (aFlag != "unsaved") {
+          return;
+        }
+
+        ok(aEditor.hasFlag("unsaved"),
+           "first stylesheet has UNSAVED flag after making a change");
+
+        // marker used to check it does not exist when we reopen
+        // ie. the window we opened is indeed a new one
+        gChromeWindow.document.documentElement.setAttribute("data-marker", "true");
+        gChromeWindow.close();
+      }
+    });
+
+    waitForFocus(function () {
+      // insert char so that this stylesheet has the UNSAVED flag
+      EventUtils.synthesizeKey("x", {}, gChromeWindow);
+    }, gChromeWindow);
+  });
+}
+
+function testNewChrome(aEditor)
+{
+  ok(aEditor.savedFile,
+     "first stylesheet editor will save directly into the same file");
+
+  is(aEditor.getFriendlyName(), gFilename.leafName,
+     "first stylesheet still has the filename as it was saved");
+  gFilename = null;
+
+  ok(aEditor.hasFlag("unsaved"),
+     "first stylesheet still has UNSAVED flag at reopening");
+
+  ok(!aEditor.hasFlag("inline"),
+     "first stylesheet does not have INLINE flag");
+
+  ok(!aEditor.hasFlag("error"),
+     "editor does not have error flag initially");
+  let hadError = false;
+
+  let onSaveCallback = function (aFile) {
+    aEditor.addActionListener({
+      onFlagChange: function (aEditor, aFlag) {
+        if (!hadError && aFlag == "error") {
+          ok(aEditor.hasFlag("error"),
+             "editor has ERROR flag after attempting to save with invalid path");
+          hadError = true;
+
+          // save using source editor key binding (previous successful path)
+          waitForFocus(function () {
+            EventUtils.synthesizeKey("S", {accelKey: true}, gChromeWindow);
+          }, gChromeWindow);
+          return;
+        }
+
+        if (hadError && aFlag == "unsaved") {
+          executeSoon(function () {
+            ok(!aEditor.hasFlag("unsaved"),
+               "first stylesheet has no UNSAVED flag after successful save");
+            ok(!aEditor.hasFlag("error"),
+               "ERROR flag has been removed since last operation succeeded");
+            finish();
+          });
+        }
+      }
+    });
+  }
+
+  let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
+  if (os == "WINNT") {
+    aEditor.saveToFile("C:\\I_DO_NOT_EXIST_42\\bogus.css", onSaveCallback);
+  } else {
+    aEditor.saveToFile("/I_DO_NOT_EXIST_42/bogos.css", onSaveCallback);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_sv_filter.js
@@ -0,0 +1,101 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE + "simple.html";
+
+
+function test()
+{
+  waitForExplicitFinish();
+
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    aChrome.addChromeListener({
+      onContentAttach: run
+    });
+    if (aChrome.isContentAttached) {
+      run(aChrome);
+    }
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+function getFilteredItemsCount(nav)
+{
+  let matches = nav.querySelectorAll("*.splitview-filtered");
+  return matches ? matches.length : 0;
+}
+
+function run(aChrome)
+{
+  aChrome.editors[0].addActionListener({onAttach: onFirstEditorAttach});
+  aChrome.editors[1].addActionListener({onAttach: onSecondEditorAttach});
+}
+
+function onFirstEditorAttach(aEditor)
+{
+  let filter = gChromeWindow.document.querySelector(".splitview-filter");
+  // force the command event on input since it is not possible to disable
+  // the search textbox's timeout.
+  let forceCommandEvent = function forceCommandEvent() {
+    let evt = gChromeWindow.document.createEvent("XULCommandEvent");
+    evt.initCommandEvent("command", true, true, gChromeWindow, 0, false, false,
+                         false, false, null);
+    filter.dispatchEvent(evt);
+  }
+  filter.addEventListener("input", forceCommandEvent, false);
+
+  let nav = gChromeWindow.document.querySelector(".splitview-nav");
+  nav.focus();
+
+  is(getFilteredItemsCount(nav), 0,
+     "there is 0 filtered item initially");
+
+  waitForFocus(function () {
+    // Search [s] (type-on-search since we focused nav above - not filter directly)
+    EventUtils.synthesizeKey("s", {}, gChromeWindow);
+
+    // the search space is "simple.css" and "inline stylesheet #1" (2 sheets)
+    is(getFilteredItemsCount(nav), 0,
+       "there is 0 filtered item if searching for 's'");
+
+    EventUtils.synthesizeKey("i", {}, gChromeWindow); // Search [si]
+
+    is(getFilteredItemsCount(nav), 1, // inline stylesheet is filtered
+       "there is 1 filtered item if searching for 's'");
+
+    // use uppercase to check that filtering is case-insensitive
+    EventUtils.synthesizeKey("X", {}, gChromeWindow); // Search [siX]
+    is(getFilteredItemsCount(nav), 2,
+       "there is 2 filtered items if searching for 's'"); // no match
+
+    // clear the search
+    EventUtils.synthesizeKey("VK_ESCAPE", {}, gChromeWindow);
+
+    is(filter.value, "",
+       "filter is back to empty");
+    is(getFilteredItemsCount(nav), 0,
+       "there is 0 filtered item when filter is empty again");
+
+    for each (let c in "inline") {
+      EventUtils.synthesizeKey(c, {}, gChromeWindow);
+    }
+
+    is(getFilteredItemsCount(nav), 1, // simple.css is filtered
+       "there is 1 filtered item if searching for 'inline'");
+
+    // auto-select the only result (enter the editor)
+    EventUtils.synthesizeKey("VK_ENTER", {}, gChromeWindow);
+
+    filter.removeEventListener("input", forceCommandEvent, false);
+  }, gChromeWindow);
+}
+
+function onSecondEditorAttach(aEditor)
+{
+  ok(aEditor.sourceEditor.hasFocus(),
+     "second editor has been selected and focused automatically.");
+
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_sv_keynav.js
@@ -0,0 +1,80 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE + "four.html";
+
+
+function test()
+{
+  waitForExplicitFinish();
+
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    aChrome.addChromeListener({
+      onContentAttach: run
+    });
+    if (aChrome.isContentAttached) {
+      run(aChrome);
+    }
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+let gChrome;
+
+function run(aChrome)
+{
+  gChrome = aChrome;
+  aChrome.editors[0].addActionListener({onAttach: onEditor0Attach});
+  aChrome.editors[2].addActionListener({onAttach: onEditor2Attach});
+}
+
+function getStylesheetNameLinkFor(aEditor)
+{
+  return gChrome.getSummaryElementForEditor(aEditor).querySelector(".stylesheet-name");
+}
+
+function onEditor0Attach(aEditor)
+{
+  waitForFocus(function () {
+    let summary = gChrome.getSummaryElementForEditor(aEditor);
+    EventUtils.synthesizeMouseAtCenter(summary, {}, gChromeWindow);
+
+    let item = getStylesheetNameLinkFor(gChrome.editors[0]);
+    is(gChromeWindow.document.activeElement, item,
+       "editor 0 item is the active element");
+
+    EventUtils.synthesizeKey("VK_DOWN", {}, gChromeWindow);
+    item = getStylesheetNameLinkFor(gChrome.editors[1]);
+    is(gChromeWindow.document.activeElement, item,
+       "editor 1 item is the active element");
+
+    EventUtils.synthesizeKey("VK_HOME", {}, gChromeWindow);
+    item = getStylesheetNameLinkFor(gChrome.editors[0]);
+    is(gChromeWindow.document.activeElement, item,
+       "fist editor item is the active element");
+
+    EventUtils.synthesizeKey("VK_END", {}, gChromeWindow);
+    item = getStylesheetNameLinkFor(gChrome.editors[3]);
+    is(gChromeWindow.document.activeElement, item,
+       "last editor item is the active element");
+
+    EventUtils.synthesizeKey("VK_UP", {}, gChromeWindow);
+    item = getStylesheetNameLinkFor(gChrome.editors[2]);
+    is(gChromeWindow.document.activeElement, item,
+       "editor 2 item is the active element");
+
+    EventUtils.synthesizeKey("VK_RETURN", {}, gChromeWindow);
+    // this will attach and give focus editor 2
+  }, gChromeWindow);
+}
+
+function onEditor2Attach(aEditor)
+{
+  ok(aEditor.sourceEditor.hasFocus(),
+     "editor 2 has focus");
+
+  gChrome = null;
+  finish();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_sv_resize.js
@@ -0,0 +1,63 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TESTCASE_URI = TEST_BASE + "simple.html";
+
+let gOriginalWidth; // these are set by run() when gChromeWindow is ready
+let gOriginalHeight;
+
+function test()
+{
+  waitForExplicitFinish();
+
+  addTabAndLaunchStyleEditorChromeWhenLoaded(function (aChrome) {
+    if (aChrome.isContentAttached) {
+      run(aChrome);
+    } else {
+      aChrome.addChromeListener({
+        onContentAttach: run
+      });
+    }
+  });
+
+  content.location = TESTCASE_URI;
+}
+
+function run(aChrome)
+{
+  is(aChrome.editors.length, 2,
+     "there is 2 stylesheets initially");
+
+  aChrome.editors[0].addActionListener({
+    onAttach: function onEditorAttached(aEditor) {
+      let originalSourceEditor = aEditor.sourceEditor;
+      aEditor.sourceEditor.setCaretOffset(4); // to check the caret is preserved
+
+      // queue a resize to inverse aspect ratio
+      // this will trigger a detach and reattach (to workaround bug 254144)
+      executeSoon(function () {
+        waitForFocus(function () {
+          gOriginalWidth = gChromeWindow.outerWidth;
+          gOriginalHeight = gChromeWindow.outerHeight;
+          gChromeWindow.resizeTo(120, 480);
+
+          executeSoon(function () {
+            is(aEditor.sourceEditor, originalSourceEditor,
+               "the editor still references the same SourceEditor instance");
+            is(aEditor.sourceEditor.getCaretOffset(), 4,
+               "the caret position has been preserved");
+
+            // queue a resize to original aspect ratio
+            waitForFocus(function () {
+              gChromeWindow.resizeTo(gOriginalWidth, gOriginalHeight);
+              executeSoon(function () {
+                finish();
+              });
+            }, gChromeWindow);
+          });
+        }, gChromeWindow);
+      });
+    }
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/four.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html>
+<head>
+  <title>four stylesheets</title>
+  <link rel="stylesheet" type="text/css" media="scren" href="simple.css"/>
+  <style type="text/css">
+  div {
+    font-size: 2em;
+  }
+  </style>
+  <style type="text/css">
+  span {
+    font-size: 3em;
+  }
+  </style>
+  <style type="text/css">
+  p {
+    font-size: 4em;
+  }
+  </style>
+</head>
+<body>
+	<div>four <span>stylesheets</span></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/head.js
@@ -0,0 +1,46 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const TEST_BASE = "chrome://mochitests/content/browser/browser/devtools/styleeditor/test/";
+const TEST_BASE_HTTP = "http://example.com/browser/browser/devtools/styleeditor/test/";
+const TEST_BASE_HTTPS = "https://example.com/browser/browser/devtools/styleeditor/test/";
+
+let gChromeWindow;               //StyleEditorChrome window
+
+function cleanup()
+{
+  if (gChromeWindow) {
+    gChromeWindow.close();
+    gChromeWindow = null;
+  }
+  while (gBrowser.tabs.length > 1) {
+    gBrowser.removeCurrentTab();
+  }
+}
+
+function launchStyleEditorChrome(aCallback)
+{
+  gChromeWindow = StyleEditor.openChrome();
+  if (gChromeWindow.document.readyState != "complete") {
+    gChromeWindow.addEventListener("load", function onChromeLoad() {
+      gChromeWindow.removeEventListener("load", onChromeLoad, true);
+      gChromeWindow.styleEditorChrome._alwaysDisableAnimations = true;
+      aCallback(gChromeWindow.styleEditorChrome);
+    }, true);
+  } else {
+    gChromeWindow.styleEditorChrome._alwaysDisableAnimations = true;
+    aCallback(gChromeWindow.styleEditorChrome);
+  }
+}
+
+function addTabAndLaunchStyleEditorChromeWhenLoaded(aCallback)
+{
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onLoad() {
+    gBrowser.selectedBrowser.removeEventListener("load", onLoad, true);
+    launchStyleEditorChrome(aCallback);
+  }, true);
+}
+
+registerCleanupFunction(cleanup);
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/media-small.css
@@ -0,0 +1,5 @@
+/* this stylesheet applies when min-width<400px */
+body {
+	background: red;
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/media.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<html>
+<head>
+  <link rel="stylesheet" type="text/css" href="simple.css" media="screen,print"/>
+  <link rel="stylesheet" type="text/css" href="media-small.css" media="screen and (min-width: 200px)"/>
+</head>
+<body>
+	<div>test for media labels</div>
+</body>
+</html>
+
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/minified.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<html>
+<head>
+  <title>minified testcase</title>
+  <style type="text/css"><!--
+body{background:white;}div{font-size:4em;color:red}
+--></style>
+  <style type="text/css">body { background: red; }
+div {
+font-size: 5em;
+color: red
+}</style>
+</head>
+<body>
+	<div>minified <span>testcase</span></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/simple.css
@@ -0,0 +1,8 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+body {
+  margin: 0;
+}
+
new file mode 100644
index 0000000000000000000000000000000000000000..ee3b9efbc1db99982c1bd4d723a4d33694170ab2
GIT binary patch
literal 166
zc$@*I09pSZiwFodqB2eZ19NF@aBO8RV{>x=9gV>bf-n#T@BNB*=D?ySQ^Uc;5AYXC
z+rTEaOWGBJ#(x(%&1EJ-4HixoH7d0BXY8!&PF?#;XVH+M2DiMy%e)mHCk0o87}z_F
z0V>cb;_(`u>~WXmIJXACq&iz7U!qf9qL-8;*H+3%^C5@BrFSesr?#X2%M21WeD*%M
UGIF-)uI6jZ4+5k&-lqTn09FA?{r~^~
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/simple.css.gz^headers^
@@ -0,0 +1,4 @@
+Vary: Accept-Encoding
+Content-Encoding: gzip
+Content-Type: text/css
+
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/simple.gz.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html>
+<head>
+  <title>simple testcase</title>
+  <link rel="stylesheet" type="text/css" media="scren" href="simple.css.gz"/>
+  <style type="text/css">
+  body {
+    background: white;
+  }
+
+  div {
+    font-size: 4em;
+  }
+
+  div > span {
+     text-decoration: underline;
+  }
+  </style>
+</head>
+<body>
+	<div>simple <span>testcase</span></div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/simple.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html>
+<head>
+  <title>simple testcase</title>
+  <link rel="stylesheet" type="text/css" media="scren" href="simple.css"/>
+  <style type="text/css">
+  body {
+    background: white;
+  }
+
+  div {
+    font-size: 4em;
+  }
+
+  div > span {
+     text-decoration: underline;
+  }
+  </style>
+</head>
+<body>
+	<div>simple <span>testcase</span></div>
+</body>
+</html>
--- a/browser/devtools/styleinspector/CssRuleView.jsm
+++ b/browser/devtools/styleinspector/CssRuleView.jsm
@@ -650,23 +650,20 @@ TextProperty.prototype = {
  *        set of disabled properties.
  * @constructor
  */
 function CssRuleView(aDoc, aStore)
 {
   this.doc = aDoc;
   this.store = aStore;
 
-  this.element = this.doc.createElementNS(HTML_NS, "div");
+  this.element = this.doc.createElementNS(XUL_NS, "vbox");
   this.element.setAttribute("tabindex", "0");
   this.element.classList.add("ruleview");
-
-  // Give a relative position for the inplace editor's measurement
-  // span to be placed absolutely against.
-  this.element.style.position = "relative";
+  this.element.flex = 1;
 }
 
 CssRuleView.prototype = {
   // The element that we're inspecting.
   _viewedElement: null,
 
   /**
    * Update the highlighted element.
@@ -756,16 +753,20 @@ function RuleEditor(aDoc, aRule)
 }
 
 RuleEditor.prototype = {
   _create: function RuleEditor_create()
   {
     this.element = this.doc.createElementNS(HTML_NS, "div");
     this.element._ruleEditor = this;
 
+    // Give a relative position for the inplace editor's measurement
+    // span to be placed absolutely against.
+    this.element.style.position = "relative";
+
     // Add the source link.
     let source = createChild(this.element, "div", {
       class: "ruleview-rule-source",
       textContent: this.rule.title
     });
 
     let code = createChild(this.element, "div", {
       class: "ruleview-code"
--- a/browser/devtools/styleinspector/styleinspector.css
+++ b/browser/devtools/styleinspector/styleinspector.css
@@ -30,11 +30,15 @@
  * 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 ***** */
 
+.ruleview {
+  overflow: auto;
+}
+
 .ruleview-computedlist:not(.styleinspector-open) {
   display: none;
 }
--- a/browser/devtools/styleinspector/test/browser/Makefile.in
+++ b/browser/devtools/styleinspector/test/browser/Makefile.in
@@ -42,33 +42,31 @@ srcdir    = @srcdir@
 VPATH     = @srcdir@
 relativesrcdir  = browser/devtools/styleinspector/test/browser
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _BROWSER_TEST_FILES = \
   browser_styleinspector.js \
-  browser_styleinspector_webconsole.js \
   browser_bug683672.js \
   browser_styleinspector_bug_672746_default_styles.js \
   browser_styleinspector_bug_672744_search_filter.js \
   browser_styleinspector_bug_689759_no_results_placeholder.js \
   browser_bug_692400_element_style.js \
   browser_csslogic_inherited.js \
   browser_ruleview_editor.js \
   browser_ruleview_inherit.js \
   browser_ruleview_manipulation.js \
   browser_ruleview_override.js \
   browser_ruleview_ui.js \
   head.js \
   $(NULL)
 
 _BROWSER_TEST_PAGES = \
-  browser_styleinspector_webconsole.htm \
   browser_bug683672.html \
   $(NULL)
 
 libs:: $(_BROWSER_TEST_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
 
 libs:: $(_BROWSER_TEST_PAGES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
deleted file mode 100644
--- a/browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.htm
+++ /dev/null
@@ -1,185 +0,0 @@
-<!DOCTYPE HTML>
-<html dir="ltr" xml:lang="en-US" lang="en-US"><head>
-  <title>Style inspector test</title>
-  <style>
-    .text {
-      font-family: sans-serif;
-    }
-
-    .container > .text {
-      font-family: serif;
-    }
-
-    .text2 {
-      font-family: sans-serif;
-    }
-
-    .text3 {
-      font-size: 100px;
-    }
-
-    .text[dir=rtl] {
-      font-family: monospace;
-    }
-
-    .container .text {
-      font-family: fantasy;
-    }
-
-    #container .text {
-      font-family: fantasy;
-    }
-
-    .container > .text {
-      font-family: cursive;
-    }
-
-    #container > .text {
-      font-family: cursive;
-    }
-
-    #container > .dummy, #container > .dummy2 {
-      font-family: cursive;
-    }
-
-    div {
-      font-family: fantasy;
-    }
-
-    #container {
-      font-family: fantasy;
-    }
-
-    #container > span {
-      font-family: cursive;
-    }
-
-    #container .dummy {
-      font-family: fantasy;
-    }
-
-    html + .dummy {
-      font-family: fantasy;
-    }
-
-    span + span {
-      font-family: fantasy;
-    }
-
-    span[id=text] {
-      font-family: cursive;
-    }
-
-    span[att=glue] {
-      font-family: cursive;
-    }
-
-    span::before {
-      font-family: cursive;
-      content: "START ";
-    }
-
-    span::after {
-      font-family: cursive;
-      content: " END";
-    }
-
-    spawn::before {
-      font-family: cursive;
-      content: "START ";
-    }
-
-    spawn::after {
-      font-family: cursive;
-      content: " END";
-    }
-
-    a:link {
-      font-family: sans-serif;
-    }
-
-    .link:link {
-      font-family: fantasy;
-    }
-
-    a:visited {
-      font-family: sans-serif;
-    }
-
-    .link:visited {
-      font-family: fantasy;
-    }
-
-    a:active {
-      font-family: sans-serif;
-    }
-
-    .link:active {
-      font-family: fantasy;
-    }
-
-    a:hover {
-      font-family: sans-serif;
-    }
-
-    .link:hover {
-      font-family: fantasy;
-    }
-
-    a:focus {
-      font-family: sans-serif;
-      outline: 5px solid #0f0;
-    }
-
-    .link:focus {
-      font-family: fantasy;
-    }
-
-    span::first-letter {
-      font-family: sans-serif;
-    }
-
-    .text::first-letter {
-      font-family: fantasy;
-    }
-
-    span::first-line {
-      font-family: sans-serif;
-    }
-
-    .text::first-line {
-      font-family: fantasy;
-    }
-
-    #container:first-child {
-      font-family: sans-serif;
-    }
-
-    div:first-child {
-      font-family: fantasy;
-    }
-
-    span:lang(en) {
-      font-family: sans-serif;
-    }
-
-    span:lang(it) {
-      font-family: fantasy;
-    }
-
-    html::-moz-selection {
-      background-color: #f00;
-      font-family: fantasy;
-    }
-  </style>
-</head>
-<body>
-  <h2>font-size</h2>
-  <div id="container">
-    <span id="text" lang="en" class="text">Use inspectstyle($('text')) to inspect me</span><br />
-    <span id="text2" class="text2">Use inspectstyle($('text2'))</span><br />
-    <a class="link" href="#">Some Link</a>
-    <h2>font-family has a single unmatched rule</h2>
-  </div>
-</body>
-</html>
deleted file mode 100644
--- a/browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.js
+++ /dev/null
@@ -1,194 +0,0 @@
-/* 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 DevTools test 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):
- *  Michael Ratcliffe <mratcliffe@mozilla.com>
- *
- * 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 ***** */
-
-// Tests that inspectstyle(node) works properly
-
-const TEST_URI = "http://example.com/browser/browser/devtools/styleinspector/test/browser/browser_styleinspector_webconsole.htm";
-
-let doc;
-let jsterm;
-let hudBox;
-let stylePanels = [];
-
-function test() {
-  addTab(TEST_URI);
-  browser.addEventListener("DOMContentLoaded", prepConsole, false);
-}
-
-function prepConsole() {
-  browser.removeEventListener("DOMContentLoaded", prepConsole, false);
-  doc = content.document;
-  openConsole();
- 
-  ok(window.StyleInspector, "StyleInspector exists");
- 
-  let hud = HUDService.getHudByWindow(content);
-  ok(hud, "we have a console");
- 
-  hudBox = hud.HUDBox;
-  ok(hudBox, "we have the console display");
- 
-  jsterm = hud.jsterm;
-  ok(jsterm, "we have a jsterm");
- 
-  openStyleInspector1();
-}
-
-function openStyleInspector1() {
-  info("opening style inspector instance 1");
-  Services.obs.addObserver(openStyleInspector2, "StyleInspector-opened", false);
-  jsterm.execute("inspectstyle($('text'))");
-}
-
-function openStyleInspector2() {
-  Services.obs.removeObserver(openStyleInspector2, "StyleInspector-opened", false);
-  info("opening style inspector instance 2");
-  Services.obs.addObserver(openStyleInspector3, "StyleInspector-opened", false);
-  jsterm.execute("inspectstyle($('text2'))");
-}
-
-function openStyleInspector3() {
-  Services.obs.removeObserver(openStyleInspector3, "StyleInspector-opened", false);
-  info("opening style inspector instance 3");
-  Services.obs.addObserver(teststylePanels, "StyleInspector-opened", false);
-  jsterm.execute("inspectstyle($('container'))");
-}
-
-function teststylePanels() {
-  Services.obs.removeObserver(teststylePanels, "StyleInspector-opened", false);
-
-  info("adding style inspector instances to stylePanels array");
-  let popupSet = document.getElementById("mainPopupSet");
-  let len = popupSet.childNodes.length - 3;
-  stylePanels.push(popupSet.childNodes[len++]);
-  stylePanels.push(popupSet.childNodes[len++]);
-  stylePanels.push(popupSet.childNodes[len++]);
-
-  let eltArray = [
-    doc.getElementById("text"),
-    doc.getElementById("text2"),
-    doc.getElementById("container")
-  ];
-
-  // We have 3 style inspector instances, each with an element selected:
-  // 1. #text
-  // 2. #text2
-  // 3. #container
-  //
-  // We will loop through each instance and check that the correct node is
-  // selected and that the correct css selector has been selected as active
-  info("looping through array to check initialization");
-  for (let i = 0, max = stylePanels.length; i < max; i++) {
-    ok(stylePanels[i], "style inspector instance " + i +
-       " correctly initialized");
-    is(stylePanels[i].state, "open", "style inspector " + i + " is open");
-
-/*  // the following should be tested elsewhere
-    // TODO bug 696166
-    let htmlTree = stylePanels[i].cssHtmlTree;
-    let cssLogic = htmlTree.cssLogic;
-    let elt = eltArray[i];
-    let eltId = elt.id;
-
-    // Check that the correct node is selected
-    is(elt, htmlTree.viewedElement,
-      "style inspector node matches the selected node (id=" + eltId + ")");
-    is(htmlTree.viewedElement, stylePanels[i].cssLogic.viewedElement,
-      "cssLogic node matches the cssHtmlTree node (id=" + eltId + ")");
-
-    // Check that the correct css selector has been selected as active
-    let matchedSelectors = cssLogic.getPropertyInfo("font-family").matchedSelectors;
-    let sel = matchedSelectors[0];
-    let selector = sel.selector.text;
-    let value = sel.value;
-
-    // Because we know which selectors should be the best match and what their
-    // values should be we can check them
-    switch(eltId) {
-      case "text":
-        is(selector, "#container > .text", "correct best match for #text");
-        is(value, "cursive", "correct css property value for #" + eltId);
-        break;
-      case "text2":
-        is(selector, "#container > span", "correct best match for #text2");
-        is(value, "cursive", "correct css property value for #" + eltId);
-        break;
-      case "container":
-        is(selector, "#container", "correct best match for #container");
-        is(value, "fantasy", "correct css property value for #" + eltId);
-    }
-*/
-  }
-
-  info("hiding stylePanels[1]");
-  Services.obs.addObserver(styleInspectorClosedByHide,
-                           "StyleInspector-closed", false);
-  stylePanels[1].hidePopup();
-}
-
-function styleInspectorClosedByHide()
-{
-  Services.obs.removeObserver(styleInspectorClosedByHide, "StyleInspector-closed", false);
-  is(stylePanels[0].state, "open", "instance stylePanels[0] is still open");
-  isnot(stylePanels[1].state, "open", "instance stylePanels[1] is not open");
-  is(stylePanels[2].state, "open", "instance stylePanels[2] is still open");
-
-  info("closing web console");
-  Services.obs.addObserver(styleInspectorClosedFromConsole1,
-                           "StyleInspector-closed", false);
-  closeConsole();
-}
-
-function styleInspectorClosedFromConsole1()
-{
-  Services.obs.removeObserver(styleInspectorClosedFromConsole1,
-                              "StyleInspector-closed", false);
-  info("Style Inspector 1 and 2 closed");
-  executeSoon(cleanUp);
-}
-
-function cleanUp()
-{
-  let panels = document.querySelector("panel[hudToolId]");
-  ok(!panels,
-     "all style inspector panels are now detached and ready for garbage collection");
-
-  info("cleaning up");
-
-  doc = hudBox = stylePanels = jsterm = null;
-  finishTest();
-}
--- a/browser/devtools/webconsole/HUDService.jsm
+++ b/browser/devtools/webconsole/HUDService.jsm
@@ -74,22 +74,16 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "nsIClipboardHelper");
 
 XPCOMUtils.defineLazyGetter(this, "gcli", function () {
   var obj = {};
   Cu.import("resource:///modules/gcli.jsm", obj);
   return obj.gcli;
 });
 
-XPCOMUtils.defineLazyGetter(this, "StyleInspector", function () {
-  var obj = {};
-  Cu.import("resource:///modules/devtools/StyleInspector.jsm", obj);
-  return obj.StyleInspector;
-});
-
 XPCOMUtils.defineLazyGetter(this, "CssRuleView", function() {
   let tmp = {};
   Cu.import("resource:///modules/devtools/CssRuleView.jsm", tmp);
   return tmp.CssRuleView;
 });
 
 XPCOMUtils.defineLazyGetter(this, "NetUtil", function () {
   var obj = {};
@@ -1855,20 +1849,16 @@ HUD_SERVICE.prototype =
 
     this.unregisterActiveContext(aHUDId);
 
     let popupset = hud.chromeDocument.getElementById("mainPopupSet");
     let panels = popupset.querySelectorAll("panel[hudId=" + aHUDId + "]");
     for (let i = 0; i < panels.length; i++) {
       panels[i].hidePopup();
     }
-    panels = popupset.querySelectorAll("panel[hudToolId=" + aHUDId + "]");
-    for (let i = 0; i < panels.length; i++) {
-      panels[i].hidePopup();
-    }
 
     let id = ConsoleUtils.supString(aHUDId);
     Services.obs.notifyObservers(id, "web-console-destroyed", null);
 
     if (Object.keys(this.hudReferences).length == 0) {
       let autocompletePopup = hud.chromeDocument.
                               getElementById("webConsole_autocompletePopup");
       if (autocompletePopup) {
@@ -4593,50 +4583,16 @@ function JSTermHelper(aJSTerm)
    */
   aJSTerm.sandbox.inspect = function JSTH_inspect(aObject)
   {
     aJSTerm.helperEvaluated = true;
     let propPanel = aJSTerm.openPropertyPanel(null, unwrap(aObject));
     propPanel.panel.setAttribute("hudId", aJSTerm.hudId);
   };
 
-  /**
-   * Inspects the passed aNode in the style inspector.
-   *
-   * @param object aNode
-   *        aNode to inspect.
-   * @returns void
-   */
-  aJSTerm.sandbox.inspectstyle = function JSTH_inspectstyle(aNode)
-  {
-    let errstr = null;
-    aJSTerm.helperEvaluated = true;
-
-    if (!Services.prefs.getBoolPref("devtools.styleinspector.enabled")) {
-      errstr = HUDService.getStr("inspectStyle.styleInspectorNotEnabled");
-    } else if (!aNode) {
-      errstr = HUDService.getStr("inspectStyle.nullObjectPassed");
-    } else if (!(aNode instanceof Ci.nsIDOMNode)) {
-      errstr = HUDService.getStr("inspectStyle.mustBeDomNode");
-    } else if (!(aNode.style instanceof Ci.nsIDOMCSSStyleDeclaration)) {
-      errstr = HUDService.getStr("inspectStyle.nodeHasNoStyleProps");
-    }
-
-    if (!errstr) {
-      let chromeWin = HUDService.getHudReferenceById(aJSTerm.hudId).chromeWindow;
-      let styleInspector = new StyleInspector(chromeWin);
-      styleInspector.createPanel(false, function() {
-        styleInspector.panel.setAttribute("hudToolId", aJSTerm.hudId);
-        styleInspector.open(aNode);
-      });
-    } else {
-      aJSTerm.writeOutput(errstr + "\n", CATEGORY_OUTPUT, SEVERITY_ERROR);
-    }
-  };
-
   aJSTerm.sandbox.inspectrules = function JSTH_inspectrules(aNode)
   {
     aJSTerm.helperEvaluated = true;
     let doc = aJSTerm.parentNode.ownerDocument;
     let win = doc.defaultView;
     let panel = createElement(doc, "panel", {
       label: "CSS Rules",
       titlebar: "normal",
@@ -5168,16 +5124,17 @@ JSTerm.prototype = {
         hud.pruneConsoleDirNode(node.firstChild);
       }
       else {
         hud.outputNode.removeChild(node.firstChild);
       }
     }
 
     hud.HUDBox.lastTimestamp = 0;
+    hud.groupDepth = 0;
   },
 
   /**
    * Updates the size of the input field (command line) to fit its contents.
    *
    * @returns void
    */
   resizeInput: function JST_resizeInput()
--- a/browser/devtools/webconsole/test/browser/browser_webconsole_bug_664131_console_group.js
+++ b/browser/devtools/webconsole/test/browser/browser_webconsole_bug_664131_console_group.js
@@ -38,11 +38,19 @@ function onLoad(aEvent) {
   is(msg.length, 3, "three message nodes displayed");
   is(msg[2].style.marginLeft, "0px", "correct group indent found");
   content.console.groupCollapsed("b");
   findLogEntry("b");
   let msg = outputNode.querySelectorAll(".webconsole-msg-icon-container");
   is(msg.length, 4, "four message nodes displayed");
   is(msg[3].style.marginLeft, GROUP_INDENT + "px", "correct group indent found");
 
+  // Test that clearing the console removes the indentation.
+  hud.jsterm.clearOutput();
+  content.console.log("cleared");
+  findLogEntry("cleared");
+  let msg = outputNode.querySelectorAll(".webconsole-msg-icon-container");
+  is(msg.length, 1, "one message node displayed");
+  is(msg[0].style.marginLeft, "0px", "correct group indent found");
+
   finishTest();
 }
 
--- 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,64 @@
+# 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
+
+# LOCALIZATION NOTE  (undo.commandkey): This the key to use in
+# conjunction with accel (Command on Mac or Ctrl on other platforms) to Undo a
+# change in the editor.
+undo.commandkey=Z
+
+# LOCALIZATION NOTE  (redo.commandkey): This the key to use in
+# conjunction with accel+shift (accel is Command on Mac or Ctrl on other
+# platforms) to Redo a change in the editor.
+redo.commandkey=Z
+
--- a/browser/locales/en-US/chrome/browser/devtools/webconsole.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/webconsole.properties
@@ -110,33 +110,16 @@ NetworkPanel.imageSizeDeltaDurationMS=%Sx%Spx, Δ%Sms
 # flash data received from the server can't be displayed.
 #
 # The %S is replaced by the content type, that can't be displayed, examples are
 #  o application/x-shockwave-flash
 #  o music/crescendo
 NetworkPanel.responseBodyUnableToDisplay.content=Unable to display responses of type "%S"
 ConsoleAPIDisabled=The Web Console logging API (console.log, console.info, console.warn, console.error) has been disabled by a script on this page.
 
-# LOCALIZATION NOTE (inspectStyle.nullObjectPassed):
-# This message is returned when a null object is passed in to inspectstyle()
-inspectStyle.nullObjectPassed=Object is null
-
-# LOCALIZATION NOTE (inspectStyle.mustBeDomNode):
-# This message is returned when a non-DOM node is passed in to inspectstyle()
-inspectStyle.mustBeDomNode=Object must be a valid DOM node
-
-# LOCALIZATION NOTE (inspectStyle.nodeHasNoStyleProps):
-# This message is returned when an unstyleable object is passed in to inspectstyle()
-inspectStyle.nodeHasNoStyleProps=Object cannot be styled
-
-# LOCALIZATION NOTE (inspectStyle.styleInspectorNotEnabled):
-# This message is returned when devtools.styleinspector.enabled is not set to
-# true
-inspectStyle.styleInspectorNotEnabled=The style inspector is not enabled. Please set the option devtools.styleinspector.enabled to true in about:config to use this command.
-
 # LOCALIZATION NOTE (webConsolePosition): The label shown for the menu which
 # allows the user to toggle between the Web Console positioning types.
 webConsolePosition=Position
 
 # LOCALIZATION NOTE (webConsolePositionTooltip): The tooltip shown when the user
 # hovers the Position button in the Web Console toolbar.
 webConsolePositionTooltip=Position the Web Console above or below the document
 
--- 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)
--- a/browser/themes/gnomestripe/browser.css
+++ b/browser/themes/gnomestripe/browser.css
@@ -2025,16 +2025,17 @@ panel[dimmed="true"] {
   min-width: 78px;
   min-height: 22px;
   color: hsl(210,30%,85%);
   text-shadow: 0 -1px 0 hsla(210,8%,5%,.45);
   border: 1px solid hsla(210,8%,5%,.45);
   border-radius: 3px;
   background: -moz-linear-gradient(hsla(212,7%,57%,.35), hsla(212,7%,57%,.1)) padding-box;
   box-shadow: 0 1px 0 hsla(210,16%,76%,.15) inset, 0 0 0 1px hsla(210,16%,76%,.15) inset, 0 1px 0 hsla(210,16%,76%,.15);
+  margin: 0 3px;
 }
 
 #inspector-inspect-toolbutton:not([checked]):hover:active,
 #inspector-tools > toolbarbutton:not([checked]):hover:active,
 #devtools-sidebar-toolbar > toolbarbutton:not([checked]):hover:active {
   border-color: hsla(210,8%,5%,.6);
   background: -moz-linear-gradient(hsla(220,6%,10%,.3), hsla(212,7%,57%,.15) 65%, hsla(212,7%,57%,.3));
   box-shadow: 0 0 3px hsla(210,8%,5%,.25) inset, 0 1px 3px hsla(210,8%,5%,.25) inset, 0 1px 0 hsla(210,16%,76%,.15);
@@ -2164,17 +2165,17 @@ panel[dimmed="true"] {
   -moz-transition-property: background-color;
   -moz-transition-duration: 500ms;
   background-color: rgba(0,0,0,0);
 }
 
 /* Highlighter toolbar - breadcrumbs */
 
 #inspector-breadcrumbs {
-  padding: 0 6px;
+  padding: 0 3px;
   /* A fake 1px-shadow is included in the border-images of the
      inspector-breadcrumbs-buttons, to match toolbar-buttons style.
      This negative margin compensate the extra row of pixels created
      by the shadow.*/
   margin-bottom: -1px;
 }
 
 .inspector-breadcrumbs-button {
--- a/browser/themes/gnomestripe/devtools/csshtmltree.css
+++ b/browser/themes/gnomestripe/devtools/csshtmltree.css
@@ -217,21 +217,21 @@
  */
 
 .ruleview {
   background-color: #FFF;
 }
 
 .ruleview-rule-source {
   background-color: -moz-dialog;
-  padding: 2px;
+  padding: 2px 5px;
 }
 
 .ruleview-code {
-  padding: 2px;
+  padding: 2px 5px;
 }
 
 .ruleview-propertylist {
   list-style: none;
   padding: 0;
   margin: 0;
 }
 
new file mode 100644
index 0000000000000000000000000000000000000000..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
--- a/browser/themes/pinstripe/devtools/csshtmltree.css
+++ b/browser/themes/pinstripe/devtools/csshtmltree.css
@@ -219,21 +219,21 @@
  */
 
 .ruleview {
   background-color: #FFF;
 }
 
 .ruleview-rule-source {
   background-color: -moz-dialog;
-  padding: 2px;
+  padding: 2px 5px;
 }
 
 .ruleview-code {
-  padding: 2px;
+  padding: 2px 5px;
 }
 
 .ruleview-propertylist {
   list-style: none;
   padding: 0;
   margin: 0;
 }
 
new file mode 100644
index 0000000000000000000000000000000000000000..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
--- a/browser/themes/winstripe/devtools/csshtmltree.css
+++ b/browser/themes/winstripe/devtools/csshtmltree.css
@@ -218,21 +218,21 @@
  */
 
 .ruleview {
   background-color: #FFF;
 }
 
 .ruleview-rule-source {
   background-color: -moz-dialog;
-  padding: 2px;
+  padding: 2px 5px;
 }
 
 .ruleview-code {
-  padding: 2px;
+  padding: 2px 5px;
 }
 
 .ruleview-propertylist {
   list-style: none;
   padding: 0;
   margin: 0;
 }
 
new file mode 100644
index 0000000000000000000000000000000000000000..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
--- a/content/base/public/nsDOMFile.h
+++ b/content/base/public/nsDOMFile.h
@@ -240,17 +240,17 @@ protected:
   }
   virtual already_AddRefed<nsIDOMBlob>
   CreateSlice(PRUint64 aStart, PRUint64 aLength,
               const nsAString& aContentType);
 
   friend class DataOwnerAdapter; // Needs to see DataOwner
   class DataOwner {
   public:
-    NS_INLINE_DECL_REFCOUNTING(DataOwner)
+    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DataOwner)
     DataOwner(void* aMemoryBuffer)
       : mData(aMemoryBuffer)
     {
     }
     ~DataOwner() {
       PR_Free(mData);
     }
     void* mData;
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -8423,17 +8423,17 @@ nsIDocument::SizeOf() const
        node = node->GetNextNode(this)) {
     size += node->SizeOf();
   }
 
   return size;
 }
 
 static void
-DispatchFullScreenChange(nsINode* aTarget)
+DispatchFullScreenChange(nsIDocument* aTarget)
 {
   nsRefPtr<nsPLDOMEvent> e =
     new nsPLDOMEvent(aTarget,
                      NS_LITERAL_STRING("mozfullscreenchange"),
                      true,
                      false);
   e->PostDOMEvent();
 }
@@ -8647,30 +8647,30 @@ nsDocument::RequestFullScreen(Element* a
   // Remember the root document, so that if a full-screen document is hidden
   // we can reset full-screen state in the remaining visible full-screen documents.
   sFullScreenRootDoc = do_GetWeakReference(nsContentUtils::GetRootDocument(this));
 
   // Set the full-screen element. This sets the full-screen style on the
   // element, and the full-screen-ancestor styles on ancestors of the element
   // in this document.
   if (SetFullScreenState(aElement, true)) {
-    DispatchFullScreenChange(aElement);
+    DispatchFullScreenChange(aElement->OwnerDoc());
   }
 
   // Propagate up the document hierarchy, setting the full-screen element as
   // the element's container in ancestor documents. This also sets the
   // appropriate css styles as well. Note we don't propagate down the
   // document hierarchy, the full-screen element (or its container) is not
   // visible there.  
   nsIDocument* child = this;
   nsIDocument* parent;
   while ((parent = child->GetParentDocument())) {
     Element* element = parent->FindContentForSubDocument(child)->AsElement();
     if (::SetFullScreenState(parent, element, true)) {
-      DispatchFullScreenChange(element);
+      DispatchFullScreenChange(element->OwnerDoc());
     }
     child = parent;
   }
 
   // Make the window full-screen. Note we must make the state changes above
   // before making the window full-screen, as then the document reports as
   // being in full-screen mode when the chrome "fullscreen" event fires,
   // enabling chrome to distinguish between browser and dom full-screen
--- a/content/base/src/nsFrameLoader.h
+++ b/content/base/src/nsFrameLoader.h
@@ -126,16 +126,18 @@ public:
     float mXScale;
     float mYScale;
   };
 
   nsContentView(nsFrameLoader* aFrameLoader, ViewID aScrollId,
                 ViewConfig aConfig = ViewConfig())
     : mViewportSize(0, 0)
     , mContentSize(0, 0)
+    , mParentScaleX(1.0)
+    , mParentScaleY(1.0)
     , mFrameLoader(aFrameLoader)
     , mScrollId(aScrollId)
     , mConfig(aConfig)
   {}
 
   bool IsRoot() const;
 
   ViewID GetId() const
@@ -145,16 +147,18 @@ public:
 
   ViewConfig GetViewConfig() const
   {
     return mConfig;
   }
 
   nsSize mViewportSize;
   nsSize mContentSize;
+  float mParentScaleX;
+  float mParentScaleY;
 
   nsFrameLoader* mFrameLoader;  // WEAK
 
 private:
   nsresult Update(const ViewConfig& aConfig);
 
   ViewID mScrollId;
   ViewConfig mConfig;
--- a/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
+++ b/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
@@ -111,16 +111,17 @@
 #include <algorithm>
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/ipc/PDocumentRendererParent.h"
 #include "mozilla/dom/PBrowserParent.h"
 #include "mozilla/ipc/DocumentRendererParent.h"
 
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/Preferences.h"
 
 #ifdef XP_WIN
 #include "gfxWindowsPlatform.h"
 #endif
 
 // windows.h (included by chromium code) defines this, in its infinite wisdom
 #undef DrawText
 
@@ -984,19 +985,20 @@ NS_INTERFACE_MAP_END
 PRUint32 nsCanvasRenderingContext2DAzure::sNumLivingContexts = 0;
 PRUint8 (*nsCanvasRenderingContext2DAzure::sUnpremultiplyTable)[256] = nsnull;
 PRUint8 (*nsCanvasRenderingContext2DAzure::sPremultiplyTable)[256] = nsnull;
 
 nsresult
 NS_NewCanvasRenderingContext2DAzure(nsIDOMCanvasRenderingContext2D** aResult)
 {
 #ifdef XP_WIN
-  if (gfxWindowsPlatform::GetPlatform()->GetRenderMode() !=
+  if ((gfxWindowsPlatform::GetPlatform()->GetRenderMode() !=
       gfxWindowsPlatform::RENDER_DIRECT2D ||
-      !gfxWindowsPlatform::GetPlatform()->DWriteEnabled()) {
+      !gfxWindowsPlatform::GetPlatform()->DWriteEnabled()) &&
+      !Preferences::GetBool("gfx.canvas.azure.prefer-skia", false)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 #elif !defined(XP_MACOSX) && !defined(ANDROID)
   return NS_ERROR_NOT_AVAILABLE;
 #endif
 
   nsRefPtr<nsIDOMCanvasRenderingContext2D> ctx = new nsCanvasRenderingContext2DAzure();
   if (!ctx)
--- a/content/canvas/test/test_canvas.html
+++ b/content/canvas/test/test_canvas.html
@@ -24,16 +24,28 @@ function IsAzureEnabled() {
   try {
     netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
     enabled = Components.classes["@mozilla.org/gfx/info;1"].getService(Components.interfaces.nsIGfxInfo).AzureEnabled;
   } catch (e) { }
 
   return enabled;
 }
 
+function IsAzureSkia() {
+  var enabled = false;
+  
+  try {
+    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
+    var backend = Components.classes["@mozilla.org/gfx/info;1"].getService(Components.interfaces.nsIGfxInfo).getInfo().AzureBackend;
+    enabled = (backend == "skia");
+  } catch (e) { }
+
+  return enabled;
+}
+
 </script>
 <!-- Includes all the tests in the content/canvas/tests except for test_bug397524.html -->
 
 <!-- [[[ test_2d.canvas.readonly.html ]]] -->
 
 <p>Canvas test: 2d.canvas.readonly</p>
 <!-- Testing: CanvasRenderingContext2D.canvas is readonly -->
 <canvas id="c1" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
@@ -11980,17 +11992,17 @@ isPixel(ctx, 50,25, 0,255,0,255, 0);
 isPixel(ctx, 55,19, 0,255,0,255, 0);
 isPixel(ctx, 55,20, 0,255,0,255, 0);
 isPixel(ctx, 55,21, 0,255,0,255, 0);
 isPixel(ctx, 64,22, 0,255,0,255, 0);
 isPixel(ctx, 65,21, 0,255,0,255, 0);
 isPixel(ctx, 72,28, 0,255,0,255, 0);
 isPixel(ctx, 73,27, 0,255,0,255, 0);
 isPixel(ctx, 78,36, 0,255,0,255, 0);
-isPixel(ctx, 79,35, 0,255,0,255, 0);
+isPixel(ctx, 79,35, 0,255,0,255, IsAzureSkia() ? 1 : 0);
 isPixel(ctx, 80,44, 0,255,0,255, 0);
 isPixel(ctx, 80,45, 0,255,0,255, 0);
 isPixel(ctx, 80,46, 0,255,0,255, 0);
 
 
 }
 </script>
 
@@ -12027,21 +12039,21 @@ ctx.beginPath();
 ctx.moveTo(10, 25);
 ctx.arcTo(75, 25, 75, 60, 20);
 ctx.stroke();
 
 isPixel(ctx, 50,25, 0,255,0,255, 0);
 isPixel(ctx, 55,19, 0,255,0,255, 0);
 isPixel(ctx, 55,20, 0,255,0,255, 0);
 isPixel(ctx, 55,21, 0,255,0,255, 0);
-isPixel(ctx, 64,22, 0,255,0,255, 0);
+isPixel(ctx, 64,22, 0,255,0,255, IsAzureSkia() ? 1 : 0);
 isPixel(ctx, 65,21, 0,255,0,255, 0);
 isPixel(ctx, 72,28, 0,255,0,255, 0);
-isPixel(ctx, 73,27, 0,255,0,255, 0);
-isPixel(ctx, 78,36, 0,255,0,255, 0);
+isPixel(ctx, 73,27, 0,255,0,255, IsAzureSkia() ? 1 : 0);
+isPixel(ctx, 78,36, 0,255,0,255, IsAzureSkia() ? 1 : 0);
 isPixel(ctx, 79,35, 0,255,0,255, 0);
 isPixel(ctx, 80,44, 0,255,0,255, 0);
 isPixel(ctx, 80,45, 0,255,0,255, 0);
 isPixel(ctx, 80,46, 0,255,0,255, 0);
 
 
 }
 </script>
@@ -14334,18 +14346,21 @@ ctx.lineCap = 'round';
 ctx.lineJoin = 'round';
 
 ctx.beginPath();
 ctx.moveTo(50, 25);
 ctx.lineTo(50, 25);
 ctx.closePath();
 ctx.stroke();
 
-todo_isPixel(ctx, 50,25, 0,255,0,255, 0);
-
+if (IsAzureEnabled() && IsAzureSkia()) {
+  isPixel(ctx, 50,25, 0,255,0,255, 0);
+} else {
+  todo_isPixel(ctx, 50,25, 0,255,0,255, 0);
+}
 
 }
 </script>
 
 <!-- [[[ test_2d.path.stroke.prune.corner.html ]]] -->
 
 <p>Canvas test: 2d.path.stroke.prune.corner</p>
 <!-- Testing: Zero-length line segments are removed before stroking with miters -->
@@ -14407,17 +14422,21 @@ ctx.moveTo(50, 25);
 ctx.quadraticCurveTo(50, 25, 50, 25);
 ctx.stroke();
 
 ctx.beginPath();
 ctx.moveTo(50, 25);
 ctx.bezierCurveTo(50, 25, 50, 25, 50, 25);
 ctx.stroke();
 
-todo_isPixel(ctx, 50,25, 0,255,0,255, 0);
+if (IsAzureEnabled() && IsAzureSkia()) {
+  isPixel(ctx, 50,25, 0,255,0,255, 0);
+} else {
+  todo_isPixel(ctx, 50,25, 0,255,0,255, 0);
+}
 
 
 }
 </script>
 
 <!-- [[[ test_2d.path.stroke.prune.line.html ]]] -->
 
 <p>Canvas test: 2d.path.stroke.prune.line</p>
@@ -14440,18 +14459,21 @@ ctx.lineWidth = 100;
 ctx.lineCap = 'round';
 ctx.lineJoin = 'round';
 
 ctx.beginPath();
 ctx.moveTo(50, 25);
 ctx.lineTo(50, 25);
 ctx.stroke();
 
-todo_isPixel(ctx, 50,25, 0,255,0,255, 0);
-
+if (IsAzureEnabled() && IsAzureSkia()) {
+  isPixel(ctx, 50,25, 0,255,0,255, 0);
+} else {
+  todo_isPixel(ctx, 50,25, 0,255,0,255, 0);
+}
 
 }
 </script>
 
 <!-- [[[ test_2d.path.stroke.prune.rect.html ]]] -->
 
 <p>Canvas test: 2d.path.stroke.prune.rect</p>
 <!-- Testing: Zero-length line segments from rect and strokeRect are removed before stroking -->
@@ -14474,18 +14496,21 @@ ctx.lineCap = 'round';
 ctx.lineJoin = 'round';
 
 ctx.beginPath();
 ctx.rect(50, 25, 0, 0);
 ctx.stroke();
 
 ctx.strokeRect(50, 25, 0, 0);
 
-todo_isPixel(ctx, 50,25, 0,255,0,255, 0);
-
+if (IsAzureEnabled() && IsAzureSkia()) {
+  isPixel(ctx, 50,25, 0,255,0,255, 0);
+} else {
+  todo_isPixel(ctx, 50,25, 0,255,0,255, 0);
+}
 
 }
 </script>
 
 <!-- [[[ test_2d.path.stroke.scale1.html ]]] -->
 
 <p>Canvas test: 2d.path.stroke.scale1</p>
 <!-- Testing: Stroke line widths are scaled by the current transformation matrix -->
@@ -19180,17 +19205,17 @@ ctx.beginPath();
 ctx.moveTo(pts[0][0], pts[0][1]);
 for (var i = 0; i < pts.length; ++i)
     ctx.lineTo(pts[i][0], pts[i][1]);
 ctx.fill();
 isPixel(ctx, 21,11, 0,255,0,255, 0);
 isPixel(ctx, 79,11, 0,255,0,255, 0);
 isPixel(ctx, 21,39, 0,255,0,255, 0);
 isPixel(ctx, 79,39, 0,255,0,255, 0);
-isPixel(ctx, 39,19, 0,255,0,255, 0);
+isPixel(ctx, 39,19, 0,255,0,255, IsAzureSkia() ? 1 : 0);
 isPixel(ctx, 61,19, 0,255,0,255, 0);
 isPixel(ctx, 39,31, 0,255,0,255, 0);
 isPixel(ctx, 61,31, 0,255,0,255, 0);
 
 
 }
 </script>
 
@@ -19388,17 +19413,17 @@ ctx.beginPath();
 ctx.moveTo(pts[0][0], pts[0][1]);
 for (var i = 0; i < pts.length; ++i)
     ctx.lineTo(pts[i][0], pts[i][1]);
 ctx.fill();
 isPixel(ctx, 21,11, 0,255,0,255, 0);
 isPixel(ctx, 79,11, 0,255,0,255, 0);
 isPixel(ctx, 21,39, 0,255,0,255, 0);
 isPixel(ctx, 79,39, 0,255,0,255, 0);
-isPixel(ctx, 39,19, 0,255,0,255, 0);
+isPixel(ctx, 39,19, 0,255,0,255, IsAzureSkia() ? 1 : 0);
 isPixel(ctx, 61,19, 0,255,0,255, 0);
 isPixel(ctx, 39,31, 0,255,0,255, 0);
 isPixel(ctx, 61,31, 0,255,0,255, 0);
 
 
 }
 </script>
 
--- a/content/html/content/test/file_fullscreen-api.html
+++ b/content/html/content/test/file_fullscreen-api.html
@@ -60,17 +60,17 @@ function setRequireTrustedContext(value)
 function fullScreenElement() {
   return document.getElementById('full-screen-element');
 }
 
 function fullScreenChange(event) {
   switch (fullScreenChangeCount) {
     case 0: {
       ok(document.mozFullScreen, "Should be in full-screen mode (first time)");
-      is(event.target, fullScreenElement(), "Event target should be full-screen element");
+      is(event.target, document, "Event target should be full-screen document #1");
       is(document.mozFullScreenElement, fullScreenElement(),
          "Full-screen element should be div element.");
       ok(document.mozFullScreenElement.mozMatchesSelector(":-moz-full-screen"),
          "FSE should match :-moz-full-screen");
       var fse = fullScreenElement();
       fse.parentNode.removeChild(fse);
       is(document.mozFullScreenElement, null,
          "Full-screen element should be null after removing.");
@@ -78,28 +78,27 @@ function fullScreenChange(event) {
       document.body.appendChild(fse);
       ok(!document.mozFullScreen, "Should not return to full-screen mode when re-adding former FSE");
       is(document.mozFullScreenElement, null,
          "Full-screen element should still be null after re-adding former FSE.");
       break;
     }
     case 1: {
       ok(!document.mozFullScreen, "Should have left full-screen mode (first time)");
-      is(event.target, document, "Event target should be document when we exit via removing from doc");
+      is(event.target, document, "Event target should be full-screen document #2");
       is(document.mozFullScreenElement, null, "Full-screen element should be null.");
       iframe = document.createElement("iframe");
       iframe.mozAllowFullScreen = true;
       document.body.appendChild(iframe);
       iframe.src = iframeContents;
       break;
     }
     case 2: {
       ok(document.mozFullScreen, "Should be back in full-screen mode (second time)");
-      is(event.target, iframe,
-         "Event target should be full-screen element container");
+      is(event.target, document, "Event target should be full-screen document #3");
       is(document.mozFullScreenElement, iframe,
         "Full-screen element should be iframe element.");
  
       SimpleTest.waitForFocus(function() {
         ok(document.mozFullScreen, "Should still be full-screen mode before focus.");
         SpecialPowers.focus(window);
         ok(document.mozFullScreen, "Should still be full-screen mode after focus.");
         
@@ -121,18 +120,17 @@ function fullScreenChange(event) {
           
           document.body.appendChild(fse);
         }, 0);
       }, 0);
       break;
     }
     case 3: {
       ok(!document.mozFullScreen, "Should be back in non-full-screen mode (second time)");
-      is(event.target, document,
-         "Event target should be full-screen element container");
+      is(event.target, document, "Event target should be full-screen document #4");
       is(document.mozFullScreenElement, null, "Full-screen element should be null.");
       document.body.removeChild(iframe);
       iframe = null;
 
       // Do a request out of document. It should be denied.
       // Continue test in the following mozfullscreenerror handler.
       outOfDocElement = document.createElement("div");
       var f =
@@ -150,17 +148,17 @@ function fullScreenChange(event) {
       };
       document.addEventListener("mozfullscreenerror", f, false);
       outOfDocElement.mozRequestFullScreen();
 
       break;
     }
     case 4: {
       ok(document.mozFullScreen, "Should still be in full-screen mode (third time)");
-      is(event.target, inDocElement, "Event target should be inDocElement");
+      is(event.target, document, "Event target should be full-screen document #5");
       ok(fullScreenErrorRun, "Should have run fullscreenerror handler from previous case.");
       is(document.mozFullScreenElement, inDocElement,
         "FSE should be inDocElement.");
 
       var n = container;
       do {
         ok(n.mozMatchesSelector(":-moz-full-screen-ancestor"), "Ancestor " + n + " should match :-moz-full-screen-ancestor")
         n = n.parentNode;
--- a/dom/indexedDB/IDBTransaction.cpp
+++ b/dom/indexedDB/IDBTransaction.cpp
@@ -68,16 +68,45 @@ DoomCachedStatements(const nsACString& a
                      nsCOMPtr<mozIStorageStatement>& aStatement,
                      void* aUserArg)
 {
   CommitHelper* helper = static_cast<CommitHelper*>(aUserArg);
   helper->AddDoomedObject(aStatement);
   return PL_DHASH_REMOVE;
 }
 
+// This runnable doesn't actually do anything beyond "prime the pump" and get
+// transactions in the right order on the transaction thread pool.
+class StartTransactionRunnable : public nsIRunnable
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  NS_IMETHOD Run()
+  {
+    // NOP
+    return NS_OK;
+  }
+};
+
+// Could really use those NS_REFCOUNTING_HAHA_YEAH_RIGHT macros here.
+NS_IMETHODIMP_(nsrefcnt) StartTransactionRunnable::AddRef()
+{
+  return 2;
+}
+
+NS_IMETHODIMP_(nsrefcnt) StartTransactionRunnable::Release()
+{
+  return 1;
+}
+
+NS_IMPL_QUERY_INTERFACE1(StartTransactionRunnable, nsIRunnable)
+
+StartTransactionRunnable gStartTransactionRunnable;
+
 } // anonymous namespace
 
 // static
 already_AddRefed<IDBTransaction>
 IDBTransaction::Create(IDBDatabase* aDatabase,
                        nsTArray<nsString>& aObjectStoreNames,
                        PRUint16 aMode,
                        bool aDispatchDelayed)
@@ -116,16 +145,21 @@ IDBTransaction::Create(IDBDatabase* aDat
     transaction->mCreatedRecursionDepth = depth - 1;
 
     rv = thread->AddObserver(transaction);
     NS_ENSURE_SUCCESS(rv, nsnull);
 
     transaction->mCreating = true;
   }
 
+  if (aMode != nsIIDBTransaction::VERSION_CHANGE) {
+    TransactionThreadPool* pool = TransactionThreadPool::GetOrCreate();
+    pool->Dispatch(transaction, &gStartTransactionRunnable, false, nsnull);
+  }
+
   return transaction.forget();
 }
 
 IDBTransaction::IDBTransaction()
 : mReadyState(nsIIDBTransaction::INITIAL),
   mMode(nsIIDBTransaction::READ_ONLY),
   mPendingRequests(0),
   mCreatedRecursionDepth(0),
--- a/dom/indexedDB/TransactionThreadPool.cpp
+++ b/dom/indexedDB/TransactionThreadPool.cpp
@@ -42,18 +42,17 @@
 #include "nsIObserverService.h"
 #include "nsIThreadPool.h"
 
 #include "nsComponentManagerUtils.h"
 #include "nsThreadUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsXPCOMCIDInternal.h"
 
-using mozilla::MutexAutoLock;
-using mozilla::MutexAutoUnlock;
+using mozilla::MonitorAutoLock;
 
 USING_INDEXEDDB_NAMESPACE
 
 namespace {
 
 const PRUint32 kThreadLimit = 20;
 const PRUint32 kIdleThreadLimit = 5;
 const PRUint32 kIdleThreadTimeoutMs = 30000;
@@ -599,71 +598,67 @@ TransactionThreadPool::MaybeFireCallback
     callback.mCallback->Run();
     mCompleteCallbacks.RemoveElementAt(aCallbackIndex);
   }
 }
 
 TransactionThreadPool::
 TransactionQueue::TransactionQueue(IDBTransaction* aTransaction,
                                    nsIRunnable* aRunnable)
-: mMutex("TransactionQueue::mMutex"),
-  mCondVar(mMutex, "TransactionQueue::mCondVar"),
+: mMonitor("TransactionQueue::mMonitor"),
   mTransaction(aTransaction),
   mShouldFinish(false)
 {
   NS_ASSERTION(aTransaction, "Null pointer!");
   NS_ASSERTION(aRunnable, "Null pointer!");
   mQueue.AppendElement(aRunnable);
 }
 
 void
 TransactionThreadPool::TransactionQueue::Dispatch(nsIRunnable* aRunnable)
 {
-  MutexAutoLock lock(mMutex);
+  MonitorAutoLock lock(mMonitor);
 
   NS_ASSERTION(!mShouldFinish, "Dispatch called after Finish!");
 
-  if (!mQueue.AppendElement(aRunnable)) {
-    MutexAutoUnlock unlock(mMutex);
-    NS_RUNTIMEABORT("Out of memory!");
-  }
+  mQueue.AppendElement(aRunnable);
 
-  mCondVar.Notify();
+  mMonitor.Notify();
 }
 
 void
 TransactionThreadPool::TransactionQueue::Finish(nsIRunnable* aFinishRunnable)
 {
-  MutexAutoLock lock(mMutex);
+  MonitorAutoLock lock(mMonitor);
 
   NS_ASSERTION(!mShouldFinish, "Finish called more than once!");
 
   mShouldFinish = true;
   mFinishRunnable = aFinishRunnable;
 
-  mCondVar.Notify();
+  mMonitor.Notify();
 }
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(TransactionThreadPool::TransactionQueue,
                               nsIRunnable)
 
 NS_IMETHODIMP
 TransactionThreadPool::TransactionQueue::Run()
 {
   nsAutoTArray<nsCOMPtr<nsIRunnable>, 10> queue;
   nsCOMPtr<nsIRunnable> finishRunnable;
   bool shouldFinish = false;
 
   while(!shouldFinish) {
     NS_ASSERTION(queue.IsEmpty(), "Should have cleared this!");
 
     {
-      MutexAutoLock lock(mMutex);
+      MonitorAutoLock lock(mMonitor);
       while (!mShouldFinish && mQueue.IsEmpty()) {
-        if (NS_FAILED(mCondVar.Wait())) {
+        if (NS_FAILED(mMonitor.Wait())) {
           NS_ERROR("Failed to wait!");
         }
       }
 
       mQueue.SwapElements(queue);
       if (mShouldFinish) {
         mFinishRunnable.swap(finishRunnable);
         shouldFinish = true;
--- a/dom/indexedDB/TransactionThreadPool.h
+++ b/dom/indexedDB/TransactionThreadPool.h
@@ -41,18 +41,17 @@
 #define mozilla_dom_indexeddb_transactionthreadpool_h__
 
 // Only meant to be included in IndexedDB source files, not exported.
 #include "IndexedDatabase.h"
 
 #include "nsIObserver.h"
 #include "nsIRunnable.h"
 
-#include "mozilla/Mutex.h"
-#include "mozilla/CondVar.h"
+#include "mozilla/Monitor.h"
 #include "nsClassHashtable.h"
 #include "nsHashKeys.h"
 #include "nsRefPtrHashtable.h"
 
 #include "IDBTransaction.h"
 
 class nsIThreadPool;
 
@@ -101,18 +100,17 @@ protected:
     inline TransactionQueue(IDBTransaction* aTransaction,
                             nsIRunnable* aRunnable);
 
     inline void Dispatch(nsIRunnable* aRunnable);
 
     inline void Finish(nsIRunnable* aFinishRunnable);
 
   private:
-    mozilla::Mutex mMutex;
-    mozilla::CondVar mCondVar;
+    mozilla::Monitor mMonitor;
     IDBTransaction* mTransaction;
     nsAutoTArray<nsCOMPtr<nsIRunnable>, 10> mQueue;
     nsCOMPtr<nsIRunnable> mFinishRunnable;
     bool mShouldFinish;
   };
 
   struct TransactionInfo
   {
--- a/dom/indexedDB/test/Makefile.in
+++ b/dom/indexedDB/test/Makefile.in
@@ -97,16 +97,17 @@ TEST_FILES = \
   test_remove_objectStore.html \
   test_request_readyState.html \
   test_success_events_after_abort.html \
   test_third_party.html \
   test_traffic_jam.html \
   test_transaction_abort.html \
   test_transaction_lifetimes.html \
   test_transaction_lifetimes_nested.html \
+  test_transaction_ordering.html \
   test_setVersion.html \
   test_setVersion_abort.html \
   test_setVersion_events.html \
   test_setVersion_exclusion.html \
   test_writer_starvation.html \
   third_party_iframe1.html \
   third_party_iframe2.html \
   $(NULL)
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_transaction_ordering.html
@@ -0,0 +1,61 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html>
+<head>
+  <title>Indexed Database Property Test</title>
+
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+  <script type="text/javascript;version=1.7">
+    function testSteps()
+    {
+      let request = mozIndexedDB.open(window.location.pathname, 1);
+      request.onerror = errorHandler;
+      request.onupgradeneeded = grabEventAndContinueHandler;
+      let event = yield;
+
+      let db = event.target.result;
+      db.onerror = errorHandler;
+
+      request.onsuccess = continueToNextStep;
+
+      db.createObjectStore("foo");
+      yield;
+
+      let trans1 = db.transaction("foo", IDBTransaction.READ_WRITE);
+      let trans2 = db.transaction("foo", IDBTransaction.READ_WRITE);
+
+      let request1 = trans2.objectStore("foo").put("2", 42);
+      let request2 = trans1.objectStore("foo").put("1", 42);
+
+      request1.onerror = errorHandler;
+      request2.onerror = errorHandler;
+
+      trans1.oncomplete = grabEventAndContinueHandler;
+      trans2.oncomplete = grabEventAndContinueHandler;
+
+      yield;
+      yield;
+
+      let trans3 = db.transaction("foo", IDBTransaction.READ);
+      let request = trans3.objectStore("foo").get(42);
+      request.onsuccess = grabEventAndContinueHandler;
+      request.onerror = errorHandler;
+
+      let event = yield;
+      is(event.target.result, "2", "Transactions were ordered properly.");
+
+      finishTest();
+      yield;
+    }
+  </script>
+  <script type="text/javascript;version=1.7" src="helpers.js"></script>
+
+</head>
+
+<body onload="runTest();"></body>
+
+</html>
--- a/dom/plugins/test/reftest/reftest.list
+++ b/dom/plugins/test/reftest/reftest.list
@@ -2,17 +2,17 @@
 random-if(!haveTestPlugin) != plugin-sanity.html about:blank
 fails-if(!haveTestPlugin) == plugin-sanity.html div-sanity.html
 fails-if(!haveTestPlugin) == plugin-alpha-zindex.html div-alpha-zindex.html
 fails-if(!haveTestPlugin) == plugin-alpha-opacity.html div-alpha-opacity.html
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) == windowless-clipping-1.html windowless-clipping-1-ref.html # bug 631832
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) == border-padding-1.html border-padding-1-ref.html # bug 629430
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) == border-padding-2.html border-padding-2-ref.html # bug 629430
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) skip-if(Android) == border-padding-3.html border-padding-3-ref.html # bug 629430
-random-if(cocoaWidget||d2d) fails-if(!haveTestPlugin&&!Android) skip-if(!testPluginIsOOP()) == pluginproblemui-direction-1.html pluginproblemui-direction-1-ref.html
+random-if(cocoaWidget||d2d||/^Windows\x20NT\x205\.1/.test(http.oscpu)) fails-if(!haveTestPlugin&&!Android) skip-if(!testPluginIsOOP()) == pluginproblemui-direction-1.html pluginproblemui-direction-1-ref.html # bug 567367
 random-if(cocoaWidget) fails-if(!haveTestPlugin&&!Android) skip-if(!testPluginIsOOP()) == pluginproblemui-direction-2.html pluginproblemui-direction-2-ref.html
 fails-if(!haveTestPlugin) == plugin-canvas-alpha-zindex.html div-alpha-zindex.html
 fails-if(!haveTestPlugin) == plugin-transform-alpha-zindex.html div-alpha-zindex.html
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) == plugin-busy-alpha-zindex.html div-alpha-zindex.html
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) == plugin-background.html plugin-background-ref.html
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) == plugin-background-1-step.html plugin-background-ref.html
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) == plugin-background-2-step.html plugin-background-ref.html
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) == plugin-background-5-step.html plugin-background-ref.html
--- a/gfx/2d/DrawTargetSkia.cpp
+++ b/gfx/2d/DrawTargetSkia.cpp
@@ -132,46 +132,16 @@ GfxOpToSkiaOp(CompositionOp op)
     case OP_XOR:
       return SkXfermode::kXor_Mode;
     case OP_COUNT:
       return SkXfermode::kSrcOver_Mode;
   }
   return SkXfermode::kSrcOver_Mode;
 }
 
-SkPaint::Cap
-CapStyleToSkiaCap(CapStyle aCap)
-{
-  switch (aCap)
-  {
-    case CAP_BUTT:
-      return SkPaint::kButt_Cap;
-    case CAP_ROUND:
-      return SkPaint::kRound_Cap;
-    case CAP_SQUARE:
-      return SkPaint::kSquare_Cap;
-  }
-  return SkPaint::kDefault_Cap;
-}
-
-SkPaint::Join
-JoinStyleToSkiaJoin(JoinStyle aJoin)
-{
-  switch (aJoin)
-  {
-    case JOIN_BEVEL:
-      return SkPaint::kBevel_Join;
-    case JOIN_ROUND:
-      return SkPaint::kRound_Join;
-    case JOIN_MITER:
-    case JOIN_MITER_OR_BEVEL:
-      return SkPaint::kMiter_Join;
-  }
-  return SkPaint::kDefault_Join;
-}
 
 SkRect
 RectToSkRect(const Rect& aRect)
 {
   return SkRect::MakeXYWH(SkFloatToScalar(aRect.x), SkFloatToScalar(aRect.y), 
                           SkFloatToScalar(aRect.width), SkFloatToScalar(aRect.height));
 }
 
@@ -196,17 +166,24 @@ IntRectToSkIRect(const IntRect& aRect)
 
 
 DrawTargetSkia::DrawTargetSkia()
 {
 }
 
 DrawTargetSkia::~DrawTargetSkia()
 {
-  MarkChanged();
+  if (mSnapshots.size()) {
+    for (std::vector<SourceSurfaceSkia*>::iterator iter = mSnapshots.begin();
+         iter != mSnapshots.end(); iter++) {
+      (*iter)->DrawTargetDestroyed();
+    }
+    // All snapshots will now have copied data.
+    mSnapshots.clear();
+  }
 }
 
 TemporaryRef<SourceSurface>
 DrawTargetSkia::Snapshot()
 {
   RefPtr<SourceSurfaceSkia> source = new SourceSurfaceSkia();
   if (!source->InitWithBitmap(mBitmap, mFormat, this)) {
     return NULL;
@@ -275,16 +252,17 @@ struct AutoPaintSetup {
       temp.setAlpha(aOptions.mAlpha*255);
       //TODO: Get a rect here
       mCanvas->saveLayer(NULL, &temp);
       mNeedsRestore = true;
     } else {
       mPaint.setAlpha(aOptions.mAlpha*255.0);
       mAlpha = aOptions.mAlpha;
     }
+    mPaint.setFilterBitmap(true);
   }
 
   void SetPattern(const Pattern& aPattern)
   {
     if (aPattern.GetType() == PATTERN_COLOR) {
       Color color = static_cast<const ColorPattern&>(aPattern).mColor;
       mPaint.setColor(ColorToSkColor(color, mAlpha));
     } else if (aPattern.GetType() == PATTERN_LINEAR_GRADIENT) {
@@ -331,37 +309,16 @@ struct AutoPaintSetup {
       const SkBitmap& bitmap = static_cast<SourceSurfaceSkia*>(pat.mSurface.get())->GetBitmap();
 
       SkShader::TileMode mode = ExtendModeToTileMode(pat.mExtendMode);
       SkShader* shader = SkShader::CreateBitmapShader(bitmap, mode, mode);
       SkSafeUnref(mPaint.setShader(shader));
     }
   }
 
-  void SetStroke(const StrokeOptions &aOptions)
-  {
-    mPaint.setStrokeWidth(SkFloatToScalar(aOptions.mLineWidth));
-    mPaint.setStrokeMiter(SkFloatToScalar(aOptions.mMiterLimit));
-    mPaint.setStrokeCap(CapStyleToSkiaCap(aOptions.mLineCap));
-    mPaint.setStrokeJoin(JoinStyleToSkiaJoin(aOptions.mLineJoin));
-    if (aOptions.mDashLength) {
-      std::vector<SkScalar> pattern;
-      pattern.resize(aOptions.mDashLength);
-      for (uint32_t i = 0; i < aOptions.mDashLength; i++) {
-        pattern[i] = SkFloatToScalar(aOptions.mDashPattern[i]);
-      }
-
-      SkDashPathEffect* dash = new SkDashPathEffect(&pattern.front(), 
-                                                    aOptions.mDashLength, 
-                                                    SkFloatToScalar(aOptions.mDashOffset));
-      SkSafeUnref(mPaint.setPathEffect(dash));
-    }
-    mPaint.setStyle(SkPaint::kStroke_Style);
-  }
-
   // TODO: Maybe add an operator overload to access this easier?
   SkPaint mPaint;
   bool mNeedsRestore;
   SkCanvas* mCanvas;
   Float mAlpha;
 };
 
 void
@@ -381,29 +338,31 @@ DrawTargetSkia::DrawSurface(SourceSurfac
   }
 
   if (aSource.IsEmpty()) {
     return;
   }
 
   MarkChanged();
 
-  NS_ASSERTION(aSurfOptions.mFilter == FILTER_LINEAR, "Only linear filtering supported currently!");
   SkRect destRect = RectToSkRect(aDest);
   SkRect sourceRect = RectToSkRect(aSource);
 
   SkMatrix matrix;
   matrix.setRectToRect(sourceRect, destRect, SkMatrix::kFill_ScaleToFit);
   
   const SkBitmap& bitmap = static_cast<SourceSurfaceSkia*>(aSurface)->GetBitmap();
  
   AutoPaintSetup paint(mCanvas.get(), aOptions);
   SkShader *shader = SkShader::CreateBitmapShader(bitmap, SkShader::kClamp_TileMode, SkShader::kClamp_TileMode);
   shader->setLocalMatrix(matrix);
   SkSafeUnref(paint.mPaint.setShader(shader));
+  if (aSurfOptions.mFilter != FILTER_LINEAR) {
+    paint.mPaint.setFilterBitmap(false);
+  }
   mCanvas->drawRect(destRect, paint.mPaint);
 }
 
 void
 DrawTargetSkia::DrawSurfaceWithShadow(SourceSurface *aSurface,
                                       const Point &aDest,
                                       const Color &aColor,
                                       const Point &aOffset,
@@ -487,44 +446,50 @@ DrawTargetSkia::Stroke(const Path *aPath
   if (aPath->GetBackendType() != BACKEND_SKIA) {
     return;
   }
 
   const PathSkia *skiaPath = static_cast<const PathSkia*>(aPath);
 
 
   AutoPaintSetup paint(mCanvas.get(), aOptions, aPattern);
-  paint.SetStroke(aStrokeOptions);
+  if (!StrokeOptionsToPaint(paint.mPaint, aStrokeOptions)) {
+    return;
+  }
 
   mCanvas->drawPath(skiaPath->GetPath(), paint.mPaint);
 }
 
 void
 DrawTargetSkia::StrokeRect(const Rect &aRect,
                            const Pattern &aPattern,
                            const StrokeOptions &aStrokeOptions,
                            const DrawOptions &aOptions)
 {
   MarkChanged();
   AutoPaintSetup paint(mCanvas.get(), aOptions, aPattern);
-  paint.SetStroke(aStrokeOptions);
+  if (!StrokeOptionsToPaint(paint.mPaint, aStrokeOptions)) {
+    return;
+  }
 
   mCanvas->drawRect(RectToSkRect(aRect), paint.mPaint);
 }
 
 void 
 DrawTargetSkia::StrokeLine(const Point &aStart,
                            const Point &aEnd,
                            const Pattern &aPattern,
                            const StrokeOptions &aStrokeOptions,
                            const DrawOptions &aOptions)
 {
   MarkChanged();
   AutoPaintSetup paint(mCanvas.get(), aOptions, aPattern);
-  paint.SetStroke(aStrokeOptions);
+  if (!StrokeOptionsToPaint(paint.mPaint, aStrokeOptions)) {
+    return;
+  }
 
   mCanvas->drawLine(SkFloatToScalar(aStart.x), SkFloatToScalar(aStart.y), 
                     SkFloatToScalar(aEnd.x), SkFloatToScalar(aEnd.y), 
                     paint.mPaint);
 }
 
 void
 DrawTargetSkia::Fill(const Path *aPath,
--- a/gfx/2d/Factory.cpp
+++ b/gfx/2d/Factory.cpp
@@ -41,16 +41,19 @@
 #include "DrawTargetCairo.h"
 #endif
 
 #ifdef USE_SKIA
 #include "DrawTargetSkia.h"
 #ifdef XP_MACOSX
 #include "ScaledFontMac.h"
 #endif
+#ifdef WIN32
+#include "ScaledFontWin.h"
+#endif
 #include "ScaledFontSkia.h"
 #endif
 
 #ifdef WIN32
 #include "DrawTargetD2D.h"
 #include "ScaledFontDWrite.h"
 #include <d3d10_1.h>
 #endif
@@ -120,16 +123,22 @@ Factory::CreateScaledFontForNativeFont(c
 #endif
 #ifdef USE_SKIA
 #ifdef XP_MACOSX
   case NATIVE_FONT_MAC_FONT_FACE:
     {
       return new ScaledFontMac(static_cast<CGFontRef>(aNativeFont.mFont), aSize);
     }
 #endif
+#ifdef WIN32
+  case NATIVE_FONT_GDI_FONT_FACE:
+    {
+      return new ScaledFontWin(static_cast<gfxGDIFont*>(aNativeFont.mFont), aSize);
+    }
+#endif
   case NATIVE_FONT_SKIA_FONT_FACE:
     {
       return new ScaledFontSkia(static_cast<gfxFont*>(aNativeFont.mFont), aSize);
     }
 #endif
   default:
     gfxWarning() << "Invalid native font type specified.";
     return NULL;
--- a/gfx/2d/HelpersSkia.h
+++ b/gfx/2d/HelpersSkia.h
@@ -35,16 +35,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef MOZILLA_GFX_HELPERSSKIA_H_
 #define MOZILLA_GFX_HELPERSSKIA_H_
 
 #include "2D.h"
 #include "skia/SkCanvas.h"
+#include "skia/SkDashPathEffect.h"
 
 namespace mozilla {
 namespace gfx {
 
 static inline SkBitmap::Config
 GfxFormatToSkiaConfig(SurfaceFormat format)
 {
   switch (format)
@@ -67,12 +68,71 @@ GfxFormatToSkiaConfig(SurfaceFormat form
 static inline void
 GfxMatrixToSkiaMatrix(const Matrix& mat, SkMatrix& retval)
 {
     retval.setAll(SkFloatToScalar(mat._11), SkFloatToScalar(mat._21), SkFloatToScalar(mat._31),
                   SkFloatToScalar(mat._12), SkFloatToScalar(mat._22), SkFloatToScalar(mat._32),
                   0, 0, SK_Scalar1);
 }
 
+static inline SkPaint::Cap
+CapStyleToSkiaCap(CapStyle aCap)
+{
+  switch (aCap)
+  {
+    case CAP_BUTT:
+      return SkPaint::kButt_Cap;
+    case CAP_ROUND:
+      return SkPaint::kRound_Cap;
+    case CAP_SQUARE:
+      return SkPaint::kSquare_Cap;
+  }
+  return SkPaint::kDefault_Cap;
+}
+
+static inline SkPaint::Join
+JoinStyleToSkiaJoin(JoinStyle aJoin)
+{
+  switch (aJoin)
+  {
+    case JOIN_BEVEL:
+      return SkPaint::kBevel_Join;
+    case JOIN_ROUND:
+      return SkPaint::kRound_Join;
+    case JOIN_MITER:
+    case JOIN_MITER_OR_BEVEL:
+      return SkPaint::kMiter_Join;
+  }
+  return SkPaint::kDefault_Join;
+}
+
+static inline bool
+StrokeOptionsToPaint(SkPaint& aPaint, const StrokeOptions &aOptions)
+{
+  // Skia rendewrs 0 width strokes with a width of 1 (and in black),
+  // so we should just skip the draw call entirely.
+  if (!aOptions.mLineWidth) {
+    return false;
+  }
+  aPaint.setStrokeWidth(SkFloatToScalar(aOptions.mLineWidth));
+  aPaint.setStrokeMiter(SkFloatToScalar(aOptions.mMiterLimit));
+  aPaint.setStrokeCap(CapStyleToSkiaCap(aOptions.mLineCap));
+  aPaint.setStrokeJoin(JoinStyleToSkiaJoin(aOptions.mLineJoin));
+  if (aOptions.mDashLength > 1) {
+    std::vector<SkScalar> pattern;
+    pattern.resize(aOptions.mDashLength);
+    for (uint32_t i = 0; i < aOptions.mDashLength; i++) {
+      pattern[i] = SkFloatToScalar(aOptions.mDashPattern[i]);
+    }
+    
+    SkDashPathEffect* dash = new SkDashPathEffect(&pattern.front(), 
+                                                  aOptions.mDashLength, 
+                                                  SkFloatToScalar(aOptions.mDashOffset));
+    SkSafeUnref(aPaint.setPathEffect(dash));
+  }
+  aPaint.setStyle(SkPaint::kStroke_Style);
+  return true;
+}
+
 }
 }
 
 #endif /* MOZILLA_GFX_HELPERSSKIA_H_ */
--- a/gfx/2d/Makefile.in
+++ b/gfx/2d/Makefile.in
@@ -102,19 +102,23 @@ endif
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
 CPPSRCS	+= \
         DrawTargetD2D.cpp \
         SourceSurfaceD2D.cpp \
         SourceSurfaceD2DTarget.cpp \
         PathD2D.cpp \
         ScaledFontDWrite.cpp \
-	$(NULL)
-
-DEFINES += -DWIN32
+        SourceSurfaceSkia.cpp \
+        DrawTargetSkia.cpp \
+        PathSkia.cpp \
+        ScaledFontSkia.cpp \
+        ScaledFontWin.cpp \
+        $(NULL)
+DEFINES += -DWIN32 -DUSE_SKIA
 endif
 
 #ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
 #CPPSRCS	+= \
 #        DrawTargetCG.cpp \
 #        SourceSurfaceCG.cpp \
 #	$(NULL)
 #
--- a/gfx/2d/PathSkia.cpp
+++ b/gfx/2d/PathSkia.cpp
@@ -191,14 +191,20 @@ PathSkia::GetBounds(const Matrix &aTrans
   Rect bounds = SkRectToRect(mPath.getBounds());
   return aTransform.TransformBounds(bounds);
 }
 
 Rect
 PathSkia::GetStrokedBounds(const StrokeOptions &aStrokeOptions,
                            const Matrix &aTransform) const
 {
-  NS_ASSERTION(false, "GetStrokedBounds not supported yet!");
-  return Rect(0, 0, 0, 0);
+  SkPaint paint;
+  StrokeOptionsToPaint(paint, aStrokeOptions);
+  
+  SkPath result;
+  paint.getFillPath(mPath, &result);
+
+  Rect bounds = SkRectToRect(result.getBounds());
+  return aTransform.TransformBounds(bounds);
 }
 
 }
 }
new file mode 100644
--- /dev/null
+++ b/gfx/2d/ScaledFontWin.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * ***** 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 Mozilla Corporation 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):
+ *   Marco Castelluccio <mar.castelluccio@studenti.unina.it>
+ *
+ * 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 ***** */
+
+#include "ScaledFontWin.h"
+#include "skia/SkTypeface_win.h"
+
+namespace mozilla {
+namespace gfx {
+
+ScaledFontWin::ScaledFontWin(gfxGDIFont* aFont, Float aSize)
+  : ScaledFontSkia(aSize)
+{
+  LOGFONT lf;
+  GetObject(aFont->GetHFONT(), sizeof(LOGFONT), &lf);
+  mTypeface = SkCreateTypefaceFromLOGFONT(lf);
+}
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/gfx/2d/ScaledFontWin.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * ***** 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 Mozilla Corporation 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):
+ *   Marco Castelluccio <mar.castelluccio@studenti.unina.it>
+ *
+ * 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 ***** */
+
+#ifndef MOZILLA_GFX_SCALEDFONTWIN_H_
+#define MOZILLA_GFX_SCALEDFONTWIN_H_
+
+#include "ScaledFontSkia.h"
+#include "gfxGDIFont.h"
+
+namespace mozilla {
+namespace gfx {
+
+class ScaledFontWin : public ScaledFontSkia
+{
+public:
+  ScaledFontWin(gfxGDIFont* aFont, Float aSize);
+
+  virtual FontType GetType() const { return FONT_GDI; }
+
+private:
+  friend class DrawTargetSkia;
+};
+
+}
+}
+
+#endif /* MOZILLA_GFX_SCALEDFONTWIN_H_ */
--- a/gfx/2d/SourceSurfaceSkia.cpp
+++ b/gfx/2d/SourceSurfaceSkia.cpp
@@ -125,19 +125,24 @@ SourceSurfaceSkia::DrawTargetWillChange(
     mDrawTarget = NULL;
     SkBitmap temp = mBitmap;
     mBitmap.reset();
     temp.copyTo(&mBitmap, temp.getConfig());
   }
 }
 
 void
+SourceSurfaceSkia::DrawTargetDestroyed()
+{
+  mDrawTarget = NULL;
+}
+
+void
 SourceSurfaceSkia::MarkIndependent()
 {
   if (mDrawTarget) {
     mDrawTarget->RemoveSnapshot(this);
     mDrawTarget = NULL;
   }
 }
 
-
 }
 }
--- a/gfx/2d/SourceSurfaceSkia.h
+++ b/gfx/2d/SourceSurfaceSkia.h
@@ -77,16 +77,17 @@ public:
   virtual unsigned char *GetData();
 
   virtual int32_t Stride() { return mStride; }
 
 private:
   friend class DrawTargetSkia;
 
   void DrawTargetWillChange();
+  void DrawTargetDestroyed();
   void MarkIndependent();
 
   SkBitmap mBitmap;
   SurfaceFormat mFormat;
   IntSize mSize;
   int32_t mStride;
   DrawTargetSkia* mDrawTarget;
 };
--- a/gfx/2d/Tools.h
+++ b/gfx/2d/Tools.h
@@ -38,17 +38,17 @@
 #ifndef MOZILLA_GFX_TOOLS_H_
 #define MOZILLA_GFX_TOOLS_H_
 
 #include "Types.h"
 
 namespace mozilla {
 namespace gfx {
 
-bool
+static inline bool
 IsOperatorBoundByMask(CompositionOp aOp) {
   switch (aOp) {
   case OP_IN:
   case OP_OUT:
   case OP_DEST_IN:
   case OP_DEST_ATOP:
   case OP_SOURCE:
     return false;
--- a/gfx/2d/Types.h
+++ b/gfx/2d/Types.h
@@ -86,28 +86,30 @@ enum BackendType
   BACKEND_COREGRAPHICS,
   BACKEND_CAIRO,
   BACKEND_SKIA
 };
 
 enum FontType
 {
   FONT_DWRITE,
+  FONT_GDI,
   FONT_MAC,
   FONT_SKIA
 };
 
 enum NativeSurfaceType
 {
   NATIVE_SURFACE_D3D10_TEXTURE
 };
 
 enum NativeFontType
 {
   NATIVE_FONT_DWRITE_FONT_FACE,
+  NATIVE_FONT_GDI_FONT_FACE,
   NATIVE_FONT_MAC_FONT_FACE,
   NATIVE_FONT_SKIA_FONT_FACE
 };
 
 enum CompositionOp { OP_OVER, OP_ADD, OP_ATOP, OP_OUT, OP_IN, OP_SOURCE, OP_DEST_IN, OP_DEST_OUT, OP_DEST_OVER, OP_DEST_ATOP, OP_XOR, OP_COUNT };
 enum ExtendMode { EXTEND_CLAMP, EXTEND_WRAP, EXTEND_MIRROR };
 enum FillRule { FILL_WINDING, FILL_EVEN_ODD };
 enum AntialiasMode { AA_NONE, AA_GRAY, AA_SUBPIXEL };
--- a/gfx/Makefile.in
+++ b/gfx/Makefile.in
@@ -45,17 +45,17 @@ include $(DEPTH)/config/autoconf.mk
 MODULE		= gfx
 
 ifdef MOZ_TREE_CAIRO
 DIRS		= cairo
 endif
 
 DIRS		+= 2d ycbcr angle src qcms layers harfbuzz/src ots/src thebes ipc
 
-ifeq (,$(filter-out cocoa android,$(MOZ_WIDGET_TOOLKIT)))
+ifeq (,$(filter-out cocoa android windows,$(MOZ_WIDGET_TOOLKIT)))
 DIRS        += skia
 endif
 
 ifdef ENABLE_TESTS
 TOOL_DIRS	+= tests
 endif
 
 include $(topsrcdir)/config/rules.mk
--- a/gfx/cairo/cairo/src/cairo-d2d-surface.cpp
+++ b/gfx/cairo/cairo/src/cairo-d2d-surface.cpp
@@ -4792,8 +4792,34 @@ cairo_d2d_get_image_surface_cache_usage(
 }
 
 int
 cairo_d2d_get_surface_vram_usage(cairo_device_t *device)
 {
     cairo_d2d_device_t *d2d_device = reinterpret_cast<cairo_d2d_device_t*>(device);
     return d2d_device->mVRAMUsage;
 }
+
+int
+cairo_d2d_surface_get_width(cairo_surface_t *surface)
+{
+    if (surface->backend != &cairo_d2d_surface_backend) {
+	    _cairo_error_throw (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
+	    return 0;
+    }
+
+    cairo_d2d_surface_t *d2dsurf = reinterpret_cast<cairo_d2d_surface_t*>(surface);
+    D2D1_SIZE_U size = d2dsurf->rt->GetPixelSize();
+    return size.width;
+}
+
+int
+cairo_d2d_surface_get_height(cairo_surface_t *surface)
+{
+    if (surface->backend != &cairo_d2d_surface_backend) {
+	    _cairo_error_throw (CAIRO_STATUS_SURFACE_TYPE_MISMATCH);
+	    return 0;
+    }
+
+    cairo_d2d_surface_t *d2dsurf = reinterpret_cast<cairo_d2d_surface_t*>(surface);
+    D2D1_SIZE_U size = d2dsurf->rt->GetPixelSize();
+    return size.height;
+}
--- a/gfx/cairo/cairo/src/cairo-win32-surface.c
+++ b/gfx/cairo/cairo/src/cairo-win32-surface.c
@@ -3905,8 +3905,28 @@ cairo_win32_surface_get_can_convert_to_d
 {
     cairo_win32_surface_t *surface = (cairo_win32_surface_t*) asurface;
     if (surface->base.type != CAIRO_SURFACE_TYPE_WIN32)
 	return CAIRO_STATUS_SURFACE_TYPE_MISMATCH;
 
     *can_convert = ((surface->flags & CAIRO_WIN32_SURFACE_CAN_CONVERT_TO_DIB) != 0);
     return CAIRO_STATUS_SUCCESS;
 }
+
+int
+cairo_win32_surface_get_width (cairo_surface_t *asurface)
+{
+    cairo_win32_surface_t *surface = (cairo_win32_surface_t*) asurface;
+    if (surface->base.type != CAIRO_SURFACE_TYPE_WIN32)
+	return CAIRO_STATUS_SURFACE_TYPE_MISMATCH;
+
+    return surface->extents.width;
+}
+
+int
+cairo_win32_surface_get_height (cairo_surface_t *asurface)
+{
+    cairo_win32_surface_t *surface = (cairo_win32_surface_t*) asurface;
+    if (surface->base.type != CAIRO_SURFACE_TYPE_WIN32)
+	return CAIRO_STATUS_SURFACE_TYPE_MISMATCH;
+
+    return surface->extents.height;
+}
--- a/gfx/cairo/cairo/src/cairo-win32.h
+++ b/gfx/cairo/cairo/src/cairo-win32.h
@@ -77,16 +77,22 @@ cairo_win32_surface_get_image (cairo_sur
 cairo_public cairo_status_t
 cairo_win32_surface_set_can_convert_to_dib (cairo_surface_t *surface, cairo_bool_t can_convert);
 
 cairo_public cairo_status_t
 cairo_win32_surface_get_can_convert_to_dib (cairo_surface_t *surface, cairo_bool_t *can_convert);
 
 BYTE cairo_win32_get_system_text_quality (void);
 
+cairo_public int
+cairo_win32_surface_get_width (cairo_surface_t *surface);
+
+cairo_public int
+cairo_win32_surface_get_height (cairo_surface_t *surface);
+
 #if CAIRO_HAS_WIN32_FONT
 
 /*
  * Win32 font support
  */
 
 cairo_public cairo_font_face_t *
 cairo_win32_font_face_create_for_logfontw (LOGFONTW *logfont);
@@ -304,16 +310,26 @@ void cairo_d2d_release_dc(cairo_surface_
 int cairo_d2d_get_image_surface_cache_usage();
 
 /**
  * Get an estimate of the amount of VRAM which is currently used by the d2d
  * surfaces for a device. This does -not- include the internal image surface
  * cache.
  */
 int cairo_d2d_get_surface_vram_usage(cairo_device_t *device);
+
+/**
+ * Get the width of the surface.
+ */
+int cairo_d2d_surface_get_width(cairo_surface_t *surface);
+
+/**
+ * Get the height of the surface.
+ */
+int cairo_d2d_surface_get_height(cairo_surface_t *surface);
 #endif
 
 CAIRO_END_DECLS
 
 #else  /* CAIRO_HAS_WIN32_SURFACE */
 # error Cairo was not compiled with support for the win32 backend
 #endif /* CAIRO_HAS_WIN32_SURFACE */
 
--- a/gfx/layers/d3d9/CanvasLayerD3D9.cpp
+++ b/gfx/layers/d3d9/CanvasLayerD3D9.cpp
@@ -57,30 +57,34 @@ CanvasLayerD3D9::~CanvasLayerD3D9()
   }
 }
 
 void
 CanvasLayerD3D9::Initialize(const Data& aData)
 {
   NS_ASSERTION(mSurface == nsnull, "BasicCanvasLayer::Initialize called twice!");
 
-  if (aData.mSurface) {
+  if (aData.mDrawTarget) {
+    mDrawTarget = aData.mDrawTarget;
+    mNeedsYFlip = false;
+    mDataIsPremultiplied = true;
+  } else if (aData.mSurface) {
     mSurface = aData.mSurface;
     NS_ASSERTION(aData.mGLContext == nsnull,
                  "CanvasLayer can't have both surface and GLContext");
     mNeedsYFlip = false;
     mDataIsPremultiplied = true;
   } else if (aData.mGLContext) {
     NS_ASSERTION(aData.mGLContext->IsOffscreen(), "canvas gl context isn't offscreen");
     mGLContext = aData.mGLContext;
     mCanvasFramebuffer = mGLContext->GetOffscreenFBO();
     mDataIsPremultiplied = aData.mGLBufferIsPremultiplied;
     mNeedsYFlip = true;
   } else {
-    NS_ERROR("CanvasLayer created without mSurface or mGLContext?");
+    NS_ERROR("CanvasLayer created without mSurface, mGLContext or mDrawTarget?");
   }
 
   mBounds.SetRect(0, 0, aData.mSize.width, aData.mSize.height);
 
   CreateTexture();
 }
 
 void
@@ -145,37 +149,44 @@ CanvasLayerD3D9::UpdateSurface()
     if (r.Pitch != mBounds.width * 4) {
       for (int y = 0; y < mBounds.height; y++) {
         memcpy((PRUint8*)r.pBits + r.Pitch * y,
                destination + mBounds.width * 4 * y,
                mBounds.width * 4);
       }
       delete [] destination;
     }
-  } else if (mSurface) {
+  } else {
     RECT r;
     r.left = mBounds.x;
     r.top = mBounds.y;
     r.right = mBounds.XMost();
     r.bottom = mBounds.YMost();
 
     LockTextureRectD3D9 textureLock(mTexture);
     if (!textureLock.HasLock()) {
       NS_WARNING("Failed to lock CanvasLayer texture.");
       return;
     }
 
     D3DLOCKED_RECT lockedRect = textureLock.GetLockRect();
 
     nsRefPtr<gfxImageSurface> sourceSurface;
+    nsRefPtr<gfxASurface> tempSurface;
+    if (mDrawTarget) {
+      tempSurface = gfxPlatform::GetPlatform()->GetThebesSurfaceForDrawTarget(mDrawTarget);
+    }
+    else {
+      tempSurface = mSurface;
+    }
 
-    if (mSurface->GetType() == gfxASurface::SurfaceTypeWin32) {
-      sourceSurface = mSurface->GetAsImageSurface();
-    } else if (mSurface->GetType() == gfxASurface::SurfaceTypeImage) {
-      sourceSurface = static_cast<gfxImageSurface*>(mSurface.get());
+    if (tempSurface->GetType() == gfxASurface::SurfaceTypeWin32) {
+      sourceSurface = tempSurface->GetAsImageSurface();
+    } else if (tempSurface->GetType() == gfxASurface::SurfaceTypeImage) {
+      sourceSurface = static_cast<gfxImageSurface*>(tempSurface.get());
       if (sourceSurface->Format() != gfxASurface::ImageFormatARGB32 &&
           sourceSurface->Format() != gfxASurface::ImageFormatRGB24)
       {
         return;
       }
     } else {
       sourceSurface = new gfxImageSurface(gfxIntSize(mBounds.width, mBounds.height),
                                           gfxASurface::ImageFormatARGB32);
--- a/gfx/layers/d3d9/CanvasLayerD3D9.h
+++ b/gfx/layers/d3d9/CanvasLayerD3D9.h
@@ -80,16 +80,17 @@ public:
 protected:
   typedef mozilla::gl::GLContext GLContext;
 
   void UpdateSurface();
 
   nsRefPtr<gfxASurface> mSurface;
   nsRefPtr<GLContext> mGLContext;
   nsRefPtr<IDirect3DTexture9> mTexture;
+  RefPtr<gfx::DrawTarget> mDrawTarget;
 
   PRUint32 mCanvasFramebuffer;
 
   bool mDataIsPremultiplied;
   bool mNeedsYFlip;
   bool mHasAlpha;
 };
 
--- a/gfx/skia/Makefile.in
+++ b/gfx/skia/Makefile.in
@@ -53,16 +53,17 @@ DEFINES += -DSK_A32_SHIFT=24 -DSK_R32_SH
 
 LOCAL_INCLUDES += \
 	-I$(srcdir)/include/core \
 	-I$(srcdir)/include/config \
 	-I$(srcdir)/include/ports \
 	-I$(srcdir)/src/core \
 	-I$(srcdir)/include/images \
 	-I$(srcdir)/include/utils/mac \
+	-I$(srcdir)/include/utils/win \
 	-I$(srcdir)/include/views \
 	-I$(srcdir)/include/effects \
 	$(NULL)
 
 VPATH += \
 	$(srcdir)/src/core \
 	$(srcdir)/src/ports \
 	$(srcdir)/src/opts \
@@ -167,17 +168,16 @@ EXPORTS_skia = \
 	include/core/SkAdvancedTypefaceMetrics.h \
 	include/config/SkUserConfig.h \
 	include/effects/SkGradientShader.h \
 	include/effects/SkBlurDrawLooper.h \
 	include/effects/SkBlurMaskFilter.h \
 	include/effects/SkLayerDrawLooper.h \
 	include/effects/SkLayerRasterizer.h \
 	include/effects/SkDashPathEffect.h \
-	include/ports/SkTypeface_mac.h \
 	$(NULL)
 
 DEFINES += -DUSE_SKIA
 
 CPPSRCS = \
 	Sk64.cpp \
 	SkAAClip.cpp \
 	SkAdvancedTypefaceMetrics.cpp \
@@ -225,17 +225,16 @@ CPPSRCS = \
 	SkFloat.cpp \
 	SkFloatBits.cpp \
 	SkFontHost.cpp \
 	SkGeometry.cpp \
 	SkGlobals.cpp \
 	SkGlyphCache.cpp \
 	SkGraphics.cpp \
 	SkLineClipper.cpp \
-	SkMMapStream.cpp \
 	SkMallocPixelRef.cpp \
 	SkMask.cpp \
 	SkMaskFilter.cpp \
 	SkMath.cpp \
 	SkMatrix.cpp \
 	SkMemory_stdlib.cpp \
 	SkMetaData.cpp \
 	SkPackBits.cpp \
@@ -281,17 +280,16 @@ CPPSRCS = \
 	SkUnPreMultiply.cpp \
 	SkUtils.cpp \
 	SkWriter32.cpp \
 	SkXfermode.cpp \
 	SkDebug_stdio.cpp \
 	SkGlobals_global.cpp \
 	SkOSFile_stdio.cpp \
 	SkThread_none.cpp \
-	SkTime_Unix.cpp \
 	SkGradientShader.cpp \
 	SkBitmapCache.cpp \
 	SkBlurDrawLooper.cpp \
 	SkBlurMaskFilter.cpp \
 	SkBlurMask.cpp \
 	SkColorFilters.cpp \
 	SkLayerDrawLooper.cpp \
 	SkLayerRasterizer.cpp \
@@ -303,26 +301,44 @@ EXPORTS_skia += \
 	include/ports/SkTypeface_mac.h \
 	$(NULL)
 CPPSRCS += \
 	SkFontHost_mac_coretext.cpp \
 	SkBitmapProcState_opts_SSE2.cpp \
 	SkBlitRow_opts_SSE2.cpp \
 	SkUtils_opts_SSE2.cpp \
 	opts_check_SSE2.cpp \
+	SkTime_Unix.cpp \
 	$(NULL)
 endif
 
 ifeq (android,$(MOZ_WIDGET_TOOLKIT))
 CPPSRCS += \
 	SkBitmapProcState_opts_arm.cpp \
 	SkBlitRow_opts_arm.cpp \
 	SkFontHost_FreeType.cpp \
 	SkFontHost_android.cpp \
 	SkFontHost_gamma.cpp \
 	SkUtils_opts_none.cpp \
+	SkMMapStream.cpp \
+	SkTime_Unix.cpp \
 	$(NULL)
 
 DEFINES += -DSK_BUILD_FOR_ANDROID_NDK
 OS_CXXFLAGS += $(CAIRO_FT_CFLAGS)
 endif
 
+ifeq (windows,$(MOZ_WIDGET_TOOLKIT))
+EXPORTS_skia += \
+	include/config/sk_stdint.h \
+	include/ports/SkTypeface_win.h \
+	$(NULL)
+CPPSRCS += \
+	SkFontHost_win.cpp \
+	SkTime_win.cpp \
+	SkBitmapProcState_opts_SSE2.cpp \
+	SkBlitRow_opts_SSE2.cpp \
+	SkUtils_opts_SSE2.cpp \
+	opts_check_SSE2.cpp \
+	$(NULL)
+endif
+
 include $(topsrcdir)/config/rules.mk
--- a/gfx/skia/include/config/SkUserConfig.h
+++ b/gfx/skia/include/config/SkUserConfig.h
@@ -140,16 +140,18 @@
 /*  If SK_DEBUG is defined, then you can optionally define SK_SUPPORT_UNITTEST
     which will run additional self-tests at startup. These can take a long time,
     so this flag is optional.
  */
 #ifdef SK_DEBUG
 //#define SK_SUPPORT_UNITTEST
 #endif
 
+#define SK_DISABLE_DITHER_32BIT_GRADIENT
+
 /* If your system embeds skia and has complex event logging, define this
    symbol to name a file that maps the following macros to your system's
    equivalents:
        SK_TRACE_EVENT0(event)
        SK_TRACE_EVENT1(event, name1, value1)
        SK_TRACE_EVENT2(event, name1, value1, name2, value2)
    src/utils/SkDebugTrace.h has a trivial implementation that writes to
    the debug output stream. If SK_USER_TRACE_INCLUDE_FILE is not defined,
@@ -161,9 +163,13 @@
  */
 #ifdef SK_SAMPLES_FOR_X
         #define SK_R32_SHIFT    16
         #define SK_G32_SHIFT    8
         #define SK_B32_SHIFT    0
         #define SK_A32_SHIFT    24
 #endif
 
+#ifdef SK_BUILD_FOR_WIN32 
+        #define SK_IGNORE_STDINT_DOT_H 
+ #endif 
+
 #endif
new file mode 100644
--- /dev/null
+++ b/gfx/skia/skia_restrict_problem.patch
@@ -0,0 +1,463 @@
+diff --git a/gfx/skia/src/effects/SkGradientShader.cpp b/gfx/skia/src/effects/SkGradientShader.cpp
+--- a/gfx/skia/src/effects/SkGradientShader.cpp
++++ b/gfx/skia/src/effects/SkGradientShader.cpp
+@@ -1170,117 +1170,18 @@ public:
+           fRadius(radius)
+     {
+         // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
+         SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
+ 
+         rad_to_unit_matrix(center, radius, &fPtsToUnit);
+     }
+ 
+-    virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count);
+-    virtual void shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) {
+-        SkASSERT(count > 0);
+-
+-        SkPoint             srcPt;
+-        SkMatrix::MapXYProc dstProc = fDstToIndexProc;
+-        TileProc            proc = fTileProc;
+-        const uint16_t* SK_RESTRICT cache = this->getCache16();
+-        int                 toggle = ((x ^ y) & 1) << kCache16Bits;
+-
+-        if (fDstToIndexClass != kPerspective_MatrixClass) {
+-            dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
+-                                 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+-            SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
+-            SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
+-
+-            if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+-                SkFixed storage[2];
+-                (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
+-                dx = storage[0];
+-                dy = storage[1];
+-            } else {
+-                SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+-                dx = SkScalarToFixed(fDstToIndex.getScaleX());
+-                dy = SkScalarToFixed(fDstToIndex.getSkewY());
+-            }
+-
+-            if (proc == clamp_tileproc) {
+-                const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
+-
+-                /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
+-                    rather than 0xFFFF which is slower. This is a compromise, since it reduces our
+-                    precision, but that appears to be visually OK. If we decide this is OK for
+-                    all of our cases, we could (it seems) put this scale-down into fDstToIndex,
+-                    to avoid having to do these extra shifts each time.
+-                */
+-                fx >>= 1;
+-                dx >>= 1;
+-                fy >>= 1;
+-                dy >>= 1;
+-                if (dy == 0) {    // might perform this check for the other modes, but the win will be a smaller % of the total
+-                    fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
+-                    fy *= fy;
+-                    do {
+-                        unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
+-                        unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
+-                        fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
+-                        fx += dx;
+-                        *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
+-                        toggle ^= (1 << kCache16Bits);
+-                    } while (--count != 0);
+-                } else {
+-                    do {
+-                        unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
+-                        unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
+-                        fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
+-                        fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
+-                        fx += dx;
+-                        fy += dy;
+-                        *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
+-                        toggle ^= (1 << kCache16Bits);
+-                    } while (--count != 0);
+-                }
+-            } else if (proc == mirror_tileproc) {
+-                do {
+-                    SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
+-                    unsigned fi = mirror_tileproc(dist);
+-                    SkASSERT(fi <= 0xFFFF);
+-                    fx += dx;
+-                    fy += dy;
+-                    *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
+-                    toggle ^= (1 << kCache16Bits);
+-                } while (--count != 0);
+-            } else {
+-                SkASSERT(proc == repeat_tileproc);
+-                do {
+-                    SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
+-                    unsigned fi = repeat_tileproc(dist);
+-                    SkASSERT(fi <= 0xFFFF);
+-                    fx += dx;
+-                    fy += dy;
+-                    *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
+-                    toggle ^= (1 << kCache16Bits);
+-                } while (--count != 0);
+-            }
+-        } else {    // perspective case
+-            SkScalar dstX = SkIntToScalar(x);
+-            SkScalar dstY = SkIntToScalar(y);
+-            do {
+-                dstProc(fDstToIndex, dstX, dstY, &srcPt);
+-                unsigned fi = proc(SkScalarToFixed(srcPt.length()));
+-                SkASSERT(fi <= 0xFFFF);
+-
+-                int index = fi >> (16 - kCache16Bits);
+-                *dstC++ = cache[toggle + index];
+-                toggle ^= (1 << kCache16Bits);
+-
+-                dstX += SK_Scalar1;
+-            } while (--count != 0);
+-        }
+-    }
++    virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
++    virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
+ 
+     virtual BitmapType asABitmap(SkBitmap* bitmap,
+                                  SkMatrix* matrix,
+                                  TileMode* xy,
+                                  SkScalar* twoPointRadialParams) const {
+         if (bitmap) {
+             this->commonAsABitmap(bitmap);
+         }
+@@ -1494,16 +1395,117 @@ void Radial_Gradient::shadeSpan(int x, i
+             unsigned fi = proc(SkScalarToFixed(srcPt.length()));
+             SkASSERT(fi <= 0xFFFF);
+             *dstC++ = cache[fi >> (16 - kCache32Bits)];
+             dstX += SK_Scalar1;
+         } while (--count != 0);
+     }
+ }
+ 
++void Radial_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) {
++    SkASSERT(count > 0);
++
++    SkPoint             srcPt;
++    SkMatrix::MapXYProc dstProc = fDstToIndexProc;
++    TileProc            proc = fTileProc;
++    const uint16_t* SK_RESTRICT cache = this->getCache16();
++    int                 toggle = ((x ^ y) & 1) << kCache16Bits;
++
++    if (fDstToIndexClass != kPerspective_MatrixClass) {
++        dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
++                             SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
++        SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
++        SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
++
++        if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
++            SkFixed storage[2];
++            (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
++            dx = storage[0];
++            dy = storage[1];
++        } else {
++            SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
++            dx = SkScalarToFixed(fDstToIndex.getScaleX());
++            dy = SkScalarToFixed(fDstToIndex.getSkewY());
++        }
++
++        if (proc == clamp_tileproc) {
++            const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
++
++            /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
++                rather than 0xFFFF which is slower. This is a compromise, since it reduces our
++                precision, but that appears to be visually OK. If we decide this is OK for
++                all of our cases, we could (it seems) put this scale-down into fDstToIndex,
++                to avoid having to do these extra shifts each time.
++            */
++            fx >>= 1;
++            dx >>= 1;
++            fy >>= 1;
++            dy >>= 1;
++            if (dy == 0) {    // might perform this check for the other modes, but the win will be a smaller % of the total
++                fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
++                fy *= fy;
++                do {
++                    unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
++                    unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
++                    fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
++                    fx += dx;
++                    *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
++                    toggle ^= (1 << kCache16Bits);
++                } while (--count != 0);
++            } else {
++                do {
++                    unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
++                    unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
++                    fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
++                    fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
++                    fx += dx;
++                    fy += dy;
++                    *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
++                    toggle ^= (1 << kCache16Bits);
++                } while (--count != 0);
++            }
++        } else if (proc == mirror_tileproc) {
++            do {
++                SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
++                unsigned fi = mirror_tileproc(dist);
++                SkASSERT(fi <= 0xFFFF);
++                fx += dx;
++                fy += dy;
++                *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
++                toggle ^= (1 << kCache16Bits);
++            } while (--count != 0);
++        } else {
++            SkASSERT(proc == repeat_tileproc);
++            do {
++                SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
++                unsigned fi = repeat_tileproc(dist);
++                SkASSERT(fi <= 0xFFFF);
++                fx += dx;
++                fy += dy;
++                *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
++                toggle ^= (1 << kCache16Bits);
++            } while (--count != 0);
++        }
++    } else {    // perspective case
++        SkScalar dstX = SkIntToScalar(x);
++        SkScalar dstY = SkIntToScalar(y);
++        do {
++            dstProc(fDstToIndex, dstX, dstY, &srcPt);
++            unsigned fi = proc(SkScalarToFixed(srcPt.length()));
++            SkASSERT(fi <= 0xFFFF);
++
++            int index = fi >> (16 - kCache16Bits);
++            *dstC++ = cache[toggle + index];
++            toggle ^= (1 << kCache16Bits);
++
++            dstX += SK_Scalar1;
++        } while (--count != 0);
++    }
++}
++
+ /* Two-point radial gradients are specified by two circles, each with a center
+    point and radius.  The gradient can be considered to be a series of
+    concentric circles, with the color interpolated from the start circle
+    (at t=0) to the end circle (at t=1).
+ 
+    For each point (x, y) in the span, we want to find the
+    interpolated circle that intersects that point.  The center
+    of the desired circle (Cx, Cy) falls at some distance t
+@@ -1648,109 +1650,17 @@ public:
+             info->fPoint[0] = fCenter1;
+             info->fPoint[1] = fCenter2;
+             info->fRadius[0] = fRadius1;
+             info->fRadius[1] = fRadius2;
+         }
+         return kRadial2_GradientType;
+     }
+ 
+-    virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
+-        SkASSERT(count > 0);
+-
+-        // Zero difference between radii:  fill with transparent black.
+-        // TODO: Is removing this actually correct? Two circles with the 
+-        // same radius, but different centers doesn't sound like it
+-        // should be cleared
+-        if (fDiffRadius == 0 && fCenter1 == fCenter2) {
+-          sk_bzero(dstC, count * sizeof(*dstC));
+-          return;
+-        }
+-        SkMatrix::MapXYProc dstProc = fDstToIndexProc;
+-        TileProc            proc = fTileProc;
+-        const SkPMColor* SK_RESTRICT cache = this->getCache32();
+-
+-        SkScalar foura = fA * 4;
+-        bool posRoot = fDiffRadius < 0;
+-        if (fDstToIndexClass != kPerspective_MatrixClass) {
+-            SkPoint srcPt;
+-            dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
+-                                 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+-            SkScalar dx, fx = srcPt.fX;
+-            SkScalar dy, fy = srcPt.fY;
+-
+-            if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+-                SkFixed fixedX, fixedY;
+-                (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
+-                dx = SkFixedToScalar(fixedX);
+-                dy = SkFixedToScalar(fixedY);
+-            } else {
+-                SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+-                dx = fDstToIndex.getScaleX();
+-                dy = fDstToIndex.getSkewY();
+-            }
+-            SkScalar b = (SkScalarMul(fDiff.fX, fx) +
+-                         SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
+-            SkScalar db = (SkScalarMul(fDiff.fX, dx) +
+-                          SkScalarMul(fDiff.fY, dy)) * 2;
+-            if (proc == clamp_tileproc) {
+-                for (; count > 0; --count) {
+-                    SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
+-                    if (t < 0) {
+-                      *dstC++ = cache[-1];
+-                    } else if (t > 0xFFFF) {
+-                      *dstC++ = cache[kCache32Count * 2];
+-                    } else {
+-                      SkASSERT(t <= 0xFFFF);
+-                      *dstC++ = cache[t >> (16 - kCache32Bits)];
+-                    }
+-                    fx += dx;
+-                    fy += dy;
+-                    b += db;
+-                }
+-            } else if (proc == mirror_tileproc) {
+-                for (; count > 0; --count) {
+-                    SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
+-                    SkFixed index = mirror_tileproc(t);
+-                    SkASSERT(index <= 0xFFFF);
+-                    *dstC++ = cache[index >> (16 - kCache32Bits)];
+-                    fx += dx;
+-                    fy += dy;
+-                    b += db;
+-                }
+-            } else {
+-                SkASSERT(proc == repeat_tileproc);
+-                for (; count > 0; --count) {
+-                    SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
+-                    SkFixed index = repeat_tileproc(t);
+-                    SkASSERT(index <= 0xFFFF);
+-                    *dstC++ = cache[index >> (16 - kCache32Bits)];
+-                    fx += dx;
+-                    fy += dy;
+-                    b += db;
+-                }
+-            }
+-        } else {    // perspective case
+-            SkScalar dstX = SkIntToScalar(x);
+-            SkScalar dstY = SkIntToScalar(y);
+-            for (; count > 0; --count) {
+-                SkPoint             srcPt;
+-                dstProc(fDstToIndex, dstX, dstY, &srcPt);
+-                SkScalar fx = srcPt.fX;
+-                SkScalar fy = srcPt.fY;
+-                SkScalar b = (SkScalarMul(fDiff.fX, fx) +
+-                             SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
+-                SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
+-                SkFixed index = proc(t);
+-                SkASSERT(index <= 0xFFFF);
+-                *dstC++ = cache[index >> (16 - kCache32Bits)];
+-                dstX += SK_Scalar1;
+-            }
+-        }
+-    }
++    virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
+ 
+     virtual bool setContext(const SkBitmap& device,
+                             const SkPaint& paint,
+                             const SkMatrix& matrix) {
+         if (!this->INHERITED::setContext(device, paint, matrix)) {
+             return false;
+         }
+ 
+@@ -1804,16 +1714,110 @@ private:
+         fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
+         fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
+ 
+         fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
+         fPtsToUnit.postScale(inv, inv);
+     }
+ };
+ 
++void Two_Point_Radial_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
++    SkASSERT(count > 0);
++
++    // Zero difference between radii:  fill with transparent black.
++    // TODO: Is removing this actually correct? Two circles with the 
++    // same radius, but different centers doesn't sound like it
++    // should be cleared
++    if (fDiffRadius == 0 && fCenter1 == fCenter2) {
++      sk_bzero(dstC, count * sizeof(*dstC));
++      return;
++    }
++    SkMatrix::MapXYProc dstProc = fDstToIndexProc;
++    TileProc            proc = fTileProc;
++    const SkPMColor* SK_RESTRICT cache = this->getCache32();
++
++    SkScalar foura = fA * 4;
++    bool posRoot = fDiffRadius < 0;
++    if (fDstToIndexClass != kPerspective_MatrixClass) {
++        SkPoint srcPt;
++        dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
++                                 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
++        SkScalar dx, fx = srcPt.fX;
++        SkScalar dy, fy = srcPt.fY;
++
++        if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
++            SkFixed fixedX, fixedY;
++            (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
++            dx = SkFixedToScalar(fixedX);
++            dy = SkFixedToScalar(fixedY);
++        } else {
++            SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
++            dx = fDstToIndex.getScaleX();
++            dy = fDstToIndex.getSkewY();
++        }
++        SkScalar b = (SkScalarMul(fDiff.fX, fx) +
++                     SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
++        SkScalar db = (SkScalarMul(fDiff.fX, dx) +
++                      SkScalarMul(fDiff.fY, dy)) * 2;
++        if (proc == clamp_tileproc) {
++            for (; count > 0; --count) {
++                SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
++                if (t < 0) {
++                  *dstC++ = cache[-1];
++                } else if (t > 0xFFFF) {
++                  *dstC++ = cache[kCache32Count * 2];
++                } else {
++                  SkASSERT(t <= 0xFFFF);
++                  *dstC++ = cache[t >> (16 - kCache32Bits)];
++                }
++                fx += dx;
++                fy += dy;
++                b += db;
++            }
++        } else if (proc == mirror_tileproc) {
++            for (; count > 0; --count) {
++                SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
++                SkFixed index = mirror_tileproc(t);
++                SkASSERT(index <= 0xFFFF);
++                *dstC++ = cache[index >> (16 - kCache32Bits)];
++                fx += dx;
++                fy += dy;
++                b += db;
++            }
++        } else {
++            SkASSERT(proc == repeat_tileproc);
++            for (; count > 0; --count) {
++                SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
++                SkFixed index = repeat_tileproc(t);
++                SkASSERT(index <= 0xFFFF);
++                *dstC++ = cache[index >> (16 - kCache32Bits)];
++                fx += dx;
++                fy += dy;
++                b += db;
++            }
++        }
++    } else {    // perspective case
++        SkScalar dstX = SkIntToScalar(x);
++        SkScalar dstY = SkIntToScalar(y);
++        for (; count > 0; --count) {
++            SkPoint             srcPt;
++            dstProc(fDstToIndex, dstX, dstY, &srcPt);
++            SkScalar fx = srcPt.fX;
++            SkScalar fy = srcPt.fY;
++            SkScalar b = (SkScalarMul(fDiff.fX, fx) +
++                         SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
++            SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
++            SkFixed index = proc(t);
++            SkASSERT(index <= 0xFFFF);
++            *dstC++ = cache[index >> (16 - kCache32Bits)];
++            dstX += SK_Scalar1;
++        }
++    }
++}
++
+ ///////////////////////////////////////////////////////////////////////////////
+ 
+ class Sweep_Gradient : public Gradient_Shader {
+ public:
+     Sweep_Gradient(SkScalar cx, SkScalar cy, const SkColor colors[],
+                    const SkScalar pos[], int count, SkUnitMapper* mapper)
+     : Gradient_Shader(colors, pos, count, SkShader::kClamp_TileMode, mapper),
+       fCenter(SkPoint::Make(cx, cy))
--- a/gfx/skia/src/effects/SkGradientShader.cpp
+++ b/gfx/skia/src/effects/SkGradientShader.cpp
@@ -1170,117 +1170,18 @@ public:
           fRadius(radius)
     {
         // make sure our table is insync with our current #define for kSQRT_TABLE_SIZE
         SkASSERT(sizeof(gSqrt8Table) == kSQRT_TABLE_SIZE);
 
         rad_to_unit_matrix(center, radius, &fPtsToUnit);
     }
 
-    virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count);
-    virtual void shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) {
-        SkASSERT(count > 0);
-
-        SkPoint             srcPt;
-        SkMatrix::MapXYProc dstProc = fDstToIndexProc;
-        TileProc            proc = fTileProc;
-        const uint16_t* SK_RESTRICT cache = this->getCache16();
-        int                 toggle = ((x ^ y) & 1) << kCache16Bits;
-
-        if (fDstToIndexClass != kPerspective_MatrixClass) {
-            dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
-                                 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
-            SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
-            SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
-
-            if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
-                SkFixed storage[2];
-                (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
-                dx = storage[0];
-                dy = storage[1];
-            } else {
-                SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
-                dx = SkScalarToFixed(fDstToIndex.getScaleX());
-                dy = SkScalarToFixed(fDstToIndex.getSkewY());
-            }
-
-            if (proc == clamp_tileproc) {
-                const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
-
-                /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
-                    rather than 0xFFFF which is slower. This is a compromise, since it reduces our
-                    precision, but that appears to be visually OK. If we decide this is OK for
-                    all of our cases, we could (it seems) put this scale-down into fDstToIndex,
-                    to avoid having to do these extra shifts each time.
-                */
-                fx >>= 1;
-                dx >>= 1;
-                fy >>= 1;
-                dy >>= 1;
-                if (dy == 0) {    // might perform this check for the other modes, but the win will be a smaller % of the total
-                    fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
-                    fy *= fy;
-                    do {
-                        unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
-                        unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
-                        fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
-                        fx += dx;
-                        *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
-                        toggle ^= (1 << kCache16Bits);
-                    } while (--count != 0);
-                } else {
-                    do {
-                        unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
-                        unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
-                        fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
-                        fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
-                        fx += dx;
-                        fy += dy;
-                        *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
-                        toggle ^= (1 << kCache16Bits);
-                    } while (--count != 0);
-                }
-            } else if (proc == mirror_tileproc) {
-                do {
-                    SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
-                    unsigned fi = mirror_tileproc(dist);
-                    SkASSERT(fi <= 0xFFFF);
-                    fx += dx;
-                    fy += dy;
-                    *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
-                    toggle ^= (1 << kCache16Bits);
-                } while (--count != 0);
-            } else {
-                SkASSERT(proc == repeat_tileproc);
-                do {
-                    SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
-                    unsigned fi = repeat_tileproc(dist);
-                    SkASSERT(fi <= 0xFFFF);
-                    fx += dx;
-                    fy += dy;
-                    *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
-                    toggle ^= (1 << kCache16Bits);
-                } while (--count != 0);
-            }
-        } else {    // perspective case
-            SkScalar dstX = SkIntToScalar(x);
-            SkScalar dstY = SkIntToScalar(y);
-            do {
-                dstProc(fDstToIndex, dstX, dstY, &srcPt);
-                unsigned fi = proc(SkScalarToFixed(srcPt.length()));
-                SkASSERT(fi <= 0xFFFF);
-
-                int index = fi >> (16 - kCache16Bits);
-                *dstC++ = cache[toggle + index];
-                toggle ^= (1 << kCache16Bits);
-
-                dstX += SK_Scalar1;
-            } while (--count != 0);
-        }
-    }
+    virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
+    virtual void shadeSpan16(int x, int y, uint16_t dstC[], int count);
 
     virtual BitmapType asABitmap(SkBitmap* bitmap,
                                  SkMatrix* matrix,
                                  TileMode* xy,
                                  SkScalar* twoPointRadialParams) const {
         if (bitmap) {
             this->commonAsABitmap(bitmap);
         }
@@ -1494,16 +1395,117 @@ void Radial_Gradient::shadeSpan(int x, i
             unsigned fi = proc(SkScalarToFixed(srcPt.length()));
             SkASSERT(fi <= 0xFFFF);
             *dstC++ = cache[fi >> (16 - kCache32Bits)];
             dstX += SK_Scalar1;
         } while (--count != 0);
     }
 }
 
+void Radial_Gradient::shadeSpan16(int x, int y, uint16_t* SK_RESTRICT dstC, int count) {
+    SkASSERT(count > 0);
+
+    SkPoint             srcPt;
+    SkMatrix::MapXYProc dstProc = fDstToIndexProc;
+    TileProc            proc = fTileProc;
+    const uint16_t* SK_RESTRICT cache = this->getCache16();
+    int                 toggle = ((x ^ y) & 1) << kCache16Bits;
+
+    if (fDstToIndexClass != kPerspective_MatrixClass) {
+        dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
+                             SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+        SkFixed dx, fx = SkScalarToFixed(srcPt.fX);
+        SkFixed dy, fy = SkScalarToFixed(srcPt.fY);
+
+        if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+            SkFixed storage[2];
+            (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &storage[0], &storage[1]);
+            dx = storage[0];
+            dy = storage[1];
+        } else {
+            SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+            dx = SkScalarToFixed(fDstToIndex.getScaleX());
+            dy = SkScalarToFixed(fDstToIndex.getSkewY());
+        }
+
+        if (proc == clamp_tileproc) {
+            const uint8_t* SK_RESTRICT sqrt_table = gSqrt8Table;
+
+            /* knock these down so we can pin against +- 0x7FFF, which is an immediate load,
+                rather than 0xFFFF which is slower. This is a compromise, since it reduces our
+                precision, but that appears to be visually OK. If we decide this is OK for
+                all of our cases, we could (it seems) put this scale-down into fDstToIndex,
+                to avoid having to do these extra shifts each time.
+            */
+            fx >>= 1;
+            dx >>= 1;
+            fy >>= 1;
+            dy >>= 1;
+            if (dy == 0) {    // might perform this check for the other modes, but the win will be a smaller % of the total
+                fy = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
+                fy *= fy;
+                do {
+                    unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
+                    unsigned fi = (xx * xx + fy) >> (14 + 16 - kSQRT_TABLE_BITS);
+                    fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
+                    fx += dx;
+                    *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
+                    toggle ^= (1 << kCache16Bits);
+                } while (--count != 0);
+            } else {
+                do {
+                    unsigned xx = SkPin32(fx, -0xFFFF >> 1, 0xFFFF >> 1);
+                    unsigned fi = SkPin32(fy, -0xFFFF >> 1, 0xFFFF >> 1);
+                    fi = (xx * xx + fi * fi) >> (14 + 16 - kSQRT_TABLE_BITS);
+                    fi = SkFastMin32(fi, 0xFFFF >> (16 - kSQRT_TABLE_BITS));
+                    fx += dx;
+                    fy += dy;
+                    *dstC++ = cache[toggle + (sqrt_table[fi] >> (8 - kCache16Bits))];
+                    toggle ^= (1 << kCache16Bits);
+                } while (--count != 0);
+            }
+        } else if (proc == mirror_tileproc) {
+            do {
+                SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
+                unsigned fi = mirror_tileproc(dist);
+                SkASSERT(fi <= 0xFFFF);
+                fx += dx;
+                fy += dy;
+                *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
+                toggle ^= (1 << kCache16Bits);
+            } while (--count != 0);
+        } else {
+            SkASSERT(proc == repeat_tileproc);
+            do {
+                SkFixed dist = SkFixedSqrt(SkFixedSquare(fx) + SkFixedSquare(fy));
+                unsigned fi = repeat_tileproc(dist);
+                SkASSERT(fi <= 0xFFFF);
+                fx += dx;
+                fy += dy;
+                *dstC++ = cache[toggle + (fi >> (16 - kCache16Bits))];
+                toggle ^= (1 << kCache16Bits);
+            } while (--count != 0);
+        }
+    } else {    // perspective case
+        SkScalar dstX = SkIntToScalar(x);
+        SkScalar dstY = SkIntToScalar(y);
+        do {
+            dstProc(fDstToIndex, dstX, dstY, &srcPt);
+            unsigned fi = proc(SkScalarToFixed(srcPt.length()));
+            SkASSERT(fi <= 0xFFFF);
+
+            int index = fi >> (16 - kCache16Bits);
+            *dstC++ = cache[toggle + index];
+            toggle ^= (1 << kCache16Bits);
+
+            dstX += SK_Scalar1;
+        } while (--count != 0);
+    }
+}
+
 /* Two-point radial gradients are specified by two circles, each with a center
    point and radius.  The gradient can be considered to be a series of
    concentric circles, with the color interpolated from the start circle
    (at t=0) to the end circle (at t=1).
 
    For each point (x, y) in the span, we want to find the
    interpolated circle that intersects that point.  The center
    of the desired circle (Cx, Cy) falls at some distance t
@@ -1648,109 +1650,17 @@ public:
             info->fPoint[0] = fCenter1;
             info->fPoint[1] = fCenter2;
             info->fRadius[0] = fRadius1;
             info->fRadius[1] = fRadius2;
         }
         return kRadial2_GradientType;
     }
 
-    virtual void shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
-        SkASSERT(count > 0);
-
-        // Zero difference between radii:  fill with transparent black.
-        // TODO: Is removing this actually correct? Two circles with the 
-        // same radius, but different centers doesn't sound like it
-        // should be cleared
-        if (fDiffRadius == 0 && fCenter1 == fCenter2) {
-          sk_bzero(dstC, count * sizeof(*dstC));
-          return;
-        }
-        SkMatrix::MapXYProc dstProc = fDstToIndexProc;
-        TileProc            proc = fTileProc;
-        const SkPMColor* SK_RESTRICT cache = this->getCache32();
-
-        SkScalar foura = fA * 4;
-        bool posRoot = fDiffRadius < 0;
-        if (fDstToIndexClass != kPerspective_MatrixClass) {
-            SkPoint srcPt;
-            dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
-                                 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
-            SkScalar dx, fx = srcPt.fX;
-            SkScalar dy, fy = srcPt.fY;
-
-            if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
-                SkFixed fixedX, fixedY;
-                (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
-                dx = SkFixedToScalar(fixedX);
-                dy = SkFixedToScalar(fixedY);
-            } else {
-                SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
-                dx = fDstToIndex.getScaleX();
-                dy = fDstToIndex.getSkewY();
-            }
-            SkScalar b = (SkScalarMul(fDiff.fX, fx) +
-                         SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
-            SkScalar db = (SkScalarMul(fDiff.fX, dx) +
-                          SkScalarMul(fDiff.fY, dy)) * 2;
-            if (proc == clamp_tileproc) {
-                for (; count > 0; --count) {
-                    SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
-                    if (t < 0) {
-                      *dstC++ = cache[-1];
-                    } else if (t > 0xFFFF) {
-                      *dstC++ = cache[kCache32Count * 2];
-                    } else {
-                      SkASSERT(t <= 0xFFFF);
-                      *dstC++ = cache[t >> (16 - kCache32Bits)];
-                    }
-                    fx += dx;
-                    fy += dy;
-                    b += db;
-                }
-            } else if (proc == mirror_tileproc) {
-                for (; count > 0; --count) {
-                    SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
-                    SkFixed index = mirror_tileproc(t);
-                    SkASSERT(index <= 0xFFFF);
-                    *dstC++ = cache[index >> (16 - kCache32Bits)];
-                    fx += dx;
-                    fy += dy;
-                    b += db;
-                }
-            } else {
-                SkASSERT(proc == repeat_tileproc);
-                for (; count > 0; --count) {
-                    SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
-                    SkFixed index = repeat_tileproc(t);
-                    SkASSERT(index <= 0xFFFF);
-                    *dstC++ = cache[index >> (16 - kCache32Bits)];
-                    fx += dx;
-                    fy += dy;
-                    b += db;
-                }
-            }
-        } else {    // perspective case
-            SkScalar dstX = SkIntToScalar(x);
-            SkScalar dstY = SkIntToScalar(y);
-            for (; count > 0; --count) {
-                SkPoint             srcPt;
-                dstProc(fDstToIndex, dstX, dstY, &srcPt);
-                SkScalar fx = srcPt.fX;
-                SkScalar fy = srcPt.fY;
-                SkScalar b = (SkScalarMul(fDiff.fX, fx) +
-                             SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
-                SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
-                SkFixed index = proc(t);
-                SkASSERT(index <= 0xFFFF);
-                *dstC++ = cache[index >> (16 - kCache32Bits)];
-                dstX += SK_Scalar1;
-            }
-        }
-    }
+    virtual void shadeSpan(int x, int y, SkPMColor dstC[], int count);
 
     virtual bool setContext(const SkBitmap& device,
                             const SkPaint& paint,
                             const SkMatrix& matrix) {
         if (!this->INHERITED::setContext(device, paint, matrix)) {
             return false;
         }
 
@@ -1804,16 +1714,110 @@ private:
         fA = SkScalarSquare(fDiff.fX) + SkScalarSquare(fDiff.fY) - SK_Scalar1;
         fOneOverTwoA = fA ? SkScalarInvert(fA * 2) : 0;
 
         fPtsToUnit.setTranslate(-fCenter1.fX, -fCenter1.fY);
         fPtsToUnit.postScale(inv, inv);
     }
 };
 
+void Two_Point_Radial_Gradient::shadeSpan(int x, int y, SkPMColor* SK_RESTRICT dstC, int count) {
+    SkASSERT(count > 0);
+
+    // Zero difference between radii:  fill with transparent black.
+    // TODO: Is removing this actually correct? Two circles with the 
+    // same radius, but different centers doesn't sound like it
+    // should be cleared
+    if (fDiffRadius == 0 && fCenter1 == fCenter2) {
+      sk_bzero(dstC, count * sizeof(*dstC));
+      return;
+    }
+    SkMatrix::MapXYProc dstProc = fDstToIndexProc;
+    TileProc            proc = fTileProc;
+    const SkPMColor* SK_RESTRICT cache = this->getCache32();
+
+    SkScalar foura = fA * 4;
+    bool posRoot = fDiffRadius < 0;
+    if (fDstToIndexClass != kPerspective_MatrixClass) {
+        SkPoint srcPt;
+        dstProc(fDstToIndex, SkIntToScalar(x) + SK_ScalarHalf,
+                                 SkIntToScalar(y) + SK_ScalarHalf, &srcPt);
+        SkScalar dx, fx = srcPt.fX;
+        SkScalar dy, fy = srcPt.fY;
+
+        if (fDstToIndexClass == kFixedStepInX_MatrixClass) {
+            SkFixed fixedX, fixedY;
+            (void)fDstToIndex.fixedStepInX(SkIntToScalar(y), &fixedX, &fixedY);
+            dx = SkFixedToScalar(fixedX);
+            dy = SkFixedToScalar(fixedY);
+        } else {
+            SkASSERT(fDstToIndexClass == kLinear_MatrixClass);
+            dx = fDstToIndex.getScaleX();
+            dy = fDstToIndex.getSkewY();
+        }
+        SkScalar b = (SkScalarMul(fDiff.fX, fx) +
+                     SkScalarMul(fDiff.fY, fy) - fStartRadius) * 2;
+        SkScalar db = (SkScalarMul(fDiff.fX, dx) +
+                      SkScalarMul(fDiff.fY, dy)) * 2;
+        if (proc == clamp_tileproc) {
+            for (; count > 0; --count) {
+                SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
+                if (t < 0) {
+                  *dstC++ = cache[-1];
+                } else if (t > 0xFFFF) {
+                  *dstC++ = cache[kCache32Count * 2];
+                } else {
+                  SkASSERT(t <= 0xFFFF);
+                  *dstC++ = cache[t >> (16 - kCache32Bits)];
+                }
+                fx += dx;
+                fy += dy;
+                b += db;
+            }
+        } else if (proc == mirror_tileproc) {
+            for (; count > 0; --count) {
+                SkFixed t = two_point_radial(b, fx, fy, fSr2D2, foura, fOneOverTwoA, posRoot);
+                SkFixed index = mirror_tileproc(t);
+                SkASSERT(index <= 0xFFFF);
+                *dstC++ = cache[index >> (16 - kCache32Bits)];
+                fx += dx;
+                fy += dy;
+                b += db;
+            }
+        } else {
+            SkASSERT(proc == repeat_tileproc);
+            for (; count > 0; --count) {