Merge m-c to s-c.
authorRichard Newman <rnewman@mozilla.com>
Sat, 25 Aug 2012 07:16:25 -0700
changeset 111041 c05ca15d97b562914ff885779f393c6ca08a6d74
parent 111040 07854b5d6052ae601dcf37f3c1e3137ddb73b8d8 (current diff)
parent 105390 ead893fc780c8e4c1fa43a4c688d75e725709448 (diff)
child 111042 cf04bd598966ec84dad283264d2513fadeb0d888
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
milestone17.0a1
Merge m-c to s-c.
browser/devtools/highlighter/test/browser_inspector_editor.js
browser/devtools/highlighter/test/browser_inspector_editor_name.js
browser/devtools/highlighter/test/browser_inspector_treePanel_click.js
browser/devtools/highlighter/test/browser_inspector_treePanel_input.html
browser/devtools/highlighter/test/browser_inspector_treePanel_menu.js
browser/devtools/highlighter/test/browser_inspector_treePanel_navigation.html
browser/devtools/highlighter/test/browser_inspector_treePanel_navigation.js
browser/devtools/highlighter/test/browser_inspector_treePanel_output.js
browser/devtools/highlighter/test/browser_inspector_treePanel_result.html
content/base/public/nsBlobProtocolHandler.h
ipc/glue/IPCSerializableParams.ipdlh
netwerk/base/public/nsIIPCSerializableObsolete.idl
toolkit/components/telemetry/TelemetryHistograms.h
xpcom/tests/unit/test_seek_multiplex.js
--- a/accessible/src/base/AccEvent.h
+++ b/accessible/src/base/AccEvent.h
@@ -102,17 +102,17 @@ public:
   virtual unsigned int GetEventGroups() const
   {
     return 1U << eGenericEvent;
   }
 
   /**
    * Reference counting and cycle collection.
    */
-  NS_INLINE_DECL_REFCOUNTING(AccEvent)
+  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(AccEvent)
   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(AccEvent)
 
 protected:
   /**
    * Get an accessible from event target node.
    */
   Accessible* GetAccessibleForNode() const;
 
--- a/accessible/src/base/NotificationController.cpp
+++ b/accessible/src/base/NotificationController.cpp
@@ -50,18 +50,18 @@ NotificationController::~NotificationCon
   NS_ASSERTION(!mDocument, "Controller wasn't shutdown properly!");
   if (mDocument)
     Shutdown();
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // NotificationCollector: AddRef/Release and cycle collection
 
-NS_IMPL_ADDREF(NotificationController)
-NS_IMPL_RELEASE(NotificationController)
+NS_IMPL_CYCLE_COLLECTING_NATIVE_ADDREF(NotificationController)
+NS_IMPL_CYCLE_COLLECTING_NATIVE_RELEASE(NotificationController)
 
 NS_IMPL_CYCLE_COLLECTION_NATIVE_CLASS(NotificationController)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_NATIVE(NotificationController)
   if (tmp->mDocument)
     tmp->Shutdown();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
--- a/accessible/src/base/NotificationController.h
+++ b/accessible/src/base/NotificationController.h
@@ -168,17 +168,17 @@ public:
   }
 
 #ifdef DEBUG
   bool IsUpdating() const
     { return mObservingState == eRefreshProcessingForUpdate; }
 #endif
 
 protected:
-  nsAutoRefCnt mRefCnt;
+  nsCycleCollectingAutoRefCnt mRefCnt;
   NS_DECL_OWNINGTHREAD
 
   /**
    * Start to observe refresh to make notifications and events processing after
    * layout.
    */
   void ScheduleProcessing();
 
@@ -267,17 +267,17 @@ private:
    * Storage for content inserted notification information.
    */
   class ContentInsertion
   {
   public:
     ContentInsertion(DocAccessible* aDocument, Accessible* aContainer);
     virtual ~ContentInsertion() { mDocument = nullptr; }
 
-    NS_INLINE_DECL_REFCOUNTING(ContentInsertion)
+    NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(ContentInsertion)
     NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(ContentInsertion)
 
     bool InitChildList(nsIContent* aStartChildNode, nsIContent* aEndChildNode);
     void Process();
 
   private:
     ContentInsertion();
     ContentInsertion(const ContentInsertion&);
--- a/accessible/src/base/RoleAsserts.cpp
+++ b/accessible/src/base/RoleAsserts.cpp
@@ -7,11 +7,13 @@
 #include "nsIAccessibleRole.h"
 #include "Role.h"
 
 #include "mozilla/Assertions.h"
 
 using namespace mozilla::a11y;
 
 #define ROLE(geckoRole, stringRole, atkRole, macRole, msaaRole, ia2Role, nameRule) \
-  MOZ_STATIC_ASSERT(roles::geckoRole == nsIAccessibleRole::ROLE_ ## geckoRole, "internal and xpcom roles differ!");
+  MOZ_STATIC_ASSERT(static_cast<uint32_t>(roles::geckoRole) \
+                    == static_cast<uint32_t>(nsIAccessibleRole::ROLE_ ## geckoRole), \
+                    "internal and xpcom roles differ!");
 #include "RoleMap.h"
 #undef ROLE
--- a/accessible/src/jsat/TouchAdapter.jsm
+++ b/accessible/src/jsat/TouchAdapter.jsm
@@ -80,48 +80,51 @@ var TouchAdapter = {
     if (Utils.OS != 'Android')
       Mouse2Touch.detach(aWindow);
 
     delete this.chromeWin;
   },
 
   handleEvent: function TouchAdapter_handleEvent(aEvent) {
     let touches = aEvent.changedTouches;
+    // XXX: Until bug 77992 is resolved, on desktop we get microseconds
+    // instead of milliseconds.
+    let timeStamp = (Utils.OS == 'Android') ? aEvent.timeStamp : Date.now();
     switch (aEvent.type) {
       case 'touchstart':
         for (var i = 0; i < touches.length; i++) {
           let touch = touches[i];
-          let touchPoint = new TouchPoint(touch, aEvent.timeStamp, this._dpi);
+          let touchPoint = new TouchPoint(touch, timeStamp, this._dpi);
           this._touchPoints[touch.identifier] = touchPoint;
-          this._lastExploreTime = aEvent.timeStamp + this.SWIPE_MAX_DURATION;
+          this._lastExploreTime = timeStamp + this.SWIPE_MAX_DURATION;
         }
         this._dwellTimeout = this.chromeWin.setTimeout(
           (function () {
-             this.compileAndEmit(aEvent.timeStamp + this.DWELL_THRESHOLD);
+             this.compileAndEmit(timeStamp + this.DWELL_THRESHOLD);
            }).bind(this), this.DWELL_THRESHOLD);
         break;
       case 'touchmove':
         for (var i = 0; i < touches.length; i++) {
           let touch = touches[i];
           let touchPoint = this._touchPoints[touch.identifier];
-          touchPoint.update(touch, aEvent.timeStamp);
+          touchPoint.update(touch, timeStamp);
         }
-        if (aEvent.timeStamp - this._lastExploreTime >= EXPLORE_THROTTLE) {
-          this.compileAndEmit(aEvent.timeStamp);
-          this._lastExploreTime = aEvent.timeStamp;
+        if (timeStamp - this._lastExploreTime >= EXPLORE_THROTTLE) {
+          this.compileAndEmit(timeStamp);
+          this._lastExploreTime = timeStamp;
         }
         break;
       case 'touchend':
         for (var i = 0; i < touches.length; i++) {
           let touch = touches[i];
           let touchPoint = this._touchPoints[touch.identifier];
-          touchPoint.update(touch, aEvent.timeStamp);
+          touchPoint.update(touch, timeStamp);
           touchPoint.finish();
         }
-        this.compileAndEmit(aEvent.timeStamp);
+        this.compileAndEmit(timeStamp);
         break;
     }
   },
 
   cleanupTouches: function cleanupTouches() {
     for (var identifier in this._touchPoints) {
       if (!this._touchPoints[identifier].done)
         continue;
--- a/accessible/src/jsat/UtteranceGenerator.jsm
+++ b/accessible/src/jsat/UtteranceGenerator.jsm
@@ -147,17 +147,17 @@ var UtteranceGenerator = {
     'toolbar': INCLUDE_DESC,
     'table': INCLUDE_DESC | INCLUDE_NAME,
     'link': INCLUDE_DESC,
     'listitem': INCLUDE_DESC,
     'outline': INCLUDE_DESC,
     'outlineitem': INCLUDE_DESC,
     'pagetab': INCLUDE_DESC,
     'graphic': INCLUDE_DESC,
-    'pushbutton': INCLUDE_DESC | INCLUDE_NAME,
+    'pushbutton': INCLUDE_DESC,
     'checkbutton': INCLUDE_DESC,
     'radiobutton': INCLUDE_DESC,
     'combobox': INCLUDE_DESC,
     'droplist': INCLUDE_DESC,
     'progressbar': INCLUDE_DESC,
     'slider': INCLUDE_DESC,
     'spinbutton': INCLUDE_DESC,
     'diagram': INCLUDE_DESC,
--- a/accessible/src/mac/mozAccessible.mm
+++ b/accessible/src/mac/mozAccessible.mm
@@ -96,17 +96,18 @@ GetClosestInterestingAccessible(id anObj
 #pragma mark -
 
 - (BOOL)accessibilityIsIgnored
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
 
   // unknown (either unimplemented, or irrelevant) elements are marked as ignored
   // as well as expired elements.
-  return !mGeckoAccessible || [[self role] isEqualToString:NSAccessibilityUnknownRole];
+  return !mGeckoAccessible || ([[self role] isEqualToString:NSAccessibilityUnknownRole] &&
+                               !(mGeckoAccessible->NativeInteractiveState() & states::FOCUSABLE));
 
   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
 }
 
 - (NSArray*)accessibilityAttributeNames
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
--- a/accessible/src/mac/mozActionElements.mm
+++ b/accessible/src/mac/mozActionElements.mm
@@ -333,16 +333,29 @@ enum CheckboxValue {
   [mTabs release];
   mTabs = nil;
 }
 
 @end
 
 @implementation mozPaneAccessible
 
+- (NSUInteger)accessibilityArrayAttributeCount:(NSString*)attribute
+{
+  if (!mGeckoAccessible)
+    return 0;
+
+  // By default this calls -[[mozAccessible children] count].
+  // Since we don't cache mChildren. This is faster.
+  if ([attribute isEqualToString:NSAccessibilityChildrenAttribute])
+    return mGeckoAccessible->ChildCount() ? 1 : 0;
+
+  return [super accessibilityArrayAttributeCount:attribute];
+}
+
 - (NSArray*)children
 {
   if (!mGeckoAccessible)
     return nil;
 
   nsDeckFrame* deckFrame = do_QueryFrame(mGeckoAccessible->GetFrame());
   nsIFrame* selectedFrame = deckFrame ? deckFrame->GetSelectedBox() : nullptr;
 
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -510,16 +510,18 @@ pref("dom.ipc.processPriorityManager.ena
 pref("dom.ipc.processPriorityManager.gracePeriodMS", 1000);
 pref("hal.processPriorityManager.gonk.masterOomAdjust", 0);
 pref("hal.processPriorityManager.gonk.foregroundOomAdjust", 1);
 pref("hal.processPriorityManager.gonk.backgroundOomAdjust", 2);
 pref("hal.processPriorityManager.gonk.masterNice", -1);
 pref("hal.processPriorityManager.gonk.foregroundNice", 0);
 pref("hal.processPriorityManager.gonk.backgroundNice", 10);
 
+#ifndef DEBUG
 // Enable pre-launching content processes for improved startup time
 // (hiding latency).
 pref("dom.ipc.processPrelauch.enabled", true);
 // Wait this long before pre-launching a new subprocess.
 pref("dom.ipc.processPrelauch.delayMs", 1000);
+#endif
 
 // Ignore the "dialog=1" feature in window.open.
 pref("dom.disable_window_open_dialog_feature", true);
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0"?>
-<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1345147390000">
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1345657032000">
   <emItems>
       <emItem  blockID="i58" id="webmaster@buzzzzvideos.info">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i41" id="{99079a25-328f-4bd4-be04-00955acaa0a7}">
                         <versionRange  minVersion="0.1" maxVersion="4.3.1.00" severity="1">
                     </versionRange>
@@ -434,16 +434,19 @@
                   <match name="filename" exp="npmozax\.dll" />                      <versionRange  minVersion="0" maxVersion="*"></versionRange>
                   </pluginItem>
       <pluginItem  blockID="p113">
                   <match name="filename" exp="npuplaypc\.dll" />                      <versionRange  minVersion="0" maxVersion="1.0.0.0" severity="1"></versionRange>
                   </pluginItem>
       <pluginItem  blockID="p123">
                   <match name="filename" exp="JavaPlugin2_NPAPI\.plugin" />                      <versionRange  minVersion="0" maxVersion="14.2.0" severity="1"></versionRange>
                   </pluginItem>
+      <pluginItem  blockID="p129">
+                  <match name="filename" exp="Silverlight\.plugin" />                      <versionRange  minVersion="0" maxVersion="5.0.99999" severity="1"></versionRange>
+                  </pluginItem>
     </pluginItems>
 
   <gfxItems>
     <gfxBlacklistEntry  blockID="g35">      <os>WINNT 6.1</os>      <vendor>0x10de</vendor>              <devices>
                       <device>0x0a6c</device>
                   </devices>
             <feature>DIRECT2D</feature>      <featureStatus>BLOCKED_DRIVER_VERSION</featureStatus>      <driverVersion>8.17.12.5896</driverVersion>      <driverVersionComparator>LESS_THAN_OR_EQUAL</driverVersionComparator>    </gfxBlacklistEntry>
     <gfxBlacklistEntry  blockID="g36">      <os>WINNT 6.1</os>      <vendor>0x10de</vendor>              <devices>
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -187,33 +187,33 @@
       <hbox flex="1">
         <image src="chrome://browser/content/social-icon.png" class="popup-notification-icon"/>
         <vbox flex="1">
           <description id="social-activation-message" class="popup-notification-description"/>
           <spacer flex="1"/>
           <hbox pack="end" align="center" class="popup-notification-button-container">
 #ifdef XP_UNIX
         <button id="social-undoactivation-button"
-                label="&social.activated.button.label;"
-                accesskey="&social.activated.button.accesskey;"
+                label="&social.activated.undobutton.label;"
+                accesskey="&social.activated.undobutton.accesskey;"
                 onclick="SocialUI.undoActivation();"/>
         <button default="true"
                 autofocus="autofocus"
                 label="&social.ok.label;"
                 accesskey="&social.ok.accesskey;"
                 oncommand="SocialUI.notificationPanel.hidePopup();"/>
 #else
         <button default="true"
                 autofocus="autofocus"
                 label="&social.ok.label;"
                 accesskey="&social.ok.accesskey;"
                 oncommand="SocialUI.notificationPanel.hidePopup();"/>
         <button id="social-undoactivation-button"
-                label="&social.activated.button.label;"
-                accesskey="&social.activated.button.accesskey;"
+                label="&social.activated.undobutton.label;"
+                accesskey="&social.activated.undobutton.accesskey;"
                 onclick="SocialUI.undoActivation();"/>
 #endif
           </hbox>
         </vbox>
       </hbox>
     </panel>
 
     <panel id="editSharePopup"
--- a/browser/base/content/test/test_contextmenu.html
+++ b/browser/base/content/test/test_contextmenu.html
@@ -47,23 +47,23 @@ function openContextMenuFor(element, shi
 function closeContextMenu() {
     contextMenu.hidePopup();
 }
 
 function executeCopyCommand(command, expectedValue)
 {
   // Just execute the command directly rather than simulating a context menu
   // press to avoid having to deal with its asynchronous nature
-  subwindow.controllers.getControllerForCommand(command).doCommand(command);
+  SpecialPowers.wrap(subwindow).controllers.getControllerForCommand(command).doCommand(command);
 
   // The easiest way to check the clipboard is to paste the contents into a
   // textbox
   input.focus();
   input.value = "";
-  input.controllers.getControllerForCommand("cmd_paste").doCommand("cmd_paste");
+  SpecialPowers.wrap(input).controllers.getControllerForCommand("cmd_paste").doCommand("cmd_paste");
   is(input.value, expectedValue, "paste for command " + command);
 }
 
 function invokeItemAction(generatedItemId)
 {
   var item = contextMenu.getElementsByAttribute("generateditemid",
                                                 generatedItemId)[0];
   ok(item, "Got generated XUL menu item");
@@ -245,17 +245,16 @@ function checkMenu(menu, expectedItems, 
  *
  * Called by a popupshowing event handler. Each test checks for expected menu
  * contents, closes the popup, and finally triggers the popup on a new element
  * (thus kicking off another cycle).
  *
  */
 function runTest(testNum) {
   // Seems we need to enable this again, or sendKeyEvent() complaints.
-  netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
   ok(true, "Starting test #" + testNum);
 
   var inspectItems = [];
   if (SpecialPowers.getBoolPref("devtools.inspector.enabled")) {
     inspectItems = ["---", null,
                     "context-inspect", true];
   }
 
--- a/browser/devtools/Makefile.in
+++ b/browser/devtools/Makefile.in
@@ -9,16 +9,17 @@ srcdir    = @srcdir@
 VPATH   = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 include $(topsrcdir)/config/config.mk
 
 DIRS = \
   highlighter \
+  markupview \
   webconsole \
   commandline \
   sourceeditor \
   styleeditor \
   styleinspector \
   tilt \
   scratchpad \
   debugger \
--- a/browser/devtools/highlighter/inspector.jsm
+++ b/browser/devtools/highlighter/inspector.jsm
@@ -9,16 +9,17 @@ const Cu = Components.utils;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 
 var EXPORTED_SYMBOLS = ["InspectorUI"];
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/TreePanel.jsm");
+Cu.import("resource:///modules/devtools/MarkupView.jsm");
 Cu.import("resource:///modules/highlighter.jsm");
 Cu.import("resource:///modules/devtools/LayoutView.jsm");
 Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
 
 // Inspector notifications dispatched through the nsIObserverService.
 const INSPECTOR_NOTIFICATIONS = {
   // Fires once the Inspector completes the initialization and opens up on
   // screen.
@@ -64,16 +65,25 @@ const LAYOUT_CHANGE_TIMER = 250;
 function Inspector(aIUI)
 {
   this._IUI = aIUI;
   this._winID = aIUI.winID;
   this._browser = aIUI.browser;
   this._listeners = {};
 
   this._browser.addEventListener("resize", this, true);
+
+  this._markupButton = this._IUI.chromeDoc.getElementById("inspector-treepanel-toolbutton");
+
+  if (Services.prefs.getBoolPref("devtools.inspector.htmlPanelOpen")) {
+    this.openMarkup();
+  } else {
+    this.closeMarkup();
+  }
+
 }
 
 Inspector.prototype = {
   /**
    * True if the highlighter is locked on a node.
    */
   get locked() {
     return !this._IUI.inspecting;
@@ -129,16 +139,17 @@ Inspector.prototype = {
   },
 
   /**
    * Called by the InspectorUI when the inspector is being destroyed.
    */
   _destroy: function Inspector__destroy()
   {
     this._cancelLayoutChange();
+    this._destroyMarkup();
     this._browser.removeEventListener("resize", this, true);
     delete this._IUI;
     delete this._listeners;
   },
 
   /**
    * Event handler for DOM events.
    *
@@ -173,37 +184,169 @@ Inspector.prototype = {
   _cancelLayoutChange: function Inspector_cancelLayoutChange()
   {
     if (this._timer) {
       this._IUI.win.clearTimeout(this._timer);
       delete this._timer;
     }
   },
 
+  toggleMarkup: function Inspector_toggleMarkup()
+  {
+    if (this._markupFrame) {
+      this.closeMarkup();
+      Services.prefs.setBoolPref("devtools.inspector.htmlPanelOpen", false);
+    } else {
+      this.openMarkup(true);
+      Services.prefs.setBoolPref("devtools.inspector.htmlPanelOpen", true);
+    }
+  },
+
+  /**
+   * XXX: The sidebar has an object that exists and is manipulated
+   * separately from its actual loading.  So the public api for
+   * the sidebar looks like:
+   *
+   * if (inspector.sidebar.visible) { inspector.sidebar.close() }
+   *
+   * whereas the markup API looks more like
+   *
+   * if (inspector.markupOpen) { inspector.closeMarkup() }
+   *
+   * Maybe we should add an InspectorMarkup object that presents
+   * the public api for the markup panel?
+   */
+  get markupOpen() {
+    return this._markupOpen;
+  },
+
+  openMarkup: function Inspector_openMarkup(aFocus)
+  {
+    this._markupButton.setAttribute("checked", "true");
+    this._markupOpen = true;
+    if (!this._markupFrame) {
+      this._initMarkup(aFocus);
+    }
+  },
+
+  closeMarkup: function Inspector_closeMarkup()
+  {
+    this._markupButton.removeAttribute("checked");
+    this._markupOpen = false;
+    this._destroyMarkup();
+  },
+
+  _initMarkup: function Inspector_initMarkupPane(aFocus)
+  {
+    let doc = this._IUI.chromeDoc;
+
+    this._markupBox = doc.createElement("vbox");
+    try {
+      this._markupBox.height =
+        Services.prefs.getIntPref("devtools.inspector.htmlHeight");
+    } catch(e) {
+      this._markupBox.height = 112;
+    }
+    this._markupBox.minHeight = 64;
+
+    this._markupSplitter = doc.createElement("splitter");
+    this._markupSplitter.className = "devtools-horizontal-splitter";
+
+    let container = doc.getElementById("appcontent");
+    container.appendChild(this._markupSplitter);
+    container.appendChild(this._markupBox);
+
+    // create tool iframe
+    this._markupFrame = doc.createElement("iframe");
+    this._markupFrame.setAttribute("flex", "1");
+    this._markupFrame.setAttribute("tooltip", "aHTMLTooltip");
+
+    // This is needed to enable tooltips inside the iframe document.
+    this._boundMarkupFrameLoad = function Inspector_initMarkupPanel_onload() {
+      if (aFocus) {
+        this._markupFrame.contentWindow.focus();
+      }
+      this._onMarkupFrameLoad();
+    }.bind(this);
+    this._markupFrame.addEventListener("load", this._boundMarkupFrameLoad, true);
+
+    this._markupSplitter.setAttribute("hidden", true);
+    this._markupBox.setAttribute("hidden", true);
+    this._markupBox.appendChild(this._markupFrame);
+    this._markupFrame.setAttribute("src", "chrome://browser/content/devtools/markup-view.xhtml");
+  },
+
+  _onMarkupFrameLoad: function Inspector__onMarkupFrameLoad()
+  {
+    this._markupFrame.removeEventListener("load", this._boundMarkupFrameLoad, true);
+    delete this._boundMarkupFrameLoad;
+
+    this._markupSplitter.removeAttribute("hidden");
+    this._markupBox.removeAttribute("hidden");
+
+    this.markup = new MarkupView(this, this._markupFrame);
+    this._emit("markuploaded");
+  },
+
+  _destroyMarkup: function Inspector__destroyMarkup()
+  {
+    if (this._boundMarkupFrameLoad) {
+      this._markupFrame.removeEventListener("load", this._boundMarkupFrameLoad, true);
+      delete this._boundMarkupFrameLoad;
+    }
+
+    if (this.markup) {
+      this.markup.destroy();
+      delete this.markup;
+    }
+
+    if (this._markupFrame) {
+      delete this._markupFrame;
+    }
+
+    if (this._markupBox) {
+      Services.prefs.setIntPref("devtools.inspector.htmlHeight", this._markupBox.height);
+      this._markupBox.parentNode.removeChild(this._markupBox);
+      delete this._markupBox;
+    }
+
+    if (this._markupSplitter) {
+      this._markupSplitter.parentNode.removeChild(this._markupSplitter);
+    }
+  },
+
   /**
    * Called by InspectorUI after a tab switch, when the
    * inspector is no longer the active tab.
    */
   _freeze: function Inspector__freeze()
   {
+    if (this._markupBox) {
+      this._markupSplitter.setAttribute("hidden", true);
+      this._markupBox.setAttribute("hidden", true);
+    }
     this._cancelLayoutChange();
     this._browser.removeEventListener("resize", this, true);
     this._frozen = true;
   },
 
   /**
    * Called by InspectorUI after a tab switch when the
    * inspector is back to being the active tab.
    */
   _thaw: function Inspector__thaw()
   {
     if (!this._frozen) {
       return;
     }
 
+    if (this._markupOpen && !this._boundMarkupFrameLoad) {
+      this._markupSplitter.removeAttribute("hidden");
+      this._markupBox.removeAttribute("hidden");
+    }
     this._browser.addEventListener("resize", this, true);
     delete this._frozen;
   },
 
   /// Event stuff.  Would like to refactor this eventually.
   /// Emulates the jetpack event source, which has a nice API.
 
   /**
@@ -437,25 +580,17 @@ InspectorUI.prototype = {
     }
   },
 
   /**
    * Toggle the TreePanel.
    */
   toggleHTMLPanel: function IUI_toggleHTMLPanel()
   {
-    if (this.treePanel.isOpen()) {
-      this.treePanel.close();
-      Services.prefs.setBoolPref("devtools.inspector.htmlPanelOpen", false);
-      this.currentInspector._htmlPanelOpen = false;
-    } else {
-      this.treePanel.open();
-      Services.prefs.setBoolPref("devtools.inspector.htmlPanelOpen", true);
-      this.currentInspector._htmlPanelOpen = true;
-    }
+    this.currentInspector.toggleMarkup();
   },
 
   /**
    * Is the inspector UI open? Simply check if the toolbar is visible or not.
    *
    * @returns boolean
    */
   get isInspectorOpen()
@@ -549,17 +684,16 @@ InspectorUI.prototype = {
     this.inspectCommand = this.chromeDoc.getElementById("Inspector:Inspect");
 
     // Update menus:
     this.inspectorUICommand = this.chromeDoc.getElementById("Tools:Inspect");
     this.inspectorUICommand.setAttribute("checked", "true");
 
     this.chromeWin.Tilt.setup();
 
-    this.treePanel = new TreePanel(this.chromeWin, this);
     this.toolbar.hidden = false;
 
     // initialize the HTML Breadcrumbs
     this.breadcrumbs = new HTMLBreadcrumbs(this);
 
     this.isDirty = false;
 
     this.progressListener = new InspectorProgressListener(this);
@@ -675,23 +809,16 @@ InspectorUI.prototype = {
    *
    * @param boolean aKeepInspector
    *        Tells if you want the inspector associated to the current tab/window to
    *        be cleared or not. Set this to true to save the inspector, or false
    *        to destroy it.
    */
   closeInspectorUI: function IUI_closeInspectorUI(aKeepInspector)
   {
-    // if currently editing an attribute value, closing the
-    // highlighter/HTML panel dismisses the editor
-    if (this.treePanel && this.treePanel.editingContext)
-      this.treePanel.closeEditor();
-
-    this.treePanel.destroy();
-
     if (this.closing || !this.win || !this.browser) {
       return;
     }
 
     let winId = new String(this.winID); // retain this to notify observers.
 
     this.closing = true;
     this.toolbar.hidden = true;
@@ -747,17 +874,16 @@ InspectorUI.prototype = {
     this.inspectorUICommand.setAttribute("checked", "false");
 
     this.browser = this.win = null; // null out references to browser and window
     this.winID = null;
     this.selection = null;
     this.closing = false;
     this.isDirty = false;
 
-    delete this.treePanel;
     delete this.stylePanel;
     delete this.inspectorUICommand;
     delete this.inspectCommand;
     delete this.toolbar;
 
     Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.CLOSED, null);
 
     if (!aKeepInspector)
@@ -765,21 +891,16 @@ InspectorUI.prototype = {
   },
 
   /**
    * Begin inspecting webpage, attach page event listeners, activate
    * highlighter event listeners.
    */
   startInspecting: function IUI_startInspecting()
   {
-    // if currently editing an attribute value, starting
-    // "live inspection" mode closes the editor
-    if (this.treePanel && this.treePanel.editingContext)
-      this.treePanel.closeEditor();
-
     this.inspectCommand.setAttribute("checked", "true");
 
     this.inspecting = true;
     this.highlighter.unlock();
     this._notifySelected();
     this._currentInspector._emit("unlocked");
   },
 
@@ -823,38 +944,32 @@ InspectorUI.prototype = {
    *        force an update?
    * @param aScroll boolean
    *        scroll the tree panel?
    * @param aFrom [optional] string
    *        which part of the UI the selection occured from
    */
   select: function IUI_select(aNode, forceUpdate, aScroll, aFrom)
   {
-    // if currently editing an attribute value, using the
-    // highlighter dismisses the editor
-    if (this.treePanel && this.treePanel.editingContext)
-      this.treePanel.closeEditor();
-
     if (!aNode)
       aNode = this.defaultSelection;
 
     if (forceUpdate || aNode != this.selection) {
       if (aFrom != "breadcrumbs") {
         this.clearPseudoClassLocks();
       }
 
       this.selection = aNode;
       if (!this.inspecting) {
         this.highlighter.highlight(this.selection);
       }
     }
 
     this.breadcrumbs.update();
     this.chromeWin.Tilt.update(aNode);
-    this.treePanel.select(aNode, aScroll, aFrom);
 
     this._notifySelected(aFrom);
   },
 
   /**
    * Toggle the pseudo-class lock on the currently inspected element. If the
    * pseudo-class is :hover or :active, that pseudo-class will also be toggled
    * on every ancestor of the element, mirroring real :hover and :active
@@ -934,20 +1049,16 @@ InspectorUI.prototype = {
     } else {
       this.highlighter.lock();
     }
 
     Services.obs.notifyObservers(null, INSPECTOR_NOTIFICATIONS.STATE_RESTORED, null);
 
     this.highlighter.highlight();
 
-    if (this.currentInspector._htmlPanelOpen) {
-      this.treePanel.open();
-    }
-
     if (this.currentInspector._sidebarOpen) {
       this._sidebar.show();
     }
 
     let menu = this.chromeDoc.getElementById("inspectorToggleVeil");
     if (this.currentInspector._highlighterShowVeil) {
       menu.setAttribute("checked", "true");
     } else {
@@ -1120,25 +1231,21 @@ InspectorUI.prototype = {
     let root = selection.ownerDocument.documentElement;
     if (selection === root) {
       // We can't delete the root element.
       return;
     }
 
     let parent = selection.parentNode;
 
-    // remove the node from the treepanel
-    if (this.treePanel.isOpen())
-      this.treePanel.deleteChildBox(selection);
-
     // remove the node from content
     parent.removeChild(selection);
     this.breadcrumbs.invalidateHierarchy();
 
-    // select the parent node in the highlighter, treepanel, breadcrumbs
+    // select the parent node in the highlighter and breadcrumbs
     this.inspectNode(parent);
   },
 
   /////////////////////////////////////////////////////////////////////////
   //// Utility Methods
 
   /**
    * inspect the given node, highlighting it on the page and selecting the
--- a/browser/devtools/highlighter/test/Makefile.in
+++ b/browser/devtools/highlighter/test/Makefile.in
@@ -13,42 +13,32 @@ include $(topsrcdir)/config/rules.mk
 
 _BROWSER_FILES = \
 		browser_inspector_initialization.js \
 		browser_inspector_treeSelection.js \
 		browser_inspector_highlighter.js \
 		browser_inspector_iframeTest.js \
 		browser_inspector_scrolling.js \
 		browser_inspector_tab_switch.js \
-		browser_inspector_treePanel_output.js \
-		browser_inspector_treePanel_input.html \
-		browser_inspector_treePanel_result.html \
 		browser_inspector_bug_665880.js \
 		browser_inspector_bug_674871.js \
-		browser_inspector_editor.js \
-		browser_inspector_editor_name.js \
 		browser_inspector_bug_566084_location_changed.js \
 		browser_inspector_infobar.js \
 		browser_inspector_bug_690361.js \
 		browser_inspector_bug_672902_keyboard_shortcuts.js \
 		browser_inspector_keybindings.js \
 		browser_inspector_breadcrumbs.html \
 		browser_inspector_breadcrumbs.js \
 		browser_inspector_bug_699308_iframe_navigation.js \
 		browser_inspector_changes.js \
 		browser_inspector_ruleviewstore.js \
 		browser_inspector_invalidate.js \
 		browser_inspector_sidebarstate.js \
-		browser_inspector_treePanel_menu.js \
+		browser_inspector_menu.js \
 		browser_inspector_pseudoclass_lock.js \
 		browser_inspector_pseudoClass_menu.js \
 		browser_inspector_destroyselection.html \
 		browser_inspector_destroyselection.js \
-		browser_inspector_treePanel_navigation.html \
-		browser_inspector_treePanel_navigation.js \
 		head.js \
 		$(NULL)
 
-# Disabled due to constant failures
-# 		browser_inspector_treePanel_click.js \
-
 libs::	$(_BROWSER_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
--- a/browser/devtools/highlighter/test/browser_inspector_bug_690361.js
+++ b/browser/devtools/highlighter/test/browser_inspector_bug_690361.js
@@ -38,34 +38,34 @@ function runInspectorTests()
   Services.obs.removeObserver(runInspectorTests,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
   Services.obs.addObserver(closeInspectorTests,
     InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
 
   ok(InspectorUI.toolbar, "we have the toolbar.");
   ok(!InspectorUI.toolbar.hidden, "toolbar is visible");
   ok(InspectorUI.inspecting, "Inspector is inspecting");
-  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
+  ok(!InspectorUI.currentInspector._markupOpen, "Inspector Tree Panel is not open");
   ok(InspectorUI.highlighter, "Highlighter is up");
 
   salutation = doc.getElementById("salutation");
   InspectorUI.inspectNode(salutation);
 
   let button = document.getElementById("highlighter-closebutton");
   button.click();
 }
 
 function closeInspectorTests()
 {
   Services.obs.removeObserver(closeInspectorTests,
     InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
   Services.obs.addObserver(inspectorOpenedTrap,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
-  ok(!InspectorUI.isInspectorOpen, "Inspector Tree Panel is not open");
+  ok(!InspectorUI.isInspectorOpen, "Inspector is not open");
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     gBrowser.removeCurrentTab();
   }, true);
 
   gBrowser.tabContainer.addEventListener("TabSelect", finishInspectorTests, false);
deleted file mode 100644
--- a/browser/devtools/highlighter/test/browser_inspector_editor.js
+++ /dev/null
@@ -1,273 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* ***** BEGIN LICENSE BLOCK *****
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- * ***** END LICENSE BLOCK *****
- */
-
-let doc;
-let div;
-let editorTestSteps;
-
-function doNextStep() {
-  try {
-    editorTestSteps.next();
-  } catch(exception) {
-    info("caught:", exception);
-  }
-}
-
-function setupEditorTests()
-{
-  div = doc.createElement("div");
-  div.setAttribute("id", "foobar");
-  div.setAttribute("class", "barbaz");
-  doc.body.appendChild(div);
-
-  Services.obs.addObserver(setupHTMLPanel, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
-  InspectorUI.toggleInspectorUI();
-}
-
-function setupHTMLPanel()
-{
-  Services.obs.removeObserver(setupHTMLPanel, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
-  Services.obs.addObserver(runEditorTests, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
-  InspectorUI.toggleHTMLPanel();
-}
-
-function runEditorTests()
-{
-  Services.obs.removeObserver(runEditorTests, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
-  InspectorUI.stopInspecting();
-  InspectorUI.inspectNode(doc.body, true);
-
-  // setup generator for async test steps
-  editorTestSteps = doEditorTestSteps();
-
-  // add step listeners
-  Services.obs.addObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_OPENED, false);
-  Services.obs.addObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED, false);
-  Services.obs.addObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_SAVED, false);
-
-  // start the tests
-  doNextStep();
-}
-
-function highlighterTrap()
-{
-  // bug 696107
-  InspectorUI.highlighter.removeListener("nodeselected", highlighterTrap);
-  ok(false, "Highlighter moved. Shouldn't be here!");
-  finishUp();
-}
-
-function doEditorTestSteps()
-{
-  let treePanel = InspectorUI.treePanel;
-  let editor = treePanel.treeBrowserDocument.getElementById("attribute-editor");
-  let editorInput = treePanel.treeBrowserDocument.getElementById("attribute-editor-input");
-
-  // Step 1: grab and test the attribute-value nodes in the HTML panel, then open editor
-  let attrValNode_id = treePanel.treeBrowserDocument.querySelectorAll(".nodeValue.editable[data-attributeName='id']")[0];
-  let attrValNode_class = treePanel.treeBrowserDocument.querySelectorAll(".nodeValue.editable[data-attributeName='class']")[0];
-
-  is(attrValNode_id.innerHTML, "foobar", "Step 1: we have the correct `id` attribute-value node in the HTML panel");
-  is(attrValNode_class.innerHTML, "barbaz", "we have the correct `class` attribute-value node in the HTML panel");
-
-  // double-click the `id` attribute-value node to open the editor
-  executeSoon(function() {
-    // firing 2 clicks right in a row to simulate a double-click
-    EventUtils.synthesizeMouse(attrValNode_id, 2, 2, {clickCount: 2}, attrValNode_id.ownerDocument.defaultView);
-  });
-
-
-  yield; // End of Step 1
-
-  // Step 2: validate editing session, enter new attribute value into editor, and save input
-  ok(InspectorUI.treePanel.editingContext, "Step 2: editor session started");
-  let selection = InspectorUI.selection;
-
-  ok(selection, "Selection is: " + selection);
-
-  let editorVisible = editor.classList.contains("editing");
-  ok(editorVisible, "editor popup visible");
-
-  // check if the editor popup is "near" the correct position
-  let editorDims = editor.getBoundingClientRect();
-  let attrValNodeDims = attrValNode_id.getBoundingClientRect();
-  let editorPositionOK = (editorDims.left >= (attrValNodeDims.left - editorDims.width - 5)) &&
-                          (editorDims.right <= (attrValNodeDims.right + editorDims.width + 5)) &&
-                          (editorDims.top >= (attrValNodeDims.top - editorDims.height - 5)) &&
-                          (editorDims.bottom <= (attrValNodeDims.bottom + editorDims.height + 5));
-
-  ok(editorPositionOK, "editor position acceptable");
-
-  // check to make sure the attribute-value node being edited is properly highlighted
-  let attrValNodeHighlighted = attrValNode_id.classList.contains("editingAttributeValue");
-  ok(attrValNodeHighlighted, "`id` attribute-value node is editor-highlighted");
-
-  is(treePanel.editingContext.repObj, div, "editor session has correct reference to div");
-  is(treePanel.editingContext.attrObj, attrValNode_id, "editor session has correct reference to `id` attribute-value node in HTML panel");
-  is(treePanel.editingContext.attrName, "id", "editor session knows correct attribute-name");
-
-  editorInput.value = "Hello World";
-  editorInput.focus();
-
-  InspectorUI.highlighter.addListener("nodeselected", highlighterTrap);
-
-  // hit <enter> to save the textbox value
-  executeSoon(function() {
-    // Extra key to test that keyboard handlers have been removed. bug 696107.
-    EventUtils.synthesizeKey("VK_LEFT", {}, attrValNode_id.ownerDocument.defaultView);
-    EventUtils.synthesizeKey("VK_RETURN", {}, attrValNode_id.ownerDocument.defaultView);
-  });
-
-  // two `yield` statements, to trap both the "SAVED" and "CLOSED" events that will be triggered
-  yield;
-  yield; // End of Step 2
-
-  // remove this from previous step
-  InspectorUI.highlighter.removeListener("nodeselected", highlighterTrap);
-
-  // Step 3: validate that the previous editing session saved correctly, then open editor on `class` attribute value
-  ok(!treePanel.editingContext, "Step 3: editor session ended");
-  editorVisible = editor.classList.contains("editing");
-  ok(!editorVisible, "editor popup hidden");
-  attrValNodeHighlighted = attrValNode_id.classList.contains("editingAttributeValue");
-  ok(!attrValNodeHighlighted, "`id` attribute-value node is no longer editor-highlighted");
-  is(div.getAttribute("id"), "Hello World", "`id` attribute-value successfully updated");
-  is(attrValNode_id.innerHTML, "Hello World", "attribute-value node in HTML panel successfully updated");
-
-  // double-click the `class` attribute-value node to open the editor
-  executeSoon(function() {
-    // firing 2 clicks right in a row to simulate a double-click
-    EventUtils.synthesizeMouse(attrValNode_class, 2, 2, {clickCount: 2}, attrValNode_class.ownerDocument.defaultView);
-  });
-
-  yield; // End of Step 3
-
-
-  // Step 4: enter value into editor, then hit <escape> to discard it
-  ok(treePanel.editingContext, "Step 4: editor session started");
-  editorVisible = editor.classList.contains("editing");
-  ok(editorVisible, "editor popup visible");
-
-  is(treePanel.editingContext.attrObj, attrValNode_class, "editor session has correct reference to `class` attribute-value node in HTML panel");
-  is(treePanel.editingContext.attrName, "class", "editor session knows correct attribute-name");
-
-  editorInput.value = "Hello World";
-  editorInput.focus();
-
-  // hit <escape> to discard the inputted value
-  executeSoon(function() {
-    EventUtils.synthesizeKey("VK_ESCAPE", {}, attrValNode_class.ownerDocument.defaultView);
-  });
-
-  yield; // End of Step 4
-
-
-  // Step 5: validate that the previous editing session discarded correctly, then open editor on `id` attribute value again
-  ok(!treePanel.editingContext, "Step 5: editor session ended");
-  editorVisible = editor.classList.contains("editing");
-  ok(!editorVisible, "editor popup hidden");
-  is(div.getAttribute("class"), "barbaz", "`class` attribute-value *not* updated");
-  is(attrValNode_class.innerHTML, "barbaz", "attribute-value node in HTML panel *not* updated");
-
-  // double-click the `id` attribute-value node to open the editor
-  executeSoon(function() {
-    // firing 2 clicks right in a row to simulate a double-click
-    EventUtils.synthesizeMouse(attrValNode_id, 2, 2, {clickCount: 2}, attrValNode_id.ownerDocument.defaultView);
-  });
-
-  yield; // End of Step 5
-
-
-  // Step 6: validate that editor opened again, then test double-click inside of editor (should do nothing)
-  ok(treePanel.editingContext, "Step 6: editor session started");
-  editorVisible = editor.classList.contains("editing");
-  ok(editorVisible, "editor popup visible");
-
-  // double-click on the editor input box
-  executeSoon(function() {
-    // firing 2 clicks right in a row to simulate a double-click
-    EventUtils.synthesizeMouse(editorInput, 2, 2, {clickCount: 2}, editorInput.ownerDocument.defaultView);
-
-    // since the previous double-click is supposed to do nothing,
-    // wait a brief moment, then move on to the next step
-    executeSoon(function() {
-      doNextStep();
-    });
-  });
-
-  yield; // End of Step 6
-
-
-  // Step 7: validate that editing session is still correct, then enter a value and try a click
-  //         outside of editor (should cancel the editing session)
-  ok(treePanel.editingContext, "Step 7: editor session still going");
-  editorVisible = editor.classList.contains("editing");
-  ok(editorVisible, "editor popup still visible");
-
-  editorInput.value = "all your base are belong to us";
-
-  // single-click the `class` attribute-value node
-  executeSoon(function() {
-    EventUtils.synthesizeMouse(attrValNode_class, 2, 2, {}, attrValNode_class.ownerDocument.defaultView);
-  });
-
-  yield; // End of Step 7
-
-  // Step 8: validate that the editor was closed and that the editing was not saved
-  ok(!treePanel.editingContext, "Step 8: editor session ended");
-  editorVisible = editor.classList.contains("editing");
-  ok(!editorVisible, "editor popup hidden");
-  is(div.getAttribute("id"), "Hello World", "`id` attribute-value *not* updated");
-  is(attrValNode_id.innerHTML, "Hello World", "attribute-value node in HTML panel *not* updated");
-  executeSoon(doNextStep);
-
-  yield; // End of Step 8
-
-  // Step 9: Open the Editor and verify that closing the tree panel does not make the
-  // Inspector go cray-cray.
-  executeSoon(function() {
-    // firing 2 clicks right in a row to simulate a double-click
-    EventUtils.synthesizeMouse(attrValNode_id, 2, 2, {clickCount: 2}, attrValNode_id.ownerDocument.defaultView);
-    doNextStep();
-  });
-
-  yield; // End of Step 9
-
-  ok(treePanel.editingContext, "Step 9: editor session started");
-  editorVisible = editor.classList.contains("editing");
-  ok(editorVisible, "editor popup is visible");
-  executeSoon(function() {
-    InspectorUI.toggleHTMLPanel();
-    finishUp();
-  });
-}
-
-function finishUp() {
-  // end of all steps, so clean up
-  Services.obs.removeObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_OPENED, false);
-  Services.obs.removeObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED, false);
-  Services.obs.removeObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_SAVED, false);
-  doc = div = null;
-  InspectorUI.closeInspectorUI();
-  gBrowser.removeCurrentTab();
-  finish();
-}
-
-function test()
-{
-  waitForExplicitFinish();
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function() {
-    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
-    doc = content.document;
-    waitForFocus(setupEditorTests, content);
-  }, true);
-
-  content.location = "data:text/html,basic tests for html panel attribute-value editor";
-}
-
deleted file mode 100644
--- a/browser/devtools/highlighter/test/browser_inspector_editor_name.js
+++ /dev/null
@@ -1,253 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* ***** BEGIN LICENSE BLOCK *****
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- * ***** END LICENSE BLOCK *****
- */
-
-let doc;
-let div;
-let editorTestSteps;
-
-function doNextStep() {
-  editorTestSteps.next();
-}
-
-function setupEditorTests()
-{
-  div = doc.createElement("div");
-  div.setAttribute("id", "foobar");
-  div.setAttribute("class", "barbaz");
-  doc.body.appendChild(div);
-
-  Services.obs.addObserver(setupHTMLPanel, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
-  InspectorUI.toggleInspectorUI();
-}
-
-function setupHTMLPanel()
-{
-  Services.obs.removeObserver(setupHTMLPanel, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
-  Services.obs.addObserver(runEditorTests, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
-  InspectorUI.toggleHTMLPanel();
-}
-
-function runEditorTests()
-{
-  Services.obs.removeObserver(runEditorTests, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
-  InspectorUI.stopInspecting();
-  InspectorUI.inspectNode(doc.body, true);
-
-  // setup generator for async test steps
-  editorTestSteps = doEditorTestSteps();
-
-  // add step listeners
-  Services.obs.addObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_OPENED, false);
-  Services.obs.addObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED, false);
-  Services.obs.addObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_SAVED, false);
-
-  // start the tests
-  doNextStep();
-}
-
-function highlighterTrap()
-{
-  // bug 696107
-  InspectorUI.highlighter.removeListener("nodeselected", highlighterTrap);
-  ok(false, "Highlighter moved. Shouldn't be here!");
-  finishUp();
-}
-
-function doEditorTestSteps()
-{
-  let treePanel = InspectorUI.treePanel;
-  let editor = treePanel.treeBrowserDocument.getElementById("attribute-editor");
-  let editorInput = treePanel.treeBrowserDocument.getElementById("attribute-editor-input");
-
-  // Step 1: grab and test the attribute-name nodes in the HTML panel, then open editor
-  let nodes = treePanel.treeBrowserDocument.querySelectorAll(".nodeName.editable");
-  let attrNameNode_id = nodes[0]
-  let attrNameNode_class = nodes[1];
-
-  is(attrNameNode_id.innerHTML, "id", "Step 1: we have the correct `id` attribute-name node in the HTML panel");
-  is(attrNameNode_class.innerHTML, "class", "we have the correct `class` attribute-name node in the HTML panel");
-
-  // double-click the `id` attribute-name node to open the editor
-  executeSoon(function() {
-    // firing 2 clicks right in a row to simulate a double-click
-    EventUtils.synthesizeMouse(attrNameNode_id, 2, 2, {clickCount: 2}, attrNameNode_id.ownerDocument.defaultView);
-  });
-
-  yield; // End of Step 1
-
-
-  // Step 2: validate editing session, enter new attribute value into editor, and save input
-  ok(InspectorUI.treePanel.editingContext, "Step 2: editor session started");
-  let selection = InspectorUI.selection;
-
-  ok(selection, "Selection is: " + selection);
-
-  let editorVisible = editor.classList.contains("editing");
-  ok(editorVisible, "editor popup visible");
-
-  // check if the editor popup is "near" the correct position
-  let editorDims = editor.getBoundingClientRect();
-  let attrNameNodeDims = attrNameNode_id.getBoundingClientRect();
-  let editorPositionOK = (editorDims.left >= (attrNameNodeDims.left - editorDims.width - 5)) &&
-                          (editorDims.right <= (attrNameNodeDims.right + editorDims.width + 5)) &&
-                          (editorDims.top >= (attrNameNodeDims.top - editorDims.height - 5)) &&
-                          (editorDims.bottom <= (attrNameNodeDims.bottom + editorDims.height + 5));
-
-  ok(editorPositionOK, "editor position acceptable");
-
-  // check to make sure the attribute-value node being edited is properly highlighted
-  let attrNameNodeHighlighted = attrNameNode_id.classList.contains("editingAttributeValue");
-  ok(attrNameNodeHighlighted, "`id` attribute-name node is editor-highlighted");
-
-  is(treePanel.editingContext.repObj, div, "editor session has correct reference to div");
-  is(treePanel.editingContext.attrObj, attrNameNode_id, "editor session has correct reference to `id` attribute-name node in HTML panel");
-  is(treePanel.editingContext.attrName, "id", "editor session knows correct attribute-name");
-
-  editorInput.value = "burp";
-  editorInput.focus();
-
-  InspectorUI.highlighter.addListener("nodeselected", highlighterTrap);
-
-  // hit <enter> to save the textbox value
-  executeSoon(function() {
-    // Extra key to test that keyboard handlers have been removed. bug 696107.
-    EventUtils.synthesizeKey("VK_LEFT", {}, attrNameNode_id.ownerDocument.defaultView);
-    EventUtils.synthesizeKey("VK_RETURN", {}, attrNameNode_id.ownerDocument.defaultView);
-  });
-
-  // two `yield` statements, to trap both the "SAVED" and "CLOSED" events that will be triggered
-  yield;
-  yield; // End of Step 2
-
-  // remove this from previous step
-  InspectorUI.highlighter.removeListener("nodeselected", highlighterTrap);
-
-  // Step 3: validate that the previous editing session saved correctly, then open editor on `class` attribute value
-  ok(!treePanel.editingContext, "Step 3: editor session ended");
-  editorVisible = editor.classList.contains("editing");
-  ok(!editorVisible, "editor popup hidden");
-  attrNameNodeHighlighted = attrNameNode_id.classList.contains("editingAttributeValue");
-  ok(!attrNameNodeHighlighted, "`id` attribute-value node is no longer editor-highlighted");
-  is(div.getAttribute("burp"), "foobar", "`id` attribute-name successfully updated");
-  is(attrNameNode_id.innerHTML, "burp", "attribute-name node in HTML panel successfully updated");
-
-  // double-click the `class` attribute-value node to open the editor
-  executeSoon(function() {
-    // firing 2 clicks right in a row to simulate a double-click
-    EventUtils.synthesizeMouse(attrNameNode_class, 2, 2, {clickCount: 2}, attrNameNode_class.ownerDocument.defaultView);
-  });
-
-  yield; // End of Step 3
-
-
-  // Step 4: enter value into editor, then hit <escape> to discard it
-  ok(treePanel.editingContext, "Step 4: editor session started");
-  editorVisible = editor.classList.contains("editing");
-  ok(editorVisible, "editor popup visible");
-
-  is(treePanel.editingContext.attrObj, attrNameNode_class, "editor session has correct reference to `class` attribute-name node in HTML panel");
-  is(treePanel.editingContext.attrName, "class", "editor session knows correct attribute-name");
-
-  editorInput.value = "Hello World";
-  editorInput.focus();
-
-  // hit <escape> to discard the inputted value
-  executeSoon(function() {
-    EventUtils.synthesizeKey("VK_ESCAPE", {}, attrNameNode_class.ownerDocument.defaultView);
-  });
-
-  yield; // End of Step 4
-
-
-  // Step 5: validate that the previous editing session discarded correctly, then open editor on `id` attribute value again
-  ok(!treePanel.editingContext, "Step 5: editor session ended");
-  editorVisible = editor.classList.contains("editing");
-  ok(!editorVisible, "editor popup hidden");
-  is(div.getAttribute("class"), "barbaz", "`class` attribute-name *not* updated");
-  is(attrNameNode_class.innerHTML, "class", "attribute-name node in HTML panel *not* updated");
-
-  // double-click the `id` attribute-name node to open the editor
-  executeSoon(function() {
-    // firing 2 clicks right in a row to simulate a double-click
-    EventUtils.synthesizeMouse(attrNameNode_id, 2, 2, {clickCount: 2}, attrNameNode_id.ownerDocument.defaultView);
-  });
-
-  yield; // End of Step 5
-
-
-  // Step 6: validate that editor opened again, then test double-click inside of editor (should do nothing)
-  ok(treePanel.editingContext, "Step 6: editor session started");
-  editorVisible = editor.classList.contains("editing");
-  ok(editorVisible, "editor popup visible");
-
-  // double-click on the editor input box
-  executeSoon(function() {
-    // firing 2 clicks right in a row to simulate a double-click
-    EventUtils.synthesizeMouse(editorInput, 2, 2, {clickCount: 2}, editorInput.ownerDocument.defaultView);
-
-    // since the previous double-click is supposed to do nothing,
-    // wait a brief moment, then move on to the next step
-    executeSoon(function() {
-      doNextStep();
-    });
-  });
-
-  yield; // End of Step 6
-
-
-  // Step 7: validate that editing session is still correct, then enter a value and try a click
-  //         outside of editor (should cancel the editing session)
-  ok(treePanel.editingContext, "Step 7: editor session still going");
-  editorVisible = editor.classList.contains("editing");
-  ok(editorVisible, "editor popup still visible");
-
-  editorInput.value = "all your base are belong to us";
-
-  // single-click the `class` attribute-value node
-  executeSoon(function() {
-    EventUtils.synthesizeMouse(attrNameNode_class, 2, 2, {}, attrNameNode_class.ownerDocument.defaultView);
-  });
-
-  yield; // End of Step 7
-
-
-  // Step 8: validate that the editor was closed and that the editing was not saved
-  ok(!treePanel.editingContext, "Step 8: editor session ended");
-  editorVisible = editor.classList.contains("editing");
-  ok(!editorVisible, "editor popup hidden");
-  is(div.getAttribute("burp"), "foobar", "`id` attribute-name *not* updated");
-  is(attrNameNode_id.innerHTML, "burp", "attribute-value node in HTML panel *not* updated");
-
-  // End of Step 8
-  executeSoon(finishUp);
-}
-
-function finishUp() {
-  // end of all steps, so clean up
-  Services.obs.removeObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_OPENED, false);
-  Services.obs.removeObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_CLOSED, false);
-  Services.obs.removeObserver(doNextStep, InspectorUI.INSPECTOR_NOTIFICATIONS.EDITOR_SAVED, false);
-  doc = div = null;
-  InspectorUI.closeInspectorUI();
-  gBrowser.removeCurrentTab();
-  finish();
-}
-
-function test()
-{
-  waitForExplicitFinish();
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function() {
-    gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
-    doc = content.document;
-    waitForFocus(setupEditorTests, content);
-  }, true);
-
-  content.location = "data:text/html,basic tests for html panel attribute-value editor";
-}
-
--- a/browser/devtools/highlighter/test/browser_inspector_initialization.js
+++ b/browser/devtools/highlighter/test/browser_inspector_initialization.js
@@ -32,36 +32,33 @@ function startInspectorTests()
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
   InspectorUI.toggleInspectorUI();
 }
 
 function runInspectorTests()
 {
   Services.obs.removeObserver(runInspectorTests,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
-  Services.obs.addObserver(treePanelTests,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
 
   ok(InspectorUI.toolbar, "we have the toolbar.");
   ok(!InspectorUI.toolbar.hidden, "toolbar is visible");
   ok(InspectorUI.inspecting, "Inspector is inspecting");
-  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
+  ok(!InspectorUI.markupOpen, "Inspector Tree Panel is not open");
   ok(!InspectorUI.sidebar.visible, "Inspector sidebar should not visible.");
   ok(InspectorUI.highlighter, "Highlighter is up");
   InspectorUI.inspectNode(doc.body);
   InspectorUI.stopInspecting();
 
-  InspectorUI.treePanel.open();
+  InspectorUI.currentInspector.once("markuploaded", treePanelTests);
+  InspectorUI.currentInspector.openMarkup();
 }
 
 function treePanelTests()
 {
-  Services.obs.removeObserver(treePanelTests,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
-  ok(InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is open");
+  ok(InspectorUI.currentInspector.markupOpen, "Inspector Tree Panel is open");
 
   InspectorUI.toggleSidebar();
   ok(InspectorUI.sidebar.visible, "Inspector Sidebar should be open");
   InspectorUI.toggleSidebar();
   ok(!InspectorUI.sidebar.visible, "Inspector Sidebar should be closed");
   InspectorUI.sidebar.show();
   InspectorUI.currentInspector.once("sidebaractivated-computedview",
     stylePanelTests)
@@ -165,17 +162,16 @@ function inspectNodesFromContextTestTrap
 
 function finishInspectorTests(subject, topic, aWinIdString)
 {
   Services.obs.removeObserver(finishInspectorTests,
     InspectorUI.INSPECTOR_NOTIFICATIONS.DESTROYED);
 
   is(parseInt(aWinIdString), winId, "winId of destroyed Inspector matches");
   ok(!InspectorUI.highlighter, "Highlighter is gone");
-  ok(!InspectorUI.treePanel, "Inspector Tree Panel is closed");
   ok(!InspectorUI.inspecting, "Inspector is not inspecting");
   ok(!InspectorUI._sidebar, "Inspector Sidebar is closed");
   ok(!InspectorUI.toolbar, "toolbar is hidden");
 
   Services.obs.removeObserver(inspectNodesFromContextTestTrap, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
   gBrowser.removeCurrentTab();
   finish();
 }
rename from browser/devtools/highlighter/test/browser_inspector_treePanel_menu.js
rename to browser/devtools/highlighter/test/browser_inspector_menu.js
--- a/browser/devtools/highlighter/test/browser_inspector_treePanel_menu.js
+++ b/browser/devtools/highlighter/test/browser_inspector_menu.js
@@ -36,20 +36,19 @@ function test() {
 
   function setupTest() {
     Services.obs.addObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
     InspectorUI.toggleInspectorUI();
   }
 
   function runTests() {
     Services.obs.removeObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
-    Services.obs.addObserver(testCopyInnerMenu, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
     InspectorUI.stopInspecting();
     InspectorUI.inspectNode(node1, true);
-    InspectorUI.treePanel.open();
+    testCopyInnerMenu();
   }
 
   function testCopyInnerMenu() {
     let copyInner = document.getElementById("inspectorHTMLCopyInner");
     ok(copyInner, "the popup menu has a copy inner html menu item");
 
     waitForClipboard("This is some example text",
                      function() { copyInner.doCommand(); },
--- a/browser/devtools/highlighter/test/browser_inspector_tab_switch.js
+++ b/browser/devtools/highlighter/test/browser_inspector_tab_switch.js
@@ -22,17 +22,17 @@ function inspectorTabOpen1()
 
 function inspectorUIOpen1()
 {
   Services.obs.removeObserver(inspectorUIOpen1,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   // Make sure the inspector is open.
   ok(InspectorUI.inspecting, "Inspector is highlighting");
-  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
+  ok(!InspectorUI.currentInspector.markupOpen, "Inspector Tree Panel is not open");
   ok(!InspectorUI.sidebar.visible, "Inspector Sidebar is not open");
   ok(!InspectorUI.store.isEmpty(), "InspectorUI.store is not empty");
   is(InspectorUI.store.length, 1, "Inspector.store.length = 1");
 
   // Highlight a node.
   div = content.document.getElementsByTagName("div")[0];
   InspectorUI.inspectNode(div);
   is(InspectorUI.selection, div, "selection matches the div element");
@@ -49,17 +49,16 @@ function inspectorUIOpen1()
 
   content.location = "data:text/html,<p>tab 2: the inspector should close now";
 }
 
 function inspectorTabOpen2()
 {
   // Make sure the inspector is closed.
   ok(!InspectorUI.inspecting, "Inspector is not highlighting");
-  ok(!InspectorUI.treePanel, "Inspector Tree Panel is closed");
   is(InspectorUI.store.length, 1, "Inspector.store.length = 1");
 
   // Activate the inspector again.
   executeSoon(function() {
     Services.obs.addObserver(inspectorUIOpen2,
       InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
     clearUserPrefs();
     InspectorUI.openInspectorUI();
@@ -68,17 +67,17 @@ function inspectorTabOpen2()
 
 function inspectorUIOpen2()
 {
   Services.obs.removeObserver(inspectorUIOpen2,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   // Make sure the inspector is open.
   ok(InspectorUI.inspecting, "Inspector is highlighting");
-  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
+  ok(!InspectorUI.currentInspector.markupOpen, "Inspector Tree Panel is not open");
   is(InspectorUI.store.length, 2, "Inspector.store.length = 2");
 
   // Disable highlighting.
   InspectorUI.toggleInspection();
   ok(!InspectorUI.inspecting, "Inspector is not highlighting");
 
 
   // Switch back to tab 1.
@@ -91,33 +90,28 @@ function inspectorUIOpen2()
 
 function inspectorFocusTab1()
 {
   Services.obs.removeObserver(inspectorFocusTab1,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   // Make sure the inspector is still open.
   ok(InspectorUI.inspecting, "Inspector is highlighting");
-  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
+  ok(!InspectorUI.currentInspector.markupOpen, "Inspector Tree Panel is not open");
   is(InspectorUI.store.length, 2, "Inspector.store.length = 2");
   is(InspectorUI.selection, div, "selection matches the div element");
 
-  Services.obs.addObserver(inspectorOpenTreePanelTab1,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
-
+  InspectorUI.currentInspector.once("markuploaded", inspectorOpenTreePanelTab1);
   InspectorUI.toggleHTMLPanel();
 }
 
 function inspectorOpenTreePanelTab1()
 {
-  Services.obs.removeObserver(inspectorOpenTreePanelTab1,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
-
   ok(InspectorUI.inspecting, "Inspector is highlighting");
-  ok(InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is open");
+  ok(InspectorUI.currentInspector.markupOpen, "Inspector Tree Panel is open");
   is(InspectorUI.store.length, 2, "Inspector.store.length = 2");
   is(InspectorUI.selection, div, "selection matches the div element");
 
   InspectorUI.currentInspector.once("sidebaractivated-computedview",
     inspectorSidebarStyleView1);
 
   executeSoon(function() {
     InspectorUI.sidebar.show();
@@ -145,43 +139,48 @@ function inspectorSidebarStyleView1()
 
 function inspectorFocusTab2()
 {
   Services.obs.removeObserver(inspectorFocusTab2,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   // Make sure the inspector is still open.
   ok(!InspectorUI.inspecting, "Inspector is not highlighting");
-  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
+  ok(!InspectorUI.currentInspector.markupOpen, "Inspector Tree Panel is not open");
   ok(!InspectorUI.sidebar.visible, "Inspector Sidebar is not open");
   is(InspectorUI.store.length, 2, "Inspector.store.length is 2");
   isnot(InspectorUI.selection, div, "selection does not match the div element");
 
 
   executeSoon(function() {
     // Make sure keybindings still work
     synthesizeKeyFromKeyTag("key_inspect");
 
     ok(InspectorUI.inspecting, "Inspector is highlighting");
     InspectorUI.toggleInspection();
 
     // Switch back to tab 1.
     Services.obs.addObserver(inspectorSecondFocusTab1,
-      InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
+      InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
     gBrowser.selectedTab = tab1;
   });
 }
 
 function inspectorSecondFocusTab1()
 {
   Services.obs.removeObserver(inspectorSecondFocusTab1,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
+    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
+  InspectorUI.currentInspector.once("sidebaractivated-computedview",
+    inspectorSecondFocusTabSidebarLoaded);
+}
 
+function inspectorSecondFocusTabSidebarLoaded()
+{
   ok(InspectorUI.inspecting, "Inspector is highlighting");
-  ok(InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is open");
+  ok(InspectorUI.currentInspector.markupOpen, "Inspector Tree Panel is open");
   is(InspectorUI.store.length, 2, "Inspector.store.length = 2");
   is(InspectorUI.selection, div, "selection matches the div element");
 
   ok(InspectorUI.sidebar.visible, "Inspector Sidebar is open");
   ok(computedView(), "Inspector Has a Style Panel Instance");
   InspectorUI.sidebar._toolObjects().forEach(function(aTool) {
     let btn = aTool.button;
     is(btn.hasAttribute("checked"),
@@ -197,17 +196,17 @@ function inspectorSecondFocusTab1()
 
 function inspectorSecondFocusTab2()
 {
   Services.obs.removeObserver(inspectorSecondFocusTab2,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
 
   // Make sure the inspector is still open.
   ok(!InspectorUI.inspecting, "Inspector is not highlighting");
-  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
+  ok(!InspectorUI.currentInspector.markupOpen, "Inspector Tree Panel is not open");
   ok(!InspectorUI.isSidebarOpen, "Inspector Sidebar is not open");
 
   is(InspectorUI.store.length, 2, "Inspector.store.length is 2");
   isnot(InspectorUI.selection, div, "selection does not match the div element");
 
   // Remove tab 1.
   tab1window = gBrowser.getBrowserForTab(tab1).contentWindow;
   tab1window.addEventListener("pagehide", inspectorTabUnload1, false);
@@ -216,17 +215,17 @@ function inspectorSecondFocusTab2()
 
 function inspectorTabUnload1(evt)
 {
   tab1window.removeEventListener(evt.type, arguments.callee, false);
   tab1window = tab1 = tab2 = div = null;
 
   // Make sure the Inspector is still open and that the state is correct.
   ok(!InspectorUI.inspecting, "Inspector is not highlighting");
-  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
+  ok(!InspectorUI.currentInspector.markupOpen, "Inspector Tree Panel is not open");
   is(InspectorUI.store.length, 1, "Inspector.store.length = 1");
 
   InspectorUI.closeInspectorUI();
   gBrowser.removeCurrentTab();
   finish();
 }
 
 function test()
deleted file mode 100644
--- a/browser/devtools/highlighter/test/browser_inspector_treePanel_click.js
+++ /dev/null
@@ -1,62 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-http://creativecommons.org/publicdomain/zero/1.0/ */
-
-
-function test() {
-
-  waitForExplicitFinish();
-
-  let doc;
-  let node1;
-  let node2;
-
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function onload() {
-    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
-    doc = content.document;
-    waitForFocus(setupTest, content);
-  }, true);
-
-  content.location = 'data:text/html,<div style="width: 200px; height: 200px"><p></p></div>';
-
-  function setupTest() {
-    node1 = doc.querySelector("div");
-    node2 = doc.querySelector("p");
-    Services.obs.addObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
-    InspectorUI.toggleInspectorUI();
-  }
-
-  function runTests() {
-    Services.obs.removeObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
-    Services.obs.addObserver(testNode1, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
-    InspectorUI.select(node1, true, true, true);
-    InspectorUI.openTreePanel();
-  }
-
-  function testNode1() {
-    Services.obs.removeObserver(testNode1, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
-    is(InspectorUI.selection, node1, "selection matches node");
-    is(getHighlitNode(), node1, "selection matches node");
-    testNode2();
-  }
-
-  function testNode2() {
-    InspectorUI.highlighter.addListener("nodeselected", testHighlightingNode2);
-    InspectorUI.treePanelSelect("node2");
-  }
-
-  function testHighlightingNode2() {
-    InspectorUI.highlighter.removeListener("nodeselected", testHighlightingNode2);
-    is(InspectorUI.selection, node2, "selection matches node");
-    is(getHighlitNode(), node2, "selection matches node");
-    Services.obs.addObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
-    InspectorUI.closeInspectorUI();
-  }
-
-  function finishUp() {
-    Services.obs.removeObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
-    doc = node1 = node2 = null;
-    gBrowser.removeCurrentTab();
-    finish();
-  }
-}
deleted file mode 100644
--- a/browser/devtools/highlighter/test/browser_inspector_treePanel_input.html
+++ /dev/null
@@ -1,33 +0,0 @@
-<!DOCTYPE html>
-<html xml:lang="en">
-  <head>
-    <meta charset="utf-8">
-    <title>Inspector tree panel test</title>
-    <style type="text/css"><!--
-      #duplicate { color: green }
-    --></style>
-    <script type="text/javascript"><!--
-      function fooBarBaz(arg1) {
-        return true; // do nothing
-      }
-    // --></script>
-  </head>
-  <body arbitrary:attribute="value">
-    <p>Inspector tree panel test.</p>
-
-    <div id="foo" class="foo bar baz" style="border:1px solid red;
-      unknownProperty: unknownValue; color: withUnkownValue">
-      <unknownTag unknownAttribute="fooBar">
-        <p unknownAttribute="fooBar" data-test1="value">hello world!</p>
-      </unknownTag>
-    </div>
-
-    <div id="duplicate" id="duplicate" id="different" class="test" class="foo"
-      fooBar="baz" fooBar="bazbaz">test</div>
-
-    <iframe src="data:text/html,&lt;div&gt;hello from an iframe!&lt;/div&gt;">no
-      frames!</iframe>
-
-    <!-- hello world from a comment! -->
-  </body>
-</html>
deleted file mode 100644
--- a/browser/devtools/highlighter/test/browser_inspector_treePanel_output.js
+++ /dev/null
@@ -1,102 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-let doc = null;
-let xhr = null;
-let expectedResult = "";
-
-const TEST_URI = "http://mochi.test:8888/browser/browser/devtools/highlighter/test/browser_inspector_treePanel_input.html";
-const RESULT_URI = "http://mochi.test:8888/browser/browser/devtools/highlighter/test/browser_inspector_treePanel_result.html";
-
-function tabFocused()
-{
-  xhr = new XMLHttpRequest();
-  xhr.onreadystatechange = xhr_onReadyStateChange;
-  xhr.open("GET", RESULT_URI, true);
-  xhr.send(null);
-}
-
-function xhr_onReadyStateChange() {
-  if (xhr.readyState != 4) {
-    return;
-  }
-
-  is(xhr.status, 200, "xhr.status is 200");
-  ok(!!xhr.responseText, "xhr.responseText is available");
-  expectedResult = xhr.responseText.replace(/^\s+|\s+$/mg, '');
-  xhr = null;
-
-  Services.obs.addObserver(inspectorOpened,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
-  InspectorUI.openInspectorUI();
-}
-
-function inspectorOpened()
-{
-  Services.obs.removeObserver(inspectorOpened,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
-
-  Services.obs.addObserver(treePanelOpened, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
-  InspectorUI.treePanel.open();
-}
-
-function treePanelOpened()
-{
-  Services.obs.removeObserver(treePanelOpened,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
-
-  ok(InspectorUI.inspecting, "Inspector is highlighting");
-  ok(InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is open");
-  InspectorUI.stopInspecting();
-  ok(!InspectorUI.inspecting, "Inspector is not highlighting");
-
-  let elements = doc.querySelectorAll("meta, script, style, p[unknownAttribute]");
-  for (let i = 0; i < elements.length; i++) {
-    InspectorUI.inspectNode(elements[i]);
-  }
-
-  let iframe = doc.querySelector("iframe");
-  ok(iframe, "Found the iframe tag");
-  ok(iframe.contentDocument, "Found the iframe.contentDocument");
-
-  let iframeDiv = iframe.contentDocument.querySelector("div");
-  ok(iframeDiv, "Found the div element inside the iframe");
-  InspectorUI.inspectNode(iframeDiv);
-
-  ok(InspectorUI.treePanel.treePanelDiv, "InspectorUI.treePanelDiv is available");
-  is(InspectorUI.treePanel.treePanelDiv.innerHTML.replace(/^\s+|\s+$/mg, ''),
-    expectedResult, "treePanelDiv.innerHTML is correct");
-  expectedResult = null;
-
-  Services.obs.addObserver(inspectorClosed,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
-  InspectorUI.closeInspectorUI();
-}
-
-function inspectorClosed()
-{
-  Services.obs.removeObserver(inspectorClosed,
-    InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
-
-  ok(!InspectorUI.inspecting, "Inspector is not highlighting");
-  ok(!InspectorUI.treePanel, "Inspector Tree Panel is not open");
-
-  gBrowser.removeCurrentTab();
-  finish();
-}
-
-function test()
-{
-  waitForExplicitFinish();
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function(evt) {
-    gBrowser.selectedBrowser.removeEventListener(evt.type, arguments.callee, true);
-    doc = content.document;
-    waitForFocus(tabFocused, content);
-  }, true);
-
-  content.location = TEST_URI;
-}
deleted file mode 100644
--- a/browser/devtools/highlighter/test/browser_inspector_treePanel_result.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<div role="presentation" class="nodeBox htmlNodeBox containerNodeBox  repIgnore open"><div class="docType ">&lt;!DOCTYPE html&gt;</div><div role="presentation" class="nodeLabel "><img role="presentation" class="twisty "><span role="treeitem" aria-expanded="true" class="nodeLabelBox repTarget ">&lt;<span class="nodeTag ">html</span><span class="nodeAttr editGroup ">&nbsp;<span class="nodeName editable ">xml:lang</span>="<span data-attributename="xml:lang" class="nodeValue editable ">en</span>"</span><span class="nodeBracket editable insertBefore ">&gt;</span></span></div><div role="group" class="nodeChildBox "><div role="presentation" class="nodeBox containerNodeBox  repIgnore open"><div role="presentation" class="nodeLabel "><img role="presentation" class="twisty "><span role="treeitem" aria-expanded="true" class="nodeLabelBox repTarget ">&lt;<span class="nodeTag ">head</span><span class="nodeBracket editable insertBefore ">&gt;</span></span></div><div role="group" class="nodeChildBox "><div role="presentation" class="nodeBox emptyNodeBox  repIgnore"><div role="presentation" class="nodeLabel "><span role="treeitem" class="nodeLabelBox repTarget ">&lt;<span class="nodeTag ">meta</span><span class="nodeAttr editGroup ">&nbsp;<span class="nodeName editable ">charset</span>="<span data-attributename="charset" class="nodeValue editable ">utf-8</span>"</span><span class="nodeBracket editable insertBefore ">&gt;</span></span></div></div><div role="presentation" class="nodeBox textNodeBox  repIgnore "><div role="presentation" class="nodeLabel "><span role="treeitem" class="nodeLabelBox repTarget ">&lt;<span class="nodeTag ">title</span><span class="nodeBracket editable insertBefore ">&gt;</span><span class="nodeText editable "><span class="  ">Inspector tree panel test</span></span>&lt;/<span class="nodeTag ">title</span>&gt;</span></div></div><div role="presentation" class="nodeBox containerNodeBox  repIgnore open"><div role="presentation" class="nodeLabel "><img role="presentation" class="twisty "><span role="treeitem" aria-expanded="true" class="nodeLabelBox repTarget ">&lt;<span class="nodeTag ">style</span><span class="nodeAttr editGroup ">&nbsp;<span class="nodeName editable ">type</span>="<span data-attributename="type" class="nodeValue editable ">text/css</span>"</span><span class="nodeBracket editable insertBefore ">&gt;</span></span></div><div role="group" class="nodeChildBox "><div role="presentation" class="nodeBox "><span class="nodeText editable "><span class="  ">&lt;!--
-#duplicate { color: green }
---&gt;</span></span></div></div><div role="presentation" class="nodeCloseLabel "><span class="nodeCloseLabelBox repTarget ">&lt;/<span class="nodeTag ">style</span>&gt;</span></div></div><div role="presentation" class="nodeBox containerNodeBox  repIgnore open"><div role="presentation" class="nodeLabel "><img role="presentation" class="twisty "><span role="treeitem" aria-expanded="true" class="nodeLabelBox repTarget ">&lt;<span class="nodeTag ">script</span><span class="nodeAttr editGroup ">&nbsp;<span class="nodeName editable ">type</span>="<span data-attributename="type" class="nodeValue editable ">text/javascript</span>"</span><span class="nodeBracket editable insertBefore ">&gt;</span></span></div><div role="group" class="nodeChildBox "><div role="presentation" class="nodeBox "><span class="nodeText editable "><span class="  ">&lt;!--
-function fooBarBaz(arg1) {
-return true; // do nothing
-}
-// --&gt;</span></span></div></div><div role="presentation" class="nodeCloseLabel "><span class="nodeCloseLabelBox repTarget ">&lt;/<span class="nodeTag ">script</span>&gt;</span></div></div></div><div role="presentation" class="nodeCloseLabel "><span class="nodeCloseLabelBox repTarget ">&lt;/<span class="nodeTag ">head</span>&gt;</span></div></div><div role="presentation" class="nodeBox containerNodeBox  repIgnore open"><div role="presentation" class="nodeLabel "><img role="presentation" class="twisty "><span role="treeitem" aria-expanded="true" class="nodeLabelBox repTarget ">&lt;<span class="nodeTag ">body</span><span class="nodeAttr editGroup ">&nbsp;<span class="nodeName editable ">arbitrary:attribute</span>="<span data-attributename="arbitrary:attribute" class="nodeValue editable ">value</span>"</span><span class="nodeBracket editable insertBefore ">&gt;</span></span></div><div role="group" class="nodeChildBox "><div role="presentation" class="nodeBox textNodeBox  repIgnore "><div role="presentation" class="nodeLabel "><span role="treeitem" class="nodeLabelBox repTarget ">&lt;<span class="nodeTag ">p</span><span class="nodeBracket editable insertBefore ">&gt;</span><span class="nodeText editable "><span class="  ">Inspector tree panel test.</span></span>&lt;/<span class="nodeTag ">p</span>&gt;</span></div></div><div role="presentation" class="nodeBox containerNodeBox  repIgnore open"><div role="presentation" class="nodeLabel "><img role="presentation" class="twisty "><span role="treeitem" aria-expanded="true" class="nodeLabelBox repTarget ">&lt;<span class="nodeTag ">div</span><span class="nodeAttr editGroup ">&nbsp;<span class="nodeName editable ">id</span>="<span data-attributename="id" class="nodeValue editable ">foo</span>"</span><span class="nodeAttr editGroup ">&nbsp;<span class="nodeName editable ">class</span>="<span data-attributename="class" class="nodeValue editable ">foo bar baz</span>"</span><span class="nodeAttr editGroup ">&nbsp;<span class="nodeName editable ">style</span>="<span data-attributename="style" class="nodeValue editable ">border:1px solid red;
-unknownProperty: unknownValue; color: withUnkownValue</span>"</span><span class="nodeBracket editable insertBefore ">&gt;</span></span></div><div role="group" class="nodeChildBox "><div role="presentation" class="nodeBox containerNodeBox  repIgnore open"><div role="presentation" class="nodeLabel "><img role="presentation" class="twisty "><span role="treeitem" aria-expanded="true" class="nodeLabelBox repTarget ">&lt;<span class="nodeTag ">unknowntag</span><span class="nodeAttr editGroup ">&nbsp;<span class="nodeName editable ">unknownattribute</span>="<span data-attributename="unknownattribute" class="nodeValue editable ">fooBar</span>"</span><span class="nodeBracket editable insertBefore ">&gt;</span></span></div><div role="group" class="nodeChildBox "><div role="presentation" class="nodeBox textNodeBox  repIgnore"><div role="presentation" class="nodeLabel "><span role="treeitem" class="nodeLabelBox repTarget ">&lt;<span class="nodeTag ">p</span><span class="nodeAttr editGroup ">&nbsp;<span class="nodeName editable ">data-test1</span>="<span data-attributename="data-test1" class="nodeValue editable ">value</span>"</span><span class="nodeAttr editGroup ">&nbsp;<span class="nodeName editable ">unknownattribute</span>="<span data-attributename="unknownattribute" class="nodeValue editable ">fooBar</span>"</span><span class="nodeBracket editable insertBefore ">&gt;</span><span class="nodeText editable "><span class="  ">hello world!</span></span>&lt;/<span class="nodeTag ">p</span>&gt;</span></div></div></div><div role="presentation" class="nodeCloseLabel "><span class="nodeCloseLabelBox repTarget ">&lt;/<span class="nodeTag ">unknowntag</span>&gt;</span></div></div></div><div role="presentation" class="nodeCloseLabel "><span class="nodeCloseLabelBox repTarget ">&lt;/<span class="nodeTag ">div</span>&gt;</span></div></div><div role="presentation" class="nodeBox textNodeBox  repIgnore "><div role="presentation" class="nodeLabel "><span role="treeitem" class="nodeLabelBox repTarget ">&lt;<span class="nodeTag ">div</span><span class="nodeAttr editGroup ">&nbsp;<span class="nodeName editable ">id</span>="<span data-attributename="id" class="nodeValue editable ">duplicate</span>"</span><span class="nodeAttr editGroup ">&nbsp;<span class="nodeName editable ">class</span>="<span data-attributename="class" class="nodeValue editable ">test</span>"</span><span class="nodeAttr editGroup ">&nbsp;<span class="nodeName editable ">foobar</span>="<span data-attributename="foobar" class="nodeValue editable ">baz</span>"</span><span class="nodeBracket editable insertBefore ">&gt;</span><span class="nodeText editable "><span class="  ">test</span></span>&lt;/<span class="nodeTag ">div</span>&gt;</span></div></div><div role="presentation" class="nodeBox containerNodeBox  repIgnore open"><div role="presentation" class="nodeLabel "><img role="presentation" class="twisty "><span role="treeitem" aria-expanded="true" class="nodeLabelBox repTarget ">&lt;<span class="nodeTag ">iframe</span><span class="nodeAttr editGroup ">&nbsp;<span class="nodeName editable ">src</span>="<span data-attributename="src" class="nodeValue editable ">data:text/html,&lt;div&gt;hello from an iframe!&lt;/div&gt;</span>"</span><span class="nodeBracket editable insertBefore ">&gt;</span></span></div><div role="group" class="nodeChildBox "><div role="presentation" class="nodeBox containerNodeBox  repIgnore open"><div role="presentation" class="nodeLabel "><img role="presentation" class="twisty "><span role="treeitem" aria-expanded="true" class="nodeLabelBox repTarget ">&lt;<span class="nodeTag ">html</span><span class="nodeBracket editable insertBefore ">&gt;</span></span></div><div role="group" class="nodeChildBox "><div role="presentation" class="nodeBox textNodeBox  repIgnore "><div role="presentation" class="nodeLabel "><span role="treeitem" class="nodeLabelBox repTarget ">&lt;<span class="nodeTag ">head</span><span class="nodeBracket editable insertBefore ">&gt;</span><span class="nodeText editable "><span class="  "></span></span>&lt;/<span class="nodeTag ">head</span>&gt;</span></div></div><div role="presentation" class="nodeBox containerNodeBox  repIgnore open"><div role="presentation" class="nodeLabel "><img role="presentation" class="twisty "><span role="treeitem" aria-expanded="true" class="nodeLabelBox repTarget ">&lt;<span class="nodeTag ">body</span><span class="nodeBracket editable insertBefore ">&gt;</span></span></div><div role="group" class="nodeChildBox "><div role="presentation" class="nodeBox textNodeBox  repIgnore selected"><div role="presentation" class="nodeLabel "><span role="treeitem" class="nodeLabelBox repTarget ">&lt;<span class="nodeTag ">div</span><span class="nodeBracket editable insertBefore ">&gt;</span><span class="nodeText editable "><span class="  ">hello from an iframe!</span></span>&lt;/<span class="nodeTag ">div</span>&gt;</span></div></div></div><div role="presentation" class="nodeCloseLabel "><span class="nodeCloseLabelBox repTarget ">&lt;/<span class="nodeTag ">body</span>&gt;</span></div></div></div><div role="presentation" class="nodeCloseLabel "><span class="nodeCloseLabelBox repTarget ">&lt;/<span class="nodeTag ">html</span>&gt;</span></div></div></div><div role="presentation" class="nodeCloseLabel "><span class="nodeCloseLabelBox repTarget ">&lt;/<span class="nodeTag ">iframe</span>&gt;</span></div></div><div role="presentation" class="nodeBox nodeComment ">&lt;!--<span class="nodeComment editable "> hello world from a comment! </span>--&gt;</div></div><div role="presentation" class="nodeCloseLabel "><span class="nodeCloseLabelBox repTarget ">&lt;/<span class="nodeTag ">body</span>&gt;</span></div></div></div><div role="presentation" class="nodeCloseLabel "><span class="nodeCloseLabelBox repTarget ">&lt;/<span class="nodeTag ">html</span>&gt;</span></div></div>
\ No newline at end of file
--- a/browser/devtools/jar.mn
+++ b/browser/devtools/jar.mn
@@ -1,14 +1,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 browser.jar:
     content/browser/inspector.html                (highlighter/inspector.html)
+    content/browser/devtools/markup-view.xhtml    (markupview/markup-view.xhtml)
+    content/browser/devtools/markup-view.css      (markupview/markup-view.css)
     content/browser/NetworkPanel.xhtml            (webconsole/NetworkPanel.xhtml)
     content/browser/devtools/HUDService-content.js (webconsole/HUDService-content.js)
     content/browser/devtools/webconsole.js        (webconsole/webconsole.js)
 *   content/browser/devtools/webconsole.xul       (webconsole/webconsole.xul)
 *   content/browser/scratchpad.xul                (scratchpad/scratchpad.xul)
     content/browser/scratchpad.js                 (scratchpad/scratchpad.js)
     content/browser/splitview.css                 (shared/splitview.css)
     content/browser/styleeditor.xul               (styleeditor/styleeditor.xul)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/Makefile.in
@@ -0,0 +1,18 @@
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH		= @DEPTH@
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+TEST_DIRS += test
+
+libs::
+	$(NSINSTALL) $(srcdir)/*.jsm $(FINAL_TARGET)/modules/devtools
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/MarkupView.jsm
@@ -0,0 +1,1115 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * Followup bugs to be filed:
+ * - Drag and drop should be implemented.
+ * - Node menu should be implemented.
+ * - editableField could be moved to a shared location.
+ * - I'm willing to consider that judicious use of DOMTemplater could make this
+ *   code easier to maintain.
+ * - ScrollIntoViewIfNeeded seems jumpy, that should be fixed.
+ */
+
+const Cc = Components.classes;
+const Cu = Components.utils;
+const Ci = Components.interfaces;
+
+// Page size for pageup/pagedown
+const PAGE_SIZE = 10;
+
+var EXPORTED_SYMBOLS = ["MarkupView"];
+
+Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
+Cu.import("resource:///modules/devtools/CssRuleView.jsm");
+Cu.import("resource:///modules/devtools/Templater.jsm");
+Cu.import("resource:///modules/devtools/Undo.jsm")
+
+/**
+ * Vocabulary for the purposes of this file:
+ *
+ * MarkupContainer - the structure that holds an editor and its
+ *  immediate children in the markup panel.
+ * Node - A content node.
+ * object.elt - A UI element in the markup panel.
+ */
+
+/**
+ * The markup tree.  Manages the mapping of nodes to MarkupContainers,
+ * updating based on mutations, and the undo/redo bindings.
+ *
+ * @param Inspector aInspector
+ *        The inspector we're watching.
+ * @param iframe aFrame
+ *        An iframe in which the caller has kindly loaded markup-view.xhtml.
+ */
+function MarkupView(aInspector, aFrame)
+{
+  this._inspector = aInspector;
+  this._frame = aFrame;
+  this.doc = this._frame.contentDocument;
+  this._elt = this.doc.querySelector("#root");
+
+  this.undo = new UndoStack();
+  this.undo.installController(this._frame.ownerDocument.defaultView);
+
+  this._containers = new WeakMap();
+
+  this._observer = new this.doc.defaultView.MutationObserver(this._mutationObserver.bind(this));
+
+  this._boundSelect = this._onSelect.bind(this);
+  this._inspector.on("select", this._boundSelect);
+  this._onSelect();
+
+  this._boundKeyDown = this._onKeyDown.bind(this);
+  this._frame.addEventListener("keydown", this._boundKeyDown, false);
+
+  this._boundFocus = this._onFocus.bind(this);
+  this._frame.addEventListener("focus", this._boundFocus, false);
+
+  this._onSelect();
+}
+
+MarkupView.prototype = {
+  _selectedContainer: null,
+
+  /**
+   * Return the selected node.
+   */
+  get selected() {
+    return this._selectedContainer ? this._selectedContainer.node : null;
+  },
+
+  template: function MT_template(aName, aDest, aOptions)
+  {
+    let node = this.doc.getElementById("template-" + aName).cloneNode(true);
+    node.removeAttribute("id");
+    template(node, aDest, aOptions);
+    return node;
+  },
+
+  /**
+   * Get the MarkupContainer object for a given node, or undefined if
+   * none exists.
+   */
+  getContainer: function MT_getContainer(aNode)
+  {
+    return this._containers.get(aNode);
+  },
+
+  /**
+   * Highlight the given element in the markup panel.
+   */
+  _onSelect: function MT__onSelect()
+  {
+    if (this._inspector.selection) {
+      this.showNode(this._inspector.selection);
+    }
+    this.selectNode(this._inspector.selection);
+  },
+
+  /**
+   * Create a TreeWalker to find the next/previous
+   * node for selection.
+   */
+  _selectionWalker: function MT__seletionWalker(aStart)
+  {
+    let walker = this.doc.createTreeWalker(
+      aStart || this._elt,
+      Ci.nsIDOMNodeFilter.SHOW_ELEMENT,
+      function(aElement) {
+        if (aElement.container && aElement.container.visible) {
+          return Ci.nsIDOMNodeFilter.FILTER_ACCEPT;
+        }
+        return Ci.nsIDOMNodeFilter.FILTER_SKIP;
+      },
+      false
+    );
+    walker.currentNode = this._selectedContainer.elt;
+    return walker;
+  },
+
+  /**
+   * Key handling.
+   */
+  _onKeyDown: function MT__KeyDown(aEvent)
+  {
+    let handled = true;
+
+    // Ignore keystrokes that originated in editors.
+    if (aEvent.target.tagName.toLowerCase() === "input" ||
+        aEvent.target.tagName.toLowerCase() === "textarea") {
+      return;
+    }
+
+    switch(aEvent.keyCode) {
+      case Ci.nsIDOMKeyEvent.DOM_VK_DELETE:
+      case Ci.nsIDOMKeyEvent.DOM_VK_BACK_SPACE:
+        let node = this._selectedContainer.node;
+        let doc = nodeDocument(node);
+        if (node != doc && node != doc.documentElement) {
+          this.deleteNode(this._selectedContainer.node);
+        }
+        break;
+      case Ci.nsIDOMKeyEvent.DOM_VK_HOME:
+        this.navigate(this._containers.get(this._rootNode.firstChild));
+        break;
+      case Ci.nsIDOMKeyEvent.DOM_VK_LEFT:
+        this.collapseNode(this._selectedContainer.node);
+        break;
+      case Ci.nsIDOMKeyEvent.DOM_VK_RIGHT:
+        this.expandNode(this._selectedContainer.node);
+        break;
+      case Ci.nsIDOMKeyEvent.DOM_VK_UP:
+        let prev = this._selectionWalker().previousNode();
+        if (prev) {
+          this.navigate(prev.container);
+        }
+        break;
+      case Ci.nsIDOMKeyEvent.DOM_VK_DOWN:
+        let next = this._selectionWalker().nextNode();
+        if (next) {
+          this.navigate(next.container);
+        }
+        break;
+      case Ci.nsIDOMKeyEvent.DOM_VK_PAGE_UP: {
+        let walker = this._selectionWalker();
+        let selection = this._selectedContainer;
+        for (let i = 0; i < PAGE_SIZE; i++) {
+          let prev = walker.previousNode();
+          if (!prev) {
+            break;
+          }
+          selection = prev.container;
+        }
+        this.navigate(selection);
+        break;
+      }
+      case Ci.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN: {
+        let walker = this._selectionWalker();
+        let selection = this._selectedContainer;
+        for (let i = 0; i < PAGE_SIZE; i++) {
+          let next = walker.nextNode();
+          if (!next) {
+            break;
+          }
+          selection = next.container;
+        }
+        this.navigate(selection);
+        break;
+      }
+      default:
+        handled = false;
+    }
+    if (handled) {
+      aEvent.stopPropagation();
+      aEvent.preventDefault();
+    }
+  },
+
+  /**
+   * Delete a node from the DOM.
+   * This is an undoable action.
+   */
+  deleteNode: function MC__deleteNode(aNode)
+  {
+    let parentNode = aNode.parentNode;
+    let sibling = aNode.nextSibling;
+
+    this.undo.do(function() {
+      parentNode.removeChild(aNode);
+    }, function() {
+      parentNode.insertBefore(aNode, sibling);
+    });
+  },
+
+  /**
+   * If an editable item is focused, select its container.
+   */
+  _onFocus: function MC__onFocus(aEvent) {
+    let parent = aEvent.target;
+    while (!parent.container) {
+      parent = parent.parentNode;
+    }
+    if (parent) {
+      this.navigate(parent.container, true);
+    }
+  },
+
+  /**
+   * Handle a user-requested navigation to a given MarkupContainer,
+   * updating the inspector's currently-selected node.
+   *
+   * @param MarkupContainer aContainer
+   *        The container we're navigating to.
+   * @param aIgnoreFocus aIgnoreFocus
+   *        If falsy, keyboard focus will be moved to the container too.
+   */
+  navigate: function MT__navigate(aContainer, aIgnoreFocus)
+  {
+    if (!aContainer) {
+      return;
+    }
+
+    let node = aContainer.node;
+    this.showNode(node);
+    this.selectNode(node);
+
+    if (this._inspector._IUI.highlighter.isNodeHighlightable(node)) {
+      this._inspector._IUI.select(node, true, false, "treepanel");
+      this._inspector._IUI.highlighter.highlight(node);
+    }
+
+    if (!aIgnoreFocus) {
+      aContainer.focus();
+    }
+  },
+
+  /**
+   * Make sure a node is included in the markup tool.
+   *
+   * @param DOMNode aNode
+   *        The node in the content document.
+   *
+   * @returns MarkupContainer The MarkupContainer object for this element.
+   */
+  importNode: function MT_importNode(aNode, aExpand)
+  {
+    if (!aNode) {
+      return null;
+    }
+
+    if (this._containers.has(aNode)) {
+      return this._containers.get(aNode);
+    }
+
+    this._observer.observe(aNode, {
+      attributes: true,
+      childList: true,
+      characterData: true,
+    });
+
+    let walker = documentWalker(aNode);
+    let parent = walker.parentNode();
+    if (parent) {
+      // Make sure parents of this node are imported too.
+      var container = new MarkupContainer(this, aNode);
+    } else {
+      var container = new RootContainer(this, aNode);
+      this._elt.appendChild(container.elt);
+      this._rootNode = aNode;
+      aNode.addEventListener("load", function MP_watch_contentLoaded(aEvent) {
+        // Fake a childList mutation here.
+        this._mutationObserver([{target: aEvent.target, type: "childList"}]);
+      }.bind(this), true);
+
+    }
+
+    this._containers.set(aNode, container);
+    container.expanded = aExpand;
+
+    this._updateChildren(container);
+
+    if (parent) {
+      this.importNode(parent, true);
+    }
+    return container;
+  },
+
+  /**
+   * Mutation observer used for included nodes.
+   */
+  _mutationObserver: function MT__mutationObserver(aMutations)
+  {
+    for (let mutation of aMutations) {
+      let container = this._containers.get(mutation.target);
+      if (mutation.type === "attributes" || mutation.type === "characterData") {
+        container.update();
+      } else if (mutation.type === "childList") {
+        this._updateChildren(container);
+      }
+    }
+    this._inspector._emit("markupmutation");
+  },
+
+  /**
+   * Make sure the given node's parents are expanded and the
+   * node is scrolled on to screen.
+   */
+  showNode: function MT_showNode(aNode)
+  {
+    this.importNode(aNode);
+    let walker = documentWalker(aNode);
+    let parent;
+    while (parent = walker.parentNode()) {
+      this.expandNode(parent);
+    }
+//    LayoutHelpers.scrollIntoViewIfNeeded(this._containers.get(aNode).elt, false);
+  },
+
+  /**
+   * Expand the container's children.
+   */
+  _expandContainer: function MT__expandContainer(aContainer)
+  {
+    if (aContainer.hasChildren && !aContainer.expanded) {
+      aContainer.expanded = true;
+      this._updateChildren(aContainer);
+    }
+  },
+
+  /**
+   * Expand the node's children.
+   */
+  expandNode: function MT_expandNode(aNode)
+  {
+    let container = this._containers.get(aNode);
+    this._expandContainer(container);
+  },
+
+  /**
+   * Expand the entire tree beneath a container.
+   *
+   * @param aContainer The container to expand.
+   */
+  _expandAll: function MT_expandAll(aContainer)
+  {
+    this._expandContainer(aContainer);
+    let child = aContainer.children.firstChild;
+    while (child) {
+      this._expandAll(child.container);
+      child = child.nextSibling;
+    }
+  },
+
+  /**
+   * Expand the entire tree beneath a node.
+   *
+   * @param aContainer The node to expand, or null
+   *        to start from the top.
+   */
+  expandAll: function MT_expandAll(aNode)
+  {
+    aNode = aNode || this._rootNode;
+    this._expandAll(this._containers.get(aNode));
+  },
+
+  /**
+   * Collapse the node's children.
+   */
+  collapseNode: function MT_collapseNode(aNode)
+  {
+    let container = this._containers.get(aNode);
+    container.expanded = false;
+  },
+
+  /**
+   * Mark the given node selected.
+   */
+  selectNode: function MT_selectNode(aNode)
+  {
+    let container = this._containers.get(aNode);
+    if (this._selectedContainer === container) {
+      return false;
+    }
+    if (this._selectedContainer) {
+      this._selectedContainer.selected = false;
+    }
+    this._selectedContainer = container;
+    if (aNode) {
+      this._selectedContainer.selected = true;
+    }
+
+    this._selectedContainer.focus();
+
+    return true;
+  },
+
+  /**
+   * Make sure all children of the given container's node are
+   * imported and attached to the container in the right order.
+   */
+  _updateChildren: function MT__updateChildren(aContainer)
+  {
+    // Get a tree walker pointing at the first child of the node.
+    let treeWalker = documentWalker(aContainer.node);
+    let child = treeWalker.firstChild();
+    aContainer.hasChildren = !!child;
+    if (aContainer.expanded) {
+      let lastContainer = null;
+      while (child) {
+        let container = this.importNode(child, false);
+
+        // Make sure children are in the right order.
+        let before = lastContainer ? lastContainer.nextSibling : aContainer.children.firstChild;
+        aContainer.children.insertBefore(container.elt, before);
+        lastContainer = container.elt;
+        child = treeWalker.nextSibling();
+      }
+
+      while (aContainer.children.lastChild != lastContainer) {
+        aContainer.children.removeChild(aContainer.children.lastChild);
+      }
+    }
+  },
+
+  /**
+   * Tear down the markup panel.
+   */
+  destroy: function MT_destroy()
+  {
+    this.undo.destroy();
+    delete this.undo;
+
+    this._frame.addEventListener("focus", this._boundFocus, false);
+    delete this._boundFocus;
+
+    this._frame.removeEventListener("keydown", this._boundKeyDown, true);
+    delete this._boundKeyDown;
+
+    this._inspector.removeListener("select", this._boundSelect);
+    delete this._boundSelect;
+
+    delete this._elt;
+
+    delete this._containers;
+    this._observer.disconnect();
+    delete this._observer;
+  }
+};
+
+
+/**
+ * The main structure for storing a document node in the markup
+ * tree.  Manages creation of the editor for the node and
+ * a <ul> for placing child elements, and expansion/collapsing
+ * of the element.
+ *
+ * @param MarkupView aMarkupView
+ *        The markup view that owns this container.
+ * @param DOMNode aNode
+ *        The node to display.
+ */
+function MarkupContainer(aMarkupView, aNode)
+{
+  this.markup = aMarkupView;
+  this.doc = this.markup.doc;
+  this.undo = this.markup.undo;
+  this.node = aNode;
+
+  if (aNode.nodeType == Ci.nsIDOMNode.TEXT_NODE) {
+    this.editor = new TextEditor(this, aNode, "text");
+  } else if (aNode.nodeType == Ci.nsIDOMNode.COMMENT_NODE) {
+    this.editor = new TextEditor(this, aNode, "comment");
+  } else if (aNode.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
+    this.editor = new ElementEditor(this, aNode);
+  } else if (aNode.nodeType == Ci.nsIDOMNode.DOCUMENT_TYPE_NODE) {
+    this.editor = new DoctypeEditor(this, aNode);
+  } else {
+    this.editor = new GenericEditor(this.markup, aNode);
+  }
+
+  // The template will fill the following properties
+  this.elt = null;
+  this.expander = null;
+  this.codeBox = null;
+  this.children = null;
+  let options = { stack: "markup-view.xhtml" };
+  this.markup.template("container", this, options);
+
+  this.elt.container = this;
+
+  this.expander.addEventListener("click", function() {
+    this.markup.navigate(this);
+
+    if (this.expanded) {
+      this.markup.collapseNode(this.node);
+    } else {
+      this.markup.expandNode(this.node);
+    }
+  }.bind(this));
+
+  this.codeBox.insertBefore(this.editor.elt, this.children);
+
+  this.editor.elt.addEventListener("mousedown", function(evt) {
+    this.markup.navigate(this);
+  }.bind(this), false);
+
+  if (this.editor.closeElt) {
+    this.codeBox.appendChild(this.editor.closeElt);
+  }
+
+}
+
+MarkupContainer.prototype = {
+  /**
+   * True if the current node has children.  The MarkupView
+   * will set this attribute for the MarkupContainer.
+   */
+  _hasChildren: false,
+
+  get hasChildren() {
+    return this._hasChildren;
+  },
+
+  set hasChildren(aValue) {
+    this._hasChildren = aValue;
+    if (aValue) {
+      this.expander.style.visibility = "visible";
+    } else {
+      this.expander.style.visibility = "hidden";
+    }
+  },
+
+  /**
+   * True if the node has been visually expanded in the tree.
+   */
+  get expanded() {
+    return this.children.hasAttribute("expanded");
+  },
+
+  set expanded(aValue) {
+    if (aValue) {
+      this.expander.setAttribute("expanded", "");
+      this.children.setAttribute("expanded", "");
+    } else {
+      this.expander.removeAttribute("expanded");
+      this.children.removeAttribute("expanded");
+    }
+  },
+
+  /**
+   * True if the container is visible in the markup tree.
+   */
+  get visible()
+  {
+    return this.elt.getBoundingClientRect().height > 0;
+  },
+
+  /**
+   * True if the container is currently selected.
+   */
+  _selected: false,
+
+  get selected() {
+    return this._selected;
+  },
+
+  set selected(aValue) {
+    this._selected = aValue;
+    if (this._selected) {
+      this.editor.elt.classList.add("selected");
+      if (this.editor.closeElt) {
+        this.editor.closeElt.classList.add("selected");
+      }
+    } else {
+      this.editor.elt.classList.remove("selected");
+      if (this.editor.closeElt) {
+        this.editor.closeElt.classList.remove("selected");
+      }
+    }
+  },
+
+  /**
+   * Update the container's editor to the current state of the
+   * viewed node.
+   */
+  update: function MC_update()
+  {
+    if (this.editor.update) {
+      this.editor.update();
+    }
+  },
+
+  /**
+   * Try to put keyboard focus on the current editor.
+   */
+  focus: function MC_focus()
+  {
+    let focusable = this.editor.elt.querySelector("[tabindex]");
+    if (focusable) {
+      focusable.focus();
+    }
+  }
+}
+
+/**
+ * Dummy container node used for the root document element.
+ */
+function RootContainer(aMarkupView, aNode)
+{
+  this.doc = aMarkupView.doc;
+  this.elt = this.doc.createElement("ul");
+  this.children = this.elt;
+  this.node = aNode;
+}
+
+/**
+ * Creates an editor for simple nodes.
+ */
+function GenericEditor(aContainer, aNode)
+{
+  this.elt = aContainer.doc.createElement("span");
+  this.elt.className = "editor";
+  this.elt.textContent = aNode.nodeName;
+}
+
+/**
+ * Creates an editor for a DOCTYPE node.
+ *
+ * @param MarkupContainer aContainer The container owning this editor.
+ * @param DOMNode aNode The node being edited.
+ */
+function DoctypeEditor(aContainer, aNode)
+{
+  this.elt = aContainer.doc.createElement("span");
+  this.elt.className = "editor comment";
+  this.elt.textContent = '<!DOCTYPE ' + aNode.name +
+     (aNode.publicId ? ' PUBLIC "' +  aNode.publicId + '"': '') +
+     (aNode.systemId ? ' "' + aNode.systemId + '"' : '') +
+     '>';
+}
+
+/**
+ * Creates a simple text editor node, used for TEXT and COMMENT
+ * nodes.
+ *
+ * @param MarkupContainer aContainer The container owning this editor.
+ * @param DOMNode aNode The node being edited.
+ * @param string aTemplate The template id to use to build the editor.
+ */
+function TextEditor(aContainer, aNode, aTemplate)
+{
+  this.node = aNode;
+
+  aContainer.markup.template(aTemplate, this);
+
+  _editableField({
+    element: this.value,
+    stopOnReturn: true,
+    trigger: "dblclick",
+    multiline: true,
+    done: function TE_done(aVal, aCommit) {
+      if (!aCommit) {
+        return;
+      }
+      let oldValue = this.node.nodeValue;
+      aContainer.undo.do(function() {
+        this.node.nodeValue = aVal;
+      }.bind(this), function() {
+        this.node.nodeValue = oldValue;
+      }.bind(this));
+    }.bind(this)
+  });
+
+  this.update();
+}
+
+TextEditor.prototype = {
+  update: function TE_update()
+  {
+    this.value.textContent = this.node.nodeValue;
+  }
+};
+
+/**
+ * Creates an editor for an Element node.
+ *
+ * @param MarkupContainer aContainer The container owning this editor.
+ * @param Element aNode The node being edited.
+ */
+function ElementEditor(aContainer, aNode)
+{
+  this.doc = aContainer.doc;
+  this.undo = aContainer.undo;
+  this.template = aContainer.markup.template.bind(aContainer.markup);
+  this.node = aNode;
+
+  this.attrs = [];
+
+  // The templates will fill the following properties
+  this.elt = null;
+  this.tag = null;
+  this.attrList = null;
+  this.newAttr = null;
+  this.closeElt = null;
+  let options = { stack: "markup-view.xhtml" };
+
+  // Create the main editor
+  this.template("element", this, options);
+
+  // Create the closing tag
+  this.template("elementClose", this, options);
+
+  // Make the tag name editable (unless this is a document element)
+  if (aNode != aNode.ownerDocument.documentElement) {
+    this.tag.setAttribute("tabindex", "0");
+    _editableField({
+      element: this.tag,
+      trigger: "dblclick",
+      stopOnReturn: true,
+      done: this.onTagEdit.bind(this),
+    });
+  }
+
+  // Make the new attribute space editable.
+  _editableField({
+    element: this.newAttr,
+    trigger: "dblclick",
+    stopOnReturn: true,
+    done: function EE_onNew(aVal, aCommit) {
+      if (!aCommit) {
+        return;
+      }
+
+      this._applyAttributes(aVal);
+    }.bind(this)
+  });
+
+  let tagName = this.node.nodeName.toLowerCase();
+  this.tag.textContent = tagName;
+  this.closeTag.textContent = tagName;
+
+  this.update();
+}
+
+ElementEditor.prototype = {
+  /**
+   * Update the state of the editor from the node.
+   */
+  update: function EE_update()
+  {
+    let attrs = this.node.attributes;
+    if (!attrs) {
+      return;
+    }
+
+    // Hide all the attribute editors, they'll be re-shown if they're
+    // still applicable.  Don't update attributes that are being
+    // actively edited.
+    let attrEditors = this.attrList.querySelectorAll(".attreditor");
+    for (let i = 0; i < attrEditors.length; i++) {
+      if (!attrEditors[i].inplaceEditor) {
+        attrEditors[i].style.display = "none";
+      }
+    }
+
+    // Get the attribute editor for each attribute that exists on
+    // the node and show it.
+    for (let i = 0; i < attrs.length; i++) {
+      let attr = this._createAttribute(attrs[i]);
+      if (!attr.inplaceEditor) {
+        attr.style.removeProperty("display");
+      }
+    }
+  },
+
+  _createAttribute: function EE_createAttribute(aAttr, aBefore)
+  {
+    if (aAttr.name in this.attrs) {
+      var attr = this.attrs[aAttr.name];
+      var name = attr.querySelector(".attrname");
+      var val = attr.querySelector(".attrvalue");
+    } else {
+      // Create the template editor, which will save some variables here.
+      let data = {
+        attrName: aAttr.name,
+      };
+      let options = { stack: "markup-view.xhtml" };
+      this.template("attribute", data, options);
+      var {attr, inner, name, val} = data;
+
+      // Figure out where we should place the attribute.
+      let before = aBefore || null;
+      if (aAttr.name == "id") {
+        before = this.attrList.firstChild;
+      } else if (aAttr.name == "class") {
+        let idNode = this.attrs["id"];
+        before = idNode ? idNode.nextSibling : this.attrList.firstChild;
+      }
+      this.attrList.insertBefore(attr, before);
+
+      // Make the attribute editable.
+      _editableField({
+        element: inner,
+        trigger: "dblclick",
+        stopOnReturn: true,
+        selectAll: false,
+        start: function EE_editAttribute_start(aEditor, aEvent) {
+          // If the editing was started inside the name or value areas,
+          // select accordingly.
+          if (aEvent.target === name) {
+            aEditor.input.setSelectionRange(0, name.textContent.length);
+          } else if (aEvent.target === val) {
+            let length = val.textContent.length;
+            let editorLength = aEditor.input.value.length;
+            let start = editorLength - (length + 1);
+            aEditor.input.setSelectionRange(start, start + length);
+          } else {
+            aEditor.input.select();
+          }
+        },
+        done: function EE_editAttribute_done(aVal, aCommit) {
+          if (!aCommit) {
+            return;
+          }
+
+          this.undo.startBatch();
+
+          // Remove the attribute stored in this editor and re-add any attributes
+          // parsed out of the input element.
+          this._removeAttribute(this.node, aAttr.name)
+          this._applyAttributes(aVal, attr);
+
+          this.undo.endBatch();
+        }.bind(this)
+      });
+
+      this.attrs[aAttr.name] = attr;
+    }
+
+    name.textContent = aAttr.name;
+    val.textContent = aAttr.value;
+
+    return attr;
+  },
+
+  /**
+   * Parse a user-entered attribute string and apply the resulting
+   * attributes to the node.  This operation is undoable.
+   *
+   * @param string aValue the user-entered value.
+   * @param Element aAttrNode the attribute editor that created this
+   *        set of attributes, used to place new attributes where the
+   *        user put them.
+   */
+  _applyAttributes: function EE__applyAttributes(aValue, aAttrNode)
+  {
+    // Create a dummy node for parsing the attribute list.
+    let dummyNode = this.doc.createElement("div");
+
+    let parseTag = (this.node.namespaceURI.match(/svg/i) ? "svg" :
+                   (this.node.namespaceURI.match(/mathml/i) ? "math" : "div"));
+    let parseText = "<" + parseTag + " " + aValue + "/>";
+    dummyNode.innerHTML = parseText;
+    let parsedNode = dummyNode.firstChild;
+
+    let attrs = parsedNode.attributes;
+
+    this.undo.startBatch();
+
+    for (let i = 0; i < attrs.length; i++) {
+      // Create an attribute editor next to the current attribute if needed.
+      this._createAttribute(attrs[i], aAttrNode ? aAttrNode.nextSibling : null);
+      this._setAttribute(this.node, attrs[i].name, attrs[i].value);
+    }
+
+    this.undo.endBatch();
+  },
+
+  /**
+   * Helper function for _setAttribute and _removeAttribute,
+   * returns a function that puts an attribute back the way it was.
+   */
+  _restoreAttribute: function EE_restoreAttribute(aNode, aName)
+  {
+    if (aNode.hasAttribute(aName)) {
+      let oldValue = aNode.getAttribute(aName);
+      return function() { aNode.setAttribute(aName, oldValue); };
+    } else {
+      return function() { aNode.removeAttribute(aName) };
+    }
+  },
+
+  /**
+   * Sets an attribute.  This operation is undoable.
+   */
+  _setAttribute: function EE_setAttribute(aNode, aName, aValue)
+  {
+    this.undo.do(function() {
+      aNode.setAttribute(aName, aValue);
+    }, this._restoreAttribute(aNode, aName));
+  },
+
+  /**
+   * Removes an attribute.  This operation is undoable.
+   */
+  _removeAttribute: function EE_removeAttribute(aNode, aName)
+  {
+    this.undo.do(function() {
+      aNode.removeAttribute(aName);
+    }, this._restoreAttribute(aNode, aName));
+  },
+
+  /**
+   * Handler for the new attribute editor.
+   */
+  _onNewAttribute: function EE_onNewAttribute(aValue, aCommit)
+  {
+    if (!aValue || !aCommit) {
+      return;
+    }
+
+    this._setAttribute(this.node, aValue, "");
+    let attr = this._createAttribute({ name: aValue, value: ""});
+    attr.style.removeAttribute("display");
+    attr.querySelector("attrvalue").click();
+  },
+
+
+  /**
+   * Called when the tag name editor has is done editing.
+   */
+  onTagEdit: function EE_onTagEdit(aVal, aCommit) {
+    if (!aCommit || aVal == this.node.tagName) {
+      return;
+    }
+
+    // Create a new element with the same attributes as the
+    // current element and prepare to replace the current node
+    // with it.
+    let newElt = nodeDocument(this.node).createElement(aVal);
+    let attrs = this.node.attributes;
+
+    for (let i = 0 ; i < attrs.length; i++) {
+      newElt.setAttribute(attrs[i].name, attrs[i].value);
+    }
+
+    function swapNodes(aOld, aNew) {
+      while (aOld.firstChild) {
+        aNew.appendChild(aOld.firstChild);
+      }
+      aOld.parentNode.insertBefore(aNew, aOld);
+      aOld.parentNode.removeChild(aOld);
+    }
+
+    // Queue an action to swap out the element.
+    this.undo.do(function() {
+      swapNodes(this.node, newElt);
+    }.bind(this), function() {
+      swapNodes(newElt, this.node);
+    }.bind(this));
+  },
+}
+
+
+
+RootContainer.prototype = {
+  hasChildren: true,
+  expanded: true,
+  update: function RC_update() {}
+};
+
+function documentWalker(node) {
+  return new DocumentWalker(node, Ci.nsIDOMNodeFilter.SHOW_ALL, whitespaceTextFilter, false);
+}
+
+function nodeDocument(node) {
+  return node.ownerDocument || (node.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE ? node : null);
+}
+
+/**
+ * Similar to a TreeWalker, except will dig in to iframes and it doesn't
+ * implement the good methods like previousNode and nextNode.
+ *
+ * See TreeWalker documentation for explanations of the methods.
+ */
+function DocumentWalker(aNode, aShow, aFilter, aExpandEntityReferences)
+{
+  let doc = nodeDocument(aNode);
+  this.walker = doc.createTreeWalker(nodeDocument(aNode),
+    aShow, aFilter, aExpandEntityReferences);
+  this.walker.currentNode = aNode;
+  this.filter = aFilter;
+}
+
+DocumentWalker.prototype = {
+  get node() this.walker.node,
+  get whatToShow() this.walker.whatToShow,
+  get expandEntityReferences() this.walker.expandEntityReferences,
+  get currentNode() this.walker.currentNode,
+  set currentNode(aVal) this.walker.currentNode = aVal,
+
+  /**
+   * Called when the new node is in a different document than
+   * the current node, creates a new treewalker for the document we've
+   * run in to.
+   */
+  _reparentWalker: function DW_reparentWalker(aNewNode) {
+    if (!aNewNode) {
+      return null;
+    }
+    let doc = nodeDocument(aNewNode);
+    let walker = doc.createTreeWalker(doc,
+      this.whatToShow, this.filter, this.expandEntityReferences);
+    walker.currentNode = aNewNode;
+    this.walker = walker;
+    return aNewNode;
+  },
+
+  parentNode: function DW_parentNode()
+  {
+    let currentNode = this.walker.currentNode;
+    let parentNode = this.walker.parentNode();
+
+    if (!parentNode) {
+      if (currentNode && currentNode.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE
+          && currentNode.defaultView) {
+        let embeddingFrame = currentNode.defaultView.frameElement;
+        if (embeddingFrame) {
+          return this._reparentWalker(embeddingFrame);
+        }
+      }
+      return null;
+    }
+
+    return parentNode;
+  },
+
+  firstChild: function DW_firstChild()
+  {
+    let node = this.walker.currentNode;
+    if (!node)
+      return;
+    if (node.contentDocument) {
+      return this._reparentWalker(node.contentDocument);
+    } else if (node instanceof nodeDocument(node).defaultView.GetSVGDocument) {
+      return this._reparentWalker(node.getSVGDocument());
+    }
+    return this.walker.firstChild();
+  },
+
+  lastChild: function DW_lastChild()
+  {
+    let node = this.walker.currentNode;
+    if (!node)
+      return;
+    if (node.contentDocument) {
+      return this._reparentWalker(node.contentDocument);
+    } else if (node instanceof nodeDocument(node).defaultView.GetSVGDocument) {
+      return this._reparentWalker(node.getSVGDocument());
+    }
+    return this.walker.lastChild();
+  },
+
+  previousSibling: function DW_previousSibling() this.walker.previousSibling(),
+  nextSibling: function DW_nextSibling() this.walker.nextSibling(),
+
+  // XXX bug 785143: not doing previousNode or nextNode, which would sure be useful.
+}
+
+/**
+ * A tree walker filter for avoiding empty whitespace text nodes.
+ */
+function whitespaceTextFilter(aNode)
+{
+    if (aNode.nodeType == Ci.nsIDOMNode.TEXT_NODE &&
+        !/[^\s]/.exec(aNode.nodeValue)) {
+      return Ci.nsIDOMNodeFilter.FILTER_SKIP;
+    } else {
+      return Ci.nsIDOMNodeFilter.FILTER_ACCEPT;
+    }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/markup-view.css
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ul {
+  list-style: none;
+}
+
+ul.children:not([expanded]) {
+  display: none;
+}
+
+.codebox {
+  display: inline-block;
+}
+
+.newattr {
+  display: inline-block;
+  width: 1em;
+  height: 1ex;
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/markup-view.xhtml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<!DOCTYPE html>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+  <link rel="stylesheet" href="chrome://browser/content/devtools/markup-view.css" type="text/css"/>
+  <link rel="stylesheet" href="chrome://browser/skin/devtools/markup-view.css" type="text/css"/>
+</head>
+<body role="application">
+  <div id="root"></div>
+  <div id="templates" style="display:none">
+  	<ul>
+  	  <li id="template-container" save="${elt}" class="container"><span save="${expander}" class="expander"></span><span save="${codeBox}" class="codebox"><ul save="${children}" class="children"></ul></span></li>
+    </ul>
+
+    <span id="template-element" save="${elt}" class="editor"><span>&lt;</span><span save="${tag}" class="tagname"></span><span save="${attrList}"></span><span save="${newAttr}" class="newattr" tabindex="0"></span>&gt;</span>
+
+    <span id="template-attribute" save="${attr}" data-attr="${attrName}" class="attreditor" style="display:none"> <span class="editable" save="${inner}" tabindex="0"><span save="${name}" class="attrname"></span>=&quot;<span save="${val}" class="attrvalue"></span>&quot;</span></span>
+
+    <span id="template-text" save="${elt}" class="editor">
+      <pre save="${value}" style="display:inline-block;" tabindex="0"></pre>
+    </span>
+
+    <span id="template-comment" save="${elt}" class="editor comment">
+      <span>&lt;!--</span><pre save="${value}" style="display:inline-block;" tabindex="0"></pre><span>--&gt;</span>
+    </span>
+
+    <span id="template-elementClose" save="${closeElt}">&lt;/<span save="${closeTag}" class="tagname"></span>&gt;</span>
+   </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/Makefile.in
@@ -0,0 +1,25 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH     = @DEPTH@
+topsrcdir	= @top_srcdir@
+srcdir		= @srcdir@
+VPATH		= @srcdir@
+relativesrcdir  = @relativesrcdir@
+
+include $(DEPTH)/config/autoconf.mk
+include $(topsrcdir)/config/rules.mk
+
+_BROWSER_FILES = \
+		browser_inspector_markup_navigation.html \
+		browser_inspector_markup_navigation.js \
+		browser_inspector_markup_mutation.html \
+		browser_inspector_markup_mutation.js \
+		browser_inspector_markup_edit.html \
+		browser_inspector_markup_edit.js \
+		head.js \
+		$(NULL)
+
+libs::	$(_BROWSER_FILES)
+	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/browser_inspector_markup_edit.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+
+<html class="html">
+
+  <body class="body">
+    <div class="node0">
+      <div id="node1" class="node1">line1</div>
+      <div id="node2" class="node2">line2</div>
+      <p class="node3">line3</p>
+      <!-- A comment -->
+      <p id="node4" class="node4">line4
+        <span class="node5">line5</span>
+        <span class="node6">line6</span>
+        <!-- A comment -->
+        <a class="node7">line7<span class="node8">line8</span></a>
+        <span class="node9">line9</span>
+        <span class="node10">line10</span>
+        <span class="node11">line11</span>
+        <a class="node12">line12<span class="node13">line13</span></a>
+      </p>
+      <p id="node14">line14</p>
+      <p class="node15">line15</p>
+    </div>
+    <div id="node16">
+      <p id="node17">line17</p>
+    </div>
+    <div id="node18">
+      <div id="node19">
+        <div id="node20">
+          <div id="node21">
+            line21
+          </div>
+        </div>
+      </div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/browser_inspector_markup_edit.js
@@ -0,0 +1,223 @@
+/* Any copyright", " is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that various editors work as expected.  Also checks
+ * that the various changes are properly undoable and redoable.
+ * For each step in the test, we:
+ * - Check that the node we're editing is as we expect
+ * - Make the change, check that the change was made as we expect
+ * - Undo the change, check that the node is back in its original state
+ * - Redo the change, check that the node change was made again correctly.
+ *
+ * This test mostly tries to verify that the editor makes changes to the
+ * underlying DOM, not that the UI updates - UI updates are based on
+ * underlying DOM changes, and the mutation tests should cover those cases.
+ */
+
+function test() {
+  let tempScope = {}
+  Cu.import("resource:///modules/devtools/CssRuleView.jsm", tempScope);
+  let inplaceEditor = tempScope._getInplaceEditorForSpan;
+
+  waitForExplicitFinish();
+
+  // Will hold the doc we're viewing
+  let doc;
+
+  // Holds the MarkupTool object we're testing.
+  let markup;
+
+  /**
+   * Edit a given editableField
+   */
+  function editField(aField, aValue)
+  {
+    aField.focus();
+    EventUtils.sendKey("return");
+    let input = inplaceEditor(aField).input;
+    input.value = aValue;
+    input.blur();
+  }
+
+  function assertAttributes(aElement, aAttributes)
+  {
+    let attrs = Object.getOwnPropertyNames(aAttributes);
+    is(aElement.attributes.length, attrs.length, "Node has the correct number of attributes");
+    for (let attr of attrs) {
+      is(aElement.getAttribute(attr), aAttributes[attr], "Node has the correct " + attr + " attribute.");
+    }
+  }
+
+  // All the mutation types we want to test.
+  let edits = [
+    // Change an attribute
+    {
+      before: function() {
+        assertAttributes(doc.querySelector("#node1"), {
+          id: "node1",
+          class: "node1"
+        });
+      },
+      execute: function() {
+        let editor = markup.getContainer(doc.querySelector("#node1")).editor;
+        let attr = editor.attrs["class"].querySelector(".editable");
+        editField(attr, 'class="changednode1"');
+      },
+      after: function() {
+        assertAttributes(doc.querySelector("#node1"), {
+          id: "node1",
+          class: "changednode1"
+        });
+      }
+    },
+
+    // Remove an attribute
+    {
+      before: function() {
+        assertAttributes(doc.querySelector("#node4"), {
+          id: "node4",
+          class: "node4"
+        });
+      },
+      execute: function() {
+        let editor = markup.getContainer(doc.querySelector("#node4")).editor;
+        let attr = editor.attrs["class"].querySelector(".editable");
+        editField(attr, '');
+      },
+      after: function() {
+        assertAttributes(doc.querySelector("#node4"), {
+          id: "node4",
+        });
+      }
+    },
+
+    // Add an attribute by clicking the empty space after a node
+    {
+      before: function() {
+        assertAttributes(doc.querySelector("#node14"), {
+          id: "node14",
+        });
+      },
+      execute: function() {
+        let editor = markup.getContainer(doc.querySelector("#node14")).editor;
+        let attr = editor.newAttr;
+        editField(attr, 'class="newclass" style="color:green"');
+      },
+      after: function() {
+        assertAttributes(doc.querySelector("#node14"), {
+          id: "node14",
+          class: "newclass",
+          style: "color:green"
+        });
+      }
+    },
+
+    // Add attributes by adding to an existing attribute's entry
+    {
+      before: function() {
+        assertAttributes(doc.querySelector("#node18"), {
+          id: "node18",
+        });
+      },
+      execute: function() {
+        let editor = markup.getContainer(doc.querySelector("#node18")).editor;
+        let attr = editor.attrs["id"].querySelector(".editable");
+        editField(attr, attr.textContent + ' class="newclass" style="color:green"');
+      },
+      after: function() {
+        assertAttributes(doc.querySelector("#node18"), {
+          id: "node18",
+          class: "newclass",
+          style: "color:green"
+        });
+      }
+    },
+
+    // Remove an element with the delete key
+    {
+      before: function() {
+        ok(!!doc.querySelector("#node18"), "Node 18 should exist.");
+      },
+      execute: function() {
+        markup.selectNode(doc.querySelector("#node18"));
+        EventUtils.sendKey("delete");
+      },
+      after: function() {
+        ok(!doc.querySelector("#node18"), "Node 18 should not exist.")
+      }
+    },
+
+    // Edit text
+    {
+      before: function() {
+        let node = doc.querySelector('.node6').firstChild;
+        is(node.nodeValue, "line6", "Text should be unchanged");
+      },
+      execute: function() {
+        let node = doc.querySelector('.node6').firstChild;
+        let editor = markup.getContainer(node).editor;
+        let field = editor.elt.querySelector("pre");
+        editField(field, "New text");
+      },
+      after: function() {
+        let node = doc.querySelector('.node6').firstChild;
+        is(node.nodeValue, "New text", "Text should be changed.");
+      },
+    }
+  ];
+
+  // Create the helper tab for parsing...
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onload() {
+    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+    doc = content.document;
+    waitForFocus(setupTest, content);
+  }, true);
+  content.location = "http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_edit.html";
+
+  function setupTest() {
+    Services.obs.addObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.toggleInspectorUI();
+  }
+
+  function runTests() {
+    Services.obs.removeObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
+    InspectorUI.currentInspector.once("markuploaded", startTests);
+    InspectorUI.select(doc.body, true, true, true);
+    InspectorUI.stopInspecting();
+    InspectorUI.toggleHTMLPanel();
+  }
+
+  function startTests() {
+    let startNode = doc.documentElement.cloneNode();
+    markup = InspectorUI.currentInspector.markup;
+    markup.expandAll();
+    for (let step of edits) {
+      step.before();
+      step.execute();
+      step.after();
+      ok(markup.undo.canUndo(), "Should be able to undo.");
+      markup.undo.undo();
+      step.before();
+      ok(markup.undo.canRedo(), "Should be able to redo.");
+      markup.undo.redo();
+      step.after();
+    }
+    while (markup.undo.canUndo()) {
+      markup.undo.undo();
+    }
+    // By now we should have a healthy undo stack, clear it out and we should be back where
+    // we started.
+    ok(doc.documentElement.isEqualNode(startNode), "Clearing the undo stack should leave us where we started.");
+    Services.obs.addObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
+    InspectorUI.closeInspectorUI();
+  }
+
+  function finishUp() {
+    Services.obs.removeObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
+    doc = null;
+    gBrowser.removeCurrentTab();
+    finish();
+  }
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/browser_inspector_markup_mutation.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+
+<html class="html">
+
+  <body class="body">
+    <div class="node0">
+      <div id="node1" class="node1">line1</div>
+      <div id="node2" class="node2">line2</div>
+      <p class="node3">line3</p>
+      <!-- A comment -->
+      <p id="node4" class="node4">line4
+        <span class="node5">line5</span>
+        <span class="node6">line6</span>
+        <!-- A comment -->
+        <a class="node7">line7<span class="node8">line8</span></a>
+        <span class="node9">line9</span>
+        <span class="node10">line10</span>
+        <span class="node11">line11</span>
+        <a class="node12">line12<span class="node13">line13</span></a>
+      </p>
+      <p id="node14">line14</p>
+      <p class="node15">line15</p>
+    </div>
+    <div id="node16">
+      <p id="node17">line17</p>
+    </div>
+    <div id="node18">
+      <div id="node19">
+        <div id="node20">
+          <div id="node21">
+            line21
+          </div>
+        </div>
+      </div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/browser_inspector_markup_mutation.js
@@ -0,0 +1,186 @@
+/* Any copyright", " is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that various mutations to the dom update the markup tool correctly.
+ * The test for comparing the markup tool to the real dom is a bit weird:
+ * - Select the text in the markup tool
+ * - Parse that as innerHTML in a document we've created for the purpose.
+ * - Remove extraneous whitespace in that tree
+ * - Compare it to the real dom with isEqualNode.
+ */
+
+function test() {
+  waitForExplicitFinish();
+
+  // Will hold the doc we're viewing
+  let contentTab;
+  let doc;
+
+  // Holds the MarkupTool object we're testing.
+  let markup;
+
+  // Holds the document we use to help re-parse the markup tool's output.
+  let parseTab;
+  let parseDoc;
+
+  // Strip whitespace from a node and its children.
+  function stripWhitespace(node)
+  {
+    node.normalize();
+    let iter = node.ownerDocument.createNodeIterator(node, NodeFilter.SHOW_TEXT + NodeFilter.SHOW_COMMENT,
+      null, false);
+
+    while ((node = iter.nextNode())) {
+      node.nodeValue = node.nodeValue.replace(/\s+/g, '');
+      if (node.nodeType == Node.TEXT_NODE &&
+        !/[^\s]/.exec(node.nodeValue)) {
+        node.parentNode.removeChild(node);
+      }
+    }
+  }
+
+  // Verify that the markup in the tool is the same as the markup in the document.
+  function checkMarkup()
+  {
+    markup.expandAll();
+
+    let contentNode = doc.querySelector("body");
+    let panelNode = markup._containers.get(contentNode).elt;
+    let parseNode = parseDoc.querySelector("body");
+
+    // Grab the text from the markup panel...
+    let sel = panelNode.ownerDocument.defaultView.getSelection();
+    sel.selectAllChildren(panelNode);
+
+    // Parse it
+    parseNode.outerHTML = sel;
+    parseNode = parseDoc.querySelector("body");
+
+    // Pull whitespace out of text and comment nodes, there will
+    // be minor unimportant differences.
+    stripWhitespace(parseNode);
+
+    ok(contentNode.isEqualNode(parseNode), "Markup panel should match document.");
+  }
+
+  // All the mutation types we want to test.
+  let mutations = [
+    // Add an attribute
+    function() {
+      let node1 = doc.querySelector("#node1");
+      node1.setAttribute("newattr", "newattrval");
+    },
+    function() {
+      let node1 = doc.querySelector("#node1");
+      node1.removeAttribute("newattr");
+    },
+    function() {
+      let node1 = doc.querySelector("#node1");
+      node1.textContent = "newtext";
+    },
+    function() {
+      let node2 = doc.querySelector("#node2");
+      node2.innerHTML = "<div><span>foo</span></div>";
+    },
+
+    function() {
+      let node4 = doc.querySelector("#node4");
+      while (node4.firstChild) {
+        node4.removeChild(node4.firstChild);
+      }
+    },
+    function() {
+      // Move a child to a new parent.
+      let node17 = doc.querySelector("#node17");
+      let node1 = doc.querySelector("#node2");
+      node1.appendChild(node17);
+    },
+
+    function() {
+      // Swap a parent and child element, putting them in the same tree.
+      // body
+      //  node1
+      //  node18
+      //    node19
+      //      node20
+      //        node21
+      // will become:
+      // body
+      //   node1
+      //     node20
+      //      node21
+      //      node18
+      //        node19
+      let node18 = doc.querySelector("#node18");
+      let node20 = doc.querySelector("#node20");
+
+      let node1 = doc.querySelector("#node1");
+
+      node1.appendChild(node20);
+      node20.appendChild(node18);
+    },
+  ];
+
+  // Create the helper tab for parsing...
+  parseTab = gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onload() {
+    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+    parseDoc = content.document;
+
+    // Then create the actual dom we're inspecting...
+    contentTab = gBrowser.selectedTab = gBrowser.addTab();
+    gBrowser.selectedBrowser.addEventListener("load", function onload2() {
+      gBrowser.selectedBrowser.removeEventListener("load", onload2, true);
+      doc = content.document;
+      // Strip whitespace from the doc for easier comparison.
+      stripWhitespace(doc.documentElement);
+      waitForFocus(setupTest, content);
+    }, true);
+    content.location = "http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_mutation.html";
+  }, true);
+
+  content.location = "data:text/html,<html></html>";
+
+  function setupTest() {
+    Services.obs.addObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
+    InspectorUI.toggleInspectorUI();
+  }
+
+  function runTests() {
+    Services.obs.removeObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
+    InspectorUI.currentInspector.once("markuploaded", startTests);
+    InspectorUI.select(doc.body, true, true, true);
+    InspectorUI.stopInspecting();
+    InspectorUI.toggleHTMLPanel();
+  }
+
+  function startTests() {
+    markup = InspectorUI.currentInspector.markup;
+    checkMarkup();
+    nextStep(0);
+  }
+
+  function nextStep(cursor) {
+    if (cursor >= mutations.length) {
+      Services.obs.addObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
+      InspectorUI.closeInspectorUI();
+      return;
+    }
+    mutations[cursor]();
+    InspectorUI.currentInspector.once("markupmutation", function() {
+      executeSoon(function() {
+        checkMarkup();
+        nextStep(cursor + 1);
+      });
+    });
+  }
+
+  function finishUp() {
+    Services.obs.removeObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
+    doc = null;
+    gBrowser.removeTab(contentTab);
+    gBrowser.removeTab(parseTab);
+    finish();
+  }
+}
rename from browser/devtools/highlighter/test/browser_inspector_treePanel_navigation.html
rename to browser/devtools/markupview/test/browser_inspector_markup_navigation.html
rename from browser/devtools/highlighter/test/browser_inspector_treePanel_navigation.js
rename to browser/devtools/markupview/test/browser_inspector_markup_navigation.js
--- a/browser/devtools/highlighter/test/browser_inspector_treePanel_navigation.js
+++ b/browser/devtools/markupview/test/browser_inspector_markup_navigation.js
@@ -1,68 +1,107 @@
-/* Any copyright is dedicated to the Public Domain.
+/* Any copyright", " is dedicated to the Public Domain.
 http://creativecommons.org/publicdomain/zero/1.0/ */
 
 
 function test() {
 
   waitForExplicitFinish();
 
   let doc;
 
-  let keySequence = "right down right ";
-  keySequence += "down down down down right ";
-  keySequence += "down down down right ";
-  keySequence += "down down down down down right ";
-  keySequence += "down down down down down ";
-  keySequence += "up up up left down home ";
-  keySequence += "pagedown left down down pageup pageup left down";
-
-  keySequence = keySequence.split(" ");
-
-  let keySequenceRes = "body node0 node0 ";
-  keySequenceRes += "node1 node2 node3 node4 node4 ";
-  keySequenceRes += "node5 node6 node7 node7 ";
-  keySequenceRes += "node8 node9 node10 node11 node12 node12 ";
-  keySequenceRes += "node13 node14 node15 node15 node15 ";
-  keySequenceRes += "node14 node13 node12 node12 node14 html ";
-  keySequenceRes += "node7 node7 node9 node10 body html html html";
-
-  keySequenceRes = keySequenceRes.split(" ");
-
+  let keySequences = [
+    ["right", "body"],
+    ["down", "node0"],
+    ["right", "node0"],
+    ["down", "node1"],
+    ["down", "node2"],
+    ["down", "node3"],
+    ["down", "*comment*"],
+    ["down", "node4"],
+    ["right", "node4"],
+    ["down", "*text*"],
+    ["down", "node5"],
+    ["down", "node6"],
+    ["down", "*comment*"],
+    ["down" , "node7"],
+    ["right", "node7"],
+    ["down", "*text*"],
+    ["down", "node8"],
+    ["down", "node9"],
+    ["down", "node10"],
+    ["down", "node11"],
+    ["down", "node12"],
+    ["right", "node12"],
+    ["down", "*text*"],
+    ["down", "node13"],
+    ["down", "node14"],
+    ["down", "node15"],
+    ["down", "node15"],
+    ["down", "node15"],
+    ["up", "node14"],
+    ["up", "node13"],
+    ["up", "*text*"],
+    ["up", "node12"],
+    ["left", "node12"],
+    ["down", "node14"],
+    ["home", "*doctype*"],
+    ["pagedown", "*text*"],
+    ["down", "node5"],
+    ["down", "node6"],
+    ["down", "*comment*"],
+    ["down", "node7"],
+    ["left", "node7"],
+    ["down", "node9"],
+    ["down", "node10"],
+    ["pageup", "node2"],
+    ["pageup", "*doctype*"],
+    ["down", "html"],
+    ["left", "html"],
+    ["down", "html"]
+  ];
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onload() {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
     doc = content.document;
     waitForFocus(setupTest, content);
   }, true);
 
-  content.location = "http://mochi.test:8888/browser/browser/devtools/highlighter/test/browser_inspector_treePanel_navigation.html";
+  content.location = "http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_navigation.html";
+
+  let markup = null;
 
   function setupTest() {
     Services.obs.addObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
     InspectorUI.toggleInspectorUI();
   }
 
   function runTests() {
     Services.obs.removeObserver(runTests, InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED);
-    Services.obs.addObserver(startNavigation, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY, false);
+    InspectorUI.currentInspector.once("markuploaded", startNavigation);
     InspectorUI.select(doc.body, true, true, true);
     InspectorUI.toggleHTMLPanel();
   }
 
   function startNavigation() {
-    Services.obs.removeObserver(startNavigation, InspectorUI.INSPECTOR_NOTIFICATIONS.TREEPANELREADY);
+    markup = InspectorUI.currentInspector.markup;
     nextStep(0);
   }
 
   function nextStep(cursor) {
-    let key = keySequence[cursor];
-    let className = keySequenceRes[cursor];
+    if (cursor >= keySequences.length) {
+      Services.obs.addObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
+      InspectorUI.closeInspectorUI();
+      return;
+    }
+
+    let key = keySequences[cursor][0];
+    let className = keySequences[cursor][1];
+
     switch(key) {
       case "right":
         EventUtils.synthesizeKey("VK_RIGHT", {});
         break;
       case "down":
         EventUtils.synthesizeKey("VK_DOWN", {});
         break;
       case "left":
@@ -78,24 +117,27 @@ function test() {
         EventUtils.synthesizeKey("VK_PAGE_DOWN", {});
         break;
       case "home":
         EventUtils.synthesizeKey("VK_HOME", {});
         break;
     }
 
     executeSoon(function() {
-      if (cursor >= keySequence.length) {
-        Services.obs.addObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED, false);
-        InspectorUI.closeInspectorUI();
+      let node = markup.selected;
+      if (className == "*comment*") {
+        is(node.nodeType, Node.COMMENT_NODE, "[" + cursor + "] should be a comment after moving " + key);
+      } else if (className == "*text*") {
+        is(node.nodeType, Node.TEXT_NODE, "[" + cursor + "] should be text after moving " + key);
+      } else if (className == "*doctype*") {
+        is(node.nodeType, Node.DOCUMENT_TYPE_NODE, "[" + cursor + "] should be doctype after moving " + key);
       } else {
-        let node = InspectorUI.treePanel.ioBox.selectedObjectBox.repObject;
-        is(node.className, className, "[" + cursor + "] right node selected: " + className);
-        nextStep(cursor + 1);
+        is(node.className, className, "[" + cursor + "] right node selected: " + className + " after moving " + key);
       }
+      nextStep(cursor + 1);
     });
   }
 
   function finishUp() {
     Services.obs.removeObserver(finishUp, InspectorUI.INSPECTOR_NOTIFICATIONS.CLOSED);
     doc = null;
     gBrowser.removeCurrentTab();
     finish();
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/head.js
@@ -0,0 +1,15 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Cu = Components.utils;
+
+// Clear preferences that may be set during the course of tests.
+function clearUserPrefs()
+{
+  Services.prefs.clearUserPref("devtools.inspector.htmlPanelOpen");
+  Services.prefs.clearUserPref("devtools.inspector.sidebarOpen");
+  Services.prefs.clearUserPref("devtools.inspector.activeSidebar");
+}
+
+registerCleanupFunction(clearUserPrefs);
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/Undo.jsm
@@ -0,0 +1,204 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+const Cu = Components.utils;
+
+var EXPORTED_SYMBOLS=["UndoStack"];
+
+/**
+ * A simple undo stack manager.
+ *
+ * Actions are added along with the necessary code to
+ * reverse the action.
+ *
+ * @param function aChange Called whenever the size or position
+ *   of the undo stack changes, to use for updating undo-related
+ *   UI.
+ * @param integer aMaxUndo Maximum number of undo steps.
+ *   defaults to 50.
+ */
+function UndoStack(aChange, aMaxUndo)
+{
+  this.maxUndo = aMaxUndo || 50;
+  this._stack = [];
+}
+
+UndoStack.prototype = {
+  // Current index into the undo .
+  _index: 0,
+
+  // The current batch depth (see startBatch() for details)
+  _batchDepth: 0,
+
+  destroy: function Undo_destroy()
+  {
+    this.uninstallController();
+    delete this._stack;
+  },
+
+  /**
+   * Start a collection of related changes.  Changes will be batched
+   * together into one undo/redo item until endBatch() is called.
+   *
+   * Batches can be nested, in which case the outer batch will contain
+   * all items from the inner batches.  This allows larger user
+   * actions made up of a collection of smaller actions to be
+   * undone as a single action.
+   */
+  startBatch: function Undo_startBatch()
+  {
+    if (this._batchDepth++ === 0) {
+      this._batch = [];
+    }
+  },
+
+  /**
+   * End a batch of related changes, performing its action and adding
+   * it to the undo stack.
+   */
+  endBatch: function Undo_endBatch()
+  {
+    if (--this._batchDepth > 0) {
+      return;
+    }
+
+    // Cut off the undo stack wherever we currently are.
+    let start = Math.max(++this._index - this.maxUndo, 0);
+    this._stack = this._stack.slice(start, this._index);
+
+    let batch = this._batch;
+    delete this._batch;
+    let entry = {
+      do: function() {
+        for (let item of batch) {
+          item.do();
+        }
+      },
+      undo: function() {
+        for (let i = batch.length - 1; i >= 0; i--) {
+          batch[i].undo();
+        }
+      }
+    };
+    this._stack.push(entry);
+    entry.do();
+    this._change();
+  },
+
+  /**
+   * Perform an action, adding it to the undo stack.
+   *
+   * @param function aDo Called to perform the action.
+   * @param function aUndo Called to reverse the action.
+   */
+  do: function Undo_do(aDo, aUndo) {
+    this.startBatch();
+    this._batch.push({ do: aDo, undo: aUndo });
+    this.endBatch();
+  },
+
+  /*
+   * Returns true if undo() will do anything.
+   */
+  canUndo: function Undo_canUndo()
+  {
+    return this._index > 0;
+  },
+
+  /**
+   * Undo the top of the undo stack.
+   *
+   * @return true if an action was undone.
+   */
+  undo: function Undo_canUndo()
+  {
+    if (!this.canUndo()) {
+      return false;
+    }
+    this._stack[--this._index].undo();
+    this._change();
+    return true;
+  },
+
+  /**
+   * Returns true if redo() will do anything.
+   */
+  canRedo: function Undo_canRedo()
+  {
+    return this._stack.length >= this._index;
+  },
+
+  /**
+   * Redo the most recently undone action.
+   *
+   * @return true if an action was redone.
+   */
+  redo: function Undo_canRedo()
+  {
+    if (!this.canRedo()) {
+      return false;
+    }
+    this._stack[this._index++].do();
+    this._change();
+    return true;
+  },
+
+  _change: function Undo__change()
+  {
+    if (this._controllerWindow) {
+      this._controllerWindow.goUpdateCommand("cmd_undo");
+      this._controllerWindow.goUpdateCommand("cmd_redo");
+    }
+  },
+
+  /**
+   * ViewController implementation for undo/redo.
+   */
+
+  /**
+   * Install this object as a command controller.
+   */
+  installController: function Undo_installController(aControllerWindow)
+  {
+    this._controllerWindow = aControllerWindow;
+    aControllerWindow.controllers.appendController(this);
+  },
+
+  /**
+   * Uninstall this object from the command controller.
+   */
+  uninstallController: function Undo_uninstallController()
+  {
+    if (!this._controllerWindow) {
+      return;
+    }
+    this._controllerWindow.controllers.removeController(this);
+  },
+
+  supportsCommand: function Undo_supportsCommand(aCommand)
+  {
+    return (aCommand == "cmd_undo" ||
+            aCommand == "cmd_redo");
+  },
+
+  isCommandEnabled: function Undo_isCommandEnabled(aCommand)
+  {
+    switch(aCommand) {
+      case "cmd_undo": return this.canUndo();
+      case "cmd_redo": return this.canRedo();
+    };
+    return false;
+  },
+
+  doCommand: function Undo_doCommand(aCommand)
+  {
+    switch(aCommand) {
+      case "cmd_undo": return this.undo();
+      case "cmd_redo": return this.redo();
+    }
+  },
+
+  onEvent: function Undo_onEvent(aEvent) {},
+}
--- a/browser/devtools/styleinspector/CssRuleView.jsm
+++ b/browser/devtools/styleinspector/CssRuleView.jsm
@@ -28,16 +28,17 @@ const CSS_LINE_RE = /(?:[^;\(]*(?:\([^\)
 const CSS_PROP_RE = /\s*([^:\s]*)\s*:\s*(.*?)\s*(?:! (important))?;?$/;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource:///modules/devtools/CssLogic.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 var EXPORTED_SYMBOLS = ["CssRuleView",
                         "_ElementStyle",
+                        "editableItem",
                         "_editableField",
                         "_getInplaceEditorForSpan"];
 
 /**
  * Our model looks like this:
  *
  * ElementStyle:
  *   Responsible for keeping track of which properties are overridden.
@@ -1388,17 +1389,17 @@ RuleEditor.prototype = {
 
     this.closeBrace = createChild(code, "div", {
       class: "ruleview-ruleclose",
       tabindex: "0",
       textContent: "}"
     });
 
     // Create a property editor when the close brace is clicked.
-    editableItem(this.closeBrace, function(aElement) {
+    editableItem({ element: this.closeBrace }, function(aElement) {
       this.newProperty();
     }.bind(this));
   },
 
   /**
    * Update the rule editor with the contents of the rule.
    */
   populate: function RuleEditor_populate()
@@ -1833,103 +1834,117 @@ TextPropertyEditor.prototype = {
  * be focused and create an InlineEditor to handle text input.
  * Changes will be committed when the InlineEditor's input is blurred
  * or dropped when the user presses escape.
  *
  * @param {object} aOptions
  *    Options for the editable field, including:
  *    {Element} element:
  *      (required) The span to be edited on focus.
+ *    {function} canEdit:
+ *       Will be called before creating the inplace editor.  Editor
+ *       won't be created if canEdit returns false.
  *    {function} start:
  *       Will be called when the inplace editor is initialized.
  *    {function} change:
  *       Will be called when the text input changes.  Will be called
  *       with the current value of the text input.
  *    {function} done:
  *       Called when input is committed or blurred.  Called with
  *       current value and a boolean telling the caller whether to
  *       commit the change.  This function is called before the editor
  *       has been torn down.
  *    {function} destroy:
  *       Called when the editor is destroyed and has been torn down.
  *    {string} advanceChars:
  *       If any characters in advanceChars are typed, focus will advance
  *       to the next element.
+ *    {boolean} stopOnReturn:
+ *       If true, the return key will not advance the editor to the next
+ *       focusable element.
+ *    {string} trigger: The DOM event that should trigger editing,
+ *      defaults to "click"
  */
 function editableField(aOptions)
 {
-  editableItem(aOptions.element, function(aElement) {
-    new InplaceEditor(aOptions);
+  return editableItem(aOptions, function(aElement, aEvent) {
+    new InplaceEditor(aOptions, aEvent);
   });
 }
 
 /**
  * Handle events for an element that should respond to
  * clicks and sit in the editing tab order, and call
  * a callback when it is activated.
  *
- * @param DOMElement aElement
- *        The DOM element.
+ * @param object aOptions
+ *    The options for this editor, including:
+ *    {Element} element: The DOM element.
+ *    {string} trigger: The DOM event that should trigger editing,
+ *      defaults to "click"
  * @param function aCallback
  *        Called when the editor is activated.
  */
-
-function editableItem(aElement, aCallback)
+function editableItem(aOptions, aCallback)
 {
-  aElement.addEventListener("click", function(evt) {
+  let trigger = aOptions.trigger || "click"
+  let element = aOptions.element;
+  element.addEventListener(trigger, function(evt) {
     let win = this.ownerDocument.defaultView;
     let selection = win.getSelection();
-    if (selection.isCollapsed) {
-      aCallback(aElement);
+    if (trigger != "click" || selection.isCollapsed) {
+      aCallback(element, evt);
     }
     evt.stopPropagation();
   }, false);
 
   // If focused by means other than a click, start editing by
   // pressing enter or space.
-  aElement.addEventListener("keypress", function(evt) {
+  element.addEventListener("keypress", function(evt) {
     if (evt.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN ||
         evt.charCode === Ci.nsIDOMKeyEvent.DOM_VK_SPACE) {
-      aCallback(aElement);
+      aCallback(element);
     }
   }, true);
 
   // Ugly workaround - the element is focused on mousedown but
   // the editor is activated on click/mouseup.  This leads
   // to an ugly flash of the focus ring before showing the editor.
   // So hide the focus ring while the mouse is down.
-  aElement.addEventListener("mousedown", function(evt) {
+  element.addEventListener("mousedown", function(evt) {
     let cleanup = function() {
-      aElement.style.removeProperty("outline-style");
-      aElement.removeEventListener("mouseup", cleanup, false);
-      aElement.removeEventListener("mouseout", cleanup, false);
+      element.style.removeProperty("outline-style");
+      element.removeEventListener("mouseup", cleanup, false);
+      element.removeEventListener("mouseout", cleanup, false);
     };
-    aElement.style.setProperty("outline-style", "none");
-    aElement.addEventListener("mouseup", cleanup, false);
-    aElement.addEventListener("mouseout", cleanup, false);
+    element.style.setProperty("outline-style", "none");
+    element.addEventListener("mouseup", cleanup, false);
+    element.addEventListener("mouseout", cleanup, false);
   }, false);
 
   // Mark the element editable field for tab
   // navigation while editing.
-  aElement._editable = true;
+  element._editable = true;
 }
 
 var _editableField = editableField;
 
-function InplaceEditor(aOptions)
+function InplaceEditor(aOptions, aEvent)
 {
   this.elt = aOptions.element;
   let doc = this.elt.ownerDocument;
   this.doc = doc;
   this.elt.inplaceEditor = this;
 
   this.change = aOptions.change;
   this.done = aOptions.done;
   this.destroy = aOptions.destroy;
   this.initial = aOptions.initial ? aOptions.initial : this.elt.textContent;
+  this.multiline = aOptions.multiline || false;
+  this.stopOnReturn = !!aOptions.stopOnReturn;
 
   this._onBlur = this._onBlur.bind(this);
   this._onKeyPress = this._onKeyPress.bind(this);
   this._onInput = this._onInput.bind(this);
 
   this._createInput();
   this._autosize();
 
@@ -1941,32 +1956,35 @@ function InplaceEditor(aOptions)
     this._advanceCharCodes[advanceChars.charCodeAt(i)] = true;
   }
 
   // Hide the provided element and add our editor.
   this.originalDisplay = this.elt.style.display;
   this.elt.style.display = "none";
   this.elt.parentNode.insertBefore(this.input, this.elt);
 
-  this.input.select();
+  if (typeof(aOptions.selectAll) == "undefined" || aOptions.selectAll) {
+    this.input.select();
+  }
   this.input.focus();
 
   this.input.addEventListener("blur", this._onBlur, false);
   this.input.addEventListener("keypress", this._onKeyPress, false);
   this.input.addEventListener("input", this._onInput, false);
+  this.input.addEventListener("mousedown", function(aEvt) { aEvt.stopPropagation(); }, false);
 
   if (aOptions.start) {
-    aOptions.start();
+    aOptions.start(this, aEvent);
   }
 }
 
 InplaceEditor.prototype = {
   _createInput: function InplaceEditor_createEditor()
   {
-    this.input = this.doc.createElementNS(HTML_NS, "input");
+    this.input = this.doc.createElementNS(HTML_NS, this.multiline ? "textarea" : "input");
     this.input.inplaceEditor = this;
     this.input.classList.add("styleinspector-propertyeditor");
     this.input.value = this.initial;
 
     copyTextStyles(this.elt, this.input);
   },
 
   /**
@@ -2006,17 +2024,17 @@ InplaceEditor.prototype = {
   {
     // Create a hidden, absolutely-positioned span to measure the text
     // in the input.  Boo.
 
     // We can't just measure the original element because a) we don't
     // change the underlying element's text ourselves (we leave that
     // up to the client), and b) without tweaking the style of the
     // original element, it might wrap differently or something.
-    this._measurement = this.doc.createElementNS(HTML_NS, "span");
+    this._measurement = this.doc.createElementNS(HTML_NS, this.multiline ? "pre" : "span");
     this._measurement.className = "autosizer";
     this.elt.parentNode.appendChild(this._measurement);
     let style = this._measurement.style;
     style.visibility = "hidden";
     style.position = "absolute";
     style.top = "0";
     style.left = "0";
     copyTextStyles(this.input, this._measurement);
@@ -2045,16 +2063,25 @@ InplaceEditor.prototype = {
     // will be wrong.
     this._measurement.textContent = this.input.value.replace(/ /g, '\u00a0');
 
     // We add a bit of padding to the end.  Should be enough to fit
     // any letter that could be typed, otherwise we'll scroll before
     // we get a chance to resize.  Yuck.
     let width = this._measurement.offsetWidth + 10;
 
+    if (this.multiline) {
+      // Make sure there's some content in the current line.  This is a hack to account
+      // for the fact that after adding a newline the <pre> doesn't grow unless there's
+      // text content on the line.
+      width += 15;
+      this._measurement.textContent += "M";
+      this.input.style.height = this._measurement.offsetHeight + "px";
+    }
+
     this.input.style.width = width + "px";
   },
 
   /**
    * Call the client's done handler and clear out.
    */
   _apply: function InplaceEditor_apply(aEvent)
   {
@@ -2078,34 +2105,41 @@ InplaceEditor.prototype = {
   {
     this._apply();
     this._clear();
   },
 
   _onKeyPress: function InplaceEditor_onKeyPress(aEvent)
   {
     let prevent = false;
-    if (aEvent.charCode in this._advanceCharCodes
+    if (this.multiline &&
+        aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN &&
+        aEvent.shiftKey) {
+      prevent = false;
+    } else if (aEvent.charCode in this._advanceCharCodes
        || aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN
        || aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB) {
       prevent = true;
 
       let direction = FOCUS_FORWARD;
       if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_TAB &&
           aEvent.shiftKey) {
         this.cancelled = true;
         direction = FOCUS_BACKWARD;
       }
+      if (this.stopOnReturn && aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_RETURN) {
+        direction = null;
+      }
 
       let input = this.input;
 
       this._apply();
 
       let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
-      if (fm.focusedElement === input) {
+      if (direction !== null && fm.focusedElement === input) {
         // If the focused element wasn't changed by the done callback,
         // move the focus as requested.
         let next = moveFocus(this.doc.defaultView, direction);
 
         // If the next node to be focused has been tagged as an editable
         // node, send it a click event to trigger
         if (next && next.ownerDocument === this.doc && next._editable) {
           next.click();
--- a/browser/devtools/styleinspector/test/browser_ruleview_734259_style_editor_link.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_734259_style_editor_link.js
@@ -47,17 +47,16 @@ function openInspector()
 
 function inspectorUIOpen()
 {
   Services.obs.removeObserver(inspectorUIOpen,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   // Make sure the inspector is open.
   ok(InspectorUI.inspecting, "Inspector is highlighting");
-  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
   ok(!InspectorUI.isSidebarOpen, "Inspector Sidebar is not open");
   ok(!InspectorUI.store.isEmpty(), "InspectorUI.store is not empty");
   is(InspectorUI.store.length, 1, "Inspector.store.length = 1");
 
   // Highlight a node.
   let div = content.document.getElementsByTagName("div")[0];
   InspectorUI.inspectNode(div);
   InspectorUI.stopInspecting();
--- a/browser/devtools/styleinspector/test/browser_ruleview_bug_703643_context_menu_copy.js
+++ b/browser/devtools/styleinspector/test/browser_ruleview_bug_703643_context_menu_copy.js
@@ -53,17 +53,16 @@ function openInspector()
 
 function inspectorUIOpen()
 {
   Services.obs.removeObserver(inspectorUIOpen,
     InspectorUI.INSPECTOR_NOTIFICATIONS.OPENED, false);
 
   // Make sure the inspector is open.
   ok(InspectorUI.inspecting, "Inspector is highlighting");
-  ok(!InspectorUI.treePanel.isOpen(), "Inspector Tree Panel is not open");
   ok(!InspectorUI.isSidebarOpen, "Inspector Sidebar is not open");
   ok(!InspectorUI.store.isEmpty(), "InspectorUI.store is not empty");
   is(InspectorUI.store.length, 1, "Inspector.store.length = 1");
 
   // Highlight a node.
   let div = content.document.getElementsByTagName("div")[0];
   InspectorUI.inspectNode(div);
   InspectorUI.stopInspecting();
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -668,10 +668,10 @@ toolbar button -->
 <!ENTITY social.sharePopup.undo.label     "Unshare">
 <!ENTITY social.sharePopup.undo.accesskey "U">
 <!ENTITY social.sharePopup.shared.label   "You shared this page.">
 <!ENTITY social.sharePopup.portrait.arialabel "User profile picture">
 
 <!ENTITY social.toggleSidebar.label "Show sidebar">
 <!ENTITY social.toggleSidebar.accesskey "s">
 
-<!ENTITY social.activated.button.label "Oops, undo">
-<!ENTITY social.activated.button.accesskey "u">
+<!ENTITY social.activated.undobutton.label "Undo">
+<!ENTITY social.activated.undobutton.accesskey "U">
new file mode 100644
--- /dev/null
+++ b/browser/themes/gnomestripe/devtools/markup-view.css
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+* {
+  padding: 0;
+  margin: 0;
+}
+
+body {
+  font: message-box;
+  background-color: #131c26;
+  color: #8fa1b2;
+}
+
+.tagname {
+  color: #a673bf;
+}
+
+.attrname {
+  color: #b26b47;
+}
+
+.attrvalue {
+  color: #3689b2;
+}
+
+.newattr {
+  cursor: pointer;
+}
+
+.comment {
+  color: #5c6773;
+}
+
+.selected {
+  background-color: #253847;
+}
+
+/* Give some padding to focusable elements to match the editor input
+ * that will replace them. */
+span[tabindex] {
+  display: inline-block;
+  padding: 1px 0;
+}
+
+li.container {
+  position: relative;
+  padding: 2px 0 0 2px;
+}
+
+.codebox {
+  padding-left: 14px;
+}
+
+.expander {
+  position: absolute;
+  -moz-appearance: treetwisty;
+  top: 0;
+  left: 0;
+  width: 14px;
+  height: 14px;
+}
+
+.expander[expanded] {
+  -moz-appearance: treetwistyopen;
+}
+
+.styleinspector-propertyeditor {
+  border: 1px solid #CCC;
+}
--- a/browser/themes/gnomestripe/jar.mn
+++ b/browser/themes/gnomestripe/jar.mn
@@ -104,16 +104,17 @@ browser.jar:
   skin/classic/browser/devtools/alerticon-warning.png (devtools/alerticon-warning.png)
   skin/classic/browser/devtools/goto-mdn.png          (devtools/goto-mdn.png)
   skin/classic/browser/devtools/csshtmltree.css       (devtools/csshtmltree.css)
   skin/classic/browser/devtools/webconsole.css                  (devtools/webconsole.css)
   skin/classic/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
   skin/classic/browser/devtools/webconsole.png                  (devtools/webconsole.png)
   skin/classic/browser/devtools/gcli.css              (devtools/gcli.css)
   skin/classic/browser/devtools/htmlpanel.css         (devtools/htmlpanel.css)
+  skin/classic/browser/devtools/markup-view.css      (devtools/markup-view.css)
   skin/classic/browser/devtools/orion.css             (devtools/orion.css)
   skin/classic/browser/devtools/orion-container.css   (devtools/orion-container.css)
   skin/classic/browser/devtools/orion-task.png        (devtools/orion-task.png)
   skin/classic/browser/devtools/orion-breakpoint.png  (devtools/orion-breakpoint.png)
   skin/classic/browser/devtools/orion-debug-location.png (devtools/orion-debug-location.png)
   skin/classic/browser/devtools/breadcrumbs-scrollbutton.png                 (devtools/breadcrumbs-scrollbutton.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-end-pressed.png              (devtools/breadcrumbs/ltr-end-pressed.png)
   skin/classic/browser/devtools/breadcrumbs/ltr-end-selected-pressed.png     (devtools/breadcrumbs/ltr-end-selected-pressed.png)
--- a/browser/themes/pinstripe/browser.css
+++ b/browser/themes/pinstripe/browser.css
@@ -1523,23 +1523,23 @@ window[tabsontop="false"] richlistitem[t
 #editBookmarkPanel .expander-up {
   list-style-image: url("chrome://browser/skin/panel-expander-open.png");
 }
 
 #editBookmarkPanel .expander-down {
   list-style-image: url("chrome://browser/skin/panel-expander-closed.png");
 }
 
-#editBookmarkPanel .expander-up .button-icon,
-#editBookmarkPanel .expander-down .button-icon {
+#editBookmarkPanel .expander-up > .button-box > .button-icon,
+#editBookmarkPanel .expander-down > .button-box > .button-icon {
   margin: 1px 0 0;
 }
 
-#editBookmarkPanel .expander-up .button-text,
-#editBookmarkPanel .expander-down .button-text {
+#editBookmarkPanel .expander-up > .button-box > .button-text,
+#editBookmarkPanel .expander-down > .button-box > .button-text {
   display: none;
 }
 
 #editBMPanel_tagsField > .autocomplete-textbox-container > .textbox-input-box > html|*.textbox-input:-moz-placeholder {
   color: #bbb;
 }
 
 .editBMPanel_rowLabel {
new file mode 100644
--- /dev/null
+++ b/browser/themes/pinstripe/devtools/markup-view.css
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+* {
+  padding: 0;
+  margin: 0;
+}
+
+body {
+  font: message-box;
+  background-color: #131c26;
+  color: #8fa1b2;
+}
+
+.tagname {
+  color: #a673bf;
+}
+
+.attrname {
+  color: #b26b47;
+}
+
+.attrvalue {
+  color: #3689b2;
+}
+
+.newattr {
+  cursor: pointer;
+}
+
+.comment {
+  color: #5c6773;
+}
+
+.selected {
+  background-color: #253847;
+}
+
+/* Give some padding to focusable elements to match the editor input
+ * that will replace them. */
+span[tabindex] {
+  display: inline-block;
+  padding: 1px 0;
+}
+
+li.container {
+  position: relative;
+  padding: 2px 0 0 2px;
+}
+
+.codebox {
+  padding-left: 14px;
+}
+
+.expander {
+  position: absolute;
+  -moz-appearance: treetwisty;
+  top: 0;
+  left: 0;
+  width: 14px;
+  height: 14px;
+}
+
+.expander[expanded] {
+  -moz-appearance: treetwistyopen;
+}
+
+.styleinspector-propertyeditor {
+  border: 1px solid #CCC;
+}
--- a/browser/themes/pinstripe/jar.mn
+++ b/browser/themes/pinstripe/jar.mn
@@ -140,16 +140,17 @@ browser.jar:
 * skin/classic/browser/devtools/common.css                  (devtools/common.css)
   skin/classic/browser/devtools/arrows.png                  (devtools/arrows.png)
   skin/classic/browser/devtools/commandline.png             (devtools/commandline.png)
   skin/classic/browser/devtools/alerticon-warning.png       (devtools/alerticon-warning.png)
   skin/classic/browser/devtools/goto-mdn.png                (devtools/goto-mdn.png)
   skin/classic/browser/devtools/csshtmltree.css             (devtools/csshtmltree.css)
   skin/classic/browser/devtools/gcli.css                    (devtools/gcli.css)
   skin/classic/browser/devtools/htmlpanel.css               (devtools/htmlpanel.css)
+  skin/classic/browser/devtools/markup-view.css             (devtools/markup-view.css)
   skin/classic/browser/devtools/orion.css                   (devtools/orion.css)
   skin/classic/browser/devtools/orion-container.css         (devtools/orion-container.css)
   skin/classic/browser/devtools/orion-task.png              (devtools/orion-task.png)
   skin/classic/browser/devtools/orion-breakpoint.png        (devtools/orion-breakpoint.png)
   skin/classic/browser/devtools/orion-debug-location.png    (devtools/orion-debug-location.png)
   skin/classic/browser/devtools/toolbarbutton-close.png     (devtools/toolbarbutton-close.png)
 * skin/classic/browser/devtools/webconsole.css                  (devtools/webconsole.css)
   skin/classic/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
new file mode 100644
--- /dev/null
+++ b/browser/themes/winstripe/devtools/markup-view.css
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+* {
+  padding: 0;
+  margin: 0;
+}
+
+body {
+  font: message-box;
+  background-color: #131c26;
+  color: #8fa1b2;
+}
+
+.tagname {
+  color: #a673bf;
+}
+
+.attrname {
+  color: #b26b47;
+}
+
+.attrvalue {
+  color: #3689b2;
+}
+
+.newattr {
+  cursor: pointer;
+}
+
+.comment {
+  color: #5c6773;
+}
+
+.selected {
+  background-color: #253847;
+}
+
+/* Give some padding to focusable elements to match the editor input
+ * that will replace them. */
+span[tabindex] {
+  display: inline-block;
+  padding: 1px 0;
+}
+
+li.container {
+  position: relative;
+  padding: 2px 0 0 2px;
+}
+
+.codebox {
+  padding-left: 14px;
+}
+
+.expander {
+  position: absolute;
+  -moz-appearance: treetwisty;
+  top: 0;
+  left: 0;
+  width: 14px;
+  height: 14px;
+}
+
+.expander[expanded] {
+  -moz-appearance: treetwistyopen;
+}
+
+.styleinspector-propertyeditor {
+  border: 1px solid #CCC;
+}
--- a/browser/themes/winstripe/jar.mn
+++ b/browser/themes/winstripe/jar.mn
@@ -128,16 +128,17 @@ browser.jar:
         skin/classic/browser/devtools/common.css                    (devtools/common.css)
         skin/classic/browser/devtools/arrows.png                    (devtools/arrows.png)
         skin/classic/browser/devtools/commandline.png               (devtools/commandline.png)
         skin/classic/browser/devtools/alerticon-warning.png         (devtools/alerticon-warning.png)
         skin/classic/browser/devtools/goto-mdn.png                  (devtools/goto-mdn.png)
         skin/classic/browser/devtools/csshtmltree.css               (devtools/csshtmltree.css)
         skin/classic/browser/devtools/gcli.css                      (devtools/gcli.css)
         skin/classic/browser/devtools/htmlpanel.css                 (devtools/htmlpanel.css)
+        skin/classic/browser/devtools/markup-view.css               (devtools/markup-view.css)
         skin/classic/browser/devtools/orion.css                     (devtools/orion.css)
         skin/classic/browser/devtools/orion-container.css           (devtools/orion-container.css)
         skin/classic/browser/devtools/orion-task.png                (devtools/orion-task.png)
         skin/classic/browser/devtools/orion-breakpoint.png          (devtools/orion-breakpoint.png)
         skin/classic/browser/devtools/orion-debug-location.png      (devtools/orion-debug-location.png)
         skin/classic/browser/devtools/toolbarbutton-close.png       (devtools/toolbarbutton-close.png)
         skin/classic/browser/devtools/webconsole.css                  (devtools/webconsole.css)
         skin/classic/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
@@ -330,16 +331,17 @@ browser.jar:
         skin/classic/aero/browser/devtools/common.css                (devtools/common.css)
         skin/classic/aero/browser/devtools/arrows.png                (devtools/arrows.png)
         skin/classic/aero/browser/devtools/commandline.png           (devtools/commandline.png)
         skin/classic/aero/browser/devtools/alerticon-warning.png     (devtools/alerticon-warning.png)
         skin/classic/aero/browser/devtools/goto-mdn.png              (devtools/goto-mdn.png)
         skin/classic/aero/browser/devtools/csshtmltree.css           (devtools/csshtmltree.css)
         skin/classic/aero/browser/devtools/gcli.css                  (devtools/gcli.css)
         skin/classic/aero/browser/devtools/htmlpanel.css             (devtools/htmlpanel.css)
+        skin/classic/aero/browser/devtools/markup-view.css           (devtools/markup-view.css)
         skin/classic/aero/browser/devtools/orion.css                 (devtools/orion.css)
         skin/classic/aero/browser/devtools/orion-container.css       (devtools/orion-container.css)
         skin/classic/aero/browser/devtools/orion-task.png            (devtools/orion-task.png)
         skin/classic/aero/browser/devtools/orion-breakpoint.png      (devtools/orion-breakpoint.png)
         skin/classic/aero/browser/devtools/orion-debug-location.png  (devtools/orion-debug-location.png)
         skin/classic/aero/browser/devtools/toolbarbutton-close.png   (devtools/toolbarbutton-close.png)
         skin/classic/aero/browser/devtools/webconsole.css                  (devtools/webconsole.css)
         skin/classic/aero/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -429,38 +429,23 @@ user_pref("extensions.hotfix.url", "http
 // Make sure opening about:addons won't hit the network
 user_pref("extensions.webservice.discoverURL", "http://%(server)s/extensions-dummy/discoveryURL");
 // Make sure AddonRepository won't hit the network
 user_pref("extensions.getAddons.maxResults", 0);
 user_pref("extensions.getAddons.get.url", "http://%(server)s/extensions-dummy/repositoryGetURL");
 user_pref("extensions.getAddons.getWithPerformance.url", "http://%(server)s/extensions-dummy/repositoryGetWithPerformanceURL");
 user_pref("extensions.getAddons.search.browseURL", "http://%(server)s/extensions-dummy/repositoryBrowseURL");
 user_pref("extensions.getAddons.search.url", "http://%(server)s/extensions-dummy/repositorySearchURL");
+
+// Make enablePrivilege continue to work for test code. :-(
+user_pref("security.enablePrivilege.enable_for_tests", true);
 """ % { "server" : self.webServer + ":" + str(self.httpPort) }
     prefs.append(part)
 
-    if useServerLocations == False:
-      part = """
-user_pref("capability.principal.codebase.p1.granted", "UniversalXPConnect");
-user_pref("capability.principal.codebase.p1.id", "%(origin)s");
-user_pref("capability.principal.codebase.p1.subjectName", "");
-"""  % { "origin": "http://" + self.webServer + ":" + str(self.httpPort) }
-      prefs.append(part)
-    else:
-      # Grant God-power to all the privileged servers on which tests run.
-      privileged = filter(lambda loc: "privileged" in loc.options, locations)
-      for (i, l) in itertools.izip(itertools.count(1), privileged):
-        part = """
-user_pref("capability.principal.codebase.p%(i)d.granted", "UniversalXPConnect");
-user_pref("capability.principal.codebase.p%(i)d.id", "%(origin)s");
-user_pref("capability.principal.codebase.p%(i)d.subjectName", "");
-"""  % { "i": i,
-         "origin": (l.scheme + "://" + l.host + ":" + str(l.port)) }
-        prefs.append(part)
-
+    if useServerLocations:
       # We need to proxy every server but the primary one.
       origins = ["'%s://%s:%s'" % (l.scheme, l.host, l.port)
                 for l in filter(lambda l: "primary" not in l.options, locations)]
       origins = ", ".join(origins)
 
       pacURL = """data:text/plain,
 function FindProxyForURL(url, host)
 {
--- a/build/mobile/b2gemulator.py
+++ b/build/mobile/b2gemulator.py
@@ -24,20 +24,27 @@ class B2GEmulator(Emulator):
 
     def _check_file(self, filePath):
         if not os.path.exists(filePath):
             raise Exception(('File not found: %s; did you pass the B2G home '
                              'directory as the homedir parameter, or set '
                              'B2G_HOME correctly?') % filePath)
 
     def _check_for_adb(self, host_dir):
-        self.adb = os.path.join(self.homedir, 'out', 'host', host_dir, 'bin', 'adb')
-        if not os.path.exists(self.adb):
-            self.adb = os.path.join(self.homedir, 'bin/adb')
-        super(B2GEmulator, self)._check_for_adb()
+        if self._default_adb() == 0:
+            return
+        adb_paths = [os.path.join(self.homedir,'glue','gonk','out','host',
+                      host_dir ,'bin','adb'),os.path.join(self.homedir, 'out',
+                      'host', host_dir,'bin','adb'),os.path.join(self.homedir,
+                      'bin','adb')]
+        for option in adb_paths:
+            if os.path.exists(option):
+                self.adb = option
+                return
+        raise Exception('adb not found!')
 
     def _locate_files(self):
         if self.homedir is None:
             self.homedir = os.getenv('B2G_HOME')
         if self.homedir is None:
             raise Exception('Must define B2G_HOME or pass the homedir parameter')
         self._check_file(self.homedir)
 
--- a/build/mobile/devicemanagerADB.py
+++ b/build/mobile/devicemanagerADB.py
@@ -285,27 +285,24 @@ class DeviceManagerADB(DeviceManager):
               else:
                   out += self.removeFile(remoteDir.strip() + "/" + f.strip())
           out += self.removeSingleDir(remoteDir.strip())
       else:
           out += self.removeFile(remoteDir.strip())
       return out
 
   def isDir(self, remotePath):
-      p = self.runCmd(["shell", "ls", "-a", remotePath])
+      p = self.runCmd(["shell", "ls", "-a", remotePath + '/'])
+
       data = p.stdout.readlines()
-      if (len(data) == 0):
-          return True
-      if (len(data) == 1):
-          if (data[0].rstrip() == remotePath):
+      if len(data) == 1:
+          res = data[0]
+          if "Not a directory" in res or "No such file or directory" in res:
               return False
-          if (data[0].find("No such file or directory") != -1):
-              return False
-          if (data[0].find("Not a directory") != -1):
-              return False
+
       return True
 
   def listFiles(self, rootdir):
       p = self.runCmd(["shell", "ls", "-a", rootdir])
       data = p.stdout.readlines()
       data[:] = [item.rstrip('\r\n') for item in data]
       if (len(data) == 1):
           if (data[0] == rootdir):
--- a/build/mobile/devicemanagerSUT.py
+++ b/build/mobile/devicemanagerSUT.py
@@ -929,17 +929,21 @@ class DeviceManagerSUT(DeviceManager):
     collapseSpaces = re.compile('  +')
 
     directives = ['os','id','uptime','uptimemillis','systime','screen',
                   'rotation','memory','process','disk','power']
     if (directive in directives):
       directives = [directive]
 
     for d in directives:
-      data = self.runCmds([{ 'cmd': 'info ' + d }])
+      try:
+        data = self.runCmds([{ 'cmd': 'info ' + d }])
+      except AgentError:
+        return result
+
       if (data is None):
         continue
       data = collapseSpaces.sub(' ', data)
       result[d] = data.split('\n')
 
     # Get rid of any 0 length members of the arrays
     for k, v in result.iteritems():
       result[k] = filter(lambda x: x != '', result[k])
--- a/build/mobile/emulator.py
+++ b/build/mobile/emulator.py
@@ -82,27 +82,29 @@ class Emulator(object):
 
     @property
     def is_running(self):
         if self._emulator_launched:
             return self.proc is not None and self.proc.poll() is None
         else:
             return self.port is not None
 
+    def _default_adb(self):
+        adb = subprocess.Popen(['which', 'adb'],
+                               stdout=subprocess.PIPE,
+                               stderr=subprocess.STDOUT)
+        retcode = adb.wait()
+        if retcode == 0:
+            self.adb = adb.stdout.read().strip() # remove trailing newline
+        return retcode
+
     def _check_for_adb(self):
         if not os.path.exists(self.adb):
-            adb = subprocess.Popen(['which', 'adb'],
-                                   stdout=subprocess.PIPE,
-                                   stderr=subprocess.STDOUT)
-            retcode = adb.wait()
-            if retcode:
+            if self._default_adb() != 0:
                 raise Exception('adb not found!')
-            out = adb.stdout.read().strip()
-            if len(out) and out.find('/') > -1:
-                self.adb = out
 
     def _run_adb(self, args):
         args.insert(0, self.adb)
         adb = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
         retcode = adb.wait()
         if retcode:
             raise Exception('adb terminated with exit code %d: %s'
             % (retcode, adb.stdout.read()))
--- a/build/unix/build-clang/create-manifest.py
+++ b/build/unix/build-clang/create-manifest.py
@@ -1,39 +1,39 @@
 #!/bin/python
 
 import os
+import os.path
 import simplejson
 import sys
 import subprocess
 import urllib
 import glob
 
 def check_run(args):
     r = subprocess.call(args)
     assert r == 0
 
-old_files = glob.glob('*.manifest') + ['tooltool.py', 'setup.sh']
+old_files = glob.glob('*.manifest')
 for f in old_files:
     try:
         os.unlink(f)
     except:
         pass
 
-urllib.urlretrieve('https://raw.github.com/jhford/tooltool/master/tooltool.py',
-                   'tooltool.py')
-urllib.urlretrieve('https://hg.mozilla.org/mozilla-central/raw-file/tip/build/unix/build-clang/setup.sh',
-                   'setup.sh')
+basedir = os.path.split(os.path.realpath(sys.argv[0]))[0]
+tooltool = basedir + '/tooltool.py'
+setup = basedir + '/setup.sh'
 
-check_run(['python', 'tooltool.py', '-m', 'linux32.manifest', 'add',
-           'clang-linux32.tar.bz2', 'setup.sh'])
-check_run(['python', 'tooltool.py', '-m', 'linux64.manifest', 'add',
-           'clang-linux64.tar.bz2', 'setup.sh'])
-check_run(['python', 'tooltool.py', '-m', 'darwin.manifest', 'add',
-           'clang-darwin.tar.bz2', 'setup.sh'])
+check_run(['python', tooltool, '-m', 'linux32.manifest', 'add',
+           'clang-linux32.tar.bz2', setup])
+check_run(['python', tooltool, '-m', 'linux64.manifest', 'add',
+           'clang-linux64.tar.bz2', setup])
+check_run(['python', tooltool, '-m', 'darwin.manifest', 'add',
+           'clang-darwin.tar.bz2', setup])
 
 def key_sort(item):
     item = item[0]
     if item == 'size':
         return 0
     if item == 'digest':
         return 1
     if item == 'algorithm':
new file mode 100644
--- /dev/null
+++ b/build/unix/build-clang/tooltool.py
@@ -0,0 +1,564 @@
+#!/usr/bin/env python
+
+#tooltool is a lookaside cache implemented in Python
+#Copyright (C) 2011 John H. Ford <john@johnford.info>
+#
+#This program is free software; you can redistribute it and/or
+#modify it under the terms of the GNU General Public License
+#as published by the Free Software Foundation version 2
+#
+#This program is distributed in the hope that it will be useful,
+#but WITHOUT ANY WARRANTY; without even the implied warranty of
+#MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#GNU General Public License for more details.
+#
+#You should have received a copy of the GNU General Public License
+#along with this program; if not, write to the Free Software
+#Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+# An manifest file specifies files in that directory that are stored
+# elsewhere.  This file should only contain file in the directory
+# which the manifest file resides in and it should be called 'manifest.manifest'
+
+__version__ = '1'
+
+import os
+import optparse
+import logging
+import hashlib
+import urllib2
+import ConfigParser
+try:
+    import simplejson as json # I hear simplejson is faster
+except ImportError:
+    import json
+
+log = logging.getLogger(__name__)
+
+class FileRecordJSONEncoderException(Exception): pass
+class InvalidManifest(Exception): pass
+class ExceptionWithFilename(Exception):
+    def __init__(self, filename):
+        Exception.__init__(self)
+        self.filename = filename
+
+class DigestMismatchException(ExceptionWithFilename): pass
+class MissingFileException(ExceptionWithFilename): pass
+
+class FileRecord(object):
+    def __init__(self, filename, size, digest, algorithm):
+        object.__init__(self)
+        self.filename = filename
+        self.size = size
+        self.digest = digest
+        self.algorithm = algorithm
+        log.debug("creating %s 0x%x" % (self.__class__.__name__, id(self)))
+
+    def __eq__(self, other):
+        if self is other:
+            return True
+        if self.filename == other.filename and \
+            self.size == other.size and \
+            self.digest == other.digest and \
+            self.algorithm == other.algorithm:
+            return True
+        else:
+            return False
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __str__(self):
+        return repr(self)
+
+    def __repr__(self):
+        return "%s.%s(filename='%s', size='%s', digest='%s', algorithm='%s')" % (__name__,
+                self.__class__.__name__,
+                self.filename, self.size, self.digest, self.algorithm)
+
+    def present(self):
+        # Doesn't check validity
+        return os.path.exists(self.filename)
+
+    def validate_size(self):
+        if self.present():
+            return self.size == os.path.getsize(self.filename)
+        else:
+            log.debug("trying to validate size on a missing file, %s", self.filename)
+            raise MissingFileException(filename=self.filename)
+
+    def validate_digest(self):
+        if self.present():
+            with open(self.filename, 'rb') as f:
+                return self.digest == digest_file(f, self.algorithm)
+        else:
+            log.debug("trying to validate digest on a missing file, %s', self.filename")
+            raise MissingFileException(filename=self.filename)
+
+    def validate(self):
+        if self.validate_size():
+            if self.validate_digest():
+                return True
+        return False
+
+    def describe(self):
+        if self.present() and self.validate():
+            return "'%s' is present and valid" % self.filename
+        elif self.present():
+            return "'%s' is present and invalid" % self.filename
+        else:
+            return "'%s' is absent" % self.filename
+
+
+def create_file_record(filename, algorithm):
+    fo = open(filename, 'rb')
+    stored_filename = os.path.split(filename)[1]
+    fr = FileRecord(stored_filename, os.path.getsize(filename), digest_file(fo, algorithm), algorithm)
+    fo.close()
+    return fr
+
+
+class FileRecordJSONEncoder(json.JSONEncoder):
+    def encode_file_record(self, obj):
+        if not issubclass(type(obj), FileRecord):
+            err = "FileRecordJSONEncoder is only for FileRecord and lists of FileRecords, not %s" % obj.__class__.__name__
+            log.warn(err)
+            raise FileRecordJSONEncoderException(err)
+        else:
+            return {'filename': obj.filename, 'size': obj.size, 'algorithm': obj.algorithm, 'digest': obj.digest}
+
+    def default(self, f):
+        if issubclass(type(f), list):
+            record_list = []
+            for i in f:
+                record_list.append(self.encode_file_record(i))
+            return record_list
+        else:
+            return self.encode_file_record(f)
+
+
+class FileRecordJSONDecoder(json.JSONDecoder):
+    """I help the json module materialize a FileRecord from
+    a JSON file.  I understand FileRecords and lists of
+    FileRecords.  I ignore things that I don't expect for now"""
+    # TODO: make this more explicit in what it's looking for
+    # and error out on unexpected things
+    def process_file_records(self, obj):
+        if isinstance(obj, list):
+            record_list = []
+            for i in obj:
+                record = self.process_file_records(i)
+                if issubclass(type(record), FileRecord):
+                    record_list.append(record)
+            return record_list
+        if isinstance(obj, dict) and \
+           len(obj.keys()) == 4 and \
+           obj.has_key('filename') and \
+           obj.has_key('size') and \
+           obj.has_key('algorithm') and \
+           obj.has_key('digest'):
+            rv = FileRecord(obj['filename'], obj['size'], obj['digest'], obj['algorithm'])
+            log.debug("materialized %s" % rv)
+            return rv
+        return obj
+
+    def decode(self, s):
+        decoded = json.JSONDecoder.decode(self, s)
+        rv = self.process_file_records(decoded)
+        return rv
+
+
+class Manifest(object):
+
+    valid_formats = ('json',)
+
+    def __init__(self, file_records=[]):
+        self.file_records = file_records
+
+    def __eq__(self, other):
+        if self is other:
+            return True
+        if len(self.file_records) != len(other.file_records):
+            log.debug('Manifests differ in number of files')
+            return False
+        #TODO: Lists in a different order should be equal
+        for record in range(0,len(self.file_records)):
+            if self.file_records[record] != other.file_records[record]:
+                log.debug('FileRecords differ, %s vs %s' % (self.file_records[record],
+                                                            other.file_records[record]))
+                return False
+        return True
+
+    def __deepcopy__(self, memo):
+        # This is required for a deep copy
+        return Manifest(self.file_records[:])
+
+    def __copy__(self):
+        return Manifest(self.file_records)
+
+    def copy(self):
+        return Manifest(self.file_records[:])
+
+    def present(self):
+        return all(i.present() for i in self.file_records)
+
+    def validate_sizes(self):
+        return all(i.validate_size() for i in self.file_records)
+
+    def validate_digests(self):
+        return all(i.validate_digest() for i in self.file_records)
+
+    def validate(self):
+        return all(i.validate() for i in self.file_records)
+
+    def sort(self):
+        #TODO: WRITE TESTS
+        self.file_records.sort(key=lambda x: x.size)
+
+    def load(self, data_file, fmt='json'):
+        assert fmt in self.valid_formats
+        if fmt == 'json':
+            try:
+                self.file_records.extend(json.load(data_file, cls=FileRecordJSONDecoder))
+                self.sort()
+            except ValueError:
+                raise InvalidManifest("trying to read invalid manifest file")
+
+    def loads(self, data_string, fmt='json'):
+        assert fmt in self.valid_formats
+        if fmt == 'json':
+            try:
+                self.file_records.extend(json.loads(data_string, cls=FileRecordJSONDecoder))
+                self.sort()
+            except ValueError:
+                raise InvalidManifest("trying to read invalid manifest file")
+
+    def dump(self, output_file, fmt='json'):
+        assert fmt in self.valid_formats
+        self.sort()
+        if fmt == 'json':
+            rv = json.dump(self.file_records, output_file, indent=0, cls=FileRecordJSONEncoder)
+            print >> output_file, ''
+            return rv
+
+    def dumps(self, fmt='json'):
+        assert fmt in self.valid_formats
+        self.sort()
+        if fmt == 'json':
+            return json.dumps(self.file_records, cls=FileRecordJSONEncoder)
+
+
+def digest_file(f, a):
+    """I take a file like object 'f' and return a hex-string containing
+    of the result of the algorithm 'a' applied to 'f'."""
+    h = hashlib.new(a)
+    chunk_size = 1024*10
+    data = f.read(chunk_size)
+    while data:
+        h.update(data)
+        data = f.read(chunk_size)
+    if hasattr(f, 'name'):
+        log.debug('hashed %s with %s to be %s', f.name, a, h.hexdigest())
+    else:
+        log.debug('hashed a file with %s to be %s', a, h.hexdigest())
+    return h.hexdigest()
+
+# TODO: write tests for this function
+def open_manifest(manifest_file):
+    """I know how to take a filename and load it into a Manifest object"""
+    if os.path.exists(manifest_file):
+        manifest = Manifest()
+        with open(manifest_file) as f:
+            manifest.load(f)
+            log.debug("loaded manifest from file '%s'" % manifest_file)
+        return manifest
+    else:
+        log.debug("tried to load absent file '%s' as manifest" % manifest_file)
+        raise InvalidManifest("manifest file '%s' does not exist" % manifest_file)
+
+# TODO: write tests for this function
+def list_manifest(manifest_file):
+    """I know how print all the files in a location"""
+    try:
+        manifest = open_manifest(manifest_file)
+    except InvalidManifest:
+        log.error("failed to load manifest file at '%s'" % manifest_file)
+        return False
+    for f in manifest.file_records:
+        print "%s\t%s\t%s" % ("P" if f.present() else "-",
+                              "V" if f.present() and f.validate() else "-",
+                              f.filename)
+    return True
+
+def validate_manifest(manifest_file):
+    """I validate that all files in a manifest are present and valid but
+    don't fetch or delete them if they aren't"""
+    try:
+        manifest = open_manifest(manifest_file)
+    except InvalidManifest:
+        log.error("failed to load manifest file at '%s'" % manifest_file)
+        return False
+    invalid_files = []
+    absent_files = []
+    for f in manifest.file_records:
+        if not f.present():
+            absent_files.append(f)
+        else:
+            if not f.validate():
+                invalid_files.append(f)
+    if len(invalid_files + absent_files) == 0:
+        return True
+    else:
+        return False
+
+# TODO: write tests for this function
+def add_files(manifest_file, algorithm, filenames):
+    # returns True if all files successfully added, False if not
+    # and doesn't catch library Exceptions.  If any files are already
+    # tracked in the manifest, return will be False because they weren't
+    # added
+    all_files_added = True
+    # Create a old_manifest object to add to
+    if os.path.exists(manifest_file):
+        old_manifest = open_manifest(manifest_file)
+    else:
+        old_manifest = Manifest()
+        log.debug("creating a new manifest file")
+    new_manifest = Manifest() # use a different manifest for the output
+    for filename in filenames:
+        log.debug("adding %s" % filename)
+        path, name = os.path.split(filename)
+        new_fr = create_file_record(filename, algorithm)
+        log.debug("appending a new file record to manifest file")
+        add = True
+        for fr in old_manifest.file_records:
+            log.debug("manifest file has '%s'" % "', ".join([x.filename for x in old_manifest.file_records]))
+            if new_fr == fr and new_fr.validate():
+                # TODO: Decide if this case should really cause a False return
+                log.info("file already in old_manifest file and matches")
+                add = False
+            elif new_fr == fr and not new_fr.validate():
+                log.error("file already in old_manifest file but is invalid")
+                add = False
+            if filename == fr.filename:
+                log.error("manifest already contains file named %s" % filename)
+                add = False
+        if add:
+            new_manifest.file_records.append(new_fr)
+            log.debug("added '%s' to manifest" % filename)
+        else:
+            all_files_added = False
+    with open(manifest_file, 'wb') as output:
+        new_manifest.dump(output, fmt='json')
+    return all_files_added
+
+
+# TODO: write tests for this function
+def fetch_file(base_url, file_record, overwrite=False, grabchunk=1024*4):
+    # A file which is requested to be fetched that exists locally will be hashed.
+    # If the hash matches the requested file's hash, nothing will be done and the
+    # function will return.  If the function is told to overwrite and there is a 
+    # digest mismatch, the exiting file will be overwritten
+    if file_record.present():
+        if file_record.validate():
+            log.info("existing '%s' is valid, not fetching" % file_record.filename)
+            return True
+        if overwrite:
+            log.info("overwriting '%s' as requested" % file_record.filename)
+        else:
+            # All of the following is for a useful error message
+            with open(file_record.filename, 'rb') as f:
+                d = digest_file(f, file_record.algorithm)
+            log.error("digest mismatch between manifest(%s...) and local file(%s...)" % \
+                    (file_record.digest[:8], d[:8]))
+            log.debug("full digests: manifest (%s) local file (%s)" % (file_record.digest, d))
+            # Let's bail!
+            return False
+
+    # Generate the URL for the file on the server side
+    url = "%s/%s/%s" % (base_url, file_record.algorithm, file_record.digest)
+
+    log.debug("fetching from '%s'" % url)
+
+    # TODO: This should be abstracted to make generic retreival protocol handling easy
+    # Well, the file doesn't exist locally.  Lets fetch it.
+    try:
+        f = urllib2.urlopen(url)
+        log.debug("opened %s for reading" % url)
+        with open(file_record.filename, 'wb') as out:
+            k = True
+            size = 0
+            while k:
+                # TODO: print statistics as file transfers happen both for info and to stop
+                # buildbot timeouts
+                indata = f.read(grabchunk)
+                out.write(indata)
+                size += len(indata)
+                if indata == '':
+                    k = False
+            if size != file_record.size:
+                log.error("transfer from %s to %s failed due to a difference of %d bytes" % (url,
+                            file_record.filename, file_record.size - size))
+                return False
+            log.info("fetched %s" % file_record.filename)
+    except (urllib2.URLError, urllib2.HTTPError) as e:
+        log.error("failed to fetch '%s': %s" % (file_record.filename, e),
+                  exc_info=True)
+        return False
+    except IOError:
+        log.error("failed to write to '%s'" % file_record.filename,
+                  exc_info=True)
+        return False
+    return True
+
+
+# TODO: write tests for this function
+def fetch_files(manifest_file, base_url, overwrite, filenames=[]):
+    # Lets load the manifest file
+    try:
+        manifest = open_manifest(manifest_file)
+    except InvalidManifest:
+        log.error("failed to load manifest file at '%s'" % manifest_file)
+        return False
+    # We want to track files that fail to be fetched as well as
+    # files that are fetched
+    failed_files = []
+
+    # Lets go through the manifest and fetch the files that we want
+    fetched_files = []
+    for f in manifest.file_records:
+        if f.filename in filenames or len(filenames) == 0:
+            log.debug("fetching %s" % f.filename)
+            if fetch_file(base_url, f, overwrite):
+                fetched_files.append(f)
+            else:
+                failed_files.append(f.filename)
+        else:
+            log.debug("skipping %s" % f.filename)
+
+    # Even if we get the file, lets ensure that it matches what the
+    # manifest specified
+    for localfile in fetched_files:
+        if not localfile.validate():
+            log.error("'%s'" % localfile.describe())
+
+    # If we failed to fetch or validate a file, we need to fail
+    if len(failed_files) > 0:
+        log.error("The following files failed: '%s'" % "', ".join(failed_files))
+        return False
+    return True
+
+
+# TODO: write tests for this function
+def process_command(options, args):
+    """ I know how to take a list of program arguments and
+    start doing the right thing with them"""
+    cmd = args[0]
+    cmd_args = args[1:]
+    log.debug("processing '%s' command with args '%s'" % (cmd, '", "'.join(cmd_args)))
+    log.debug("using options: %s" % options)
+    if cmd == 'list':
+        return list_manifest(options['manifest'])
+    if cmd == 'validate':
+        return validate_manifest(options['manifest'])
+    elif cmd == 'add':
+        return add_files(options['manifest'], options['algorithm'], cmd_args)
+    elif cmd == 'fetch':
+        if not options.has_key('base_url') or options.get('base_url') is None:
+            log.critical('fetch command requires url option')
+            return False
+        return fetch_files(options['manifest'], options['base_url'], options['overwrite'], cmd_args)
+    else:
+        log.critical('command "%s" is not implemented' % cmd)
+        return False
+
+# fetching api:
+#   http://hostname/algorithm/hash
+#   example: http://people.mozilla.org/sha1/1234567890abcedf
+# This will make it possible to have the server allow clients to
+# use different algorithms than what was uploaded to the server
+
+# TODO: Implement the following features:
+#   -optimization: do small files first, justification is that they are faster
+#    and cause a faster failure if they are invalid
+#   -store permissions
+#   -local renames i.e. call the file one thing on the server and
+#    something different locally
+#   -deal with the cases:
+#     -local data matches file requested with different filename
+#     -two different files with same name, different hash
+#   -?only ever locally to digest as filename, symlink to real name
+#   -?maybe deal with files as a dir of the filename with all files in that dir as the versions of that file
+#      - e.g. ./python-2.6.7.dmg/0123456789abcdef and ./python-2.6.7.dmg/abcdef0123456789
+
+def main():
+    # Set up logging, for now just to the console
+    ch = logging.StreamHandler()
+    cf = logging.Formatter("%(levelname)s - %(message)s")
+    ch.setFormatter(cf)
+
+    # Set up option parsing
+    parser = optparse.OptionParser()
+    # I wish there was a way to say "only allow args to be
+    # sequential and at the end of the argv.
+    # OH! i could step through sys.argv and check for things starting without -/-- before things starting with them
+    parser.add_option('-q', '--quiet', default=False,
+            dest='quiet', action='store_true')
+    parser.add_option('-v', '--verbose', default=False,
+            dest='verbose', action='store_true')
+    parser.add_option('-m', '--manifest', default='manifest.tt',
+            dest='manifest', action='store',
+            help='specify the manifest file to be operated on')
+    parser.add_option('-d', '--algorithm', default='sha512',
+            dest='algorithm', action='store',
+            help='openssl hashing algorithm to use')
+    parser.add_option('-o', '--overwrite', default=False,
+            dest='overwrite', action='store_true',
+            help='if fetching, remote copy will overwrite a local copy that is different. ')
+    parser.add_option('--url', dest='base_url', action='store',
+            help='base url for fetching files')
+    parser.add_option('--ignore-config-files', action='store_true', default=False,
+                     dest='ignore_cfg_files')
+    (options_obj, args) = parser.parse_args()
+    # Dictionaries are easier to work with
+    options = vars(options_obj)
+
+
+    # Use some of the option parser to figure out application
+    # log level
+    if options.get('verbose'):
+        ch.setLevel(logging.DEBUG)
+    elif options.get('quiet'):
+        ch.setLevel(logging.ERROR)
+    else:
+        ch.setLevel(logging.INFO)
+    log.addHandler(ch)
+
+    cfg_file = ConfigParser.SafeConfigParser()
+    if not options.get("ignore_cfg_files"):
+        read_files = cfg_file.read(['/etc/tooltool', os.path.expanduser('~/.tooltool'),
+                   os.path.join(os.getcwd(), '.tooltool')])
+        log.debug("read in the config files '%s'" % '", '.join(read_files))
+    else:
+        log.debug("skipping config files")
+
+    for option in ('base_url', 'algorithm'):
+        if not options.get(option):
+            try:
+                options[option] = cfg_file.get('general', option)
+                log.debug("read '%s' as '%s' from cfg_file" % (option, options[option]))
+            except (ConfigParser.NoSectionError, ConfigParser.NoOptionError) as e:
+                log.debug("%s in config file" % e, exc_info=True)
+
+    if not options.has_key('manifest'):
+        parser.error("no manifest file specified")
+
+    if len(args) < 1:
+        parser.error('You must specify a command')
+    exit(0 if process_command(options, args) else 1)
+
+if __name__ == "__main__":
+    main()
+else:
+    log.addHandler(logging.NullHandler())
+    #log.addHandler(logging.StreamHandler())
--- a/caps/include/nsScriptSecurityManager.h
+++ b/caps/include/nsScriptSecurityManager.h
@@ -388,19 +388,16 @@ private:
     // GetScriptSecurityManager is the only call that can make one
     nsScriptSecurityManager();
     virtual ~nsScriptSecurityManager();
 
     static JSBool
     CheckObjectAccess(JSContext *cx, JSHandleObject obj,
                       JSHandleId id, JSAccessMode mode,
                       jsval *vp);
-
-    static JSPrincipals *
-    ObjectPrincipalFinder(JSObject *obj);
     
     // Decides, based on CSP, whether or not eval() and stuff can be executed.
     static JSBool
     ContentSecurityPolicyPermitsJSAction(JSContext *cx);
 
     // Returns null if a principal cannot be found; generally callers
     // should error out at that point.
     static nsIPrincipal* doGetObjectPrincipal(JSObject *obj);
--- a/caps/src/nsScriptSecurityManager.cpp
+++ b/caps/src/nsScriptSecurityManager.cpp
@@ -462,22 +462,16 @@ NS_IMPL_ISUPPORTS4(nsScriptSecurityManag
                    nsIObserver)
 
 ///////////////////////////////////////////////////
 // Methods implementing nsIScriptSecurityManager //
 ///////////////////////////////////////////////////
 
 ///////////////// Security Checks /////////////////
 
-/* static */ JSPrincipals *
-nsScriptSecurityManager::ObjectPrincipalFinder(JSObject *aObj)
-{
-    return nsJSPrincipals::get(doGetObjectPrincipal(aObj));
-}
-
 JSBool
 nsScriptSecurityManager::ContentSecurityPolicyPermitsJSAction(JSContext *cx)
 {
     // Get the security manager
     nsScriptSecurityManager *ssm =
         nsScriptSecurityManager::GetScriptSecurityManager();
 
     NS_ASSERTION(ssm, "Failed to get security manager service");
@@ -486,23 +480,18 @@ nsScriptSecurityManager::ContentSecurity
 
     nsresult rv;
     nsIPrincipal* subjectPrincipal = ssm->GetSubjectPrincipal(cx, &rv);
 
     NS_ASSERTION(NS_SUCCEEDED(rv), "CSP: Failed to get nsIPrincipal from js context");
     if (NS_FAILED(rv))
         return JS_FALSE; // Not just absence of principal, but failure.
 
-    if (!subjectPrincipal) {
-        // See bug 553448 for discussion of this case.
-        NS_ASSERTION(!JS_GetSecurityCallbacks(js::GetRuntime(cx))->findObjectPrincipals,
-                     "CSP: Should have been able to find subject principal. "
-                     "Reluctantly granting access.");
+    if (!subjectPrincipal)
         return JS_TRUE;
-    }
 
     nsCOMPtr<nsIContentSecurityPolicy> csp;
     rv = subjectPrincipal->GetCsp(getter_AddRefs(csp));
     NS_ASSERTION(NS_SUCCEEDED(rv), "CSP: Failed to get CSP from principal.");
 
     // don't do anything unless there's a CSP
     if (!csp)
         return JS_TRUE;
@@ -2394,20 +2383,17 @@ nsScriptSecurityManager::old_doGetObject
             if (result) {
                 break;
             }
         } else {
             nsISupports *priv;
             if (!(~jsClass->flags & (JSCLASS_HAS_PRIVATE |
                                      JSCLASS_PRIVATE_IS_NSISUPPORTS))) {
                 priv = (nsISupports *) js::GetObjectPrivate(aObj);
-            } else if (IsDOMClass(jsClass) &&
-                       DOMJSClass::FromJSClass(jsClass)->mDOMObjectIsISupports) {
-                priv = UnwrapDOMObject<nsISupports>(aObj);
-            } else {
+            } else if (!UnwrapDOMObjectToISupports(aObj, priv)) {
                 priv = nullptr;
             }
 
             if (aAllowShortCircuit) {
                 nsCOMPtr<nsIXPConnectWrappedNative> xpcWrapper =
                     do_QueryInterface(priv);
 
                 NS_ASSERTION(!xpcWrapper ||
@@ -2450,81 +2436,20 @@ nsScriptSecurityManager::old_doGetObject
 }
 #endif /* DEBUG */
 
 ///////////////// Capabilities API /////////////////////
 NS_IMETHODIMP
 nsScriptSecurityManager::IsCapabilityEnabled(const char *capability,
                                              bool *result)
 {
-    nsresult rv;
-    JSStackFrame *fp = nullptr;
     JSContext *cx = GetCurrentJSContext();
-    fp = cx ? JS_FrameIterator(cx, &fp) : nullptr;
-
-    if (!fp)
-    {
-        // No script code on stack. Allow access if and only if the subject
-        // principal is system.
-        nsresult ignored;
-        nsIPrincipal *subjectPrin = doGetSubjectPrincipal(&ignored);
-        *result = (!subjectPrin || subjectPrin == mSystemPrincipal);
+    if (cx && (*result = xpc::IsUniversalXPConnectEnabled(cx)))
         return NS_OK;
-    }
-
-    *result = false;
-    nsIPrincipal* previousPrincipal = nullptr;
-    do
-    {
-        nsIPrincipal* principal = GetFramePrincipal(cx, fp, &rv);
-        if (NS_FAILED(rv))
-            return rv;
-        if (!principal)
-            continue;
-        // If caller has a different principal, stop looking up the stack.
-        if(previousPrincipal)
-        {
-            bool isEqual = false;
-            if(NS_FAILED(previousPrincipal->Equals(principal, &isEqual)) || !isEqual)
-                break;
-        }
-        else
-            previousPrincipal = principal;
-
-        // First check if the principal is even able to enable the
-        // given capability. If not, don't look any further.
-        int16_t canEnable;
-        rv = principal->CanEnableCapability(capability, &canEnable);
-        if (NS_FAILED(rv)) return rv;
-        if (canEnable != nsIPrincipal::ENABLE_GRANTED &&
-            canEnable != nsIPrincipal::ENABLE_WITH_USER_PERMISSION)
-            return NS_OK;
-
-        // Now see if the capability is enabled.
-        void *annotation = JS_GetFrameAnnotation(cx, fp);
-        rv = principal->IsCapabilityEnabled(capability, annotation, result);
-        if (NS_FAILED(rv)) return rv;
-        if (*result)
-            return NS_OK;
-
-        // Capabilities do not extend to calls into C/C++ and then back into
-        // the JS engine via JS_EvaluateScript or similar APIs.
-        if (JS_IsGlobalFrame(cx, fp))
-            break;
-    } while ((fp = JS_FrameIterator(cx, &fp)) != nullptr);
-
-    if (!previousPrincipal)
-    {
-        // No principals on the stack, all native code.  Allow
-        // execution if the subject principal is the system principal.
-
-        return SubjectPrincipalIsSystem(result);
-    }
-
-    return NS_OK;
+    return SubjectPrincipalIsSystem(result);
 }
 
 void
 nsScriptSecurityManager::FormatCapabilityString(nsAString& aCapability)
 {
     nsAutoString newcaps;
     nsAutoString rawcap;
     NS_NAMED_LITERAL_STRING(capdesc, "capdesc.");
@@ -3046,18 +2971,16 @@ nsresult nsScriptSecurityManager::Init()
         do_QueryInterface(sXPConnect, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = runtimeService->GetRuntime(&sRuntime);
     NS_ENSURE_SUCCESS(rv, rv);
 
     static const JSSecurityCallbacks securityCallbacks = {
         CheckObjectAccess,
-        nsJSPrincipals::Subsume,
-        ObjectPrincipalFinder,
         ContentSecurityPolicyPermitsJSAction
     };
 
     MOZ_ASSERT(!JS_GetSecurityCallbacks(sRuntime));
     JS_SetSecurityCallbacks(sRuntime, &securityCallbacks);
     JS_InitDestroyPrincipalsCallback(sRuntime, nsJSPrincipals::Destroy);
 
     JS_SetTrustedPrincipals(sRuntime, system);
--- a/caps/src/nsSecurityManagerFactory.cpp
+++ b/caps/src/nsSecurityManagerFactory.cpp
@@ -20,94 +20,37 @@
 #include "nsString.h"
 #include "nsNetCID.h"
 #include "nsIClassInfoImpl.h"
 #include "nsJSUtils.h"
 #include "nsPIDOMWindow.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIDocument.h"
 #include "jsfriendapi.h"
+#include "xpcprivate.h"
+#include "mozilla/Preferences.h"
 
 ///////////////////////
 // nsSecurityNameSet //
 ///////////////////////
 
 nsSecurityNameSet::nsSecurityNameSet()
 {
 }
 
 nsSecurityNameSet::~nsSecurityNameSet()
 {
 }
 
 NS_IMPL_ISUPPORTS1(nsSecurityNameSet, nsIScriptExternalNameSet)
 
-static JSString *
-getStringArgument(JSContext *cx, JSObject *obj, uint16_t argNum, unsigned argc, jsval *argv)
-{
-    if (argc <= argNum || !JSVAL_IS_STRING(argv[argNum])) {
-        JS_ReportError(cx, "String argument expected");
-        return nullptr;
-    }
-
-    /*
-     * We don't want to use JS_ValueToString because we want to be able
-     * to have an object to represent a target in subsequent versions.
-     */
-    return JSVAL_TO_STRING(argv[argNum]);
-}
-
-static bool
-getBytesArgument(JSContext *cx, JSObject *obj, uint16_t argNum, unsigned argc, jsval *argv,
-                 JSAutoByteString *bytes)
-{
-    JSString *str = getStringArgument(cx, obj, argNum, argc, argv);
-    return str && bytes->encode(cx, str);
-}
-
 static JSBool
 netscape_security_enablePrivilege(JSContext *cx, unsigned argc, jsval *vp)
 {
-    JSObject *obj = JS_THIS_OBJECT(cx, vp);
-    if (!obj)
-        return JS_FALSE;
-
-    JSAutoByteString cap;
-    if (!getBytesArgument(cx, obj, 0, argc, JS_ARGV(cx, vp), &cap))
-        return JS_FALSE;
-
-    // Can't use nsContentUtils::GetDocumentFromCaller because that
-    // depends on various XPConnect stuff that's not set up here.
-    {
-        JSAutoEnterCompartment ac;
-        if (ac.enter(cx, obj)) {
-            nsCOMPtr<nsPIDOMWindow> win =
-                do_QueryInterface(nsJSUtils::GetStaticScriptGlobal(cx, obj));
-            if (win) {
-                nsCOMPtr<nsIDocument> doc =
-                    do_QueryInterface(win->GetExtantDocument());
-                if (doc) {
-                    doc->WarnOnceAbout(nsIDocument::eEnablePrivilege);
-                }
-            }
-        }
-    }
-
-    nsresult rv;
-    nsCOMPtr<nsIScriptSecurityManager> securityManager = 
-             do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv);
-    if (NS_FAILED(rv)) 
-        return JS_FALSE;
-
-    //    NS_ASSERTION(cx == GetCurrentContext(), "unexpected context");
-
-    rv = securityManager->EnableCapability(cap.ptr());
-    if (NS_FAILED(rv))
-        return JS_FALSE;
-    JS_SET_RVAL(cx, vp, JSVAL_VOID);
+    xpc::EnableUniversalXPConnect(cx);
     return JS_TRUE;
 }
 
 static JSFunctionSpec PrivilegeManager_static_methods[] = {
     JS_FS("enablePrivilege", netscape_security_enablePrivilege, 1, 0),
     JS_FS_END
 };
 
@@ -116,16 +59,25 @@ static JSFunctionSpec PrivilegeManager_s
  * et al. so that code that worked with 4.0 can still work.
  */
 NS_IMETHODIMP 
 nsSecurityNameSet::InitializeNameSet(nsIScriptContext* aScriptContext)
 {
     JSContext* cx = aScriptContext->GetNativeContext();
     JSObject *global = JS_ObjectToInnerObject(cx, JS_GetGlobalObject(cx));
 
+    // We hide enablePrivilege behind a pref because it has been altered in a
+    // way that makes it fundamentally insecure to use in production. Mozilla
+    // uses this pref during automated testing to support legacy test code that
+    // uses enablePrivilege. If you're not doing test automation, you _must_ not
+    // flip this pref, or you will be exposing all your users to security
+    // vulnerabilities.
+    if (!mozilla::Preferences::GetBool("security.enablePrivilege.enable_for_tests"))
+        return NS_OK;
+
     /*
      * Find Object.prototype's class by walking up the global object's
      * prototype chain.
      */
     JSObject *obj = global;
     JSObject *proto;
     JSAutoRequest ar(cx);
     while ((proto = JS_GetPrototype(obj)) != nullptr)
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -1582,32 +1582,30 @@ endif # COMPILER_DEPEND
 #   a previous build in the source tree) and thus neglect to create a
 #   dependency directory in the object directory, where we really need
 #   it.
 
 $(CURDIR)/$(MDDEPDIR):
 	$(MKDIR) -p $@
 
 ifneq (,$(filter-out all chrome default export realchrome tools clean clobber clobber_all distclean realclean,$(MAKECMDGOALS)))
-ifneq (,$(OBJS)$(XPIDLSRCS)$(SIMPLE_PROGRAMS))
 MDDEPEND_FILES		:= $(strip $(wildcard $(MDDEPDIR)/*.pp))
 
 ifneq (,$(MDDEPEND_FILES))
 # The script mddepend.pl checks the dependencies and writes to stdout
 # one rule to force out-of-date objects. For example,
 #   foo.o boo.o: FORCE
 # The script has an advantage over including the *.pp files directly
 # because it handles the case when header files are removed from the build.
 # 'make' would complain that there is no way to build missing headers.
 ALL_PP_RESULTS = $(shell $(PERL) $(BUILD_TOOLS)/mddepend.pl - $(MDDEPEND_FILES))
 $(eval $(ALL_PP_RESULTS))
 endif
 
 endif
-endif
 #############################################################################
 
 -include $(topsrcdir)/$(MOZ_BUILD_APP)/app-rules.mk
 -include $(MY_RULES)
 
 #
 # Generate Emacs tags in a file named TAGS if ETAGS was set in $(MY_CONFIG)
 # or in $(MY_RULES)
--- a/content/base/public/Makefile.in
+++ b/content/base/public/Makefile.in
@@ -37,17 +37,16 @@ nsContentCID.h \
 nsCopySupport.h \
 nsContentCreatorFunctions.h \
 nsDOMFile.h \
 nsLineBreaker.h \
 nsReferencedElement.h \
 nsTreeSanitizer.h \
 nsXMLNameSpaceMap.h \
 nsIXFormsUtilityService.h \
-nsBlobProtocolHandler.h \
 $(NULL)
 
 EXPORTS_NAMESPACES = mozilla/dom mozilla
 
 EXPORTS_mozilla/dom = \
 		DirectionalityUtils.h \
 		Element.h \
 		FragmentOrElement.h \
deleted file mode 100644
--- a/content/base/public/nsBlobProtocolHandler.h
+++ /dev/null
@@ -1,51 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef nsBlobProtocolHandler_h
-#define nsBlobProtocolHandler_h
-
-#include "nsIProtocolHandler.h"
-#include "nsIURI.h"
-#include "nsCOMPtr.h"
-
-#define BLOBURI_SCHEME "blob"
-
-class nsIDOMBlob;
-class nsIPrincipal;
-class nsIInputStream;
-
-inline bool IsBlobURI(nsIURI* aUri)
-{
-  bool isBlob;
-  return NS_SUCCEEDED(aUri->SchemeIs(BLOBURI_SCHEME, &isBlob)) && isBlob;
-}
-
-extern nsresult
-NS_GetStreamForBlobURI(nsIURI* aURI, nsIInputStream** aStream);
-
-class nsBlobProtocolHandler : public nsIProtocolHandler
-{
-public:
-  NS_DECL_ISUPPORTS
-
-  // nsIProtocolHandler methods:
-  NS_DECL_NSIPROTOCOLHANDLER
-
-  // nsBlobProtocolHandler methods:
-  nsBlobProtocolHandler() {}
-  virtual ~nsBlobProtocolHandler() {}
-
-  // Methods for managing uri->file mapping
-  static void AddFileDataEntry(nsACString& aUri,
-                               nsIDOMBlob* aFile,
-                               nsIPrincipal* aPrincipal);
-  static void RemoveFileDataEntry(nsACString& aUri);
-  static nsIPrincipal* GetFileDataEntryPrincipal(nsACString& aUri);
-};
-
-#define NS_BLOBPROTOCOLHANDLER_CID \
-{ 0xb43964aa, 0xa078, 0x44b2, \
-  { 0xb0, 0x6b, 0xfd, 0x4d, 0x1b, 0x17, 0x2e, 0x66 } }
-
-#endif /* nsBlobProtocolHandler_h */
--- a/content/base/src/FragmentOrElement.cpp
+++ b/content/base/src/FragmentOrElement.cpp
@@ -386,17 +386,17 @@ NS_INTERFACE_TABLE_HEAD(nsChildContentLi
   NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION(nsChildContentList)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(NodeList)
 NS_INTERFACE_MAP_END
 
 JSObject*
 nsChildContentList::WrapObject(JSContext *cx, JSObject *scope,
                                bool *triedToWrap)
 {
-  return mozilla::dom::binding::NodeList::create(cx, scope, this, triedToWrap);
+  return mozilla::dom::oldproxybindings::NodeList::create(cx, scope, this, triedToWrap);
 }
 
 NS_IMETHODIMP
 nsChildContentList::GetLength(uint32_t* aLength)
 {
   *aLength = mNode ? mNode->GetChildCount() : 0;
 
   return NS_OK;
--- a/content/base/src/nsAttrValue.cpp
+++ b/content/base/src/nsAttrValue.cpp
@@ -205,16 +205,26 @@ nsAttrValue::SetTo(const nsAttrValue& aO
       cont->mColor = otherCont->mColor;
       break;
     }
     case eCSSStyleRule:
     {
       NS_ADDREF(cont->mCSSStyleRule = otherCont->mCSSStyleRule);
       break;
     }
+    case eURL:
+    {
+      NS_ADDREF(cont->mURL = otherCont->mURL);
+      break;
+    }
+    case eImage:
+    {
+      NS_ADDREF(cont->mImage = otherCont->mImage);
+      break;
+    }
     case eAtomArray:
     {
       if (!EnsureEmptyAtomArray() ||
           !GetAtomArrayValue()->AppendElements(*otherCont->mAtomArray)) {
         Reset();
         return;
       }
       break;
@@ -310,16 +320,27 @@ nsAttrValue::SetTo(css::StyleRule* aValu
     MiscContainer* cont = GetMiscContainer();
     NS_ADDREF(cont->mCSSStyleRule = aValue);
     cont->mType = eCSSStyleRule;
     SetMiscAtomOrString(aSerialized);
   }
 }
 
 void
+nsAttrValue::SetTo(css::URLValue* aValue, const nsAString* aSerialized)
+{
+  if (EnsureEmptyMiscContainer()) {
+    MiscContainer* cont = GetMiscContainer();
+    NS_ADDREF(cont->mURL = aValue);
+    cont->mType = eURL;
+    SetMiscAtomOrString(aSerialized);
+  }
+}
+
+void
 nsAttrValue::SetTo(const nsIntMargin& aValue)
 {
   if (EnsureEmptyMiscContainer()) {
     MiscContainer* cont = GetMiscContainer();
     cont->mIntMargin = new nsIntMargin(aValue);
     cont->mType = eIntMarginValue;
   }
 }
@@ -769,16 +790,25 @@ nsAttrValue::HashValue() const
     case eColor:
     {
       return cont->mColor;
     }
     case eCSSStyleRule:
     {
       return NS_PTR_TO_INT32(cont->mCSSStyleRule);
     }
+    // Intentionally identical, so that loading the image does not change the
+    // hash code.
+    case eURL:
+    case eImage:
+    {
+      nsString str;
+      ToString(str);
+      return HashString(str);
+    }
     case eAtomArray:
     {
       uint32_t hash = 0;
       uint32_t count = cont->mAtomArray->Length();
       for (nsCOMPtr<nsIAtom> *cur = cont->mAtomArray->Elements(),
                              *end = cur + count;
            cur != end; ++cur) {
         hash = AddToHash(hash, cur->get());
@@ -865,16 +895,24 @@ nsAttrValue::Equals(const nsAttrValue& a
         needsStringComparison = true;
       }
       break;
     }
     case eCSSStyleRule:
     {
       return thisCont->mCSSStyleRule == otherCont->mCSSStyleRule;
     }
+    case eURL:
+    {
+      return thisCont->mURL == otherCont->mURL;
+    }
+    case eImage:
+    {
+      return thisCont->mImage == otherCont->mImage;
+    }
     case eAtomArray:
     {
       // For classlists we could be insensitive to order, however
       // classlists are never mapped attributes so they are never compared.
 
       if (!(*thisCont->mAtomArray == *otherCont->mAtomArray)) {
         return false;
       }
@@ -1495,16 +1533,41 @@ nsAttrValue::ParseIntMarginValue(const n
     cont->mType = eIntMarginValue;
     SetMiscAtomOrString(&aString);
     return true;
   }
 
   return false;
 }
 
+bool
+nsAttrValue::LoadImage(nsIDocument* aDocument)
+{
+  NS_ASSERTION(Type() == eURL, "wrong type");
+
+  nsString val;
+  ToString(val);
+  if (val.IsEmpty()) {
+    return false;
+  }
+
+  MiscContainer* cont = GetMiscContainer();
+  mozilla::css::URLValue* url = cont->mURL;
+  mozilla::css::ImageValue* image = 
+    new css::ImageValue(url->GetURI(), url->mString, url->mReferrer,
+                        url->mOriginPrincipal, aDocument);
+
+  NS_ADDREF(image);
+  cont->mImage = image;
+  NS_RELEASE(url);
+  cont->mType = eImage;
+
+  return true;
+}
+
 void
 nsAttrValue::SetMiscAtomOrString(const nsAString* aValue)
 {
   NS_ASSERTION(GetMiscContainer(), "Must have MiscContainer!");
   NS_ASSERTION(!GetMiscContainer()->mStringBits,
                "Trying to re-set atom or string!");
   if (aValue) {
     uint32_t len = aValue->Length();
@@ -1571,16 +1634,26 @@ nsAttrValue::EnsureEmptyMiscContainer()
     ResetMiscAtomOrString();
     cont = GetMiscContainer();
     switch (cont->mType) {
       case eCSSStyleRule:
       {
         NS_RELEASE(cont->mCSSStyleRule);
         break;
       }
+      case eURL:
+      {
+        NS_RELEASE(cont->mURL);
+        break;
+      }
+      case eImage:
+      {
+        NS_RELEASE(cont->mImage);
+        break;
+      }
       case eAtomArray:
       {
         delete cont->mAtomArray;
         break;
       }
       case eIntMarginValue:
       {
         delete cont->mIntMargin;
--- a/content/base/src/nsAttrValue.h
+++ b/content/base/src/nsAttrValue.h
@@ -18,22 +18,25 @@
 #include "nsCaseTreatment.h"
 #include "nsMargin.h"
 #include "nsCOMPtr.h"
 #include "SVGAttrValueWrapper.h"
 
 typedef PRUptrdiff PtrBits;
 class nsAString;
 class nsIAtom;
+class nsIDocument;
 template<class E, class A> class nsTArray;
 struct nsTArrayDefaultAllocator;
 
 namespace mozilla {
 namespace css {
 class StyleRule;
+struct URLValue;
+struct ImageValue;
 }
 }
 
 #define NS_ATTRVALUE_MAX_STRINGLENGTH_ATOM 12
 
 #define NS_ATTRVALUE_BASETYPE_MASK (PtrBits(3))
 #define NS_ATTRVALUE_POINTERVALUE_MASK (~NS_ATTRVALUE_BASETYPE_MASK)
 
@@ -86,46 +89,49 @@ public:
     eAtom =         0x02, //   10
     eInteger =      0x03, // 0011
     eColor =        0x07, // 0111
     eEnum =         0x0B, // 1011  This should eventually die
     ePercent =      0x0F, // 1111
     // Values below here won't matter, they'll be always stored in the 'misc'
     // struct.
     eCSSStyleRule =    0x10
-    ,eAtomArray =      0x11
-    ,eDoubleValue  =   0x12
-    ,eIntMarginValue = 0x13
-    ,eSVGTypesBegin =  0x14
+    ,eURL =            0x11
+    ,eImage =          0x12
+    ,eAtomArray =      0x13
+    ,eDoubleValue  =   0x14
+    ,eIntMarginValue = 0x15
+    ,eSVGTypesBegin =  0x16
     ,eSVGAngle =       eSVGTypesBegin
-    ,eSVGIntegerPair = 0x15
-    ,eSVGLength =      0x16
-    ,eSVGLengthList =  0x17
-    ,eSVGNumberList =  0x18
-    ,eSVGNumberPair =  0x19
-    ,eSVGPathData   =  0x20
-    ,eSVGPointList  =  0x21
-    ,eSVGPreserveAspectRatio = 0x22
-    ,eSVGStringList =  0x23
-    ,eSVGTransformList = 0x24
-    ,eSVGViewBox =     0x25
+    ,eSVGIntegerPair = 0x17
+    ,eSVGLength =      0x18
+    ,eSVGLengthList =  0x19
+    ,eSVGNumberList =  0x20
+    ,eSVGNumberPair =  0x21
+    ,eSVGPathData   =  0x22
+    ,eSVGPointList  =  0x23
+    ,eSVGPreserveAspectRatio = 0x24
+    ,eSVGStringList =  0x25
+    ,eSVGTransformList = 0x26
+    ,eSVGViewBox =     0x27
     ,eSVGTypesEnd =    0x34
   };
 
   ValueType Type() const;
 
   void Reset();
 
   void SetTo(const nsAttrValue& aOther);
   void SetTo(const nsAString& aValue);
   void SetTo(nsIAtom* aValue);
   void SetTo(int16_t aInt);
   void SetTo(int32_t aInt, const nsAString* aSerialized);
   void SetTo(double aValue, const nsAString* aSerialized);
   void SetTo(mozilla::css::StyleRule* aValue, const nsAString* aSerialized);
+  void SetTo(mozilla::css::URLValue* aValue, const nsAString* aSerialized);
   void SetTo(const nsIntMargin& aValue);
   void SetTo(const nsSVGAngle& aValue, const nsAString* aSerialized);
   void SetTo(const nsSVGIntegerPair& aValue, const nsAString* aSerialized);
   void SetTo(const nsSVGLength2& aValue, const nsAString* aSerialized);
   void SetTo(const mozilla::SVGLengthList& aValue,
              const nsAString* aSerialized);
   void SetTo(const mozilla::SVGNumberList& aValue,
              const nsAString* aSerialized);
@@ -164,16 +170,18 @@ public:
   const nsCheapString GetStringValue() const;
   inline nsIAtom* GetAtomValue() const;
   inline int32_t GetIntegerValue() const;
   bool GetColorValue(nscolor& aColor) const;
   inline int16_t GetEnumValue() const;
   inline float GetPercentValue() const;
   inline AtomArray* GetAtomArrayValue() const;
   inline mozilla::css::StyleRule* GetCSSStyleRuleValue() const;
+  inline mozilla::css::URLValue* GetURLValue() const;
+  inline mozilla::css::ImageValue* GetImageValue() const;
   inline double GetDoubleValue() const;
   bool GetIntMarginValue(nsIntMargin& aMargin) const;
 
   /**
    * Returns the string corresponding to the stored enum value.
    *
    * @param aResult   the string representing the enum tag
    * @param aRealTag  wheter we want to have the real tag or the saved one
@@ -337,16 +345,24 @@ public:
    * Parse a margin string of format 'top, right, bottom, left' into
    * an nsIntMargin.
    *
    * @param aString the string to parse
    * @return whether the value could be parsed
    */
   bool ParseIntMarginValue(const nsAString& aString);
 
+  /**
+   * Convert a URL nsAttrValue to an Image nsAttrValue.
+   *
+   * @param aDocument the document this nsAttrValue belongs to.
+   * @return whether an image load was attempted
+   */
+  bool LoadImage(nsIDocument* aDocument);
+
   size_t SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const;
 
 private:
   // These have to be the same as in ValueType
   enum ValueBaseType {
     eStringBase =    eString,    // 00
     eOtherBase =     0x01,       // 01
     eAtomBase =      eAtom,      // 10
@@ -362,16 +378,18 @@ private:
     // mStringBits.
     PtrBits mStringBits;
     union {
       int32_t mInteger;
       nscolor mColor;
       uint32_t mEnumValue;
       int32_t mPercent;
       mozilla::css::StyleRule* mCSSStyleRule;
+      mozilla::css::URLValue* mURL;
+      mozilla::css::ImageValue* mImage;
       AtomArray* mAtomArray;
       double mDoubleValue;
       nsIntMargin* mIntMargin;
       const nsSVGAngle* mSVGAngle;
       const nsSVGIntegerPair* mSVGIntegerPair;
       const nsSVGLength2* mSVGLength;
       const mozilla::SVGLengthList* mSVGLengthList;
       const mozilla::SVGNumberList* mSVGNumberList;
@@ -490,16 +508,30 @@ nsAttrValue::GetAtomArrayValue() const
 
 inline mozilla::css::StyleRule*
 nsAttrValue::GetCSSStyleRuleValue() const
 {
   NS_PRECONDITION(Type() == eCSSStyleRule, "wrong type");
   return GetMiscContainer()->mCSSStyleRule;
 }
 
+inline mozilla::css::URLValue*
+nsAttrValue::GetURLValue() const
+{
+  NS_PRECONDITION(Type() == eURL, "wrong type");
+  return GetMiscContainer()->mURL;
+}
+
+inline mozilla::css::ImageValue*
+nsAttrValue::GetImageValue() const
+{
+  NS_PRECONDITION(Type() == eImage, "wrong type");
+  return GetMiscContainer()->mImage;
+}
+
 inline double
 nsAttrValue::GetDoubleValue() const
 {
   NS_PRECONDITION(Type() == eDoubleValue, "wrong type");
   return GetMiscContainer()->mDoubleValue;
 }
 
 inline bool
--- a/content/base/src/nsBlobProtocolHandler.cpp
+++ b/content/base/src/nsBlobProtocolHandler.cpp
@@ -158,18 +158,18 @@ nsBlobProtocolHandler::NewChannel(nsIURI
 #endif
 
   nsCOMPtr<nsIInputStream> stream;
   nsresult rv = info->mFile->GetInternalStream(getter_AddRefs(stream));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIChannel> channel;
   rv = NS_NewInputStreamChannel(getter_AddRefs(channel),
-                                uri,
-                                stream);
+				uri,
+				stream);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsISupports> owner = do_QueryInterface(info->mPrincipal);
 
   nsAutoString type;
   rv = info->mFile->GetType(type);
   NS_ENSURE_SUCCESS(rv, rv);
 
@@ -184,28 +184,8 @@ nsBlobProtocolHandler::NewChannel(nsIURI
 NS_IMETHODIMP 
 nsBlobProtocolHandler::AllowPort(int32_t port, const char *scheme,
                                      bool *_retval)
 {
     // don't override anything.  
     *_retval = false;
     return NS_OK;
 }
-
-nsresult
-NS_GetStreamForBlobURI(nsIURI* aURI, nsIInputStream** aStream)
-{
-  NS_ASSERTION(IsBlobURI(aURI), "Only call this with blob URIs");
-
-  *aStream = nullptr;
-
-  nsCString spec;
-  aURI->GetSpec(spec);
-
-  FileDataInfo* info =
-    GetFileDataInfo(spec);
-
-  if (!info) {
-    return NS_ERROR_DOM_BAD_URI;
-  }
-
-  return info->mFile->GetInternalStream(aStream);
-}
--- a/content/base/src/nsBlobProtocolHandler.h
+++ b/content/base/src/nsBlobProtocolHandler.h
@@ -1,51 +1,40 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsBlobProtocolHandler_h
 #define nsBlobProtocolHandler_h
 
 #include "nsIProtocolHandler.h"
-#include "nsIURI.h"
-#include "nsCOMPtr.h"
 
 #define BLOBURI_SCHEME "blob"
 
 class nsIDOMBlob;
 class nsIPrincipal;
-class nsIInputStream;
-
-inline bool IsBlobURI(nsIURI* aUri)
-{
-  bool isBlob;
-  return NS_SUCCEEDED(aUri->SchemeIs(BLOBURI_SCHEME, &isBlob)) && isBlob;
-}
-
-extern nsresult
-NS_GetStreamForBlobURI(nsIURI* aURI, nsIInputStream** aStream);
 
 class nsBlobProtocolHandler : public nsIProtocolHandler
 {
 public:
   NS_DECL_ISUPPORTS
 
   // nsIProtocolHandler methods:
   NS_DECL_NSIPROTOCOLHANDLER
 
   // nsBlobProtocolHandler methods:
   nsBlobProtocolHandler() {}
   virtual ~nsBlobProtocolHandler() {}
 
   // Methods for managing uri->file mapping
   static void AddFileDataEntry(nsACString& aUri,
-                               nsIDOMBlob* aFile,
+			       nsIDOMBlob* aFile,
                                nsIPrincipal* aPrincipal);
   static void RemoveFileDataEntry(nsACString& aUri);
   static nsIPrincipal* GetFileDataEntryPrincipal(nsACString& aUri);
+  
 };
 
 #define NS_BLOBPROTOCOLHANDLER_CID \
 { 0xb43964aa, 0xa078, 0x44b2, \
   { 0xb0, 0x6b, 0xfd, 0x4d, 0x1b, 0x17, 0x2e, 0x66 } }
 
 #endif /* nsBlobProtocolHandler_h */
--- a/content/base/src/nsContentList.cpp
+++ b/content/base/src/nsContentList.cpp
@@ -157,17 +157,17 @@ NS_INTERFACE_MAP_END_INHERITING(nsBaseCo
 
 NS_IMPL_ADDREF_INHERITED(nsSimpleContentList, nsBaseContentList)
 NS_IMPL_RELEASE_INHERITED(nsSimpleContentList, nsBaseContentList)
 
 JSObject*
 nsSimpleContentList::WrapObject(JSContext *cx, JSObject *scope,
                                 bool *triedToWrap)
 {
-  return mozilla::dom::binding::NodeList::create(cx, scope, this, triedToWrap);
+  return mozilla::dom::oldproxybindings::NodeList::create(cx, scope, this, triedToWrap);
 }
 
 // nsFormContentList
 
 nsFormContentList::nsFormContentList(nsIContent *aForm,
                                      nsBaseContentList& aContentList)
   : nsSimpleContentList(aForm)
 {
@@ -473,17 +473,17 @@ nsContentList::~nsContentList()
     // Clean up mData
     (*mDestroyFunc)(mData);
   }
 }
 
 JSObject*
 nsContentList::WrapObject(JSContext *cx, JSObject *scope, bool *triedToWrap)
 {
-  return mozilla::dom::binding::HTMLCollection::create(cx, scope, this,
+  return mozilla::dom::oldproxybindings::HTMLCollection::create(cx, scope, this,
                                                        triedToWrap);
 }
 
 DOMCI_DATA(ContentList, nsContentList)
 
 // QueryInterface implementation for nsContentList
 NS_INTERFACE_TABLE_HEAD(nsContentList)
   NS_NODELIST_OFFSET_AND_INTERFACE_TABLE_BEGIN(nsContentList)
--- a/content/base/src/nsContentSink.cpp
+++ b/content/base/src/nsContentSink.cpp
@@ -75,17 +75,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mDocument)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mParser)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mNodeInfoManager)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mScriptLoader)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsContentSink)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mDocument)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mParser)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mNodeInfoManager)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_MEMBER(mNodeInfoManager,
+                                                  nsNodeInfoManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mScriptLoader)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 
 nsContentSink::nsContentSink()
 {
   // We have a zeroing operator new
   NS_ASSERTION(!mLayoutStarted, "What?");
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -1770,20 +1770,17 @@ nsContentUtils::GetWindowFromCaller()
 nsIDOMDocument *
 nsContentUtils::GetDocumentFromCaller()
 {
   JSContext *cx = nullptr;
   JSObject *obj = nullptr;
   sXPConnect->GetCaller(&cx, &obj);
   NS_ASSERTION(cx && obj, "Caller ensures something is running");
 
-  JSAutoEnterCompartment ac;
-  if (!ac.enter(cx, obj)) {
-    return nullptr;
-  }
+  JSAutoCompartment ac(cx, obj);
 
   nsCOMPtr<nsPIDOMWindow> win =
     do_QueryInterface(nsJSUtils::GetStaticScriptGlobal(cx, obj));
   if (!win) {
     return nullptr;
   }
 
   return win->GetExtantDocument();
@@ -5968,24 +5965,24 @@ nsContentUtils::CanAccessNativeAnon()
     return true;
   }
   JSStackFrame* fp;
   nsIPrincipal* principal =
     sSecurityManager->GetCxSubjectPrincipalAndFrame(cx, &fp);
   NS_ENSURE_TRUE(principal, false);
 
   JSScript *script = nullptr;
-  if (!fp) {
+  if (fp) {
+    script = JS_GetFrameScript(cx, fp);
+  } else {
     if (!JS_DescribeScriptedCaller(cx, &script, nullptr)) {
       // No code at all is running. So we must be arriving here as the result
       // of C++ code asking us to do something. Allow access.
       return true;
     }
-  } else if (JS_IsScriptFrame(cx, fp)) {
-    script = JS_GetFrameScript(cx, fp);
   }
 
   bool privileged;
   if (NS_SUCCEEDED(sSecurityManager->IsSystemPrincipal(principal, &privileged)) &&
       privileged) {
     // Chrome things are allowed to touch us.
     return true;
   }
@@ -6882,20 +6879,17 @@ nsContentUtils::JSArrayToAtomArray(JSCon
                                    nsCOMArray<nsIAtom>& aRetVal)
 {
   JSAutoRequest ar(aCx);
   if (!aJSArray.isObject()) {
     return NS_ERROR_ILLEGAL_VALUE;
   }
   
   JSObject* obj = &aJSArray.toObject();
-  JSAutoEnterCompartment ac;
-  if (!ac.enter(aCx, obj)) {
-    return NS_ERROR_ILLEGAL_VALUE;
-  }
+  JSAutoCompartment ac(aCx, obj);
   
   uint32_t length;
   if (!JS_IsArrayObject(aCx, obj) || !JS_GetArrayLength(aCx, obj, &length)) {
     return NS_ERROR_ILLEGAL_VALUE;
   }
 
   JSString* str = nullptr;
   JS::Anchor<JSString *> deleteProtector(str);
--- a/content/base/src/nsDOMFile.cpp
+++ b/content/base/src/nsDOMFile.cpp
@@ -13,17 +13,16 @@
 #include "nsICharsetDetector.h"
 #include "nsICharsetConverterManager.h"
 #include "nsIClassInfo.h"
 #include "nsIConverterInputStream.h"
 #include "nsIDocument.h"
 #include "nsIFileStreams.h"
 #include "nsIInputStream.h"
 #include "nsIIPCSerializableInputStream.h"
-#include "nsIIPCSerializableObsolete.h"
 #include "nsIMIMEService.h"
 #include "nsIPlatformCharset.h"
 #include "nsISeekableStream.h"
 #include "nsIUnicharInputStream.h"
 #include "nsIUnicodeDecoder.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
 #include "nsIUUIDGenerator.h"
@@ -43,68 +42,57 @@ using namespace mozilla::dom;
 
 // XXXkhuey the input stream that we pass out of a DOMFile
 // can outlive the actual DOMFile object.  Thus, we must
 // ensure that the buffer underlying the stream we get
 // from NS_NewByteInputStream is held alive as long as the
 // stream is.  We do that by passing back this class instead.
 class DataOwnerAdapter MOZ_FINAL : public nsIInputStream,
                                    public nsISeekableStream,
-                                   public nsIIPCSerializableObsolete,
-                                   public nsIClassInfo,
                                    public nsIIPCSerializableInputStream
 {
   typedef nsDOMMemoryFile::DataOwner DataOwner;
 public:
   static nsresult Create(DataOwner* aDataOwner,
                          uint32_t aStart,
                          uint32_t aLength,
                          nsIInputStream** _retval);
 
   NS_DECL_ISUPPORTS
 
   // These are mandatory.
   NS_FORWARD_NSIINPUTSTREAM(mStream->)
   NS_FORWARD_NSISEEKABLESTREAM(mSeekableStream->)
 
-  // These are optional. We use a conditional QI to keep them from being called
-  // if the underlying stream doesn't QI to either interface.
-  NS_FORWARD_NSIIPCSERIALIZABLEOBSOLETE(mSerializableObsolete->)
-  NS_FORWARD_NSICLASSINFO(mClassInfo->)
+  // This is optional. We use a conditional QI to keep it from being called
+  // if the underlying stream doesn't support it.
   NS_FORWARD_NSIIPCSERIALIZABLEINPUTSTREAM(mSerializableInputStream->)
 
 private:
   DataOwnerAdapter(DataOwner* aDataOwner,
                    nsIInputStream* aStream)
     : mDataOwner(aDataOwner), mStream(aStream),
       mSeekableStream(do_QueryInterface(aStream)),
-      mSerializableObsolete(do_QueryInterface(aStream)),
-      mClassInfo(do_QueryInterface(aStream)),
       mSerializableInputStream(do_QueryInterface(aStream))
   {
     NS_ASSERTION(mSeekableStream, "Somebody gave us the wrong stream!");
   }
 
   nsRefPtr<DataOwner> mDataOwner;
   nsCOMPtr<nsIInputStream> mStream;
   nsCOMPtr<nsISeekableStream> mSeekableStream;
-  nsCOMPtr<nsIIPCSerializableObsolete> mSerializableObsolete;
-  nsCOMPtr<nsIClassInfo> mClassInfo;
   nsCOMPtr<nsIIPCSerializableInputStream> mSerializableInputStream;
 };
 
 NS_IMPL_THREADSAFE_ADDREF(DataOwnerAdapter)
 NS_IMPL_THREADSAFE_RELEASE(DataOwnerAdapter)
 
 NS_INTERFACE_MAP_BEGIN(DataOwnerAdapter)
   NS_INTERFACE_MAP_ENTRY(nsIInputStream)
   NS_INTERFACE_MAP_ENTRY(nsISeekableStream)
-  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableObsolete,
-                                     mSerializableObsolete)
-  NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIClassInfo, mClassInfo)
   NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream,
                                      mSerializableInputStream)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
 NS_INTERFACE_MAP_END
 
 nsresult DataOwnerAdapter::Create(DataOwner* aDataOwner,
                                   uint32_t aStart,
                                   uint32_t aLength,
@@ -683,17 +671,17 @@ NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMFileList)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMFileList)
 
 JSObject*
 nsDOMFileList::WrapObject(JSContext *cx, JSObject *scope,
                           bool *triedToWrap)
 {
-  return mozilla::dom::binding::FileList::create(cx, scope, this, triedToWrap);
+  return mozilla::dom::oldproxybindings::FileList::create(cx, scope, this, triedToWrap);
 }
 
 nsIDOMFile*
 nsDOMFileList::GetItemAt(uint32_t aIndex)
 {
   return mFiles.SafeObjectAt(aIndex);
 }
 
--- a/content/base/src/nsDOMSettableTokenList.cpp
+++ b/content/base/src/nsDOMSettableTokenList.cpp
@@ -46,11 +46,11 @@ nsDOMSettableTokenList::SetValue(const n
 
   return mElement->SetAttr(kNameSpaceID_None, mAttrAtom, aValue, true);
 }
 
 JSObject*
 nsDOMSettableTokenList::WrapObject(JSContext *cx, JSObject *scope,
                                    bool *triedToWrap)
 {
-  return mozilla::dom::binding::DOMSettableTokenList::create(cx, scope, this,
+  return mozilla::dom::oldproxybindings::DOMSettableTokenList::create(cx, scope, this,
                                                              triedToWrap);
 }
--- a/content/base/src/nsDOMTokenList.cpp
+++ b/content/base/src/nsDOMTokenList.cpp
@@ -267,12 +267,12 @@ nsDOMTokenList::ToString(nsAString& aRes
   mElement->GetAttr(kNameSpaceID_None, mAttrAtom, aResult);
 
   return NS_OK;
 }
 
 JSObject*
 nsDOMTokenList::WrapObject(JSContext *cx, JSObject *scope, bool *triedToWrap)
 {
-  return mozilla::dom::binding::DOMTokenList::create(cx, scope, this,
+  return mozilla::dom::oldproxybindings::DOMTokenList::create(cx, scope, this,
                                                      triedToWrap);
 }
 
--- a/content/base/src/nsFrameMessageManager.cpp
+++ b/content/base/src/nsFrameMessageManager.cpp
@@ -411,20 +411,17 @@ nsFrameMessageManager::ReceiveMessage(ns
         wrappedJS->GetJSObject(&object);
         if (!object) {
           continue;
         }
         nsCxPusher pusher;
         NS_ENSURE_STATE(pusher.Push(ctx, false));
 
         JSAutoRequest ar(ctx);
-
-        JSAutoEnterCompartment ac;
-        if (!ac.enter(ctx, object))
-          return NS_ERROR_FAILURE;
+        JSAutoCompartment ac(ctx, object);
 
         // The parameter for the listener function.
         JSObject* param = JS_NewObject(ctx, NULL, NULL, NULL);
         NS_ENSURE_TRUE(param, NS_ERROR_OUT_OF_MEMORY);
 
         jsval targetv;
         nsContentUtils::WrapNative(ctx,
                                    JS_GetGlobalForObject(ctx, object),
@@ -496,22 +493,20 @@ nsFrameMessageManager::ReceiveMessage(ns
         }
 
         jsval rval = JSVAL_VOID;
 
         JS::AutoValueRooter argv(ctx);
         argv.set(OBJECT_TO_JSVAL(param));
 
         {
-          JSAutoEnterCompartment tac;
-
           JSObject* thisObject = JSVAL_TO_OBJECT(thisValue);
 
-          if (!tac.enter(ctx, thisObject) ||
-              !JS_WrapValue(ctx, argv.jsval_addr()))
+          JSAutoCompartment tac(ctx, thisObject);
+          if (!JS_WrapValue(ctx, argv.jsval_addr()))
             return NS_ERROR_UNEXPECTED;
 
           JS_CallFunctionValue(ctx, thisObject,
                                funval, 1, argv.jsval_addr(), &rval);
           if (aJSONRetVal) {
             nsString json;
             if (JS_Stringify(ctx, &rval, nullptr, JSVAL_NULL,
                              JSONCreator, &json)) {
@@ -825,18 +820,18 @@ nsFrameScriptExecutor::LoadFrameScriptIn
   if (!dataString.IsEmpty()) {
     nsContentUtils::ThreadJSContextStack()->Push(mCx);
     {
       // Need to scope JSAutoRequest to happen after Push but before Pop,
       // at least for now. See bug 584673.
       JSAutoRequest ar(mCx);
       JSObject* global = nullptr;
       mGlobal->GetJSObject(&global);
-      JSAutoEnterCompartment ac;
-      if (global && ac.enter(mCx, global)) {
+      if (global) {
+        JSAutoCompartment ac(mCx, global);
         uint32_t oldopts = JS_GetOptions(mCx);
         JS_SetOptions(mCx, oldopts | JSOPTION_NO_SCRIPT_RVAL);
 
         JSScript* script =
           JS_CompileUCScriptForPrincipals(mCx, nullptr,
                                           nsJSPrincipals::get(mPrincipal),
                                           static_cast<const jschar*>(dataString.get()),
                                           dataString.Length(),
--- a/content/base/src/nsImageLoadingContent.cpp
+++ b/content/base/src/nsImageLoadingContent.cpp
@@ -1153,30 +1153,40 @@ nsImageLoadingContent::BindToTree(nsIDoc
                                   nsIContent* aBindingParent,
                                   bool aCompileEventHandlers)
 {
   // We may be entering the document, so if our image should be tracked,
   // track it.
   if (!aDocument)
     return;
 
+  // Push a null JSContext on the stack so that callbacks triggered by the
+  // below code won't think they're being called from JS.
+  nsCxPusher pusher;
+  pusher.PushNull();
+
   if (mCurrentRequestFlags & REQUEST_SHOULD_BE_TRACKED)
     aDocument->AddImage(mCurrentRequest);
   if (mPendingRequestFlags & REQUEST_SHOULD_BE_TRACKED)
     aDocument->AddImage(mPendingRequest);
 }
 
 void
 nsImageLoadingContent::UnbindFromTree(bool aDeep, bool aNullParent)
 {
   // We may be leaving the document, so if our image is tracked, untrack it.
   nsCOMPtr<nsIDocument> doc = GetOurCurrentDoc();
   if (!doc)
     return;
 
+  // Push a null JSContext on the stack so that callbacks triggered by the
+  // below code won't think they're being called from JS.
+  nsCxPusher pusher;
+  pusher.PushNull();
+
   if (mCurrentRequestFlags & REQUEST_SHOULD_BE_TRACKED)
     doc->RemoveImage(mCurrentRequest);
   if (mPendingRequestFlags & REQUEST_SHOULD_BE_TRACKED)
     doc->RemoveImage(mPendingRequest);
 }
 
 nsresult
 nsImageLoadingContent::TrackImage(imgIRequest* aImage)
--- a/content/base/src/nsNodeInfo.cpp
+++ b/content/base/src/nsNodeInfo.cpp
@@ -172,17 +172,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
     }
 
     cb.DescribeRefCountedNode(tmp->mRefCnt.get(), sizeof(nsNodeInfo), name);
   }
   else {
     NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsNodeInfo, tmp->mRefCnt.get())
   }
 
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mOwnerManager)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_MEMBER(mOwnerManager,
+                                                  nsNodeInfoManager)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNodeInfo)
 NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_DESTROY(nsNodeInfo, LastRelease())
 NS_INTERFACE_TABLE_HEAD(nsNodeInfo)
   NS_INTERFACE_TABLE1(nsNodeInfo, nsINodeInfo)
   NS_INTERFACE_TABLE_TO_MAP_SEGUE_CYCLE_COLLECTION(nsNodeInfo)
 NS_INTERFACE_MAP_END
--- a/content/base/src/nsNodeInfoManager.cpp
+++ b/content/base/src/nsNodeInfoManager.cpp
@@ -125,36 +125,32 @@ nsNodeInfoManager::~nsNodeInfoManager()
     PR_LOG(gNodeInfoManagerLeakPRLog, PR_LOG_DEBUG,
            ("NODEINFOMANAGER %p destroyed", this));
 #endif
 
   nsLayoutStatics::Release();
 }
 
 
-NS_IMPL_CYCLE_COLLECTION_CLASS(nsNodeInfoManager)
-NS_IMPL_CYCLE_COLLECTION_UNLINK_0(nsNodeInfoManager)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsNodeInfoManager)
+NS_IMPL_CYCLE_COLLECTION_NATIVE_CLASS(nsNodeInfoManager)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_NATIVE_0(nsNodeInfoManager)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_BEGIN(nsNodeInfoManager)
   if (tmp->mDocument &&
       nsCCUncollectableMarker::InGeneration(cb,
                                             tmp->mDocument->GetMarkedCCGeneration())) {
     return NS_SUCCESS_INTERRUPTED_TRAVERSE;
   }
   if (tmp->mNonDocumentNodeInfos) {
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mDocument)
   }
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mBindingManager)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(nsNodeInfoManager)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(nsNodeInfoManager)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsNodeInfoManager)
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
+NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsNodeInfoManager, AddRef)
+NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsNodeInfoManager, Release)
 
 nsresult
 nsNodeInfoManager::Init(nsIDocument *aDocument)
 {
   NS_ENSURE_TRUE(mNodeInfoHash, NS_ERROR_OUT_OF_MEMORY);
 
   NS_PRECONDITION(!mPrincipal,
                   "Being inited when we already have a principal?");
--- a/content/base/src/nsNodeInfoManager.h
+++ b/content/base/src/nsNodeInfoManager.h
@@ -22,24 +22,25 @@ class nsIDocument;
 class nsIDOMDocumentType;
 class nsINodeInfo;
 class nsIPrincipal;
 class nsNodeInfo;
 struct PLHashEntry;
 struct PLHashTable;
 template<class T> struct already_AddRefed;
 
-class nsNodeInfoManager MOZ_FINAL : public nsISupports
+class nsNodeInfoManager MOZ_FINAL
 {
 public:
   nsNodeInfoManager();
   ~nsNodeInfoManager();
 
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_CLASS(nsNodeInfoManager)
+  NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(nsNodeInfoManager)
+
+  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsNodeInfoManager)
 
   /**
    * Initialize the nodeinfo manager with a document.
    */
   nsresult Init(nsIDocument *aDocument);
 
   /**
    * Release the reference to the document, this will be called when
--- a/content/base/src/nsXMLHttpRequest.cpp
+++ b/content/base/src/nsXMLHttpRequest.cpp
@@ -2660,37 +2660,37 @@ GetRequestBody(nsIVariant* aBody, nsIInp
     nsCOMPtr<nsIXHRSendable> sendable = do_QueryInterface(supports);
     if (sendable) {
       return GetRequestBody(sendable, aResult, aContentType, aCharset);
     }
 
     // ArrayBuffer?
     jsval realVal;
     nsCxPusher pusher;
-    JSAutoEnterCompartment ac;
-    JSObject* obj;
+    Maybe<JSAutoCompartment> ac;
 
     // If there's a context on the stack, we can just use it. Otherwise, we need
     // to use the safe js context (and push it into the stack, so that it's
     // visible to cx-less functions that we might call here).
     JSContext* cx = nsContentUtils::GetCurrentJSContext();
     if (!cx) {
       cx = nsContentUtils::GetSafeJSContext();
       if (!pusher.Push(cx)) {
         return NS_ERROR_FAILURE;
       }
     }
 
     nsresult rv = aBody->GetAsJSVal(&realVal);
-    if (NS_SUCCEEDED(rv) && !JSVAL_IS_PRIMITIVE(realVal) &&
-        (obj = JSVAL_TO_OBJECT(realVal)) &&
-        ac.enter(cx, obj) &&
-        (JS_IsArrayBufferObject(obj, cx))) {
-      ArrayBuffer buf(cx, obj);
-      return GetRequestBody(&buf, aResult, aContentType, aCharset);
+    if (NS_SUCCEEDED(rv) && !JSVAL_IS_PRIMITIVE(realVal)) {
+      JSObject *obj = JSVAL_TO_OBJECT(realVal);
+      ac.construct(cx, obj);
+      if (JS_IsArrayBufferObject(obj, cx)) {
+          ArrayBuffer buf(cx, obj);
+          return GetRequestBody(&buf, aResult, aContentType, aCharset);
+      }
     }
   }
   else if (dataType == nsIDataType::VTYPE_VOID ||
            dataType == nsIDataType::VTYPE_EMPTY) {
     // Makes us act as if !aBody, don't upload anything
     aContentType.AssignLiteral("text/plain");
     aCharset.AssignLiteral("UTF-8");
 
--- a/content/base/test/test_bug333198.html
+++ b/content/base/test/test_bug333198.html
@@ -25,32 +25,25 @@ var focusTester;
 var focusTester2;
 var focusCount = 0;
 var eventCount = 0;
 function clickHandler() {
   ++eventCount;
 }
 
 function suppressEvents(suppress) {
-  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-  window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-        .getInterface(Components.interfaces.nsIDOMWindowUtils)
-        .suppressEventHandling(suppress);
+  SpecialPowers.DOMWindowUtils.suppressEventHandling(suppress);
 }
 
 function sendEvents() {
-  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-  windowUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-                      .getInterface(Components.interfaces.nsIDOMWindowUtils);
+  windowUtils = SpecialPowers.getDOMWindowUtils(window);
   windowUtils.sendMouseEvent("mousedown", 1, 1, 0, 1, 0);
   windowUtils.sendMouseEvent("mouseup", 1, 1, 0, 1, 0);
 
-  iframeUtils = document.getElementById("ifr").contentWindow
-                        .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-                        .getInterface(Components.interfaces.nsIDOMWindowUtils);
+  iframeUtils = SpecialPowers.getDOMWindowUtils(document.getElementById("ifr").contentWindow);
   iframeUtils.sendMouseEvent("mousedown", 1, 1, 0, 1, 0);
   iframeUtils.sendMouseEvent("mouseup", 1, 1, 0, 1, 0);
 }
 
 function runTest() {
   window.focus();
   focusTester = document.getElementsByTagName("input")[0];
   focusTester.blur();
--- a/content/canvas/src/WebGLContextGL.cpp
+++ b/content/canvas/src/WebGLContextGL.cpp
@@ -3701,39 +3701,39 @@ WebGLContext::LinkProgram(nsIWebGLProgra
 void
 WebGLContext::LinkProgram(WebGLProgram *program, ErrorResult& rv)
 {
     if (!IsContextStable())
         return;
 
     if (!ValidateObject("linkProgram", program))
         return;
-    
+
     GLuint progname = program->GLName();
 
     if (!program->NextGeneration()) {
         return rv.Throw(NS_ERROR_FAILURE);
     }
 
     if (!program->HasBothShaderTypesAttached()) {
         GenerateWarning("linkProgram: this program doesn't have both a vertex shader"
                         " and a fragment shader");
         program->SetLinkStatus(false);
         return;
     }
 
     // bug 777028
     // Mesa can't handle more than 16 samplers per program, counting each array entry.
-    if (mIsMesa) {
-        if (program->UpperBoundNumSamplerUniforms() > 16) {
-            GenerateWarning("Programs with more than 16 samplers are disallowed on Mesa drivers "
-                            "to avoid a Mesa crasher.");
-            program->SetLinkStatus(false);
-            return;
-        }
+    if (gl->WorkAroundDriverBugs() &&
+        mIsMesa &&
+        program->UpperBoundNumSamplerUniforms() > 16)
+    {
+        GenerateWarning("Programs with more than 16 samplers are disallowed on Mesa drivers " "to avoid a Mesa crasher.");
+        program->SetLinkStatus(false);
+        return;
     }
 
     GLint ok;
     if (gl->WorkAroundDriverBugs() &&
         program->HasBadShaderAttached())
     {
         // it's a common driver bug, caught by program-test.html, that linkProgram doesn't
         // correctly preserve the state of an in-use program that has been attached a bad shader
--- a/content/canvas/test/test_canvas.html
+++ b/content/canvas/test/test_canvas.html
@@ -16073,19 +16073,27 @@ isPixel(ctx, 98,48, 0,255,0,255, 0);
 <script>
 
 
 function test_2d_pattern_repeat_null() {
 
 var canvas = document.getElementById('c494');
 var ctx = canvas.getContext('2d');
 
-var _thrown = undefined; try {
-  ctx.createPattern(canvas, null);
-} catch (e) { _thrown = e }; ok(_thrown && _thrown.name == "SyntaxError" && _thrown.code == DOMException.SYNTAX_ERR, "should throw SyntaxError");
+ctx.fillStyle = '#f00';
+ctx.fillRect(0, 0, 100, 50);
+var img = document.getElementById('green-1x1_2.png');
+var pattern = ctx.createPattern(img, null);
+ctx.fillStyle = pattern;
+ctx.fillRect(0, 0, 100, 50);
+
+isPixel(ctx, 1,1, 0,255,0,255, 0);
+isPixel(ctx, 98,1, 0,255,0,255, 0);
+isPixel(ctx, 1,48, 0,255,0,255, 0);
+isPixel(ctx, 98,48, 0,255,0,255, 0);
 
 
 }
 </script>
 <img src="image_green-1x1.png" id="green-1x1_2.png" class="resource">
 
 <!-- [[[ test_2d.pattern.repeat.nullsuffix.html ]]] -->
 
@@ -16112,19 +16120,22 @@ var _thrown = undefined; try {
 <canvas id="c496" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
 <script>
 
 function test_2d_pattern_repeat_undefined() {
 
 var canvas = document.getElementById('c496');
 var ctx = canvas.getContext('2d');
 
+var undefinedHandler = IsAzureEnabled() ? todo : ok;
+
 var _thrown = undefined; try {
   ctx.createPattern(canvas, undefined);
-} catch (e) { _thrown = e }; ok(_thrown && _thrown.name == "SyntaxError" && _thrown.code == DOMException.SYNTAX_ERR, "should throw SyntaxError");
+  // XXXbz TODO fix bug 784869
+} catch (e) { _thrown = e }; undefinedHandler(_thrown && _thrown.name == "SyntaxError" && _thrown.code == DOMException.SYNTAX_ERR, "should throw SyntaxError");
 
 
 }
 </script>
 
 <!-- [[[ test_2d.pattern.repeat.unrecognised.html ]]] -->
 
 <p>Canvas test: 2d.pattern.repeat.unrecognised</p>
--- a/content/events/src/nsEventListenerManager.cpp
+++ b/content/events/src/nsEventListenerManager.cpp
@@ -1034,20 +1034,17 @@ nsEventListenerManager::SetJSEventListen
   JSObject *handler;
   if (JSVAL_IS_PRIMITIVE(v) ||
       !JS_ObjectIsCallable(cx, handler = JSVAL_TO_OBJECT(v))) {
     RemoveScriptEventListener(aEventName);
     return NS_OK;
   }
 
   // Now ensure that we're working in the compartment of aScope from now on.
-  JSAutoEnterCompartment ac;
-  if (!ac.enter(cx, aScope)) {
-    return NS_ERROR_UNEXPECTED;
-  }
+  JSAutoCompartment ac(cx, aScope);
 
   // Rewrap the handler into the new compartment, if needed.
   jsval tempVal = v;
   if (!JS_WrapValue(cx, &tempVal)) {
     return NS_ERROR_UNEXPECTED;
   }
   handler = &tempVal.toObject();
 
--- a/content/events/src/nsEventListenerManager.h
+++ b/content/events/src/nsEventListenerManager.h
@@ -65,17 +65,17 @@ struct nsListenerStruct
 
 class nsEventListenerManager
 {
 
 public:
   nsEventListenerManager(nsISupports* aTarget);
   virtual ~nsEventListenerManager();
 
-  NS_INLINE_DECL_REFCOUNTING(nsEventListenerManager)
+  NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsEventListenerManager)
 
   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(nsEventListenerManager)
 
   void AddEventListener(const nsAString& aType,
                         nsIDOMEventListener* aListener,
                         bool aUseCapture,
                         bool aWantsUntrusted);
   void RemoveEventListener(const nsAString& aType,
--- a/content/events/src/nsEventListenerService.cpp
+++ b/content/events/src/nsEventListenerService.cpp
@@ -62,36 +62,35 @@ nsEventListenerInfo::GetInSystemEventGro
   *aInSystemEventGroup = mInSystemEventGroup;
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS1(nsEventListenerService, nsIEventListenerService)
 
 // Caller must root *aJSVal!
 bool
-nsEventListenerInfo::GetJSVal(JSContext* aCx, JSAutoEnterCompartment& aAc, jsval* aJSVal)
+nsEventListenerInfo::GetJSVal(JSContext* aCx, mozilla::Maybe<JSAutoCompartment>& aAc, jsval* aJSVal)
 {
   *aJSVal = JSVAL_NULL;
   nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS = do_QueryInterface(mListener);
   if (wrappedJS) {
     JSObject* object = nullptr;
-    if (NS_FAILED(wrappedJS->GetJSObject(&object)) || !aAc.enter(aCx, object)) {
+    if (NS_FAILED(wrappedJS->GetJSObject(&object))) {
       return false;
     }
+    aAc.construct(aCx, object);
     *aJSVal = OBJECT_TO_JSVAL(object);
     return true;
   }
 
   nsCOMPtr<nsIJSEventListener> jsl = do_QueryInterface(mListener);
   if (jsl) {
     JSObject *handler = jsl->GetHandler();
     if (handler) {
-      if (!aAc.enter(aCx, handler)) {
-        return false;
-      }
+      aAc.construct(aCx, handler);
       *aJSVal = OBJECT_TO_JSVAL(handler);
       return true;
     }
   }
   return false;
 }
 
 NS_IMETHODIMP
@@ -102,17 +101,17 @@ nsEventListenerInfo::ToSource(nsAString&
   nsCOMPtr<nsIThreadJSContextStack> stack =
     nsContentUtils::ThreadJSContextStack();
   if (stack) {
     JSContext* cx = stack->GetSafeJSContext();
     if (cx && NS_SUCCEEDED(stack->Push(cx))) {
       {
         // Extra block to finish the auto request before calling pop
         JSAutoRequest ar(cx);
-        JSAutoEnterCompartment ac;
+        mozilla::Maybe<JSAutoCompartment> ac;
         jsval v = JSVAL_NULL;
         if (GetJSVal(cx, ac, &v)) {
           JSString* str = JS_ValueToSource(cx, v);
           if (str) {
             nsDependentJSString depStr;
             if (depStr.init(cx, str)) {
               aResult.Assign(depStr);
             }
@@ -144,17 +143,17 @@ nsEventListenerInfo::GetDebugObject(nsIS
   nsCOMPtr<nsIThreadJSContextStack> stack =
     nsContentUtils::ThreadJSContextStack();
   if (stack) {
     JSContext* cx = stack->GetSafeJSContext();
     if (cx && NS_SUCCEEDED(stack->Push(cx))) {
       {
         // Extra block to finish the auto request before calling pop
         JSAutoRequest ar(cx);
-        JSAutoEnterCompartment ac;
+        mozilla::Maybe<JSAutoCompartment> ac;
         jsval v = JSVAL_NULL;
         if (GetJSVal(cx, ac, &v)) {
           nsCOMPtr<jsdIValue> jsdValue;
           rv = jsd->WrapValue(v, getter_AddRefs(jsdValue));
           NS_ENSURE_SUCCESS(rv, rv);
           jsdValue.forget(aRetVal);
         }
       }
--- a/content/events/src/nsEventListenerService.h
+++ b/content/events/src/nsEventListenerService.h
@@ -24,17 +24,17 @@ public:
   : mType(aType), mListener(aListener), mCapturing(aCapturing),
     mAllowsUntrusted(aAllowsUntrusted),
     mInSystemEventGroup(aInSystemEventGroup) {}
   virtual ~nsEventListenerInfo() {}
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS(nsEventListenerInfo)
   NS_DECL_NSIEVENTLISTENERINFO
 protected:
-  bool GetJSVal(JSContext* aCx, JSAutoEnterCompartment& aAc, jsval* aJSVal);
+  bool GetJSVal(JSContext* aCx, mozilla::Maybe<JSAutoCompartment>& aAc, jsval* aJSVal);
 
   nsString                      mType;
   // nsReftPtr because that is what nsListenerStruct uses too.
   nsRefPtr<nsIDOMEventListener> mListener;
   bool                          mCapturing;
   bool                          mAllowsUntrusted;
   bool                          mInSystemEventGroup;
 };
--- a/content/events/src/nsPaintRequest.h
+++ b/content/events/src/nsPaintRequest.h
@@ -41,17 +41,17 @@ public:
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsPaintRequestList)
   NS_DECL_NSIDOMPAINTREQUESTLIST
   
   virtual JSObject* WrapObject(JSContext *cx, JSObject *scope,
                                bool *triedToWrap)
   {
-    return mozilla::dom::binding::PaintRequestList::create(cx, scope, this,
+    return mozilla::dom::oldproxybindings::PaintRequestList::create(cx, scope, this,
                                                            triedToWrap);
   }
 
   nsISupports* GetParentObject()
   {
     return mParent;
   }
 
--- a/content/events/test/test_bug226361.xhtml
+++ b/content/events/test/test_bug226361.xhtml
@@ -43,19 +43,17 @@ function setOrRestoreTabFocus(newValue) 
     prefs.setIntPref("tabfocus", newValue);
   }
 }
 
 // =================================
 
 var doc = document;
 function tab_to(id) {
-  netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-  var wu =  doc.defaultView.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-                .getInterface(Components.interfaces.nsIDOMWindowUtils);
+  var wu = SpecialPowers.DOMWindowUtils;
   wu.sendKeyEvent('keypress',  9, 0, 0);
   is(doc.activeElement.id, id, "element with id=" + id + " should have focus");
 }
 
 function tab_iframe() {
   doc = document;
   tab_to('iframe');
 
--- a/content/events/test/test_bug493251.html
+++ b/content/events/test/test_bug493251.html
@@ -26,37 +26,31 @@ https://bugzilla.mozilla.org/show_bug.cg
   var mouseUp = 0;
   var mouseClick = 0;
 
   var keyDown = 0;
   var keyPress = 0;
   var keyUp = 0;
 
   function suppressEventHandling(aSuppress) {
-    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-    var utils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
-                    getInterface(Components.interfaces.nsIDOMWindowUtils);
+    var utils = SpecialPowers.getDOMWindowUtils(win);
     ok(true, "suppressEventHandling: aSuppress=" + aSuppress);
     utils.suppressEventHandling(aSuppress);
   }
 
   function dispatchKeyEvent(type) {
-    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-    var utils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
-                    getInterface(Components.interfaces.nsIDOMWindowUtils);
+    var utils = SpecialPowers.getDOMWindowUtils(win);
     ok(true, "Dipatching key event: type=" + type);
     utils.sendKeyEvent(type, 
                        Components.interfaces.nsIDOMKeyEvent.DOM_VK_A,
                        0, 0);
   }
 
   function dispatchMouseEvent(aType, aX, aY, aButton, aClickCount, aModifiers) {
-    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-    var utils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
-                    getInterface(Components.interfaces.nsIDOMWindowUtils);
+    var utils = SpecialPowers.getDOMWindowUtils(win);
     ok(true, "Dipatching mouse event: aType=" + aType + ", aX=" + aX + ", aY" +
                aY + ", aButton=" + aButton + ", aClickCount=" + aClickCount +
                ", aModifiers=" + aModifiers);
     utils.sendMouseEvent(aType, aX, aY, aButton, aClickCount, aModifiers);
   }
 
   function dumpEvent(aEvent) {
     var detail = "target=" + aEvent.target + ", originalTarget=" +
@@ -83,18 +77,17 @@ https://bugzilla.mozilla.org/show_bug.cg
           ", shiftKey=" + (aEvent.shiftKey ? "PRESSED" : "no") +
           ", metaKey=" + (aEvent.metaKey ? "PRESSED" : "no") +
           ", button=" + aEvent.button +
           ", relatedTarget=" + aEvent.relatedTarget;
         break;
     }
     ok(true, aEvent.type + " event is handled: " + detail);
 
-    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-    var fm = Components.classes["@mozilla.org/focus-manager;1"].
+    var fm = SpecialPowers.wrap(Components).classes["@mozilla.org/focus-manager;1"].
                         getService(Components.interfaces.nsIFocusManager);
     ok(true, "focused element is \"" + fm.focusedElement +
              "\" and focused window is \"" + fm.focusedWindow +
              "\" (the testing window is \"" + win + "\"");
   }
 
   function doTest() {
     win.document.getElementsByTagName("input")[0].focus();
--- a/content/events/test/test_bug545268.html
+++ b/content/events/test/test_bug545268.html
@@ -29,28 +29,24 @@ https://bugzilla.mozilla.org/show_bug.cg
   var mouseUp = 0;
   var mouseClick = 0;
 
   var keyDown = 0;
   var keyPress = 0;
   var keyUp = 0;
 
   function dispatchKeyEvent(type) {
-    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-    var utils = subwin.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
-                    getInterface(Components.interfaces.nsIDOMWindowUtils);
+    var utils = SpecialPowers.getDOMWindowUtils(subwin);
     utils.sendKeyEvent(type, 
                        Components.interfaces.nsIDOMKeyEvent.DOM_VK_A,
                        0, 0);
   }
 
   function doTest() {
-    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-    var utils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
-                    getInterface(Components.interfaces.nsIDOMWindowUtils);
+    var utils = SpecialPowers.getDOMWindowUtils(win);
     var f = win.document.getElementById("f");
     subwin = f.contentWindow;
     subwin.document.getElementsByTagName("input")[0].focus();
     subwin.addEventListener("keydown", function(e) { ++keyDown; }, true);
     subwin.addEventListener("keypress", function(e) { ++keyPress; }, true);
     subwin.addEventListener("keyup", function(e) { ++keyUp; }, true);
     subwin.addEventListener("mousedown", function(e) { ++mouseDown; }, true);
     subwin.addEventListener("mouseup", function(e) { ++mouseUp; }, true);
@@ -76,34 +72,30 @@ https://bugzilla.mozilla.org/show_bug.cg
     is(keyDown, 1, "Wrong number events (7)");
     is(keyPress, 1, "Wrong number events (8)");
     is(keyUp, 1, "Wrong number events (9)");
 
     setTimeout(continueTest1, 0);
     }
 
   function continueTest1() {
-    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-    var utils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
-                    getInterface(Components.interfaces.nsIDOMWindowUtils);
+    var utils = SpecialPowers.getDOMWindowUtils(win);
     dispatchKeyEvent("keydown");
     utils.suppressEventHandling(true);
     dispatchKeyEvent("keypress");
     dispatchKeyEvent("keyup");
     is(keyDown, 2, "Wrong number events (10)");
     is(keyPress, 1, "Wrong number events (11)");
     is(keyUp, 1, "Wrong number events (12)");
     utils.suppressEventHandling(false);
     setTimeout(continueTest2, 0);
   }
 
   function continueTest2() {
-    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-    var utils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
-                    getInterface(Components.interfaces.nsIDOMWindowUtils);
+    var utils = SpecialPowers.getDOMWindowUtils(win);
     is(keyDown, 2, "Wrong number events (13)");
     is(keyPress, 2, "Wrong number events (14)");
     is(keyUp, 2, "Wrong number events (15)");
 
     utils.sendMouseEvent("mousedown", 5, 5, 0, 1, 0);
     utils.sendMouseEvent("mouseup", 5, 5, 0, 1, 0);
     is(mouseDown, 1, "Wrong number events (16)");
     is(mouseUp, 1, "Wrong number events (17)");
@@ -116,19 +108,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     is(mouseDown, 1, "Wrong number events (19)");
     is(mouseUp, 1, "Wrong number events (20)");
     is(mouseClick, 1, "Wrong number events (21)");
 
     setTimeout(continueTest3, 0);
   }
 
   function continueTest3() {
-    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-    var utils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
-                    getInterface(Components.interfaces.nsIDOMWindowUtils);
+    var utils = SpecialPowers.getDOMWindowUtils(win);
     utils.sendMouseEvent("mousedown", 5, 5, 0, 1, 0);
     utils.suppressEventHandling(true);
     utils.sendMouseEvent("mouseup", 5, 5, 0, 1, 0);
     utils.suppressEventHandling(false);
     setTimeout(continueTest4, 1000);
   }
 
   function continueTest4() {
--- a/content/events/test/test_bug574663.html
+++ b/content/events/test/test_bug574663.html
@@ -39,19 +39,17 @@ function sendTouchpadScrollMotion(scroll
 
 function runTest() {
   var win = open('data:text/html,<!DOCTYPE html>\n' +
     '<div id="scrollbox" style="height: 100px; overflow: auto;">' +
     '  <div style="height: 1000px;"></div>' +
     '</div>', '_blank', 'width=300,height=300');
   SimpleTest.waitForFocus(function () {
     var scrollbox = win.document.getElementById("scrollbox");
-    netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
-    let winUtils = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-                      .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    let winUtils = SpecialPowers.getDOMWindowUtils(win);
     let outstandingTests = [
       [false, false],
       [false, true],
       [true, false],
       [true, true],
     ];
     function nextTest() {
       netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");
--- a/content/events/test/test_bug593959.html
+++ b/content/events/test/test_bug593959.html
@@ -21,30 +21,27 @@ https://bugzilla.mozilla.org/show_bug.cg
   
 </div>
 <pre id="test">
 <script type="application/javascript">
 
 /** Test for Bug 593959 **/
 
   function doTest() {
-    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
-    var utils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-                       .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    var utils = SpecialPowers.getDOMWindowUtils(window);
     var e = document.createEvent("MouseEvent");
     e.initEvent("mousedown", false, false, window, 0, 1, 1, 1, 1,
                 false, false, false, false, 0, null);
     utils.dispatchDOMEventViaPresShell(document.body, e, true);
     
     is(document.querySelector("body:active"), document.body, "body should be active!")
     
     var ifrwindow = document.getElementById("ifr").contentWindow;
     
-    var utils2 = ifrwindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-                          .getInterface(Components.interfaces.nsIDOMWindowUtils);
+    var utils2 = SpecialPowers.getDOMWindowUtils(ifrwindow);
    
     var e2 = ifrwindow.document.createEvent("MouseEvent");
     e2.initEvent("mouseup", false, false, ifrwindow, 0, 1, 1, 1, 1,
                  false, false, false, false, 0, null);
     utils2.dispatchDOMEventViaPresShell(ifrwindow.document.body, e2, true);
     
     isnot(document.querySelector("body:active"), document.body, "body shouldn't be active!")
 
--- a/content/html/content/src/HTMLPropertiesCollection.cpp
+++ b/content/html/content/src/HTMLPropertiesCollection.cpp
@@ -103,17 +103,17 @@ HTMLPropertiesCollection::SetDocument(ns
   mNamedItemEntries.EnumerateRead(SetPropertyListDocument, aDocument);
   mIsDirty = true;
 }
 
 JSObject*
 HTMLPropertiesCollection::WrapObject(JSContext* cx, JSObject* scope,
                                      bool* triedToWrap)
 {
-  return mozilla::dom::binding::HTMLPropertiesCollection::create(cx, scope, this,
+  return mozilla::dom::oldproxybindings::HTMLPropertiesCollection::create(cx, scope, this,
                                                                  triedToWrap);
 }
 
 NS_IMETHODIMP
 HTMLPropertiesCollection::GetLength(uint32_t* aLength)
 {
   EnsureFresh();
   *aLength = mProperties.Length();
@@ -421,17 +421,17 @@ PropertyNodeList::GetParentObject()
 {
   return mParent;
 }
 
 JSObject*
 PropertyNodeList::WrapObject(JSContext *cx, JSObject *scope,
                              bool *triedToWrap)
 {
-  return mozilla::dom::binding::PropertyNodeList::create(cx, scope, this,
+  return mozilla::dom::oldproxybindings::PropertyNodeList::create(cx, scope, this,
                                                          triedToWrap);
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(PropertyNodeList)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(PropertyNodeList)
   // SetDocument(nullptr) ensures that we remove ourselves as a mutation observer
   tmp->SetDocument(nullptr);
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mParent)
--- a/content/html/content/src/nsClientRect.cpp
+++ b/content/html/content/src/nsClientRect.cpp
@@ -101,17 +101,17 @@ nsIDOMClientRect*
 nsClientRectList::GetItemAt(uint32_t aIndex)
 {
   return mArray.SafeObjectAt(aIndex);
 }
 
 JSObject*
 nsClientRectList::WrapObject(JSContext *cx, JSObject *scope, bool *triedToWrap)
 {
-  return mozilla::dom::binding::ClientRectList::create(cx, scope, this,
+  return mozilla::dom::oldproxybindings::ClientRectList::create(cx, scope, this,
                                                        triedToWrap);
 }
 
 static double
 RoundFloat(double aValue)
 {
   return floor(aValue + 0.5);
 }
--- a/content/html/content/src/nsDOMStringMap.cpp
+++ b/content/html/content/src/nsDOMStringMap.cpp
@@ -166,20 +166,17 @@ nsresult nsDOMStringMap::RemovePropInter
   NS_ENSURE_TRUE(AttrToDataProp(attr, prop), NS_OK);
 
   jsval val;
   JSContext* cx = nsContentUtils::GetCurrentJSContext();
   nsresult rv = nsContentUtils::WrapNative(cx, JS_GetGlobalForScopeChain(cx),
                                            this, &val);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  JSAutoEnterCompartment ac;
-  if (!ac.enter(cx, JSVAL_TO_OBJECT(val))) {
-    return NS_ERROR_FAILURE;
-  }
+  JSAutoCompartment ac(cx, JSVAL_TO_OBJECT(val));
 
   // Guard against infinite recursion. Prevents the stack from looking like
   // ...
   // RemoveProp
   // ...
   // RemoveDataAttr
   // ...
   // RemoveProp
--- a/content/html/content/src/nsGenericHTMLElement.cpp
+++ b/content/html/content/src/nsGenericHTMLElement.cpp
@@ -2130,16 +2130,49 @@ nsGenericHTMLElement::ParseAttribute(int
     }
   }
 
   return nsGenericHTMLElementBase::ParseAttribute(aNamespaceID, aAttribute,
                                                   aValue, aResult);
 }
 
 bool
+nsGenericHTMLElement::ParseBackgroundAttribute(int32_t aNamespaceID,
+                                               nsIAtom* aAttribute,
+                                               const nsAString& aValue,
+                                               nsAttrValue& aResult)
+{
+  if (aNamespaceID == kNameSpaceID_None &&
+      aAttribute == nsGkAtoms::background) {
+    // Resolve url to an absolute url
+    nsIDocument* doc = OwnerDoc();
+    nsCOMPtr<nsIURI> baseURI = GetBaseURI();
+    nsCOMPtr<nsIURI> uri;
+    nsresult rv = nsContentUtils::NewURIWithDocumentCharset(
+        getter_AddRefs(uri), aValue, doc, baseURI);
+    if (NS_FAILED(rv)) {
+      return false;
+    }
+
+    nsString value(aValue);
+    nsRefPtr<nsStringBuffer> buffer = nsCSSValue::BufferFromString(value);
+    if (NS_UNLIKELY(!buffer)) {
+      return false;
+    }
+
+    mozilla::css::URLValue *url =
+      new mozilla::css::URLValue(buffer, baseURI, uri, NodePrincipal());
+    aResult.SetTo(url, &aValue);
+    return true;
+  }
+
+  return false;
+}
+
+bool
 nsGenericHTMLElement::IsAttributeMapped(const nsIAtom* aAttribute) const
 {
   static const MappedAttributeEntry* const map[] = {
     sCommonAttributeMap
   };
   
   return FindAttributeDependence(aAttribute, map);
 }
@@ -2734,55 +2767,26 @@ nsGenericHTMLElement::MapBackgroundInto(
   if (!(aData->mSIDs & NS_STYLE_INHERIT_BIT(Background)))
     return;
 
   nsPresContext* presContext = aData->mPresContext;
   nsCSSValue* backImage = aData->ValueForBackgroundImage();
   if (backImage->GetUnit() == eCSSUnit_Null &&
       presContext->UseDocumentColors()) {
     // background
-    const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::background);
-    if (value && value->Type() == nsAttrValue::eString) {
-      const nsString& spec = value->GetStringValue();
-      if (!spec.IsEmpty()) {
-        // Resolve url to an absolute url
-        // XXX this breaks if the HTML element has an xml:base
-        // attribute (the xml:base will not be taken into account)
-        // as well as elements with _baseHref set. We need to be able
-        // to get to the element somehow, or store the base URI in the
-        // attributes.
-        nsIDocument* doc = presContext->Document();
-        nsCOMPtr<nsIURI> uri;
-        nsresult rv = nsContentUtils::NewURIWithDocumentCharset(
-            getter_AddRefs(uri), spec, doc, doc->GetDocBaseURI());
-        if (NS_SUCCEEDED(rv)) {
-          // Note that this should generally succeed here, due to the way
-          // |spec| is created.  Maybe we should just add an nsStringBuffer
-          // accessor on nsAttrValue?
-          nsRefPtr<nsStringBuffer> buffer = nsCSSValue::BufferFromString(spec);
-          if (NS_LIKELY(buffer)) {
-            // XXXbz it would be nice to assert that doc->NodePrincipal() is
-            // the same as the principal of the node (which we'd need to store
-            // in the mapped attrs or something?)
-            nsCSSValue::Image *img =
-              new nsCSSValue::Image(uri, buffer, doc->GetDocumentURI(),
-                                    doc->NodePrincipal(), doc);
-            if (NS_LIKELY(img)) {
-              nsCSSValueList* list = backImage->SetListValue();
-              list->mValue.SetImageValue(img);
-            }
-          }
-        }
-      }
-      else if (presContext->CompatibilityMode() == eCompatibility_NavQuirks) {
-        // in NavQuirks mode, allow the empty string to set the
-        // background to empty
-        nsCSSValueList* list = backImage->SetListValue();
-        list->mValue.SetNoneValue();
-      }
+    nsAttrValue* value =
+      const_cast<nsAttrValue*>(aAttributes->GetAttr(nsGkAtoms::background));
+    // If the value is an image, or it is a URL and we attempted a load,
+    // put it in the style tree.
+    if (value &&
+        (value->Type() == nsAttrValue::eImage ||
+         (value->Type() == nsAttrValue::eURL &&
+          value->LoadImage(presContext->Document())))) {
+      nsCSSValueList* list = backImage->SetListValue();
+      list->mValue.SetImageValue(value->GetImageValue());
     }
   }
 }
 
 void
 nsGenericHTMLElement::MapBGColorInto(const nsMappedAttributes* aAttributes,
                                      nsRuleData* aData)
 {
--- a/content/html/content/src/nsGenericHTMLElement.h
+++ b/content/html/content/src/nsGenericHTMLElement.h
@@ -208,16 +208,21 @@ public:
 
   // Helper for setting our editable flag and notifying
   void DoSetEditableFlag(bool aEditable, bool aNotify) {
     SetEditableFlag(aEditable);
     UpdateState(aNotify);
   }
 
   virtual bool ParseAttribute(int32_t aNamespaceID,
+                              nsIAtom* aAttribute,
+                              const nsAString& aValue,
+                              nsAttrValue& aResult);
+
+  bool ParseBackgroundAttribute(int32_t aNamespaceID,
                                 nsIAtom* aAttribute,
                                 const nsAString& aValue,
                                 nsAttrValue& aResult);
 
   NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const;
   virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const;
 
   /**
--- a/content/html/content/src/nsHTMLBodyElement.cpp
+++ b/content/html/content/src/nsHTMLBodyElement.cpp
@@ -78,19 +78,19 @@ public:
 #define FORWARDED_EVENT(name_, id_, type_, struct_)               \
     NS_IMETHOD GetOn##name_(JSContext *cx, jsval *vp);            \
     NS_IMETHOD SetOn##name_(JSContext *cx, const jsval &v);
 #include "nsEventNameList.h"
 #undef FORWARDED_EVENT
 #undef EVENT
 
   virtual bool ParseAttribute(int32_t aNamespaceID,
-                                nsIAtom* aAttribute,
-                                const nsAString& aValue,
-                                nsAttrValue& aResult);
+                              nsIAtom* aAttribute,
+                              const nsAString& aValue,
+                              nsAttrValue& aResult);
   virtual void UnbindFromTree(bool aDeep = true,
                               bool aNullParent = true);
   virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const;
   NS_IMETHOD WalkContentStyleRules(nsRuleWalker* aRuleWalker);
   NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const;
   virtual already_AddRefed<nsIEditor> GetAssociatedEditor();
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
   virtual nsXPCClassInfo* GetClassInfo();
@@ -319,17 +319,20 @@ nsHTMLBodyElement::ParseAttribute(int32_
         aAttribute == nsGkAtoms::topmargin ||
         aAttribute == nsGkAtoms::bottommargin ||
         aAttribute == nsGkAtoms::leftmargin ||
         aAttribute == nsGkAtoms::rightmargin) {
       return aResult.ParseIntWithBounds(aValue, 0);
     }
   }
 
-  return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+  return nsGenericHTMLElement::ParseBackgroundAttribute(aNamespaceID,
+                                                        aAttribute, aValue,
+                                                        aResult) ||
+         nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                               aResult);
 }
 
 void
 nsHTMLBodyElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
   if (mContentStyleRule) {
     mContentStyleRule->mPart = nullptr;
--- a/content/html/content/src/nsHTMLFormElement.cpp
+++ b/content/html/content/src/nsHTMLFormElement.cpp
@@ -116,17 +116,17 @@ public:
    * @return NS_OK or NS_ERROR_OUT_OF_MEMORY.
    */
   nsresult GetSortedControls(nsTArray<nsGenericHTMLFormElement*>& aControls) const;
 
   // nsWrapperCache
   virtual JSObject* WrapObject(JSContext *cx, JSObject *scope,
                                bool *triedToWrap)
   {
-    return mozilla::dom::binding::HTMLCollection::create(cx, scope, this,
+    return mozilla::dom::oldproxybindings::HTMLCollection::create(cx, scope, this,
                                                          triedToWrap);
   }
 
   nsHTMLFormElement* mForm;  // WEAK - the form owns me
 
   nsTArray<nsGenericHTMLFormElement*> mElements;  // Holds WEAK references - bug 36639
 
   // This array holds on to all form controls that are not contained
--- a/content/html/content/src/nsHTMLMediaElement.cpp
+++ b/content/html/content/src/nsHTMLMediaElement.cpp
@@ -550,22 +550,16 @@ nsHTMLMediaElement::OnChannelRedirect(ns
 }
 
 void nsHTMLMediaElement::ShutdownDecoder()
 {
   RemoveMediaElementFromURITable();
   NS_ASSERTION(mDecoder, "Must have decoder to shut down");
   mDecoder->Shutdown();
   mDecoder = nullptr;
-  // Discard all output streams. mDecoder->Shutdown() will have finished all
-  // its output streams.
-  // XXX For now we ignore mFinishWhenEnded. We'll fix this later. The
-  // immediate goal is to not crash when reloading a media element with
-  // output streams.
-  mOutputStreams.Clear();
 }
 
 void nsHTMLMediaElement::AbortExistingLoads()
 {
   // Abort any already-running instance of the resource selection algorithm.
   mLoadWaitStatus = NOT_WAITING;
 
   // Set a new load ID. This will cause events which were enqueued
@@ -1519,26 +1513,30 @@ NS_IMETHODIMP nsHTMLMediaElement::SetMut
 
   return NS_OK;
 }
 
 already_AddRefed<nsDOMMediaStream>
 nsHTMLMediaElement::CaptureStreamInternal(bool aFinishWhenEnded)
 {
   OutputMediaStream* out = mOutputStreams.AppendElement();
-  out->mStream = nsDOMMediaStream::CreateInputStream();
+  out->mStream = nsDOMMediaStream::CreateTrackUnionStream();
   nsRefPtr<nsIPrincipal> principal = GetCurrentPrincipal();
   out->mStream->CombineWithPrincipal(principal);
   out->mFinishWhenEnded = aFinishWhenEnded;
 
   mAudioCaptured = true;
+  // Block the output stream initially.
+  // Decoders are responsible for removing the block while they are playing
+  // back into the output stream.
+  out->mStream->GetStream()->ChangeExplicitBlockerCount(1);
   if (mDecoder) {
     mDecoder->SetAudioCaptured(true);
     mDecoder->AddOutputStream(
-        out->mStream->GetStream()->AsSourceStream(), aFinishWhenEnded);
+        out->mStream->GetStream()->AsProcessedStream(), aFinishWhenEnded);
   }
   nsRefPtr<nsDOMMediaStream> result = out->mStream;
   return result.forget();
 }
 
 NS_IMETHODIMP nsHTMLMediaElement::MozCaptureStream(nsIDOMMediaStream** aStream)
 {
   *aStream = CaptureStreamInternal(false).get();
@@ -2471,17 +2469,17 @@ nsresult nsHTMLMediaElement::FinishDecod
 
   // The new stream has not been suspended by us.
   mPausedForInactiveDocument = false;
 
   aDecoder->SetAudioCaptured(mAudioCaptured);
   aDecoder->SetVolume(mMuted ? 0.0 : mVolume);
   for (uint32_t i = 0; i < mOutputStreams.Length(); ++i) {
     OutputMediaStream* ms = &mOutputStreams[i];
-    aDecoder->AddOutputStream(ms->mStream->GetStream()->AsSourceStream(),
+    aDecoder->AddOutputStream(ms->mStream->GetStream()->AsProcessedStream(),
         ms->mFinishWhenEnded);
   }
 
   nsresult rv = aDecoder->Load(aStream, aListener, aCloneDonor);
   if (NS_FAILED(rv)) {
     LOG(PR_LOG_DEBUG, ("%p Failed to load for decoder %p", this, aDecoder));
     return rv;
   }
@@ -2823,16 +2821,24 @@ void nsHTMLMediaElement::Error(uint16_t 
 
 void nsHTMLMediaElement::PlaybackEnded()
 {
   // We changed state which can affect AddRemoveSelfReference
   AddRemoveSelfReference();
 
   NS_ASSERTION(!mDecoder || mDecoder->IsEnded(),
                "Decoder fired ended, but not in ended state");
+
+  // Discard all output streams that have finished now.
+  for (int32_t i = mOutputStreams.Length() - 1; i >= 0; --i) {
+    if (mOutputStreams[i].mFinishWhenEnded) {
+      mOutputStreams.RemoveElementAt(i);
+    }
+  }
+
   if (mSrcStream || (mDecoder && mDecoder->IsInfinite())) {
     LOG(PR_LOG_DEBUG, ("%p, got duration by reaching the end of the resource", this));
     DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
   }
 
   if (HasAttr(kNameSpaceID_None, nsGkAtoms::loop)) {
     SetCurrentTime(0);
     return;
--- a/content/html/content/src/nsHTMLSelectElement.cpp
+++ b/content/html/content/src/nsHTMLSelectElement.cpp
@@ -2010,17 +2010,17 @@ NS_INTERFACE_MAP_END
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsHTMLOptionCollection)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsHTMLOptionCollection)
 
 
 JSObject*
 nsHTMLOptionCollection::WrapObject(JSContext *cx, JSObject *scope,
                                    bool *triedToWrap)
 {
-  return mozilla::dom::binding::HTMLOptionsCollection::create(cx, scope, this,
+  return mozilla::dom::oldproxybindings::HTMLOptionsCollection::create(cx, scope, this,
                                                               triedToWrap);
 }
 
 NS_IMETHODIMP
 nsHTMLOptionCollection::GetLength(uint32_t* aLength)
 {
   *aLength = mElements.Length();
 
--- a/content/html/content/src/nsHTMLTableCellElement.cpp
+++ b/content/html/content/src/nsHTMLTableCellElement.cpp
@@ -39,19 +39,19 @@ public:
 
   // nsIDOMHTMLElement
   NS_FORWARD_NSIDOMHTMLELEMENT(nsGenericHTMLElement::)
 
   // nsIDOMHTMLTableCellElement
   NS_DECL_NSIDOMHTMLTABLECELLELEMENT
 
   virtual bool ParseAttribute(int32_t aNamespaceID,
-                                nsIAtom* aAttribute,
-                                const nsAString& aValue,
-                                nsAttrValue& aResult);
+                              nsIAtom* aAttribute,
+                              const nsAString& aValue,
+                              nsAttrValue& aResult);
   virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const;
   NS_IMETHOD WalkContentStyleRules(nsRuleWalker* aRuleWalker);
   NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const;
 
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
 
   virtual nsXPCClassInfo* GetClassInfo();
 
@@ -280,17 +280,20 @@ nsHTMLTableCellElement::ParseAttribute(i
     if (aAttribute == nsGkAtoms::scope) {
       return aResult.ParseEnumValue(aValue, kCellScopeTable, false);
     }
     if (aAttribute == nsGkAtoms::valign) {
       return ParseTableVAlignValue(aValue, aResult);
     }
   }
 
-  return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+  return nsGenericHTMLElement::ParseBackgroundAttribute(aNamespaceID,
+                                                        aAttribute, aValue,
+                                                        aResult) ||
+         nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                               aResult);
 }
 
 static 
 void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
                            nsRuleData* aData)
 {
   if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Position)) {
--- a/content/html/content/src/nsHTMLTableElement.cpp
+++ b/content/html/content/src/nsHTMLTableElement.cpp
@@ -51,17 +51,17 @@ public:
   NS_IMETHOD    ParentDestroyed();
 
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(TableRowsCollection)
 
   // nsWrapperCache
   virtual JSObject* WrapObject(JSContext *cx, JSObject *scope,
                                bool *triedToWrap)
   {
-    return mozilla::dom::binding::HTMLCollection::create(cx, scope, this,
+    return mozilla::dom::oldproxybindings::HTMLCollection::create(cx, scope, this,
                                                          triedToWrap);
   }
 
 protected:
   // Those rows that are not in table sections
   nsHTMLTableElement* mParent;
   nsRefPtr<nsContentList> mOrphanRows;  
 };
@@ -905,17 +905,20 @@ nsHTMLTableElement::ParseAttribute(int32
       return aResult.ParseEnumValue(aValue, kRulesTable, false);
     }
     if (aAttribute == nsGkAtoms::hspace ||
         aAttribute == nsGkAtoms::vspace) {
       return aResult.ParseIntWithBounds(aValue, 0);
     }
   }
 
-  return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+  return nsGenericHTMLElement::ParseBackgroundAttribute(aNamespaceID,
+                                                        aAttribute, aValue,
+                                                        aResult) ||
+         nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                               aResult);
 }
 
 
 
 static void
 MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
                       nsRuleData* aData)
--- a/content/html/content/src/nsHTMLTableRowElement.cpp
+++ b/content/html/content/src/nsHTMLTableRowElement.cpp
@@ -347,17 +347,20 @@ nsHTMLTableRowElement::ParseAttribute(in
     if (aAttribute == nsGkAtoms::bgcolor) {
       return aResult.ParseColor(aValue);
     }
     if (aAttribute == nsGkAtoms::valign) {
       return ParseTableVAlignValue(aValue, aResult);
     }
   }
 
-  return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+  return nsGenericHTMLElement::ParseBackgroundAttribute(aNamespaceID,
+                                                        aAttribute, aValue,
+                                                        aResult) ||
+         nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                               aResult);
 }
 
 static 
 void MapAttributesIntoRule(const nsMappedAttributes* aAttributes, nsRuleData* aData)
 {
   if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Position)) {
     // height: value
--- a/content/html/content/src/nsHTMLTableSectionElement.cpp
+++ b/content/html/content/src/nsHTMLTableSectionElement.cpp
@@ -38,19 +38,19 @@ public:
 
   // nsIDOMHTMLElement
   NS_FORWARD_NSIDOMHTMLELEMENT(nsGenericHTMLElement::)
 
   // nsIDOMHTMLTableSectionElement
   NS_DECL_NSIDOMHTMLTABLESECTIONELEMENT
 
   virtual bool ParseAttribute(int32_t aNamespaceID,
-                                nsIAtom* aAttribute,
-                                const nsAString& aValue,
-                                nsAttrValue& aResult);
+                              nsIAtom* aAttribute,
+                              const nsAString& aValue,
+                              nsAttrValue& aResult);
   virtual nsMapRuleToAttributesFunc GetAttributeMappingFunction() const;
   NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const;
 
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(nsHTMLTableSectionElement,
                                                      nsGenericHTMLElement)
 
@@ -233,17 +233,20 @@ nsHTMLTableSectionElement::ParseAttribut
     if (aAttribute == nsGkAtoms::bgcolor) {
       return aResult.ParseColor(aValue);
     }
     if (aAttribute == nsGkAtoms::valign) {
       return ParseTableVAlignValue(aValue, aResult);
     }
   }
 
-  return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
+  return nsGenericHTMLElement::ParseBackgroundAttribute(aNamespaceID,
+                                                        aAttribute, aValue,
+                                                        aResult) ||
+         nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
                                               aResult);
 }
 
 static 
 void MapAttributesIntoRule(const nsMappedAttributes* aAttributes, nsRuleData* aData)
 {
   if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Position)) {
     // height: value
--- a/content/media/MediaResource.cpp
+++ b/content/media/MediaResource.cpp
@@ -23,17 +23,16 @@
 #include "nsCrossSiteListenerProxy.h"
 #include "nsHTMLMediaElement.h"
 #include "nsError.h"
 #include "nsICachingChannel.h"
 #include "nsURILoader.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "mozilla/Util.h" // for DebugOnly
 #include "nsContentUtils.h"
-#include "nsBlobProtocolHandler.h"
 
 static const uint32_t HTTP_OK_CODE = 200;
 static const uint32_t HTTP_PARTIAL_RESPONSE_CODE = 206;
 
 using namespace mozilla;
 
 ChannelMediaResource::ChannelMediaResource(nsMediaDecoder* aDecoder,
     nsIChannel* aChannel, nsIURI* aURI)
@@ -1034,25 +1033,24 @@ nsresult FileMediaResource::Open(nsIStre
   }
 
   nsresult rv = NS_OK;
   if (aStreamListener) {
     // The channel is already open. We need a synchronous stream that
     // implements nsISeekableStream, so we have to find the underlying
     // file and reopen it
     nsCOMPtr<nsIFileChannel> fc(do_QueryInterface(mChannel));
-    if (fc) {
-      nsCOMPtr<nsIFile> file;
-      rv = fc->GetFile(getter_AddRefs(file));
-      NS_ENSURE_SUCCESS(rv, rv);
+    if (!fc)
+      return NS_ERROR_UNEXPECTED;
 
-      rv = NS_NewLocalFileInputStream(getter_AddRefs(mInput), file);
-    } else if (IsBlobURI(mURI)) {
-      rv = NS_GetStreamForBlobURI(mURI, getter_AddRefs(mInput));
-    }
+    nsCOMPtr<nsIFile> file;
+    rv = fc->GetFile(getter_AddRefs(file));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = NS_NewLocalFileInputStream(getter_AddRefs(mInput), file);
   } else {
     // Ensure that we never load a local file from some page on a
     // web server.
     nsHTMLMediaElement* element = mDecoder->GetMediaElement();
     NS_ENSURE_TRUE(element, NS_ERROR_FAILURE);
 
     rv = nsContentUtils::GetSecurityManager()->
            CheckLoadURIWithPrincipal(element->NodePrincipal(),
@@ -1198,27 +1196,27 @@ int64_t FileMediaResource::Tell()
   mSeekable->Tell(&offset);
   return offset;
 }
 
 MediaResource*
 MediaResource::Create(nsMediaDecoder* aDecoder, nsIChannel* aChannel)
 {
   NS_ASSERTION(NS_IsMainThread(),
-               "MediaResource::Open called on non-main thread");
+	             "MediaResource::Open called on non-main thread");
 
   // If the channel was redirected, we want the post-redirect URI;
   // but if the URI scheme was expanded, say from chrome: to jar:file:,
   // we want the original URI.
   nsCOMPtr<nsIURI> uri;
   nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
   NS_ENSURE_SUCCESS(rv, nullptr);
 
   nsCOMPtr<nsIFileChannel> fc = do_QueryInterface(aChannel);
-  if (fc || IsBlobURI(uri)) {
+  if (fc) {
     return new FileMediaResource(aDecoder, aChannel, uri);
   }
   return new ChannelMediaResource(aDecoder, aChannel, uri);
 }
 
 void MediaResource::MoveLoadsToBackground() {
   NS_ASSERTION(!mLoadInBackground, "Why are you calling this more than once?");
   mLoadInBackground = true;
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -2073,29 +2073,28 @@ SourceMediaStream::Finish()
 
 void
 MediaInputPort::Init()
 {
   LOG(PR_LOG_DEBUG, ("Adding MediaInputPort %p (from %p to %p) to the graph",
       this, mSource, mDest));
   mSource->AddConsumer(this);
   mDest->AddInput(this);
-  // mPortCount decremented in Disconnect()
+  // mPortCount decremented via MediaInputPort::Destroy's message
   ++mDest->GraphImpl()->mPortCount;
 }
 
 void
 MediaInputPort::Disconnect()
 {
   NS_ASSERTION(!mSource == !mDest,
                "mSource must either both be null or both non-null");
   if (!mSource)
     return;
 
-  --mDest->GraphImpl()->mPortCount;
   mSource->RemoveConsumer(this);
   mSource = nullptr;
   mDest->RemoveInput(this);
   mDest = nullptr;
 }
 
 MediaInputPort::InputInterval
 MediaInputPort::GetNextInputInterval(GraphTime aTime)
@@ -2118,32 +2117,45 @@ MediaInputPort::GetNextInputInterval(Gra
 }
 
 void
 MediaInputPort::Destroy()
 {
   class Message : public ControlMessage {
   public:
     Message(MediaInputPort* aPort)
-      : ControlMessage(aPort->GetDestination()), mPort(aPort) {}
+      : ControlMessage(nullptr), mPort(aPort) {}
     virtual void Run()
     {
       mPort->Disconnect();
+      --mPort->GraphImpl()->mPortCount;
       NS_RELEASE(mPort);
     }
     virtual void RunDuringShutdown()
     {
       Run();
     }
     // This does not need to be strongly referenced; the graph is holding
     // a strong reference to the port, which we will remove. This will be the
     // last message for the port.
     MediaInputPort* mPort;
   };
-  mSource->GraphImpl()->AppendMessage(new Message(this));
+  GraphImpl()->AppendMessage(new Message(this));
+}
+
+MediaStreamGraphImpl*
+MediaInputPort::GraphImpl()
+{
+  return gGraph;
+}
+
+MediaStreamGraph*
+MediaInputPort::Graph()
+{
+  return gGraph;
 }
 
 MediaInputPort*
 ProcessedMediaStream::AllocateInputPort(MediaStream* aStream, uint32_t aFlags)
 {
   class Message : public ControlMessage {
   public:
     Message(MediaInputPort* aPort)
--- a/content/media/MediaStreamGraph.h
+++ b/content/media/MediaStreamGraph.h
@@ -680,16 +680,22 @@ public:
     GraphTime mStart;
     GraphTime mEnd;
     bool mInputIsBlocked;
   };
   // Find the next time interval starting at or after aTime during which
   // mDest is not blocked and mSource's blocking status does not change.
   InputInterval GetNextInputInterval(GraphTime aTime);
 
+  /**
+   * Returns the graph that owns this port.
+   */
+  MediaStreamGraphImpl* GraphImpl();
+  MediaStreamGraph* Graph();
+
 protected:
   friend class MediaStreamGraphImpl;
   friend class MediaStream;
   friend class ProcessedMediaStream;
   // Never modified after Init()
   MediaStream* mSource;
   ProcessedMediaStream* mDest;
   uint32_t mFlags;
--- a/content/media/nsBuiltinDecoder.cpp
+++ b/content/media/nsBuiltinDecoder.cpp
@@ -54,24 +54,136 @@ void nsBuiltinDecoder::SetAudioCaptured(
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   mInitialAudioCaptured = aCaptured;
   if (mDecoderStateMachine) {
     mDecoderStateMachine->SetAudioCaptured(aCaptured);
   }
 }
 
-void nsBuiltinDecoder::AddOutputStream(SourceMediaStream* aStream, bool aFinishWhenEnded)
+void nsBuiltinDecoder::ConnectDecodedStreamToOutputStream(OutputStreamData* aStream)
+{
+  NS_ASSERTION(!aStream->mPort, "Already connected?");
+
+  // The output stream must stay in sync with the decoded stream, so if
+  // either stream is blocked, we block the other.
+  aStream->mPort = aStream->mStream->AllocateInputPort(mDecodedStream->mStream,
+      MediaInputPort::FLAG_BLOCK_INPUT | MediaInputPort::FLAG_BLOCK_OUTPUT);
+  // Unblock the output stream now. While it's connected to mDecodedStream,
+  // mDecodedStream is responsible for controlling blocking.
+  aStream->mStream->ChangeExplicitBlockerCount(-1);
+}
+
+nsBuiltinDecoder::DecodedStreamData::DecodedStreamData(nsBuiltinDecoder* aDecoder,
+                                                       int64_t aInitialTime,
+                                                       SourceMediaStream* aStream)
+  : mLastAudioPacketTime(-1),
+    mLastAudioPacketEndTime(-1),
+    mAudioFramesWritten(0),
+    mInitialTime(aInitialTime),
+    mNextVideoTime(aInitialTime),
+    mStreamInitialized(false),
+    mHaveSentFinish(false),
+    mHaveSentFinishAudio(false),
+    mHaveSentFinishVideo(false),
+    mStream(aStream),
+    mMainThreadListener(new DecodedStreamMainThreadListener(aDecoder)),
+    mHaveBlockedForPlayState(false)
+{
+  mStream->AddMainThreadListener(mMainThreadListener);
+}
+
+nsBuiltinDecoder::DecodedStreamData::~DecodedStreamData()
+{
+  mStream->RemoveMainThreadListener(mMainThreadListener);
+  mStream->Destroy();
+}
+
+void nsBuiltinDecoder::DestroyDecodedStream()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  mReentrantMonitor.AssertCurrentThreadIn();
+
+  // All streams are having their SourceMediaStream disconnected, so they
+  // need to be explicitly blocked again.
+  for (uint32_t i = 0; i < mOutputStreams.Length(); ++i) {
+    OutputStreamData& os = mOutputStreams[i];
+    // During cycle collection, nsDOMMediaStream can be destroyed and send
+    // its Destroy message before this decoder is destroyed. So we have to
+    // be careful not to send any messages after the Destroy().
+    if (!os.mStream->IsDestroyed()) {
+      os.mStream->ChangeExplicitBlockerCount(1);
+    }
+    // Explicitly remove all existing ports. This is not strictly necessary but it's
+    // good form.
+    os.mPort->Destroy();
+    os.mPort = nullptr;
+  }
+
+  mDecodedStream = nullptr;
+}
+
+void nsBuiltinDecoder::RecreateDecodedStream(int64_t aStartTimeUSecs)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  mReentrantMonitor.AssertCurrentThreadIn();
+  LOG(PR_LOG_DEBUG, ("nsBuiltinDecoder::RecreateDecodedStream this=%p aStartTimeUSecs=%lld!",
+                     this, (long long)aStartTimeUSecs));
+
+  DestroyDecodedStream();
+
+  mDecodedStream = new DecodedStreamData(this, aStartTimeUSecs,
+    MediaStreamGraph::GetInstance()->CreateInputStream(nullptr));
+
+  // Note that the delay between removing ports in DestroyDecodedStream
+  // and adding new ones won't cause a glitch since all graph operations
+  // between main-thread stable states take effect atomically.
+  for (uint32_t i = 0; i < mOutputStreams.Length(); ++i) {
+    ConnectDecodedStreamToOutputStream(&mOutputStreams[i]);
+  }
+
+  mDecodedStream->mHaveBlockedForPlayState = mPlayState != PLAY_STATE_PLAYING;
+  if (mDecodedStream->mHaveBlockedForPlayState) {
+    mDecodedStream->mStream->ChangeExplicitBlockerCount(1);
+  }
+}
+
+void nsBuiltinDecoder::NotifyDecodedStreamMainThreadStateChanged()
+{
+  if (mTriggerPlaybackEndedWhenSourceStreamFinishes && mDecodedStream &&
+      mDecodedStream->mStream->IsFinished()) {
+    mTriggerPlaybackEndedWhenSourceStreamFinishes = false;
+    if (GetState() == PLAY_STATE_PLAYING) {
+      nsCOMPtr<nsIRunnable> event =
+        NS_NewRunnableMethod(this, &nsBuiltinDecoder::PlaybackEnded);
+      NS_DispatchToCurrentThread(event);
+    }
+  }
+}
+
+void nsBuiltinDecoder::AddOutputStream(ProcessedMediaStream* aStream,
+                                       bool aFinishWhenEnded)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  LOG(PR_LOG_DEBUG, ("nsBuiltinDecoder::AddOutputStream this=%p aStream=%p!",
+                     this, aStream));
 
   {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-    OutputMediaStream* ms = mOutputStreams.AppendElement();
-    ms->Init(int64_t(mCurrentTime*USECS_PER_S), aStream, aFinishWhenEnded);
+    if (!mDecodedStream) {
+      RecreateDecodedStream(mDecoderStateMachine ?
+          int64_t(mDecoderStateMachine->GetCurrentTime()*USECS_PER_S) : 0);
+    }
+    OutputStreamData* os = mOutputStreams.AppendElement();
+    os->Init(aStream, aFinishWhenEnded);
+    ConnectDecodedStreamToOutputStream(os);
+    if (aFinishWhenEnded) {
+      // Ensure that aStream finishes the moment mDecodedStream does.
+      aStream->SetAutofinish(true);
+    }
   }
 
   // This can be called before Load(), in which case our mDecoderStateMachine
   // won't have been created yet and we can rely on Load() to schedule it
   // once it is created.
   if (mDecoderStateMachine) {
     // Make sure the state machine thread runs so that any buffered data
     // is fed into our stream.
@@ -111,17 +223,18 @@ nsBuiltinDecoder::nsBuiltinDecoder() :
   mRequestedSeekTime(-1.0),
   mDuration(-1),
   mSeekable(true),
   mReentrantMonitor("media.decoder"),
   mPlayState(PLAY_STATE_PAUSED),
   mNextState(PLAY_STATE_PAUSED),
   mResourceLoaded(false),
   mIgnoreProgressData(false),
-  mInfiniteStream(false)
+  mInfiniteStream(false),
+  mTriggerPlaybackEndedWhenSourceStreamFinishes(false)
 {
   MOZ_COUNT_CTOR(nsBuiltinDecoder);
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
 #ifdef PR_LOGGING
   if (!gBuiltinDecoderLog) {
     gBuiltinDecoderLog = PR_NewLogModule("nsBuiltinDecoder");
   }
 #endif
@@ -141,16 +254,21 @@ void nsBuiltinDecoder::Shutdown()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   
   if (mShuttingDown)
     return;
 
   mShuttingDown = true;
 
+  {
+    ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+    DestroyDecodedStream();
+  }
+
   // This changes the decoder state to SHUTDOWN and does other things
   // necessary to unblock the state machine thread if it's blocked, so
   // the asynchronous shutdown in nsDestroyStateMachine won't deadlock.
   if (mDecoderStateMachine) {
     mDecoderStateMachine->Shutdown();
   }
 
   // Force any outstanding seek and byterange requests to complete
@@ -538,20 +656,46 @@ bool nsBuiltinDecoder::IsSeeking() const
 bool nsBuiltinDecoder::IsEnded() const
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   return mPlayState == PLAY_STATE_ENDED || mPlayState == PLAY_STATE_SHUTDOWN;
 }
 
 void nsBuiltinDecoder::PlaybackEnded()
 {
+  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+
   if (mShuttingDown || mPlayState == nsBuiltinDecoder::PLAY_STATE_SEEKING)
     return;
 
-  printf("nsBuiltinDecoder::PlaybackEnded mPlayState=%d\n", mPlayState);
+  {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+    if (mDecodedStream && !mDecodedStream->mStream->IsFinished()) {
+      // Wait for it to finish before firing PlaybackEnded()
+      mTriggerPlaybackEndedWhenSourceStreamFinishes = true;
+      return;
+    }
+
+    for (int32_t i = mOutputStreams.Length() - 1; i >= 0; --i) {
+      OutputStreamData& os = mOutputStreams[i];
+      if (os.mFinishWhenEnded) {
+        // Shouldn't really be needed since mDecodedStream should already have
+        // finished, but doesn't hurt.
+        os.mStream->Finish();
+        os.mPort->Destroy();
+        os.mPort = nullptr;
+        // Not really needed but it keeps the invariant that a stream not
+        // connected to mDecodedStream is explicity blocked.
+        os.mStream->ChangeExplicitBlockerCount(1);
+        mOutputStreams.RemoveElementAt(i);
+      }
+    }
+  }
+
   PlaybackPositionChanged();
   ChangeState(PLAY_STATE_ENDED);
 
   if (mElement)  {
     UpdateReadyStateForData();
     mElement->PlaybackEnded();
   }
 
@@ -760,17 +904,16 @@ void nsBuiltinDecoder::SeekingStopped()
 
     // An additional seek was requested while the current seek was
     // in operation.
     if (mRequestedSeekTime >= 0.0) {
       ChangeState(PLAY_STATE_SEEKING);
       seekWasAborted = true;
     } else {
       UnpinForSeek();
-      printf("nsBuiltinDecoder::SeekingStopped, next state=%d\n", mNextState);
       ChangeState(mNextState);
     }
   }
 
   if (mElement) {
     UpdateReadyStateForData();
     if (!seekWasAborted) {
       mElement->SeekCompleted();
@@ -837,16 +980,23 @@ void nsBuiltinDecoder::ChangeState(PlayS
     mNextState = PLAY_STATE_PAUSED;
   }
 
   if (mPlayState == PLAY_STATE_SHUTDOWN) {
     mReentrantMonitor.NotifyAll();
     return;
   }
 
+  if (mDecodedStream) {
+    bool blockForPlayState = aState != PLAY_STATE_PLAYING;
+    if (mDecodedStream->mHaveBlockedForPlayState != blockForPlayState) {
+      mDecodedStream->mStream->ChangeExplicitBlockerCount(blockForPlayState ? 1 : -1);
+      mDecodedStream->mHaveBlockedForPlayState = blockForPlayState;
+    }
+  }
   mPlayState = aState;
   if (mDecoderStateMachine) {
     switch (aState) {
     case PLAY_STATE_PLAYING:
       mDecoderStateMachine->Play();
       break;
     case PLAY_STATE_SEEKING:
       mDecoderStateMachine->Seek(mRequestedSeekTime);
@@ -1043,51 +1193,8 @@ bool nsBuiltinDecoder::OnStateMachineThr
 void nsBuiltinDecoder::NotifyAudioAvailableListener()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   if (mDecoderStateMachine) {
     ReentrantMonitorAutoEnter mon(mReentrantMonitor);
     mDecoderStateMachine->NotifyAudioAvailableListener();
   }
 }
-
-nsBuiltinDecoder::OutputMediaStream::OutputMediaStream()
-{
-
-}
-
-nsBuiltinDecoder::OutputMediaStream::~OutputMediaStream()
-{
-
-}
-    
-
-void nsBuiltinDecoder::OutputMediaStream::Init(int64_t aInitialTime, SourceMediaStream* aStream, bool aFinishWhenEnded)
-{
-  mLastAudioPacketTime = -1;
-  mLastAudioPacketEndTime = -1;
-  mAudioFramesWrittenBaseTime = aInitialTime;
-  mAudioFramesWritten = 0;
-  mNextVideoTime = aInitialTime;
-  mStream = aStream;
-  mStreamInitialized = false;
-  mFinishWhenEnded = aFinishWhenEnded;
-  mHaveSentFinish = false;
-  mHaveSentFinishAudio = false;
-  mHaveSentFinishVideo = false;
-}
-
-nsBuiltinDecoder::OutputMediaStream::OutputMediaStream(const OutputMediaStream& rhs)
-{
-  mLastAudioPacketTime = rhs.mLastAudioPacketTime;
-  mLastAudioPacketEndTime = rhs.mLastAudioPacketEndTime;
-  mAudioFramesWritten = rhs.mAudioFramesWritten;
-  mAudioFramesWrittenBaseTime = rhs.mAudioFramesWrittenBaseTime;
-  mNextVideoTime = rhs.mNextVideoTime;
-  mLastVideoImage = rhs.mLastVideoImage;
-  mStream = rhs.mStream;
-  mLastVideoImageDisplaySize = rhs.mLastVideoImageDisplaySize;
-  mStreamInitialized = rhs.mStreamInitialized;
-  mFinishWhenEnded = rhs.mFinishWhenEnded;
-  mHaveSentFinish = rhs.mHaveSentFinish;
-  mHaveSentFinishAudio = rhs.mHaveSentFinishAudio;
-  mHaveSentFinishVideo = rhs.mHaveSentFinishVideo;
-}
--- a/content/media/nsBuiltinDecoder.h
+++ b/content/media/nsBuiltinDecoder.h
@@ -327,16 +327,17 @@ public:
   // element. Called on the main thread.
   virtual void NotifyAudioAvailableListener() = 0;
 };
 
 class nsBuiltinDecoder : public nsMediaDecoder
 {
 public:
   typedef mozilla::MediaChannelStatistics MediaChannelStatistics;
+  class DecodedStreamMainThreadListener;
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
   // Enumeration for the valid play states (see mPlayState)
   enum PlayState {
     PLAY_STATE_START,
     PLAY_STATE_LOADING,
@@ -372,53 +373,116 @@ public:
   virtual nsresult Seek(double aTime);
 
   virtual nsresult PlaybackRateChanged();
 
   virtual void Pause();
   virtual void SetVolume(double aVolume);
   virtual void SetAudioCaptured(bool aCaptured);
 
-  virtual void AddOutputStream(SourceMediaStream* aStream, bool aFinishWhenEnded);
-  // Protected by mReentrantMonitor. All decoder output is copied to these streams.
-  struct OutputMediaStream {
-    OutputMediaStream();
-    ~OutputMediaStream();
-    OutputMediaStream(const OutputMediaStream& rhs);
+  // All MediaStream-related data is protected by mReentrantMonitor.
+  // We have at most one DecodedStreamData per nsBuiltinDecoder. Its stream
+  // is used as the input for each ProcessedMediaStream created by calls to
+  // captureStream(UntilEnded). Seeking creates a new source stream, as does
+  // replaying after the input as ended. In the latter case, the new source is
+  // not connected to streams created by captureStreamUntilEnded.
 
-    void Init(int64_t aInitialTime, SourceMediaStream* aStream, bool aFinishWhenEnded);
-    
+  struct DecodedStreamData {
+    DecodedStreamData(nsBuiltinDecoder* aDecoder,
+                      int64_t aInitialTime, SourceMediaStream* aStream);
+    ~DecodedStreamData();
+
+    // The following group of fields are protected by the decoder's monitor
+    // and can be read or written on any thread.    
     int64_t mLastAudioPacketTime; // microseconds
     int64_t mLastAudioPacketEndTime; // microseconds
     // Count of audio frames written to the stream
     int64_t mAudioFramesWritten;
-    // Timestamp of the first audio packet whose frames we wrote.
-    int64_t mAudioFramesWrittenBaseTime; // microseconds
+    // Saved value of aInitialTime. Timestamp of the first audio and/or
+    // video packet written.
+    int64_t mInitialTime; // microseconds
     // mNextVideoTime is the end timestamp for the last packet sent to the stream.
     // Therefore video packets starting at or after this time need to be copied
     // to the output stream.
     int64_t mNextVideoTime; // microseconds
     // The last video image sent to the stream. Useful if we need to replicate
     // the image.
     nsRefPtr<Image> mLastVideoImage;
-    nsRefPtr<SourceMediaStream> mStream;
     gfxIntSize mLastVideoImageDisplaySize;
     // This is set to true when the stream is initialized (audio and
     // video tracks added).
     bool mStreamInitialized;
-    bool mFinishWhenEnded;
     bool mHaveSentFinish;
     bool mHaveSentFinishAudio;
     bool mHaveSentFinishVideo;
+
+    // The decoder is responsible for calling Destroy() on this stream.
+    // Can be read from any thread.
+    const nsRefPtr<SourceMediaStream> mStream;
+    // A listener object that receives notifications when mStream's
+    // main-thread-visible state changes. Used on the main thread only.
+    const nsRefPtr<DecodedStreamMainThreadListener> mMainThreadListener;
+    // True when we've explicitly blocked this stream because we're
+    // not in PLAY_STATE_PLAYING. Used on the main thread only.
+    bool mHaveBlockedForPlayState;
   };
-  nsTArray<OutputMediaStream>& OutputStreams()
+  struct OutputStreamData {
+    void Init(ProcessedMediaStream* aStream, bool aFinishWhenEnded)
+    {
+      mStream = aStream;
+      mFinishWhenEnded = aFinishWhenEnded;
+    }
+    nsRefPtr<ProcessedMediaStream> mStream;
+    // mPort connects mDecodedStream->mStream to our mStream.
+    nsRefPtr<MediaInputPort> mPort;
+    bool mFinishWhenEnded;
+  };
+  /**
+   * Connects mDecodedStream->mStream to aStream->mStream.
+   */
+  void ConnectDecodedStreamToOutputStream(OutputStreamData* aStream);
+  /**
+   * Disconnects mDecodedStream->mStream from all outputs and clears
+   * mDecodedStream.
+   */
+  void DestroyDecodedStream();
+  /**
+   * Recreates mDecodedStream. Call this to create mDecodedStream at first,
+   * and when seeking, to ensure a new stream is set up with fresh buffers.
+   * aStartTimeUSecs is relative to the state machine's mStartTime.
+   */
+  void RecreateDecodedStream(int64_t aStartTimeUSecs);
+  /**
+   * Called when the state of mDecodedStream as visible on the main thread
+   * has changed. In particular we want to know when the stream has finished
+   * so we can call PlaybackEnded.
+   */
+  void NotifyDecodedStreamMainThreadStateChanged();
+  nsTArray<OutputStreamData>& OutputStreams()
   {
     GetReentrantMonitor().AssertCurrentThreadIn();
     return mOutputStreams;
   }
+  DecodedStreamData* GetDecodedStream()
+  {
+    GetReentrantMonitor().AssertCurrentThreadIn();
+    return mDecodedStream;
+  }
+  class DecodedStreamMainThreadListener : public MainThreadMediaStreamListener {
+  public:
+    DecodedStreamMainThreadListener(nsBuiltinDecoder* aDecoder)
+      : mDecoder(aDecoder) {}
+    virtual void NotifyMainThreadStateChanged()
+    {
+      mDecoder->NotifyDecodedStreamMainThreadStateChanged();
+    }
+    nsBuiltinDecoder* mDecoder;
+  };
+
+  virtual void AddOutputStream(ProcessedMediaStream* aStream, bool aFinishWhenEnded);
 
   virtual double GetDuration();
 
   virtual void SetInfinite(bool aInfinite);
   virtual bool IsInfinite();
 
   virtual MediaResource* GetResource() { return mResource; }
   virtual already_AddRefed<nsIPrincipal> GetCurrentPrincipal();
@@ -705,17 +769,23 @@ public:
   nsAutoPtr<MediaResource> mResource;
 
   // ReentrantMonitor for detecting when the video play state changes. A call
   // to Wait on this monitor will block the thread until the next
   // state change.
   ReentrantMonitor mReentrantMonitor;
 
   // Data about MediaStreams that are being fed by this decoder.
-  nsTArray<OutputMediaStream> mOutputStreams;
+  nsTArray<OutputStreamData> mOutputStreams;
+  // The SourceMediaStream we are using to feed the mOutputStreams. This stream
+  // is never exposed outside the decoder.
+  // Only written on the main thread while holding the monitor. Therefore it
+  // can be read on any thread while holding the monitor, or on the main thread
+  // without holding the monitor.
+  nsAutoPtr<DecodedStreamData> mDecodedStream;
 
   // Set to one of the valid play states.
   // This can only be changed on the main thread while holding the decoder
   // monitor. Thus, it can be safely read while holding the decoder monitor
   // OR on the main thread.
   // Any change to the state on the main thread must call NotifyAll on the
   // monitor so the decode thread can wake up.
   PlayState mPlayState;
@@ -737,11 +807,15 @@ public:
   // such a manner that progress event data is inaccurate. This is set
   // during seek and duration operations to prevent the progress indicator
   // from jumping around. Read/Write from any thread. Must have decode monitor
   // locked before accessing.
   bool mIgnoreProgressData;
 
   // True if the stream is infinite (e.g. a webradio).
   bool mInfiniteStream;
+
+  // True if NotifyDecodedStreamMainThreadStateChanged should retrigger
+  // PlaybackEnded when mDecodedStream->mStream finishes.
+  bool mTriggerPlaybackEndedWhenSourceStreamFinishes;
 };
 
 #endif
--- a/content/media/nsBuiltinDecoderStateMachine.cpp
+++ b/content/media/nsBuiltinDecoderStateMachine.cpp
@@ -423,16 +423,18 @@ nsBuiltinDecoderStateMachine::nsBuiltinD
   mBufferingWait = mRealTime ? 0 : BUFFERING_WAIT_S;
   mLowDataThresholdUsecs = mRealTime ? 0 : LOW_DATA_THRESHOLD_USECS;
 }
 
 nsBuiltinDecoderStateMachine::~nsBuiltinDecoderStateMachine()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
   MOZ_COUNT_DTOR(nsBuiltinDecoderStateMachine);
+  NS_ASSERTION(!mPendingWakeDecoder.get(),
+               "WakeDecoder should have been revoked already");
   NS_ASSERTION(!StateMachineTracker::Instance().IsQueued(this),
     "Should not have a pending request for a new decode thread");
   NS_ASSERTION(!mRequestedNewDecodeThread,
     "Should not have (or flagged) a pending request for a new decode thread");
   if (mTimer)
     mTimer->Cancel();
   mTimer = nullptr;
   mReader = nullptr;
@@ -491,36 +493,37 @@ void nsBuiltinDecoderStateMachine::Decod
       DecodeSeek();
     }
   }
 
   mDecodeThreadIdle = true;
   LOG(PR_LOG_DEBUG, ("%p Decode thread finished", mDecoder.get()));
 }
 
-void nsBuiltinDecoderStateMachine::SendOutputStreamAudio(AudioData* aAudio,
-                                                         OutputMediaStream* aStream,
-                                                         AudioSegment* aOutput)
+void nsBuiltinDecoderStateMachine::SendStreamAudio(AudioData* aAudio,
+                                                   DecodedStreamData* aStream,
+                                                   AudioSegment* aOutput)
 {
+  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
   if (aAudio->mTime <= aStream->mLastAudioPacketTime) {
     // ignore packet that we've already processed
     return;
   }
   aStream->mLastAudioPacketTime = aAudio->mTime;
   aStream->mLastAudioPacketEndTime = aAudio->GetEnd();
 
   NS_ASSERTION(aOutput->GetChannels() == int32_t(aAudio->mChannels),
                "Wrong number of channels");
 
   // This logic has to mimic AudioLoop closely to make sure we write
   // the exact same silences
   CheckedInt64 audioWrittenOffset = UsecsToFrames(mInfo.mAudioRate,
-      aStream->mAudioFramesWrittenBaseTime + mStartTime) + aStream->mAudioFramesWritten;
+      aStream->mInitialTime + mStartTime) + aStream->mAudioFramesWritten;
   CheckedInt64 frameOffset = UsecsToFrames(mInfo.mAudioRate, aAudio->mTime);
   if (!audioWrittenOffset.isValid() || !frameOffset.isValid())
     return;
   if (audioWrittenOffset.value() < frameOffset.value()) {
     // Write silence to catch up
     LOG(PR_LOG_DEBUG, ("%p Decoder writing %d frames of silence to MediaStream",
                        mDecoder.get(), int32_t(frameOffset.value() - audioWrittenOffset.value())));
     AudioSegment silence;
@@ -560,121 +563,122 @@ static void WriteVideoToMediaStream(mozi
   nsRefPtr<mozilla::layers::Image> image = aImage;
   aOutput->AppendFrame(image.forget(), aDuration, aIntrinsicSize);
 }
 
 static const TrackID TRACK_AUDIO = 1;
 static const TrackID TRACK_VIDEO = 2;
 static const TrackRate RATE_VIDEO = USECS_PER_S;
 
-void nsBuiltinDecoderStateMachine::SendOutputStreamData()
+void nsBuiltinDecoderStateMachine::SendStreamData()
 {
+  NS_ASSERTION(OnDecodeThread(), "Should be on decode thread.");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
+  DecodedStreamData* stream = mDecoder->GetDecodedStream();
+  if (!stream)
+    return;
+
   if (mState == DECODER_STATE_DECODING_METADATA)
     return;
 
-  nsTArray<OutputMediaStream>& streams = mDecoder->OutputStreams();
   int64_t minLastAudioPacketTime = PR_INT64_MAX;
+  SourceMediaStream* mediaStream = stream->mStream;
+  StreamTime endPosition = 0;
+
+  if (!stream->mStreamInitialized) {
+    if (mInfo.mHasAudio) {
+      AudioSegment* audio = new AudioSegment();
+      audio->Init(mInfo.mAudioChannels);
+      mediaStream->AddTrack(TRACK_AUDIO, mInfo.mAudioRate, 0, audio);
+    }
+    if (mInfo.mHasVideo) {
+      VideoSegment* video = new VideoSegment();
+      mediaStream->AddTrack(TRACK_VIDEO, RATE_VIDEO, 0, video);
+    }
+    stream->mStreamInitialized = true;
+  }
+
+  if (mInfo.mHasAudio) {
+    nsAutoTArray<AudioData*,10> audio;
+    // It's OK to hold references to the AudioData because while audio
+    // is captured, only the decoder thread pops from the queue (see below).
+    mReader->mAudioQueue.GetElementsAfter(stream->mLastAudioPacketTime, &audio);
+    AudioSegment output;
+    output.Init(mInfo.mAudioChannels);
+    for (uint32_t i = 0; i < audio.Length(); ++i) {
+      SendStreamAudio(audio[i], stream, &output);
+    }
+    if (output.GetDuration() > 0) {
+      mediaStream->AppendToTrack(TRACK_AUDIO, &output);
+    }
+    if (mReader->mAudioQueue.IsFinished() && !stream->mHaveSentFinishAudio) {
+      mediaStream->EndTrack(TRACK_AUDIO);
+      stream->mHaveSentFinishAudio = true;
+    }
+    minLastAudioPacketTime = NS_MIN(minLastAudioPacketTime, stream->mLastAudioPacketTime);
+    endPosition = NS_MAX(endPosition,
+        TicksToTimeRoundDown(mInfo.mAudioRate, stream->mAudioFramesWritten));
+  }
+
+  if (mInfo.mHasVideo) {
+    nsAutoTArray<VideoData*,10> video;
+    // It's OK to hold references to the VideoData only the decoder thread
+    // pops from the queue.
+    mReader->mVideoQueue.GetElementsAfter(stream->mNextVideoTime + mStartTime, &video);
+    VideoSegment output;
+    for (uint32_t i = 0; i < video.Length(); ++i) {
+      VideoData* v = video[i];
+      if (stream->mNextVideoTime + mStartTime < v->mTime) {
+        LOG(PR_LOG_DEBUG, ("%p Decoder writing last video to MediaStream %p for %lld ms",
+                           mDecoder.get(), mediaStream,
+                           v->mTime - (stream->mNextVideoTime + mStartTime)));
+        // Write last video frame to catch up. mLastVideoImage can be null here
+        // which is fine, it just means there's no video.
+        WriteVideoToMediaStream(stream->mLastVideoImage,
+          v->mTime - (stream->mNextVideoTime + mStartTime), stream->mLastVideoImageDisplaySize,
+            &output);
+        stream->mNextVideoTime = v->mTime - mStartTime;
+      }
+      if (stream->mNextVideoTime + mStartTime < v->mEndTime) {
+        LOG(PR_LOG_DEBUG, ("%p Decoder writing video frame %lld to MediaStream %p for %lld ms",
+                           mDecoder.get(), v->mTime, mediaStream,
+                           v->mEndTime - (stream->mNextVideoTime + mStartTime)));
+        WriteVideoToMediaStream(v->mImage,
+            v->mEndTime - (stream->mNextVideoTime + mStartTime), v->mDisplay,
+            &output);
+        stream->mNextVideoTime = v->mEndTime - mStartTime;
+        stream->mLastVideoImage = v->mImage;
+        stream->mLastVideoImageDisplaySize = v->mDisplay;
+      } else {
+        LOG(PR_LOG_DEBUG, ("%p Decoder skipping writing video frame %lld to MediaStream",
+                           mDecoder.get(), v->mTime));
+      }
+    }
+    if (output.GetDuration() > 0) {
+      mediaStream->AppendToTrack(TRACK_VIDEO, &output);
+    }
+    if (mReader->mVideoQueue.IsFinished() && !stream->mHaveSentFinishVideo) {
+      mediaStream->EndTrack(TRACK_VIDEO);
+      stream->mHaveSentFinishVideo = true;
+    }
+    endPosition = NS_MAX(endPosition,
+        TicksToTimeRoundDown(RATE_VIDEO, stream->mNextVideoTime - stream->mInitialTime));
+  }
+
+  if (!stream->mHaveSentFinish) {
+    stream->mStream->AdvanceKnownTracksTime(endPosition);
+  }
 
   bool finished =
       (!mInfo.mHasAudio || mReader->mAudioQueue.IsFinished()) &&
       (!mInfo.mHasVideo || mReader->mVideoQueue.IsFinished());
-
-  for (uint32_t i = 0; i < streams.Length(); ++i) {
-    OutputMediaStream* stream = &streams[i];
-    SourceMediaStream* mediaStream = stream->mStream;
-    StreamTime endPosition = 0;
-
-    if (!stream->mStreamInitialized) {
-      if (mInfo.mHasAudio) {
-        AudioSegment* audio = new AudioSegment();
-        audio->Init(mInfo.mAudioChannels);
-        mediaStream->AddTrack(TRACK_AUDIO, mInfo.mAudioRate, 0, audio);
-      }
-      if (mInfo.mHasVideo) {
-        VideoSegment* video = new VideoSegment();
-        mediaStream->AddTrack(TRACK_VIDEO, RATE_VIDEO, 0, video);
-      }
-      stream->mStreamInitialized = true;
-    }
-
-    if (mInfo.mHasAudio) {
-      nsAutoTArray<AudioData*,10> audio;
-      // It's OK to hold references to the AudioData because while audio
-      // is captured, only the decoder thread pops from the queue (see below).
-      mReader->mAudioQueue.GetElementsAfter(stream->mLastAudioPacketTime, &audio);
-      AudioSegment output;
-      output.Init(mInfo.mAudioChannels);
-      for (uint32_t i = 0; i < audio.Length(); ++i) {
-        SendOutputStreamAudio(audio[i], stream, &output);
-      }
-      if (output.GetDuration() > 0) {
-        mediaStream->AppendToTrack(TRACK_AUDIO, &output);
-      }
-      if (mReader->mAudioQueue.IsFinished() && !stream->mHaveSentFinishAudio) {
-        mediaStream->EndTrack(TRACK_AUDIO);
-        stream->mHaveSentFinishAudio = true;
-      }
-      minLastAudioPacketTime = NS_MIN(minLastAudioPacketTime, stream->mLastAudioPacketTime);
-      endPosition = NS_MAX(endPosition,
-          TicksToTimeRoundDown(mInfo.mAudioRate, stream->mAudioFramesWritten));
-    }
-
-    if (mInfo.mHasVideo) {
-      nsAutoTArray<VideoData*,10> video;
-      // It's OK to hold references to the VideoData only the decoder thread
-      // pops from the queue.
-      mReader->mVideoQueue.GetElementsAfter(stream->mNextVideoTime + mStartTime, &video);
-      VideoSegment output;
-      for (uint32_t i = 0; i < video.Length(); ++i) {
-        VideoData* v = video[i];
-        if (stream->mNextVideoTime + mStartTime < v->mTime) {
-          LOG(PR_LOG_DEBUG, ("%p Decoder writing last video to MediaStream for %lld ms",
-                             mDecoder.get(), v->mTime - (stream->mNextVideoTime + mStartTime)));
-          // Write last video frame to catch up. mLastVideoImage can be null here
-          // which is fine, it just means there's no video.
-          WriteVideoToMediaStream(stream->mLastVideoImage,
-              v->mTime - (stream->mNextVideoTime + mStartTime), stream->mLastVideoImageDisplaySize,
-              &output);
-          stream->mNextVideoTime = v->mTime - mStartTime;
-        }
-        if (stream->mNextVideoTime + mStartTime < v->mEndTime) {
-          LOG(PR_LOG_DEBUG, ("%p Decoder writing video frame %lld to MediaStream",
-                             mDecoder.get(), v->mTime));
-          WriteVideoToMediaStream(v->mImage,
-              v->mEndTime - (stream->mNextVideoTime + mStartTime), v->mDisplay,
-              &output);
-          stream->mNextVideoTime = v->mEndTime - mStartTime;
-          stream->mLastVideoImage = v->mImage;
-          stream->mLastVideoImageDisplaySize = v->mDisplay;
-        } else {
-          LOG(PR_LOG_DEBUG, ("%p Decoder skipping writing video frame %lld to MediaStream",
-                             mDecoder.get(), v->mTime));
-        }
-      }
-      if (output.GetDuration() > 0) {
-        mediaStream->AppendToTrack(TRACK_VIDEO, &output);
-      }
-      if (mReader->mVideoQueue.IsFinished() && !stream->mHaveSentFinishVideo) {
-        mediaStream->EndTrack(TRACK_VIDEO);
-        stream->mHaveSentFinishVideo = true;
-      }
-      endPosition = NS_MAX(endPosition,
-          TicksToTimeRoundDown(RATE_VIDEO, stream->mNextVideoTime));
-    }
-
-    if (!stream->mHaveSentFinish) {
-      stream->mStream->AdvanceKnownTracksTime(endPosition);
-    }
-
-    if (finished && !stream->mHaveSentFinish) {
-      stream->mHaveSentFinish = true;
-      stream->mStream->Finish();
-    }
+  if (finished && !stream->mHaveSentFinish) {
+    stream->mHaveSentFinish = true;
+    stream->mStream->Finish();
   }
 
   if (mAudioCaptured) {
     // Discard audio packets that are no longer needed.
     int64_t audioPacketTimeToDiscard =
         NS_MIN(minLastAudioPacketTime, mStartTime + mCurrentFrameTime);
     while (true) {
       nsAutoPtr<AudioData> a(mReader->mAudioQueue.PopFront());
@@ -694,106 +698,68 @@ void nsBuiltinDecoderStateMachine::SendO
 
     if (finished) {
       mAudioCompleted = true;
       UpdateReadyState();
     }
   }
 }
 
-void nsBuiltinDecoderStateMachine::FinishOutputStreams()
+nsBuiltinDecoderStateMachine::WakeDecoderRunnable*
+nsBuiltinDecoderStateMachine::GetWakeDecoderRunnable()
 {
-  // Tell all our output streams that all tracks have ended and we've
-  // finished.
-  nsTArray<OutputMediaStream>& streams = mDecoder->OutputStreams();
-  for (uint32_t i = 0; i < streams.Length(); ++i) {
-    OutputMediaStream* stream = &streams[i];
-    if (!stream->mStreamInitialized) {
-      continue;
-    }
-    SourceMediaStream* mediaStream = stream->mStream;
-    if (mInfo.mHasAudio && !stream->mHaveSentFinishAudio) {
-      mediaStream->EndTrack(TRACK_AUDIO);
-      stream->mHaveSentFinishAudio = true;
-    }
-    if (mInfo.mHasVideo && !stream->mHaveSentFinishVideo) {
-      mediaStream->EndTrack(TRACK_VIDEO);
-      stream->mHaveSentFinishVideo = true;
-    }
-    // XXX ignoring mFinishWhenEnded for now. Immediate goal is to not crash.
-    if (!stream->mHaveSentFinish) {
-      mediaStream->Finish();
-      stream->mHaveSentFinish = true;
-    }
+  mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
+
+  if (!mPendingWakeDecoder.get()) {
+    mPendingWakeDecoder = new WakeDecoderRunnable(this);
   }
+  return mPendingWakeDecoder.get();
 }
 
 bool nsBuiltinDecoderStateMachine::HaveEnoughDecodedAudio(int64_t aAmpleAudioUSecs)
 {
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
   if (mReader->mAudioQueue.GetSize() == 0 ||
       GetDecodedAudioDuration() < aAmpleAudioUSecs) {
     return false;
   }
   if (!mAudioCaptured) {
     return true;
   }
 
-  nsTArray<OutputMediaStream>& streams = mDecoder->OutputStreams();
-  for (uint32_t i = 0; i < streams.Length(); ++i) {
-    OutputMediaStream* stream = &streams[i];
-    if (stream->mStreamInitialized && !stream->mHaveSentFinishAudio &&
-        !stream->mStream->HaveEnoughBuffered(TRACK_AUDIO)) {
+  DecodedStreamData* stream = mDecoder->GetDecodedStream();
+  if (stream && stream->mStreamInitialized && !stream->mHaveSentFinishAudio) {
+    if (!stream->mStream->HaveEnoughBuffered(TRACK_AUDIO)) {
       return false;
     }
+    stream->mStream->DispatchWhenNotEnoughBuffered(TRACK_AUDIO,
+        GetStateMachineThread(), GetWakeDecoderRunnable());
   }
 
-  nsIThread* thread = GetStateMachineThread();
-  nsCOMPtr<nsIRunnable> callback = NS_NewRunnableMethod(this,
-      &nsBuiltinDecoderStateMachine::ScheduleStateMachineWithLockAndWakeDecoder);
-  for (uint32_t i = 0; i < streams.Length(); ++i) {
-    OutputMediaStream* stream = &streams[i];
-    if (stream->mStreamInitialized && !stream->mHaveSentFinishAudio) {
-      stream->mStream->DispatchWhenNotEnoughBuffered(TRACK_AUDIO, thread, callback);
-    }
-  }
   return true;
 }
 
 bool nsBuiltinDecoderStateMachine::HaveEnoughDecodedVideo()
 {
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
 
   if (static_cast<uint32_t>(mReader->mVideoQueue.GetSize()) < AMPLE_VIDEO_FRAMES) {
     return false;
   }
 
-  nsTArray<OutputMediaStream>& streams = mDecoder->OutputStreams();
-  if (streams.IsEmpty()) {
-    return true;
-  }
-
-  for (uint32_t i = 0; i < streams.Length(); ++i) {
-    OutputMediaStream* stream = &streams[i];
-    if (stream->mStreamInitialized && !stream->mHaveSentFinishVideo &&
-        !stream->mStream->HaveEnoughBuffered(TRACK_VIDEO)) {
+  DecodedStreamData* stream = mDecoder->GetDecodedStream();
+  if (stream && stream->mStreamInitialized && !stream->mHaveSentFinishVideo) {
+    if (!stream->mStream->HaveEnoughBuffered(TRACK_VIDEO)) {
       return false;
     }
+    stream->mStream->DispatchWhenNotEnoughBuffered(TRACK_VIDEO,
+        GetStateMachineThread(), GetWakeDecoderRunnable());
   }
 
-  nsIThread* thread = GetStateMachineThread();
-  nsCOMPtr<nsIRunnable> callback = NS_NewRunnableMethod(this,
-      &nsBuiltinDecoderStateMachine::ScheduleStateMachineWithLockAndWakeDecoder);
-  for (uint32_t i = 0; i < streams.Length(); ++i) {
-    OutputMediaStream* stream = &streams[i];
-    if (stream->mStreamInitialized && !stream->mHaveSentFinishVideo) {
-      stream->mStream->DispatchWhenNotEnoughBuffered(TRACK_VIDEO, thread, callback);
-    }
-  }
   return true;
 }
 
 void nsBuiltinDecoderStateMachine::DecodeLoop()
 {
   LOG(PR_LOG_DEBUG, ("%p Start DecodeLoop()", mDecoder.get()));
 
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
@@ -910,17 +876,17 @@ void nsBuiltinDecoderStateMachine::Decod
       audioPump = true;
     }
     mDidThrottleAudioDecoding = throttleAudioDecoding;
     if (!mDidThrottleAudioDecoding) {
       ReentrantMonitorAutoExit exitMon(mDecoder->GetReentrantMonitor());
       audioPlaying = mReader->DecodeAudioData();
     }
 
-    SendOutputStreamData();
+    SendStreamData();
 
     // Notify to ensure that the AudioLoop() is not waiting, in case it was
     // waiting for more audio to be decoded.
     mDecoder->GetReentrantMonitor().NotifyAll();
 
     // The ready state can change when we've decoded data, so update the
     // ready state, so that DOM events can fire.
     UpdateReadyState();
@@ -1520,16 +1486,17 @@ void nsBuiltinDecoderStateMachine::Seek(
 
   // Bound the seek time to be inside the media range.
   NS_ASSERTION(mStartTime != -1, "Should know start time by now");
   NS_ASSERTION(mEndTime != -1, "Should know end time by now");
   mSeekTime = NS_MIN(mSeekTime, mEndTime);
   mSeekTime = NS_MAX(mStartTime, mSeekTime);
   LOG(PR_LOG_DEBUG, ("%p Changed state to SEEKING (to %f)", mDecoder.get(), aTime));
   mState = DECODER_STATE_SEEKING;
+  mDecoder->RecreateDecodedStream(mSeekTime - mStartTime);
   ScheduleStateMachine();
 }
 
 void nsBuiltinDecoderStateMachine::StopDecodeThread()
 {
   NS_ASSERTION(OnStateMachineThread(), "Should be on state machine thread.");
   mDecoder->GetReentrantMonitor().AssertCurrentThreadIn();
   if (mRequestedNewDecodeThread) {
@@ -1996,22 +1963,21 @@ nsresult nsBuiltinDecoderStateMachine::R
 
   switch (mState) {
     case DECODER_STATE_SHUTDOWN: {
       if (IsPlaying()) {
         StopPlayback();
       }
       StopAudioThread();
       StopDecodeThread();
+      // Now that those threads are stopped, there's no possibility of
+      // mPendingWakeDecoder being needed again. Revoke it.
+      mPendingWakeDecoder = nullptr;
       NS_ASSERTION(mState == DECODER_STATE_SHUTDOWN,
                    "How did we escape from the shutdown state?");
-      // Need to call this before dispatching nsDispatchDisposeEvent below, to
-      // ensure that any notifications dispatched by the stream graph
-      // will run before nsDispatchDisposeEvent below.
-      FinishOutputStreams();
       // We must daisy-chain these events to destroy the decoder. We must
       // destroy the decoder on the main thread, but we can't destroy the
       // decoder while this thread holds the decoder monitor. We can't
       // dispatch an event to the main thread to destroy the decoder from
       // here, as the event may run before the dispatch returns, and we
       // hold the decoder monitor here. We also want to guarantee that the
       // state machine is destroyed on the main thread, and so the
       // event runner running this function (which holds a reference to the
--- a/content/media/nsBuiltinDecoderStateMachine.h
+++ b/content/media/nsBuiltinDecoderStateMachine.h
@@ -101,17 +101,17 @@ hardware (via nsAudioStream and libsydne
 */
 class nsBuiltinDecoderStateMachine : public nsDecoderStateMachine
 {
 public:
   typedef mozilla::ReentrantMonitor ReentrantMonitor;
   typedef mozilla::TimeStamp TimeStamp;
   typedef mozilla::TimeDuration TimeDuration;
   typedef mozilla::VideoFrameContainer VideoFrameContainer;
-  typedef nsBuiltinDecoder::OutputMediaStream OutputMediaStream;
+  typedef nsBuiltinDecoder::DecodedStreamData DecodedStreamData;
   typedef mozilla::SourceMediaStream SourceMediaStream;
   typedef mozilla::AudioSegment AudioSegment;
   typedef mozilla::VideoSegment VideoSegment;
 
   nsBuiltinDecoderStateMachine(nsBuiltinDecoder* aDecoder, nsBuiltinDecoderReader* aReader, bool aRealTime = false);
   ~nsBuiltinDecoderStateMachine();
 
   // nsDecoderStateMachine interface
@@ -255,22 +255,56 @@ public:
   void ReleaseDecoder() { mDecoder = nullptr; }
 
    // Called when a "MozAudioAvailable" event listener is added to the media
    // element. Called on the main thread.
    void NotifyAudioAvailableListener();
 
   // Copy queued audio/video data in the reader to any output MediaStreams that
   // need it.
-  void SendOutputStreamData();
-  void FinishOutputStreams();
+  void SendStreamData();
+  void FinishStreamData();
   bool HaveEnoughDecodedAudio(int64_t aAmpleAudioUSecs);
   bool HaveEnoughDecodedVideo();
 
 protected:
+  class WakeDecoderRunnable : public nsRunnable {
+  public:
+    WakeDecoderRunnable(nsBuiltinDecoderStateMachine* aSM)
+      : mMutex("WakeDecoderRunnable"), mStateMachine(aSM) {}
+    NS_IMETHOD Run()
+    {
+      nsRefPtr<nsBuiltinDecoderStateMachine> stateMachine;
+      {
+        // Don't let Run() (called by media stream graph thread) race with
+        // Revoke() (called by decoder state machine thread)
+        MutexAutoLock lock(mMutex);
+        if (!mStateMachine)
+          return NS_OK;
+        stateMachine = mStateMachine;
+      }
+      stateMachine->ScheduleStateMachineWithLockAndWakeDecoder();
+      return NS_OK;
+    }
+    void Revoke()
+    {
+      MutexAutoLock lock(mMutex);
+      mStateMachine = nullptr;
+    }
+
+    Mutex mMutex;
+    // Protected by mMutex.
+    // We don't use an owning pointer here, because keeping mStateMachine alive
+    // would mean in some cases we'd have to destroy mStateMachine from this
+    // object, which would be problematic since nsBuiltinDecoderStateMachine can
+    // only be destroyed on the main thread whereas this object can be destroyed
+    // on the media stream graph thread.
+    nsBuiltinDecoderStateMachine* mStateMachine;
+  };
+  WakeDecoderRunnable* GetWakeDecoderRunnable();
 
   // Returns true if we've got less than aAudioUsecs microseconds of decoded
   // and playable data. The decoder monitor must be held.
   bool HasLowDecodedData(int64_t aAudioUsecs) const;
 
   // Returns true if we're running low on data which is not yet decoded.
   // The decoder monitor must be held.
   bool HasLowUndecodedData() const;
@@ -425,18 +459,18 @@ protected:
   void DecodeLoop();
 
   // Decode thread run function. Determines which of the Decode*() functions
   // to call.
   void DecodeThreadRun();
 
   // Copy audio from an AudioData packet to aOutput. This may require
   // inserting silence depending on the timing of the audio packet.
-  void SendOutputStreamAudio(AudioData* aAudio, OutputMediaStream* aStream,
-                             AudioSegment* aOutput);
+  void SendStreamAudio(AudioData* aAudio, DecodedStreamData* aStream,
+                       AudioSegment* aOutput);
 
   // State machine thread run function. Defers to RunStateMachine().
   nsresult CallRunStateMachine();
 
   // Performs one "cycle" of the state machine. Polls the state, and may send
   // a video frame to be displayed, and generally manages the decode. Called
   // periodically via timer to ensure the video stays in sync.
   nsresult RunStateMachine();
@@ -525,16 +559,23 @@ protected:
   // first acquire the decoder monitor and check that it is non-null.
   nsRefPtr<nsAudioStream> mAudioStream;
 
   // The reader, don't call its methods with the decoder monitor held.
   // This is created in the play state machine's constructor, and destroyed
   // in the play state machine's destructor.
   nsAutoPtr<nsBuiltinDecoderReader> mReader;
 
+  // Accessed only on the state machine thread.
+  // Not an nsRevocableEventPtr since we must Revoke() it well before
+  // this object is destroyed, anyway.
+  // Protected by decoder monitor except during the SHUTDOWN state after the
+  // decoder thread has been stopped.
+  nsRevocableEventPtr<WakeDecoderRunnable> mPendingWakeDecoder;
+
   // The time of the current frame in microseconds. This is referenced from
   // 0 which is the initial playback position. Set by the state machine
   // thread, and read-only from the main thread to get the current
   // time value. Synchronised via decoder monitor.
   int64_t mCurrentFrameTime;
 
   // The presentation time of the first audio frame that was played in
   // microseconds. We can add this to the audio stream position to determine
--- a/content/media/nsDOMMediaStream.cpp
+++ b/content/media/nsDOMMediaStream.cpp
@@ -46,13 +46,22 @@ already_AddRefed<nsDOMMediaStream>
 nsDOMMediaStream::CreateInputStream()
 {
   nsRefPtr<nsDOMMediaStream> stream = new nsDOMMediaStream();
   MediaStreamGraph* gm = MediaStreamGraph::GetInstance();
   stream->mStream = gm->CreateInputStream(stream);
   return stream.forget();
 }
 
+already_AddRefed<nsDOMMediaStream>
+nsDOMMediaStream::CreateTrackUnionStream()
+{
+  nsRefPtr<nsDOMMediaStream> stream = new nsDOMMediaStream();
+  MediaStreamGraph* gm = MediaStreamGraph::GetInstance();
+  stream->mStream = gm->CreateTrackUnionStream(stream);
+  return stream.forget();
+}
+
 bool
 nsDOMMediaStream::CombineWithPrincipal(nsIPrincipal* aPrincipal)
 {
   return nsContentUtils::CombineResourcePrincipals(&mPrincipal, aPrincipal);
 }
--- a/content/media/nsDOMMediaStream.h
+++ b/content/media/nsDOMMediaStream.h
@@ -50,16 +50,21 @@ public:
    */
   bool CombineWithPrincipal(nsIPrincipal* aPrincipal);
 
   /**
    * Create an nsDOMMediaStream whose underlying stream is a SourceMediaStream.
    */
   static already_AddRefed<nsDOMMediaStream> CreateInputStream();
 
+  /**
+   * Create an nsDOMMediaStream whose underlying stream is a TrackUnionStream.
+   */
+  static already_AddRefed<nsDOMMediaStream> CreateTrackUnionStream();
+
 protected:
   // MediaStream is owned by the graph, but we tell it when to die, and it won't
   // die until we let it.
   MediaStream* mStream;
   // Principal identifying who may access the contents of this stream.
   // If null, this stream can be used by anyone because it has no content yet.
   nsCOMPtr<nsIPrincipal> mPrincipal;
 };
--- a/content/media/nsMediaDecoder.h
+++ b/content/media/nsMediaDecoder.h
@@ -36,16 +36,19 @@ static const uint32_t FRAMEBUFFER_LENGTH
 // with the exception of GetVideoFrameContainer and GetStatistics,
 // which can be called from any thread.
 class nsMediaDecoder : public nsIObserver
 {
 public:
   typedef mozilla::MediaResource MediaResource;
   typedef mozilla::ReentrantMonitor ReentrantMonitor;
   typedef mozilla::SourceMediaStream SourceMediaStream;
+  typedef mozilla::ProcessedMediaStream ProcessedMediaStream;
+  typedef mozilla::MediaInputPort MediaInputPort;
+  typedef mozilla::MainThreadMediaStreamListener MainThreadMediaStreamListener;
   typedef mozilla::TimeStamp TimeStamp;
   typedef mozilla::TimeDuration TimeDuration;
   typedef mozilla::VideoFrameContainer VideoFrameContainer;
 
   nsMediaDecoder();
   virtual ~nsMediaDecoder();
 
   // Create a new decoder of the same type as this one.
@@ -97,17 +100,20 @@ public:
   // Set the audio volume. It should be a value from 0 to 1.0.
   virtual void SetVolume(double aVolume) = 0;
 
   // Sets whether audio is being captured. If it is, we won't play any
   // of our audio.
   virtual void SetAudioCaptured(bool aCaptured) = 0;
 
   // Add an output stream. All decoder output will be sent to the stream.
-  virtual void AddOutputStream(SourceMediaStream* aStream, bool aFinishWhenEnded) = 0;
+  // The stream is initially blocked. The decoder is responsible for unblocking
+  // it while it is playing back.
+  virtual void AddOutputStream(ProcessedMediaStream* aStream,
+                               bool aFinishWhenEnded) = 0;
 
   // Start playback of a video. 'Load' must have previously been
   // called.
   virtual nsresult Play() = 0;
 
   // Start downloading the media. Decode the downloaded data up to the
   // point of the first frame of data.
   // aResource is the media stream to use. Ownership of aResource passes to
--- a/content/media/test/test_error_on_404.html
+++ b/content/media/test/test_error_on_404.html
@@ -8,25 +8,20 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="text/javascript" src="manifest.js"></script> 
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=476731">Mozilla Bug </a>
 <p id="display"></p>
 <div id="content" style="display: none">
-  
 </div>
 <pre id="test">
-
-
-
 <script type="application/javascript">
-
-/** Test for Bug  **/
+/** Test for Bug 476731 **/
 
 var videos = [];
 
 function FinishedLoads() {
   if (videos.length == 0)
     return false;
   for (var i=0; i<videos.length; ++i) {
     if (videos[i]._loadedData) {
@@ -67,25 +62,22 @@ for (var i=0; i<g404Tests.length; ++i) {
     continue;
    }
    v.src = test.name;
    v._loadedData = false;
    v._loadStart = false;
    v._loadError = false;
    v.addEventListener("error", loadError, false);
    v.addEventListener("loadstart", loadStart, false);
-   v.addEventListener("loadedddata", loadedData, false);
+   v.addEventListener("loadeddata", loadedData, false);
    document.body.appendChild(v); // Will start load.
    videos.push(v);
 }
 
-
 if (videos.length == 0) {
   todo(false, "No types supported");
 } else {
   SimpleTest.waitForExplicitFinish();
 }
-
 </script>
 </pre>
-
 </body>
 </html>
--- a/content/media/test/test_streams_element_capture_reset.html
+++ b/content/media/test/test_streams_element_capture_reset.html
@@ -4,69 +4,92 @@
   <title>Test that reloading and seeking in a media element that's being captured doesn't crash</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" src="manifest.js"></script>
 </head>
 <body>
 <video id="v"></video>
 <video id="vout"></video>
+<video id="vout_untilended"></video>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 SimpleTest.waitForExplicitFinish();
 
 var v = document.getElementById('v');
 var vout = document.getElementById('vout');
-var stream = v.mozCaptureStream();
-vout.src = stream;
+var vout_untilended = document.getElementById('vout_untilended');
+vout.src = v.mozCaptureStream();
+vout_untilended.src = v.mozCaptureStreamUntilEnded();
 
 function dumpEvent(event) {
   dump("GOT EVENT " + event.type + " currentTime=" + event.target.currentTime +
        " paused=" + event.target.paused + " ended=" + event.target.ended +
        " readyState=" + event.target.readyState + "\n");
 }
 var events = ["timeupdate", "seeking", "seeked", "ended", "playing", "pause"];
 for (var i = 0; i < events.length; ++i) {
   v.addEventListener(events[i], dumpEvent, false);
 }
+function isWithinEps(a, b, msg) {
+  ok(Math.abs(a - b) < 0.01,
+     "Got " + a + ", expected " + b + "; " + msg);
+}
 
 function startTest(test) {
   var seekTime = test.duration/2;
 
-  function ended() {
-    ok(true, "Final ended after changing src");
+  function endedAfterReplay() {
+    isWithinEps(v.currentTime, test.duration, "checking v.currentTime at third 'ended' event");
+    isWithinEps(vout.currentTime, (v.currentTime - seekTime) + test.duration*2,
+	            "checking vout.currentTime after seeking, playing through and reloading");
     SimpleTest.finish();
   };
-  function timeupdateAfterSeek() {
-    if (v.currentTime < seekTime + 0.001)
-      return;
-    ok(true, "timeupdate after seek");
-    v.removeEventListener("timeupdate", timeupdateAfterSeek, false);
+  function endedAfterSeek() {
+    isWithinEps(v.currentTime, test.duration, "checking v.currentTime at second 'ended' event");
+    isWithinEps(vout.currentTime, (v.currentTime - seekTime) + test.duration,
+                "checking vout.currentTime after seeking and playing through again");
+    v.removeEventListener("ended", endedAfterSeek, false);
+    v.addEventListener("ended", endedAfterReplay, false);
     v.src = test.name + "?1";
     v.play();
-    v.addEventListener("ended", ended, false);
   };
   function seeked() {
-    ok(true, "Finished seeking");
+    isWithinEps(v.currentTime, seekTime, "Finished seeking");
+    isWithinEps(vout.currentTime, test.duration,
+                "checking vout.currentTime has not changed after seeking");
     v.removeEventListener("seeked", seeked, false);
-    v.addEventListener("timeupdate", timeupdateAfterSeek, false);
+    function dontPlayAgain() {
+      ok(false, "vout_untilended should not play again");
+    }
+    vout_untilended.addEventListener("playing", dontPlayAgain, false);
+    vout_untilended.addEventListener("ended", dontPlayAgain, false);
+    v.addEventListener("ended", endedAfterSeek, false);
+    v.play();
   };
-  function timeupdate() {
-    if (v.currentTime == 0)
-      return;
-    ok(true, "Initial timeupdate");
-    v.removeEventListener("timeupdate", timeupdate, false);
+  function ended() {
+    isWithinEps(vout.currentTime, test.duration, "checking vout.currentTime at first 'ended' event");
+    isWithinEps(v.currentTime, test.duration, "checking v.currentTime at first 'ended' event");
+    is(vout.ended, false, "checking vout has not ended");
+    is(vout_untilended.ended, true, "checking vout_untilended has actually ended");
+    vout_untilended.removeEventListener("ended", ended, false);
+    v.pause();
     v.currentTime = seekTime;
     v.addEventListener("seeked", seeked, false);
   };
-  v.addEventListener("timeupdate", timeupdate, false);
+  vout_untilended.addEventListener("ended", ended, false);
 
   v.src = test.name;
   v.play();
+  function checkNoEnded() {
+    ok(false, "ended event received unexpectedly");
+  };
+  vout.addEventListener("ended", checkNoEnded, false);
   vout.play();
+  vout_untilended.play();
 }
 
 var testVideo = getPlayableVideo(gSmallTests);
 if (testVideo) {
   startTest(testVideo);
 } else {
   todo(false, "No playable video");
 }
--- a/content/svg/content/src/DOMSVGAnimatedLengthList.cpp
+++ b/content/svg/content/src/DOMSVGAnimatedLengthList.cpp
@@ -53,24 +53,23 @@ DOMSVGAnimatedLengthList::GetAnimVal(nsI
 }
 
 /* static */ already_AddRefed<DOMSVGAnimatedLengthList>
 DOMSVGAnimatedLengthList::GetDOMWrapper(SVGAnimatedLengthList *aList,
                                         nsSVGElement *aElement,
                                         uint8_t aAttrEnum,
                                         uint8_t aAxis)
 {
-  DOMSVGAnimatedLengthList *wrapper =
+  nsRefPtr<DOMSVGAnimatedLengthList> wrapper =
     sSVGAnimatedLengthListTearoffTable.GetTearoff(aList);
   if (!wrapper) {
     wrapper = new DOMSVGAnimatedLengthList(aElement, aAttrEnum, aAxis);
     sSVGAnimatedLengthListTearoffTable.AddTearoff(aList, wrapper);
   }
-  NS_ADDREF(wrapper);
-  return wrapper;
+  return wrapper.forget();
 }
 
 /* static */ DOMSVGAnimatedLengthList*
 DOMSVGAnimatedLengthList::GetDOMWrapperIfExists(SVGAnimatedLengthList *aList)
 {
   return sSVGAnimatedLengthListTearoffTable.GetTearoff(aList);
 }
 
--- a/content/svg/content/src/DOMSVGAnimatedNumberList.cpp
+++ b/content/svg/content/src/DOMSVGAnimatedNumberList.cpp
@@ -52,24 +52,23 @@ DOMSVGAnimatedNumberList::GetAnimVal(nsI
   return NS_OK;
 }
 
 /* static */ already_AddRefed<DOMSVGAnimatedNumberList>
 DOMSVGAnimatedNumberList::GetDOMWrapper(SVGAnimatedNumberList *aList,
                                         nsSVGElement *aElement,
                                         uint8_t aAttrEnum)
 {
-  DOMSVGAnimatedNumberList *wrapper =
+  nsRefPtr<DOMSVGAnimatedNumberList> wrapper =
     sSVGAnimatedNumberListTearoffTable.GetTearoff(aList);
   if (!wrapper) {
     wrapper = new DOMSVGAnimatedNumberList(aElement, aAttrEnum);
     sSVGAnimatedNumberListTearoffTable.AddTearoff(aList, wrapper);
   }
-  NS_ADDREF(wrapper);
-  return wrapper;
+  return wrapper.forget();
 }
 
 /* static */ DOMSVGAnimatedNumberList*
 DOMSVGAnimatedNumberList::GetDOMWrapperIfExists(SVGAnimatedNumberList *aList)
 {
   return sSVGAnimatedNumberListTearoffTable.GetTearoff(aList);
 }
 
--- a/content/svg/content/src/DOMSVGAnimatedTransformList.cpp
+++ b/content/svg/content/src/DOMSVGAnimatedTransformList.cpp
@@ -53,24 +53,23 @@ DOMSVGAnimatedTransformList::GetAnimVal(
   NS_ADDREF(*aAnimVal = mAnimVal);
   return NS_OK;
 }
 
 /* static */ already_AddRefed<DOMSVGAnimatedTransformList>
 DOMSVGAnimatedTransformList::GetDOMWrapper(SVGAnimatedTransformList *aList,
                                            nsSVGElement *aElement)
 {
-  DOMSVGAnimatedTransformList *wrapper =
+  nsRefPtr<DOMSVGAnimatedTransformList> wrapper =
     sSVGAnimatedTransformListTearoffTable.GetTearoff(aList);
   if (!wrapper) {
     wrapper = new DOMSVGAnimatedTransformList(aElement);
     sSVGAnimatedTransformListTearoffTable.AddTearoff(aList, wrapper);
   }
-  NS_ADDREF(wrapper);
-  return wrapper;
+  return wrapper.forget();
 }
 
 /* static */ DOMSVGAnimatedTransformList*
 DOMSVGAnimatedTransformList::GetDOMWrapperIfExists(
   SVGAnimatedTransformList *aList)
 {
   return sSVGAnimatedTransformListTearoffTable.GetTearoff(aList);
 }
--- a/content/svg/content/src/DOMSVGLengthList.cpp
+++ b/content/svg/content/src/DOMSVGLengthList.cpp
@@ -71,17 +71,17 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_INTERFACE_MAP_ENTRY(nsIDOMSVGLengthList)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(SVGLengthList)
 NS_INTERFACE_MAP_END
 
 JSObject*
 DOMSVGLengthList::WrapObject(JSContext *cx, JSObject *scope, bool *triedToWrap)
 {
-  return mozilla::dom::binding::SVGLengthList::create(cx, scope, this,
+  return mozilla::dom::oldproxybindings::SVGLengthList::create(cx, scope, this,
                                                       triedToWrap);
 }
 
 nsIDOMSVGLength*
 DOMSVGLengthList::GetItemAt(uint32_t aIndex)
 {
   if (IsAnimValList()) {
     Element()->FlushAnimations();
--- a/content/svg/content/src/DOMSVGNumberList.cpp
+++ b/content/svg/content/src/DOMSVGNumberList.cpp
@@ -72,17 +72,17 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_INTERFACE_MAP_ENTRY(nsISupports)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(SVGNumberList)
 NS_INTERFACE_MAP_END
 
 
 JSObject*
 DOMSVGNumberList::WrapObject(JSContext *cx, JSObject *scope, bool *triedToWrap)
 {
-  return mozilla::dom::binding::SVGNumberList::create(cx, scope, this,
+  return mozilla::dom::oldproxybindings::SVGNumberList::create(cx, scope, this,
                                                       triedToWrap);
 }
 
 nsIDOMSVGNumber*
 DOMSVGNumberList::GetItemAt(uint32_t aIndex)
 {
   if (IsAnimValList()) {
     Element()->FlushAnimations();
--- a/content/svg/content/src/DOMSVGPathSegList.cpp
+++ b/content/svg/content/src/DOMSVGPathSegList.cpp
@@ -50,24 +50,23 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 NS_INTERFACE_MAP_END
 
 
 /* static */ already_AddRefed<DOMSVGPathSegList>
 DOMSVGPathSegList::GetDOMWrapper(void *aList,
                                  nsSVGElement *aElement,
                                  bool aIsAnimValList)
 {
-  DOMSVGPathSegList *wrapper =
+  nsRefPtr<DOMSVGPathSegList> wrapper =
     sSVGPathSegListTearoffTable.GetTearoff(aList);
   if (!wrapper) {
     wrapper = new DOMSVGPathSegList(aElement, aIsAnimValList);
     sSVGPathSegListTearoffTable.AddTearoff(aList, wrapper);
   }
-  NS_ADDREF(wrapper);
-  return wrapper;
+  return wrapper.forget();
 }
 
 /* static */ DOMSVGPathSegList*
 DOMSVGPathSegList::GetDOMWrapperIfExists(void *aList)
 {
   return sSVGPathSegListTearoffTable.GetTearoff(aList);
 }
 
@@ -79,17 +78,17 @@ DOMSVGPathSegList::~DOMSVGPathSegList()
     InternalAList().GetAnimValKey() :
     InternalAList().GetBaseValKey();
   sSVGPathSegListTearoffTable.RemoveTearoff(key);
 }
 
 JSObject*
 DOMSVGPathSegList::WrapObject(JSContext *cx, JSObject *scope, bool *triedToWrap)
 {
-  return mozilla::dom::binding::SVGPathSegList::create(cx, scope, this,
+  return mozilla::dom::oldproxybindings::SVGPathSegList::create(cx, scope, this,
                                                        triedToWrap);
 }
 
 nsIDOMSVGPathSeg*
 DOMSVGPathSegList::GetItemAt(uint32_t aIndex)
 {
   if (IsAnimValList()) {
     Element()->FlushAnimations();
--- a/content/svg/content/src/DOMSVGPointList.cpp
+++ b/content/svg/content/src/DOMSVGPointList.cpp
@@ -69,24 +69,23 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 NS_INTERFACE_MAP_END
 
 
 /* static */ already_AddRefed<DOMSVGPointList>
 DOMSVGPointList::GetDOMWrapper(void *aList,
                                nsSVGElement *aElement,
                                bool aIsAnimValList)
 {
-  DOMSVGPointList *wrapper =
+  nsRefPtr<DOMSVGPointList> wrapper =
     sSVGPointListTearoffTable.GetTearoff(aList);
   if (!wrapper) {
     wrapper = new DOMSVGPointList(aElement, aIsAnimValList);
     sSVGPointListTearoffTable.AddTearoff(aList, wrapper);
   }
-  NS_ADDREF(wrapper);
-  return wrapper;
+  return wrapper.forget();
 }
 
 /* static */ DOMSVGPointList*
 DOMSVGPointList::GetDOMWrapperIfExists(void *aList)
 {
   return sSVGPointListTearoffTable.GetTearoff(aList);
 }
 
@@ -98,17 +97,17 @@ DOMSVGPointList::~DOMSVGPointList()
     InternalAList().GetAnimValKey() :
     InternalAList().GetBaseValKey();
   sSVGPointListTearoffTable.RemoveTearoff(key);
 }
 
 JSObject*
 DOMSVGPointList::WrapObject(JSContext *cx, JSObject *scope, bool *triedToWrap)
 {
-  return mozilla::dom::binding::SVGPointList::create(cx, scope, this,
+  return mozilla::dom::oldproxybindings::SVGPointList::create(cx, scope, this,
                                                      triedToWrap);
 }
 
 nsIDOMSVGPoint*
 DOMSVGPointList::GetItemAt(uint32_t aIndex)
 {
   if (IsAnimValList()) {
     Element()->FlushAnimations();
--- a/content/svg/content/src/DOMSVGStringList.cpp
+++ b/content/svg/content/src/DOMSVGStringList.cpp
@@ -34,26 +34,25 @@ NS_INTERFACE_MAP_END
 
 
 /* static */ already_AddRefed<DOMSVGStringList>
 DOMSVGStringList::GetDOMWrapper(SVGStringList *aList,
                                 nsSVGElement *aElement,
                                 bool aIsConditionalProcessingAttribute,
                                 uint8_t aAttrEnum)
 {
-  DOMSVGStringList *wrapper =
+  nsRefPtr<DOMSVGStringList> wrapper =
     sSVGStringListTearoffTable.GetTearoff(aList);
   if (!wrapper) {
     wrapper = new DOMSVGStringList(aElement, 
                                    aIsConditionalProcessingAttribute,
                                    aAttrEnum);
     sSVGStringListTearoffTable.AddTearoff(aList, wrapper);
   }
-  NS_ADDREF(wrapper);
-  return wrapper;
+  return wrapper.forget();
 }
 
 DOMSVGStringList::~DOMSVGStringList()
 {
   // Script no longer has any references to us.
   sSVGStringListTearoffTable.RemoveTearoff(&InternalList());
 }
 
--- a/content/svg/content/src/DOMSVGTransformList.cpp
+++ b/content/svg/content/src/DOMSVGTransformList.cpp
@@ -72,17 +72,17 @@ NS_INTERFACE_MAP_END
 
 //----------------------------------------------------------------------
 // DOMSVGTransformList methods:
 
 JSObject*
 DOMSVGTransformList::WrapObject(JSContext *cx, JSObject *scope,
                                 bool *triedToWrap)
 {
-  return mozilla::dom::binding::SVGTransformList::create(cx, scope, this,
+  return mozilla::dom::oldproxybindings::SVGTransformList::create(cx, scope, this,
                                                          triedToWrap);
 }
 
 nsIDOMSVGTransform*
 DOMSVGTransformList::GetItemAt(uint32_t aIndex)
 {
   if (IsAnimValList()) {
     Element()->FlushAnimations();
--- a/content/svg/content/src/SVGLength.cpp
+++ b/content/svg/content/src/SVGLength.cpp
@@ -14,17 +14,17 @@
 #include "prdtoa.h"
 #include "nsMathUtils.h"
 #include <limits>
 
 namespace mozilla {
 
 // Declare some helpers defined below:
 static void GetUnitString(nsAString& unit, uint16_t unitType);
-static uint16_t GetUnitTypeForString(const char* unitStr);
+static uint16_t GetUnitTypeForString(const nsAString& unitStr);
 
 void
 SVGLength::GetValueAsString(nsAString &aValue) const
 {
   PRUnichar buf[24];
   nsTextFormatter::snprintf(buf, sizeof(buf)/sizeof(PRUnichar),
                             NS_LITERAL_STRING("%g").get(),
                             (double)mValue);
@@ -46,28 +46,24 @@ SVGLength::SetValueFromString(const nsAS
 
   while (*str != '\0' && IsSVGWhitespace(*str)) {
     ++str;
   }
   char *unit;
   tmpValue = float(PR_strtod(str, &unit));
   if (unit != str && NS_finite(tmpValue)) {
     char *theRest = unit;
-    if (*unit != '\0' && !IsSVGWhitespace(*unit)) {
-      while (*theRest != '\0' && !IsSVGWhitespace(*theRest)) {
-        ++theRest;
-      }
-      nsCAutoString unitStr(unit, theRest - unit);
-      tmpUnit = GetUnitTypeForString(unitStr.get());
-      if (tmpUnit == nsIDOMSVGLength::SVG_LENGTHTYPE_UNKNOWN) {
-        // nsSVGUtils::ReportToConsole
-        return false;
-      }
-    } else {
-      tmpUnit = nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER;
+    while (*theRest != '\0' && !IsSVGWhitespace(*theRest)) {
+      ++theRest;
+    }
+    tmpUnit = GetUnitTypeForString(
+                Substring(aValue, unit - str, theRest - unit));
+    if (tmpUnit == nsIDOMSVGLength::SVG_LENGTHTYPE_UNKNOWN) {
+      // nsSVGUtils::ReportToConsole
+      return false;
     }
     while (*theRest && IsSVGWhitespace(*theRest)) {
       ++theRest;
     }
     if (!*theRest) {
       mValue = tmpValue;
       mUnit = tmpUnit;
       return true;
@@ -230,24 +226,26 @@ GetUnitString(nsAString& unit, uint16_t 
     }
     return;
   }
   NS_NOTREACHED("Unknown unit type"); // Someone's using an SVGLength with an invalid unit?
   return;
 }
 
 static uint16_t
-GetUnitTypeForString(const char* unitStr)
+GetUnitTypeForString(const nsAString& unitStr)
 {
-  if (!unitStr || *unitStr == '\0')
+  if (unitStr.IsEmpty())
     return nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER;
 
-  nsCOMPtr<nsIAtom> unitAtom = do_GetAtom(unitStr);
+  nsIAtom* unitAtom = NS_GetStaticAtom(unitStr);
 
-  for (uint32_t i = 1 ; i < ArrayLength(unitMap) ; i++) {
-    if (unitMap[i] && *unitMap[i] == unitAtom) {
-      return i;
+  if (unitAtom) {
+    for (uint32_t i = 1 ; i < ArrayLength(unitMap) ; i++) {
+      if (unitMap[i] && *unitMap[i] == unitAtom) {
+        return i;
+      }
     }
   }
   return nsIDOMSVGLength::SVG_LENGTHTYPE_UNKNOWN;
 }
 
 } // namespace mozilla
--- a/content/svg/content/src/nsSVGAngle.cpp
+++ b/content/svg/content/src/nsSVGAngle.cpp
@@ -149,26 +149,28 @@ GetUnitString(nsAString& unit, uint16_t 
     return;
   }
 
   NS_NOTREACHED("Unknown unit type");
   return;
 }
 
 static uint16_t
-GetUnitTypeForString(const char* unitStr)
+GetUnitTypeForString(const nsAString& unitStr)
 {
-  if (!unitStr || *unitStr == '\0') 
+  if (unitStr.IsEmpty()) 
     return nsIDOMSVGAngle::SVG_ANGLETYPE_UNSPECIFIED;
                    
-  nsCOMPtr<nsIAtom> unitAtom = do_GetAtom(unitStr);
+  nsIAtom *unitAtom = NS_GetStaticAtom(unitStr);
 
-  for (uint32_t i = 0 ; i < ArrayLength(unitMap) ; i++) {
-    if (unitMap[i] && *unitMap[i] == unitAtom) {
-      return i;
+  if (unitAtom) {
+    for (uint32_t i = 0 ; i < ArrayLength(unitMap) ; i++) {
+      if (unitMap[i] && *unitMap[i] == unitAtom) {
+        return i;
+      }
     }
   }
 
   return nsIDOMSVGAngle::SVG_ANGLETYPE_UNKNOWN;
 }
 
 static void
 GetValueString(nsAString &aValueAsString, float aValue, uint16_t aUnitType)
@@ -193,17 +195,18 @@ GetValueFromString(const nsAString &aVal
   const char *str = value.get();
 
   if (NS_IsAsciiWhitespace(*str))
     return NS_ERROR_DOM_SYNTAX_ERR;
   
   char *rest;
   *aValue = float(PR_strtod(str, &rest));
   if (rest != str && NS_finite(*aValue)) {
-    *aUnitType = GetUnitTypeForString(rest);
+    *aUnitType = GetUnitTypeForString(
+      Substring(aValueAsString, rest - str));
     if (IsValidUnitType(*aUnitType)) {
       return NS_OK;
     }
   }
   
   return NS_ERROR_DOM_SYNTAX_ERR;
 }
 
--- a/content/svg/content/src/nsSVGElement.cpp
+++ b/content/svg/content/src/nsSVGElement.cpp
@@ -450,18 +450,19 @@ nsSVGElement::ParseAttribute(int32_t aNa
       }
     }
 
     if (!foundMatch) {
       // Check for nsSVGBoolean attribute
       BooleanAttributesInfo booleanInfo = GetBooleanInfo();
       for (i = 0; i < booleanInfo.mBooleanCount; i++) {
         if (aAttribute == *booleanInfo.mBooleanInfo[i].mName) {
-          nsCOMPtr<nsIAtom> valAtom = do_GetAtom(aValue);
-          rv = booleanInfo.mBooleans[i].SetBaseValueAtom(valAtom, this);
+          nsIAtom *valAtom = NS_GetStaticAtom(aValue);
+          rv = valAtom ? booleanInfo.mBooleans[i].SetBaseValueAtom(valAtom, this) :
+                 NS_ERROR_DOM_SYNTAX_ERR;
           if (NS_FAILED(rv)) {
             booleanInfo.Reset(i);
           } else {
             aResult.SetTo(valAtom);
             didSetResult = true;
           }
           foundMatch = true;
           break;
--- a/content/svg/content/src/nsSVGEnum.cpp
+++ b/content/svg/content/src/nsSVGEnum.cpp
@@ -137,28 +137,30 @@ nsSVGEnum::ToSMILAttr(nsSVGElement *aSVG
 }
 
 nsresult
 nsSVGEnum::SMILEnum::ValueFromString(const nsAString& aStr,
                                      const nsISMILAnimationElement* /*aSrcElement*/,
                                      nsSMILValue& aValue,
                                      bool& aPreventCachingOfSandwich) const
 {
-  nsCOMPtr<nsIAtom> valAtom = do_GetAtom(aStr);
-  nsSVGEnumMapping *mapping = mVal->GetMapping(mSVGElement);
+  nsIAtom *valAtom = NS_GetStaticAtom(aStr);
+  if (valAtom) {
+    nsSVGEnumMapping *mapping = mVal->GetMapping(mSVGElement);
 
-  while (mapping && mapping->mKey) {
-    if (valAtom == *(mapping->mKey)) {
-      nsSMILValue val(&SMILEnumType::sSingleton);
-      val.mU.mUint = mapping->mVal;
-      aValue = val;
-      aPreventCachingOfSandwich = false;
-      return NS_OK;
+    while (mapping && mapping->mKey) {
+      if (valAtom == *(mapping->mKey)) {
+        nsSMILValue val(&SMILEnumType::sSingleton);
+        val.mU.mUint = mapping->mVal;
+        aValue = val;
+        aPreventCachingOfSandwich = false;
+        return NS_OK;
+      }
+      mapping++;
     }
-    mapping++;
   }
   
   // only a warning since authors may mistype attribute values
   NS_WARNING("unknown enumeration key");
   return NS_ERROR_FAILURE;
 }
 
 nsSMILValue
</