Merge from mozilla-central.
authorJan de Mooij <jdemooij@mozilla.com>
Tue, 29 Jan 2013 10:13:51 +0100
changeset 138150 420954df93b8321b42ad776b692d642ebd23c90e
parent 138149 30ad3babd3301f1cb0f0a2fbab3cec3732b64711 (current diff)
parent 129976 0c45e6378f1f07a1e207a3f10be403c774cee850 (diff)
child 138151 9f72d9cc1461d8f2eb2ead59ed0a1d03c283e506
push id2579
push userakeybl@mozilla.com
push dateMon, 24 Jun 2013 18:52:47 +0000
treeherdermozilla-beta@b69b7de8a05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone21.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge from mozilla-central.
content/svg/content/test/test_SVGAnimatedImageSMILDisabled.html
dom/bindings/CallbackFunction.cpp
dom/telephony/CallEvent.cpp
dom/telephony/CallEvent.h
js/src/Makefile.in
js/src/gc/RootMarking.cpp
js/src/ion/Bailouts.cpp
js/src/ion/BaselineCompiler.cpp
js/src/ion/BaselineIC.cpp
js/src/ion/BaselineJIT.cpp
js/src/ion/BaselineJIT.h
js/src/ion/Ion.cpp
js/src/ion/Ion.h
js/src/ion/IonCaches.cpp
js/src/ion/IonFrames.cpp
js/src/ion/IonMacroAssembler.cpp
js/src/ion/IonMacroAssembler.h
js/src/ion/VMFunctions.cpp
js/src/jsapi.h
js/src/jscompartment.cpp
js/src/jsinterp.cpp
js/src/jsscope.cpp
js/src/jsscope.h
js/src/jsscopeinlines.h
js/src/jsscript.cpp
js/src/jsscript.h
js/src/shell/js.cpp
js/src/vm/Debugger.cpp
js/src/vm/ScopeObject.cpp
js/src/vm/Stack-inl.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
toolkit/crashreporter/google-breakpad/src/processor/scoped_ptr.h
--- a/accessible/src/base/AccEvent.h
+++ b/accessible/src/base/AccEvent.h
@@ -53,16 +53,19 @@ public:
 
     // eCoalesceOfSameType : For events of the same type, only the newest event
     // will be processed.
     eCoalesceOfSameType,
 
     // eCoalesceSelectionChange: coalescence of selection change events.
     eCoalesceSelectionChange,
 
+    // eCoalesceStateChange: coalesce state change events.
+    eCoalesceStateChange,
+
      // eRemoveDupes : For repeat events, only the newest event in queue
      //    will be emitted.
     eRemoveDupes,
 
      // eDoNotEmit : This event is confirmed as a duplicate, do not emit it.
     eDoNotEmit
   };
 
@@ -130,22 +133,22 @@ protected:
  */
 class AccStateChangeEvent: public AccEvent
 {
 public:
   AccStateChangeEvent(Accessible* aAccessible, uint64_t aState,
                       bool aIsEnabled,
                       EIsFromUserInput aIsFromUserInput = eAutoDetect) :
     AccEvent(nsIAccessibleEvent::EVENT_STATE_CHANGE, aAccessible,
-             aIsFromUserInput, eAllowDupes),
+             aIsFromUserInput, eCoalesceStateChange),
              mState(aState), mIsEnabled(aIsEnabled) { }
 
   AccStateChangeEvent(Accessible* aAccessible, uint64_t aState) :
     AccEvent(::nsIAccessibleEvent::EVENT_STATE_CHANGE, aAccessible,
-             eAutoDetect, eAllowDupes), mState(aState)
+             eAutoDetect, eCoalesceStateChange), mState(aState)
     { mIsEnabled = (mAccessible->State() & mState) != 0; }
 
   // AccEvent
   virtual already_AddRefed<nsAccEvent> CreateXPCOMObject();
 
   static const EventGroup kEventGroup = eStateChangeEvent;
   virtual unsigned int GetEventGroups() const
   {
@@ -154,16 +157,18 @@ public:
 
   // AccStateChangeEvent
   uint64_t GetState() const { return mState; }
   bool IsStateEnabled() const { return mIsEnabled; }
 
 private:
   uint64_t mState;
   bool mIsEnabled;
+
+  friend class NotificationController;
 };
 
 
 /**
  * Accessible text change event.
  */
 class AccTextChangeEvent: public AccEvent
 {
--- a/accessible/src/base/NotificationController.cpp
+++ b/accessible/src/base/NotificationController.cpp
@@ -373,31 +373,16 @@ NotificationController::CoalesceEvents()
         if (accEvent->mEventType == tailEvent->mEventType &&
           accEvent->mEventRule == tailEvent->mEventRule) {
           accEvent->mEventRule = AccEvent::eDoNotEmit;
           return;
         }
       }
     } break; // case eCoalesceOfSameType
 
-    case AccEvent::eRemoveDupes:
-    {
-      // Check for repeat events, coalesce newly appended event by more older
-      // event.
-      for (uint32_t index = tail - 1; index < tail; index--) {
-        AccEvent* accEvent = mEvents[index];
-        if (accEvent->mEventType == tailEvent->mEventType &&
-            accEvent->mEventRule == tailEvent->mEventRule &&
-            accEvent->mAccessible == tailEvent->mAccessible) {
-          tailEvent->mEventRule = AccEvent::eDoNotEmit;
-          return;
-        }
-      }
-    } break; // case eRemoveDupes
-
     case AccEvent::eCoalesceSelectionChange:
     {
       AccSelChangeEvent* tailSelChangeEvent = downcast_accEvent(tailEvent);
       for (uint32_t index = tail - 1; index < tail; index--) {
         AccEvent* thisEvent = mEvents[index];
         if (thisEvent->mEventRule == tailEvent->mEventRule) {
           AccSelChangeEvent* thisSelChangeEvent =
             downcast_accEvent(thisEvent);
@@ -407,16 +392,53 @@ NotificationController::CoalesceEvents()
             CoalesceSelChangeEvents(tailSelChangeEvent, thisSelChangeEvent, index);
             return;
           }
         }
       }
 
     } break; // eCoalesceSelectionChange
 
+    case AccEvent::eCoalesceStateChange:
+    {
+      // If state change event is duped then ignore previous event. If state
+      // change event is opposite to previous event then no event is emitted
+      // (accessible state wasn't changed).
+      for (uint32_t index = tail - 1; index < tail; index--) {
+        AccEvent* thisEvent = mEvents[index];
+        if (thisEvent->mEventRule != AccEvent::eDoNotEmit &&
+            thisEvent->mEventType == tailEvent->mEventType &&
+            thisEvent->mAccessible == tailEvent->mAccessible) {
+          AccStateChangeEvent* thisSCEvent = downcast_accEvent(thisEvent);
+          AccStateChangeEvent* tailSCEvent = downcast_accEvent(tailEvent);
+          if (thisSCEvent->mState == tailSCEvent->mState) {
+            thisEvent->mEventRule = AccEvent::eDoNotEmit;
+            if (thisSCEvent->mIsEnabled != tailSCEvent->mIsEnabled)
+              tailEvent->mEventRule = AccEvent::eDoNotEmit;
+          }
+        }
+      }
+      break; // eCoalesceStateChange
+    }
+
+    case AccEvent::eRemoveDupes:
+    {
+      // Check for repeat events, coalesce newly appended event by more older
+      // event.
+      for (uint32_t index = tail - 1; index < tail; index--) {
+        AccEvent* accEvent = mEvents[index];
+        if (accEvent->mEventType == tailEvent->mEventType &&
+          accEvent->mEventRule == tailEvent->mEventRule &&
+          accEvent->mAccessible == tailEvent->mAccessible) {
+          tailEvent->mEventRule = AccEvent::eDoNotEmit;
+          return;
+        }
+      }
+    } break; // case eRemoveDupes
+
     default:
       break; // case eAllowDupes, eDoNotEmit
   } // switch
 }
 
 void
 NotificationController::CoalesceReorderEvents(AccEvent* aTailEvent)
 {
--- a/accessible/tests/mochitest/events/test_statechange.html
+++ b/accessible/tests/mochitest/events/test_statechange.html
@@ -92,16 +92,55 @@
       ];
 
       this.getID = function stateChangeOnFileInput_getID()
       {
         return "inherited state change on file input on attribute '" + aAttr + "' change";
       }
     }
 
+    function dupeStateChange(aID, aAttr, aValue,
+                             aState, aIsExtraState, aIsEnabled)
+    {
+      this.eventSeq = [
+        new stateChangeChecker(aState, aIsExtraState, aIsEnabled, getNode(aID))
+      ];
+
+      this.invoke = function dupeStateChange_invoke()
+      {
+        getNode(aID).setAttribute(aAttr, aValue);
+        getNode(aID).setAttribute(aAttr, aValue);
+      }
+
+      this.getID = function dupeStateChange_getID()
+      {
+        return "duped state change events";
+      }
+    }
+
+    function oppositeStateChange(aID, aAttr, aState, aIsExtraState)
+    {
+      this.eventSeq = [ ];
+      this.unexpectedEventSeq = [
+        new stateChangeChecker(aState, aIsExtraState, false, getNode(aID)),
+        new stateChangeChecker(aState, aIsExtraState, true, getNode(aID))
+      ];
+
+      this.invoke = function dupeStateChange_invoke()
+      {
+        getNode(aID).setAttribute(aAttr, "false");
+        getNode(aID).setAttribute(aAttr, "true");
+      }
+
+      this.getID = function dupeStateChange_getID()
+      {
+        return "opposite state change events";
+      }
+    }
+
     ////////////////////////////////////////////////////////////////////////////
     // Do tests
 
     var gQueue = null;
 
     // var gA11yEventDumpID = "eventdump"; // debug stuff
     //gA11yEventDumpToConsole = true; // debug stuff
 
@@ -119,16 +158,21 @@
       // file input inherited state changes
       gQueue.push(new stateChangeOnFileInput("file", "aria-busy", "true",
                                              STATE_BUSY, false, true));
       gQueue.push(new stateChangeOnFileInput("file", "aria-required", "true",
                                              STATE_REQUIRED, false, true));
       gQueue.push(new stateChangeOnFileInput("file", "aria-invalid", "true",
                                              STATE_INVALID, false, true));
 
+      gQueue.push(new dupeStateChange("div", "aria-busy", "true",
+                                      STATE_BUSY, false, true));
+      gQueue.push(new oppositeStateChange("div", "aria-busy",
+                                          STATE_BUSY, false));
+
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 </head>
 
@@ -158,11 +202,13 @@
   <div id="testContainer">
     <iframe id="iframe"></iframe>
   </div>
 
   <input id="email" type='email'>
 
   <input id="file" type="file">
 
+  <div id="div"></div>
+
   <div id="eventdump"></div>
 </body>
 </html>
--- a/accessible/tests/mochitest/states/test_doc_busy.html
+++ b/accessible/tests/mochitest/states/test_doc_busy.html
@@ -15,17 +15,17 @@
   <script type="application/javascript"
           src="../role.js"></script>
   <script type="application/javascript"
           src="../states.js"></script>
   <script type="application/javascript"
           src="../events.js"></script>
 
   <script type="application/javascript">
-    //gA11yEventDumpToConsole = true; // debugging stuff
+    gA11yEventDumpToConsole = true; // debugging stuff
 
     function loadFile()
     {
       var eventSeq = [
         new stateChangeChecker(STATE_BUSY, false, true, document, null, false, true),
         new stateChangeChecker(STATE_BUSY, false, false, document)
       ];
       defineScenario(this, eventSeq); // both events were fired
--- a/b2g/app/BootAnimation.cpp
+++ b/b2g/app/BootAnimation.cpp
@@ -289,34 +289,79 @@ TransformTo565(png_structp png_ptr, png_
                     ((inbuf[i + 1] & 0xFC) << 3) |
                     ((inbuf[i + 2]       ) >> 3);
     }
 }
 
 void
 AnimationFrame::ReadPngFrame(int outputFormat)
 {
+#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+    static const png_byte unused_chunks[] =
+        { 98,  75,  71,  68, '\0',   /* bKGD */
+          99,  72,  82,  77, '\0',   /* cHRM */
+         104,  73,  83,  84, '\0',   /* hIST */
+         105,  67,  67,  80, '\0',   /* iCCP */
+         105,  84,  88, 116, '\0',   /* iTXt */
+         111,  70,  70, 115, '\0',   /* oFFs */
+         112,  67,  65,  76, '\0',   /* pCAL */
+         115,  67,  65,  76, '\0',   /* sCAL */
+         112,  72,  89, 115, '\0',   /* pHYs */
+         115,  66,  73,  84, '\0',   /* sBIT */
+         115,  80,  76,  84, '\0',   /* sPLT */
+         116,  69,  88, 116, '\0',   /* tEXt */
+         116,  73,  77,  69, '\0',   /* tIME */
+         122,  84,  88, 116, '\0'};  /* zTXt */
+    static const png_byte tRNS_chunk[] =
+        {116,  82,  78,  83, '\0'};  /* tRNS */
+#endif
+
     png_structp pngread = png_create_read_struct(PNG_LIBPNG_VER_STRING,
                                                  nullptr, nullptr, nullptr);
 
+    if (!pngread)
+        return;
+
     png_infop pnginfo = png_create_info_struct(pngread);
 
+    if (!pnginfo) {
+        png_destroy_read_struct(&pngread, &pnginfo, nullptr);
+        return;
+    }
+
+    if (setjmp(png_jmpbuf(pngread))) {
+        // libpng reported an error and longjumped here.  Clean up and return.
+        png_destroy_read_struct(&pngread, &pnginfo, nullptr);
+        return;
+    }
+
     RawReadState state;
     state.start = file->GetData();
     state.length = file->GetDataSize();
     state.offset = 0;
 
     png_set_read_fn(pngread, &state, RawReader);
 
-    setjmp(png_jmpbuf(pngread));
+#ifdef PNG_HANDLE_AS_UNKNOWN_SUPPORTED
+    /* Ignore unused chunks */
+    png_set_keep_unknown_chunks(pngread, 1, unused_chunks,
+                               (int)sizeof(unused_chunks)/5);
+
+    /* Ignore the tRNS chunk if we only want opaque output */
+    if (outputFormat == HAL_PIXEL_FORMAT_RGB_888 ||
+        outputFormat == HAL_PIXEL_FORMAT_RGB_565) {
+        png_set_keep_unknown_chunks(pngread, 1, tRNS_chunk, 1);
+    }
+#endif
 
     png_read_info(pngread, pnginfo);
 
     width = png_get_image_width(pngread, pnginfo);
     height = png_get_image_height(pngread, pnginfo);
+
     switch (outputFormat) {
     case HAL_PIXEL_FORMAT_BGRA_8888:
         png_set_bgr(pngread);
         // FALL THROUGH
     case HAL_PIXEL_FORMAT_RGBA_8888:
     case HAL_PIXEL_FORMAT_RGBX_8888:
         bytepp = 4;
         png_set_filler(pngread, 0xFF, PNG_FILLER_AFTER);
@@ -342,16 +387,17 @@ AnimationFrame::ReadPngFrame(int outputF
     vector<char *> rows(height + 1);
     uint32_t stride = width * bytepp;
     for (int i = 0; i < height; i++) {
         rows[i] = buf + (stride * i);
     }
     rows[height] = nullptr;
     png_set_strip_16(pngread);
     png_set_palette_to_rgb(pngread);
+    png_set_gray_to_rgb(pngread);
     png_read_image(pngread, (png_bytepp)&rows.front());
     png_destroy_read_struct(&pngread, &pnginfo, nullptr);
 }
 
 static void *
 AnimationThread(void *)
 {
     ZipReader reader;
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -538,17 +538,18 @@ pref("dom.workers.mem.gc_allocation_thre
 
 // Show/Hide scrollbars when active/inactive
 pref("ui.showHideScrollbars", 1);
 
 // Enable the ProcessPriorityManager, and give processes with no visible
 // documents a 1s grace period before they're eligible to be marked as
 // background.
 pref("dom.ipc.processPriorityManager.enabled", true);
-pref("dom.ipc.processPriorityManager.gracePeriodMS", 1000);
+pref("dom.ipc.processPriorityManager.backgroundGracePeriodMS", 1000);
+pref("dom.ipc.processPriorityManager.temporaryPriorityMS", 5000);
 
 // Kernel parameters for how processes are killed on low-memory.
 pref("gonk.systemMemoryPressureRecoveryPollMS", 5000);
 pref("hal.processPriorityManager.gonk.masterOomScoreAdjust", 0);
 pref("hal.processPriorityManager.gonk.masterKillUnderMB", 1);
 pref("hal.processPriorityManager.gonk.foregroundOomScoreAdjust", 67);
 pref("hal.processPriorityManager.gonk.foregroundKillUnderMB", 4);
 pref("hal.processPriorityManager.gonk.backgroundPerceivableOomScoreAdjust", 134);
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -393,16 +393,17 @@ var shell = {
       this.sendChromeEvent({type: type});
     }
   },
 
   lastHardwareButtonEventType: null, // property for the hack above
   needBufferSysMsgs: true,
   bufferedSysMsgs: [],
   timer: null,
+  visibleAudioActive: false,
 
   handleEvent: function shell_handleEvent(evt) {
     let content = this.contentBrowser.contentWindow;
     switch (evt.type) {
       case 'keydown':
       case 'keyup':
       case 'keypress':
         this.filterHardwareKeys(evt);
@@ -410,17 +411,17 @@ var shell = {
       case 'mozfullscreenchange':
         // When the screen goes fullscreen make sure to set the focus to the
         // main window so noboby can prevent the ESC key to get out fullscreen
         // mode
         if (document.mozFullScreen)
           Services.fm.focusedWindow = window;
         break;
       case 'sizemodechange':
-        if (window.windowState == window.STATE_MINIMIZED) {
+        if (window.windowState == window.STATE_MINIMIZED && !this.visibleAudioActive) {
           this.contentBrowser.setVisible(false);
         } else {
           this.contentBrowser.setVisible(true);
         }
         break;
       case 'mozbrowserloadstart':
         if (content.document.location == 'about:blank')
           return;
@@ -1047,16 +1048,26 @@ window.addEventListener('ContentStart', 
   Services.obs.addObserver(function(aSubject, aTopic, aData) {
     shell.sendChromeEvent({
       type: 'audio-channel-changed',
       channel: aData
     });
 }, "audio-channel-changed", false);
 })();
 
+(function visibleAudioChannelChangedTracker() {
+  Services.obs.addObserver(function(aSubject, aTopic, aData) {
+    shell.sendChromeEvent({
+      type: 'visible-audio-channel-changed',
+      channel: aData
+    });
+    shell.visibleAudioActive = (aData !== 'none');
+}, "visible-audio-channel-changed", false);
+})();
+
 (function recordingStatusTracker() {
   let gRecordingActiveCount = 0;
 
   Services.obs.addObserver(function(aSubject, aTopic, aData) {
     let oldCount = gRecordingActiveCount;
     if (aData == "starting") {
       gRecordingActiveCount += 1;
     } else if (aData == "shutdown") {
--- 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="1358464191000">
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1358893928000">
   <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>
@@ -209,29 +209,33 @@
                   </emItem>
       <emItem  blockID="i46" id="{841468a1-d7f4-4bd3-84e6-bb0f13a06c64}">
                         <versionRange  minVersion="0.1" maxVersion="*">
                       <targetApplication  id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
                               <versionRange  minVersion="9.0a1" maxVersion="9.0" />
                           </targetApplication>
                     </versionRange>
                   </emItem>
+      <emItem  blockID="i256" id="/^[0-9a-f]+@[0-9a-f]+\.info/">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i22" id="ShopperReports@ShopperReports.com">
                         <versionRange  minVersion="3.1.22.0" maxVersion="3.1.22.0">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i44" id="sigma@labs.mozilla">
                         </emItem>
       <emItem  blockID="i246" id="support@vide1flash2.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i48" id="admin@youtubespeedup.com">
                         </emItem>
-      <emItem  blockID="i109" id="{392e123b-b691-4a5e-b52f-c4c1027e749c}">
+      <emItem  blockID="i104" id="yasd@youasdr3.com">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i79" id="GifBlock@facebook.com">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i69" id="{977f3b97-5461-4346-92c8-a14c749b77c9}">
@@ -285,17 +289,17 @@
                     </versionRange>
                   </emItem>
       <emItem  blockID="i13" id="{E8E88AB0-7182-11DF-904E-6045E0D72085}">
                         </emItem>
       <emItem  blockID="i83" id="flash@adobee.com">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
-      <emItem  blockID="i104" id="yasd@youasdr3.com">
+      <emItem  blockID="i109" id="{392e123b-b691-4a5e-b52f-c4c1027e749c}">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i76" id="crossriderapp3924@crossrider.com">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i196" id="info@wxdownloadmanager.com">
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -996,16 +996,17 @@ pref("services.sync.prefs.sync.privacy.c
 pref("services.sync.prefs.sync.privacy.clearOnShutdown.downloads", true);
 pref("services.sync.prefs.sync.privacy.clearOnShutdown.formdata", true);
 pref("services.sync.prefs.sync.privacy.clearOnShutdown.history", true);
 pref("services.sync.prefs.sync.privacy.clearOnShutdown.offlineApps", true);
 pref("services.sync.prefs.sync.privacy.clearOnShutdown.passwords", true);
 pref("services.sync.prefs.sync.privacy.clearOnShutdown.sessions", true);
 pref("services.sync.prefs.sync.privacy.clearOnShutdown.siteSettings", true);
 pref("services.sync.prefs.sync.privacy.donottrackheader.enabled", true);
+pref("services.sync.prefs.sync.privacy.donottrackheader.value", true);
 pref("services.sync.prefs.sync.privacy.sanitize.sanitizeOnShutdown", true);
 pref("services.sync.prefs.sync.security.OCSP.disable_button.managecrl", true);
 pref("services.sync.prefs.sync.security.OCSP.enabled", true);
 pref("services.sync.prefs.sync.security.OCSP.require", true);
 pref("services.sync.prefs.sync.security.default_personal_cert", true);
 pref("services.sync.prefs.sync.security.enable_ssl3", true);
 pref("services.sync.prefs.sync.security.enable_tls", true);
 pref("services.sync.prefs.sync.signon.rememberSignons", true);
--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -179,17 +179,17 @@
       <menuitem id="context-forward"
                 label="&forwardCmd.label;"
                 accesskey="&forwardCmd.accesskey;"
                 command="Browser:ForwardOrForwardDuplicate"
                 onclick="checkForMiddleClick(this, event);"/>
       <menuitem id="context-reload"
                 label="&reloadCmd.label;"
                 accesskey="&reloadCmd.accesskey;"
-                command="Browser:ReloadOrDuplicate"
+                oncommand="gContextMenu.reload(event);"
                 onclick="checkForMiddleClick(this, event);"/>
       <menuitem id="context-stop"
                 label="&stopCmd.label;"
                 accesskey="&stopCmd.accesskey;"
                 command="Browser:Stop"/>
       <menuseparator id="context-sep-stop"/>
       <menuitem id="context-bookmarkpage"
                 label="&bookmarkPageCmd2.label;"
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -124,17 +124,17 @@ let SocialUI = {
           break;
         case "social:recommend-info-changed":
           if (this._matchesCurrentProvider(data)) {
             SocialShareButton.updateShareState();
           }
           break;
         case "social:frameworker-error":
           if (this.enabled && Social.provider.origin == data) {
-            SocialSidebar.setSidebarErrorMessage("frameworker-error");
+            SocialSidebar.setSidebarErrorMessage();
           }
           break;
 
         case "nsPref:changed":
           if (data == "social.sidebar.open") {
             SocialSidebar.update();
           } else if (data == "social.toast-notifications.enabled") {
             SocialToolbar.updateButton();
@@ -447,46 +447,35 @@ let SocialFlyout = {
     let iframe = document.createElement("iframe");
     iframe.setAttribute("type", "content");
     iframe.setAttribute("class", "social-panel-frame");
     iframe.setAttribute("flex", "1");
     iframe.setAttribute("origin", Social.provider.origin);
     panel.appendChild(iframe);
   },
 
-  setUpProgressListener: function SF_setUpProgressListener() {
-    if (!this._progressListenerSet) {
-      this._progressListenerSet = true;
-      // Force a layout flush by calling .clientTop so
-      // that the docShell of this frame is created
-      this.panel.firstChild.clientTop;
-      this.panel.firstChild.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
-                                    .getInterface(Ci.nsIWebProgress)
-                                    .addProgressListener(new SocialErrorListener("flyout"),
-                                                         Ci.nsIWebProgress.NOTIFY_STATE_REQUEST |
-                                                         Ci.nsIWebProgress.NOTIFY_LOCATION);
-    }
-  },
-
   setFlyoutErrorMessage: function SF_setFlyoutErrorMessage() {
     let iframe = this.panel.firstChild;
     if (!iframe)
       return;
 
     iframe.removeAttribute("src");
     iframe.webNavigation.loadURI("about:socialerror?mode=compactInfo", null, null, null, null);
     sizeSocialPanelToContent(this.panel, iframe);
   },
 
   unload: function() {
     let panel = this.panel;
     panel.hidePopup();
     if (!panel.firstChild)
       return
-    panel.removeChild(panel.firstChild);
+    let iframe = panel.firstChild;
+    if (iframe.socialErrorListener)
+      iframe.socialErrorListener.remove();
+    panel.removeChild(iframe);
   },
 
   onShown: function(aEvent) {
     let panel = this.panel;
     let iframe = panel.firstChild;
     this._dynamicResizer = new DynamicResizeWatcher();
     iframe.docShell.isActive = true;
     iframe.docShell.isAppTab = true;
@@ -555,17 +544,20 @@ let SocialFlyout = {
       // only "move to new screen pos".  So we remember the last yOffset,
       // calculate the adjustment needed to the new yOffset, then calc the
       // screen Y position.
       let yAdjust = yOffset - this.yOffset;
       let box = panel.boxObject;
       panel.moveTo(box.screenX, box.screenY + yAdjust);
     } else {
       panel.openPopup(anchor, "start_before", 0, yOffset, false, false);
-      this.setUpProgressListener();
+      // Force a layout flush by calling .clientTop so
+      // that the docShell of this frame is created
+      panel.firstChild.clientTop;
+      Social.setErrorListener(iframe, this.setFlyoutErrorMessage.bind(this))
     }
     this.yOffset = yOffset;
   }
 }
 
 let SocialShareButton = {
   // Called once, after window load, when the Social.provider object is initialized
   init: function SSB_init() {
@@ -941,23 +933,22 @@ var SocialToolbar = {
       let badge = icon.counter || "";
       if (toolbarButton.getAttribute("badge") != badge)
         toolbarButton.setAttribute("badge", badge);
     }
     let socialToolbarItem = document.getElementById("social-toolbar-item");
     socialToolbarItem.appendChild(toolbarButtons);
 
     for (let frame of createdFrames) {
+      if (frame.socialErrorListener) {
+        frame.socialErrorListener.remove();
+      }
       if (frame.docShell) {
         frame.docShell.isActive = false;
-        frame.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
-                      .getInterface(Ci.nsIWebProgress)
-                      .addProgressListener(new SocialErrorListener("notification-panel"),
-                                           Ci.nsIWebProgress.NOTIFY_STATE_REQUEST |
-                                           Ci.nsIWebProgress.NOTIFY_LOCATION);
+        Social.setErrorListener(frame, this.setPanelErrorMessage.bind(this));
       }
     }
   },
 
   showAmbientPopup: function SocialToolbar_showAmbientPopup(aToolbarButton) {
     // Hide any other social panels that may be open.
     SocialFlyout.panel.hidePopup();
 
@@ -1072,30 +1063,22 @@ var SocialToolbar = {
     providerMenuSep.hidden = false;
   }
 }
 
 var SocialSidebar = {
   // Called once, after window load, when the Social.provider object is initialized
   init: function SocialSidebar_init() {
     let sbrowser = document.getElementById("social-sidebar-browser");
-    this.errorListener = new SocialErrorListener("sidebar");
-    this.configureSidebarDocShell(sbrowser.docShell);
+    Social.setErrorListener(sbrowser, this.setSidebarErrorMessage.bind(this));
+    // setting isAppTab causes clicks on untargeted links to open new tabs
+    sbrowser.docShell.isAppTab = true;
     this.update();
   },
 
-  configureSidebarDocShell: function SocialSidebar_configureDocShell(aDocShell) {
-    // setting isAppTab causes clicks on untargeted links to open new tabs
-    aDocShell.isAppTab = true;
-    aDocShell.QueryInterface(Ci.nsIWebProgress)
-             .addProgressListener(SocialSidebar.errorListener,
-                                  Ci.nsIWebProgress.NOTIFY_STATE_REQUEST |
-                                  Ci.nsIWebProgress.NOTIFY_LOCATION);
-  },
-
   // Whether the sidebar can be shown for this window.
   get canShow() {
     return SocialUI.enabled && Social.provider.sidebarURL;
   },
 
   // Whether the user has toggled the sidebar on (for windows where it can appear)
   get opened() {
     return Services.prefs.getBoolPref("social.sidebar.open") && !document.mozFullScreen;
@@ -1140,17 +1123,17 @@ var SocialSidebar = {
         this._unloadTimeoutId = setTimeout(
           this.unloadSidebar,
           Services.prefs.getIntPref("social.sidebar.unload_timeout_ms")
         );
       }
     } else {
       sbrowser.setAttribute("origin", Social.provider.origin);
       if (Social.provider.errorState == "frameworker-error") {
-        SocialSidebar.setSidebarErrorMessage("frameworker-error");
+        SocialSidebar.setSidebarErrorMessage();
         return;
       }
 
       // Make sure the right sidebar URL is loaded
       if (sbrowser.getAttribute("src") != Social.provider.sidebarURL) {
         sbrowser.setAttribute("src", Social.provider.sidebarURL);
         sbrowser.addEventListener("load", SocialSidebar._loadListener, true);
       } else {
@@ -1173,89 +1156,19 @@ var SocialSidebar = {
     sbrowser.stop();
     sbrowser.removeAttribute("origin");
     sbrowser.setAttribute("src", "about:blank");
     SocialFlyout.unload();
   },
 
   _unloadTimeoutId: 0,
 
-  setSidebarErrorMessage: function(aType) {
+  setSidebarErrorMessage: function() {
     let sbrowser = document.getElementById("social-sidebar-browser");
-    switch (aType) {
-      case "sidebar-error":
-        let url = encodeURIComponent(Social.provider.sidebarURL);
-        sbrowser.loadURI("about:socialerror?mode=tryAgain&url=" + url, null, null);
-        break;
-
-      case "frameworker-error":
-        sbrowser.setAttribute("src", "about:socialerror?mode=workerFailure");
-        break;
+    // a frameworker error "trumps" a sidebar error.
+    if (Social.provider.errorState == "frameworker-error") {
+      sbrowser.setAttribute("src", "about:socialerror?mode=workerFailure");
+    } else {
+      let url = encodeURIComponent(Social.provider.sidebarURL);
+      sbrowser.loadURI("about:socialerror?mode=tryAgain&url=" + url, null, null);
     }
   }
 }
-
-// Error handling class used to listen for network errors in the social frames
-// and replace them with a social-specific error page
-function SocialErrorListener(aType) {
-  this.type = aType;
-}
-
-SocialErrorListener.prototype = {
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
-                                         Ci.nsISupportsWeakReference,
-                                         Ci.nsISupports]),
-
-  onStateChange: function SPL_onStateChange(aWebProgress, aRequest, aState, aStatus) {
-    let failure = false;
-    if ((aState & Ci.nsIWebProgressListener.STATE_STOP)) {
-      if (aRequest instanceof Ci.nsIHttpChannel) {
-        try {
-          // Change the frame to an error page on 4xx (client errors)
-          // and 5xx (server errors)
-          failure = aRequest.responseStatus >= 400 &&
-                    aRequest.responseStatus < 600;
-        } catch (e) {}
-      }
-    }
-
-    // Calling cancel() will raise some OnStateChange notifications by itself,
-    // so avoid doing that more than once
-    if (failure && aStatus != Components.results.NS_BINDING_ABORTED) {
-      aRequest.cancel(Components.results.NS_BINDING_ABORTED);
-      this.setErrorMessage(aWebProgress);
-    }
-  },
-
-  onLocationChange: function SPL_onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
-    let failure = aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE;
-    if (failure && Social.provider.errorState != "frameworker-error") {
-      aRequest.cancel(Components.results.NS_BINDING_ABORTED);
-      window.setTimeout(function(self) {
-        self.setErrorMessage(aWebProgress);
-      }, 0, this);
-    }
-  },
-
-  onProgressChange: function SPL_onProgressChange() {},
-  onStatusChange: function SPL_onStatusChange() {},
-  onSecurityChange: function SPL_onSecurityChange() {},
-
-  setErrorMessage: function(aWebProgress) {
-    switch (this.type) {
-      case "flyout":
-        SocialFlyout.setFlyoutErrorMessage();
-        break;
-
-      case "sidebar":
-        // a frameworker error "trumps" a sidebar error.
-        let reason = Social.provider.errorState || "sidebar-error";
-        SocialSidebar.setSidebarErrorMessage(reason);
-        break;
-
-      case "notification-panel":
-        let frame = aWebProgress.QueryInterface(Ci.nsIDocShell)
-                                .chromeEventHandler;
-        SocialToolbar.setPanelErrorMessage(frame);
-        break;
-    }
-  }
-};
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -309,17 +309,17 @@
       <menuitem label="&fullScreenExit.label;"
                 accesskey="&fullScreenExit.accesskey;"
                 oncommand="BrowserFullScreen();"/>
     </menupopup>
 
     <menupopup id="contentAreaContextMenu" pagemenu="start"
                onpopupshowing="if (event.target != this)
                                  return true;
-                               gContextMenu = new nsContextMenu(this, gBrowser, event.shiftKey);
+                               gContextMenu = new nsContextMenu(this, event.shiftKey);
                                if (gContextMenu.shouldDisplay)
                                  updateEditUIVisibility();
                                return gContextMenu.shouldDisplay;"
                onpopuphiding="if (event.target != this)
                                 return;
                               gContextMenu.hiding();
                               gContextMenu = null;
                               updateEditUIVisibility();">
@@ -1102,16 +1102,17 @@
               class="chromeclass-extrachrome sidebar-splitter"
               observes="socialSidebarBroadcaster"/>
     <vbox id="social-sidebar-box"
           class="chromeclass-extrachrome"
           observes="socialSidebarBroadcaster"
           persist="width">
       <browser id="social-sidebar-browser"
                type="content"
+               context="contentAreaContextMenu"
                disableglobalhistory="true"
                flex="1"
                style="min-width: 14em; width: 18em; max-width: 36em;"/>
     </vbox>
     <vbox id="browser-border-end" hidden="true" layer="true"/>
   </hbox>
 
   <hbox id="full-screen-warning-container" hidden="true" fadeout="true">
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1,30 +1,28 @@
 # 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/.
 
 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 
-function nsContextMenu(aXulMenu, aBrowser, aIsShift) {
+function nsContextMenu(aXulMenu, aIsShift) {
   this.shouldDisplay = true;
-  this.initMenu(aBrowser, aXulMenu, aIsShift);
+  this.initMenu(aXulMenu, aIsShift);
 }
 
 // Prototype for nsContextMenu "class."
 nsContextMenu.prototype = {
-  initMenu: function CM_initMenu(aBrowser, aXulMenu, aIsShift) {
+  initMenu: function CM_initMenu(aXulMenu, aIsShift) {
     // Get contextual info.
     this.setTarget(document.popupNode, document.popupRangeParent,
                    document.popupRangeOffset);
     if (!this.shouldDisplay)
       return;
 
-    this.browser = aBrowser;
-
     this.hasPageMenu = false;
     if (!aIsShift) {
       this.hasPageMenu = PageMenu.maybeBuildAndAttachMenu(this.target,
                                                           aXulMenu);
     }
 
     this.isFrameImage = document.getElementById("isFrameImage");
     this.ellipsis = "\u2026";
@@ -149,23 +147,30 @@ nsContextMenu.prototype = {
     this.showItem("context-openlinkintab", shouldShow);
     this.showItem("context-openlinkincurrent", this.onPlainTextLink);
     this.showItem("context-sep-open", shouldShow);
   },
 
   initNavigationItems: function CM_initNavigationItems() {
     var shouldShow = !(this.isContentSelected || this.onLink || this.onImage ||
                        this.onCanvas || this.onVideo || this.onAudio ||
-                       this.onTextInput);
+                       this.onTextInput || this.onSocial);
     this.showItem("context-back", shouldShow);
     this.showItem("context-forward", shouldShow);
-    var shouldShowReload = XULBrowserWindow.stopCommand.getAttribute("disabled") == "true";
-    this.showItem("context-reload", shouldShow && shouldShowReload);
-    this.showItem("context-stop", shouldShow && !shouldShowReload);
-    this.showItem("context-sep-stop", shouldShow);
+
+    let stopped = XULBrowserWindow.stopCommand.getAttribute("disabled") == "true";
+
+    let stopReloadItem = "";
+    if (shouldShow || this.onSocial) {
+      stopReloadItem = (stopped || this.onSocial) ? "reload" : "stop";
+    }
+
+    this.showItem("context-reload", stopReloadItem == "reload");
+    this.showItem("context-stop", stopReloadItem == "stop");
+    this.showItem("context-sep-stop", !!stopReloadItem);
 
     // XXX: Stop is determined in browser.js; the canStop broadcaster is broken
     //this.setItemAttrFromNode( "context-stop", "disabled", "canStop" );
   },
 
   initLeaveDOMFullScreenItems: function CM_initLeaveFullScreenItem() {
     // only show the option if the user is in DOM fullscreen
     var shouldShow = (this.target.ownerDocument.mozFullScreenElement != null);
@@ -206,17 +211,17 @@ nsContextMenu.prototype = {
                   this.isContentSelected);
     this.showItem("context-viewpartialsource-mathml",
                   this.onMathML && !this.isContentSelected);
 
     var shouldShow = !(this.isContentSelected ||
                        this.onImage || this.onCanvas ||
                        this.onVideo || this.onAudio ||
                        this.onLink || this.onTextInput);
-    var showInspect = gPrefService.getBoolPref("devtools.inspector.enabled");
+    var showInspect = !this.onSocial && gPrefService.getBoolPref("devtools.inspector.enabled");
     this.showItem("context-viewsource", shouldShow);
     this.showItem("context-viewinfo", shouldShow);
     this.showItem("inspect-separator", showInspect);
     this.showItem("context-inspect", showInspect);
 
     this.showItem("context-sep-viewsource", shouldShow);
 
     // Set as Desktop background depends on whether an image was clicked on,
@@ -263,18 +268,19 @@ nsContextMenu.prototype = {
   },
 
   initMiscItems: function CM_initMiscItems() {
     var isTextSelected = this.isTextSelected;
 
     // Use "Bookmark This Link" if on a link.
     this.showItem("context-bookmarkpage",
                   !(this.isContentSelected || this.onTextInput || this.onLink ||
-                    this.onImage || this.onVideo || this.onAudio));
-    this.showItem("context-bookmarklink", (this.onLink && !this.onMailtoLink) || this.onPlainTextLink);
+                    this.onImage || this.onVideo || this.onAudio || this.onSocial));
+    this.showItem("context-bookmarklink", (this.onLink && !this.onMailtoLink &&
+                                           !this.onSocial) || this.onPlainTextLink);
     this.showItem("context-searchselect", isTextSelected);
     this.showItem("context-keywordfield",
                   this.onTextInput && this.onKeywordField);
     this.showItem("frame", this.inFrame);
     this.showItem("frame-sep", this.inFrame && isTextSelected);
 
     // Hide menu entries for images, show otherwise
     if (this.inFrame) {
@@ -469,16 +475,23 @@ nsContextMenu.prototype = {
     this.bgImageURL        = "";
     this.onEditableArea    = false;
     this.isDesignMode      = false;
     this.onCTPPlugin       = false;
 
     // Remember the node that was clicked.
     this.target = aNode;
 
+    this.browser = this.target.ownerDocument.defaultView
+                                .QueryInterface(Ci.nsIInterfaceRequestor)
+                                .getInterface(Ci.nsIWebNavigation)
+                                .QueryInterface(Ci.nsIDocShell)
+                                .chromeEventHandler;
+    this.onSocial = !!this.browser.getAttribute("origin");
+
     // Check if we are in a synthetic document (stand alone image, video, etc.).
     this.inSyntheticDoc =  this.target.ownerDocument.mozSyntheticDocument;
     // First, do checks for nodes that never have children.
     if (this.target.nodeType == Node.ELEMENT_NODE) {
       // See if the user clicked on an image.
       if (this.target instanceof Ci.nsIImageLoadingContent &&
           this.target.currentURI) {
         this.onImage = true;
@@ -758,16 +771,28 @@ nsContextMenu.prototype = {
 
     urlSecurityCheck(frameURL, this.browser.contentPrincipal,
                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
     var referrer = doc.referrer;
     openUILinkIn(frameURL, "current", { disallowInheritPrincipal: true,
                                         referrerURI: referrer ? makeURI(referrer) : null });
   },
 
+  reload: function(event) {
+    if (this.onSocial) {
+      // full reload of social provider
+      Social.enabled = false;
+      Services.tm.mainThread.dispatch(function() {
+        Social.enabled = true;
+      }, Components.interfaces.nsIThread.DISPATCH_NORMAL);
+    } else {
+      BrowserReloadOrDuplicate(event);
+    }
+  },
+
   // View Partial Source
   viewPartialSource: function(aContext) {
     var focusedWindow = document.commandDispatcher.focusedWindow;
     if (focusedWindow == window)
       focusedWindow = content;
 
     var docCharset = null;
     if (focusedWindow)
--- a/browser/base/content/socialchat.xml
+++ b/browser/base/content/socialchat.xml
@@ -10,16 +10,17 @@
       <xul:hbox class="chat-titlebar" xbl:inherits="minimized,selected,activity"
                 onclick="document.getBindingParent(this).onTitlebarClick(event);" align="baseline">
         <xul:image class="chat-status-icon" xbl:inherits="src=image"/>
         <xul:label class="chat-title" flex="1" xbl:inherits="value=label" crop="center"/>
         <xul:toolbarbutton class="chat-close-button chat-toolbarbutton"
                            oncommand="document.getBindingParent(this).close();"/>
       </xul:hbox>
       <xul:iframe anonid="iframe" class="chat-frame" flex="1"
+                  context="contentAreaContextMenu"
                   xbl:inherits="src,origin,collapsed=minimized" type="content"/>
     </content>
 
     <implementation implements="nsIDOMEventListener">
       <field name="iframe" readonly="true">
         document.getAnonymousElementByAttribute(this, "anonid", "iframe");
       </field>
 
@@ -76,17 +77,17 @@
       <method name="toggle">
         <body><![CDATA[
           this.minimized = !this.minimized;
         ]]></body>
       </method>
     </implementation>
 
     <handlers>
-      <handler event="focus" phase="capturing">
+      <handler event="focus">
         this.parentNode.selectedChat = this;
       </handler>
       <handler event="DOMTitleChanged"><![CDATA[
         this.setAttribute('label', this.iframe.contentDocument.title);
         this.parentNode.updateTitlebar(this);
       ]]></handler>
       <handler event="DOMLinkAdded"><![CDATA[
         // much of this logic is from DOMLinkHandler in browser.js
@@ -350,16 +351,17 @@
       </method>
 
       <method name="_remove">
         <parameter name="aChatbox"/>
         <body><![CDATA[
           if (this.selectedChat == aChatbox) {
             this._selectAnotherChat();
           }
+          aChatbox.iframe.socialErrorListener.remove();
           this.removeChild(aChatbox);
           // child might have been collapsed.
           let menuitem = this.menuitemMap.get(aChatbox);
           if (menuitem) {
             this.menuitemMap.delete(aChatbox);
             this.menupopup.removeChild(menuitem);
           }
           this.chatboxForURL.delete(aChatbox.getAttribute('src'));
@@ -387,16 +389,21 @@
         <parameter name="aURL"/>
         <parameter name="aCallback"/>
         <body><![CDATA[
           // callbacks to be called when onload fires - more might be added
           // if the same chat is requested before onload has fired.  Set back
           // to null in DOMContentLoaded, so null means DOMContentLoaded has
           // already fired and new callbacks can be made immediately.
           aChatBox._callbacks = [aCallback];
+          var tmp = {};
+          Components.utils.import("resource:///modules/Social.jsm", tmp);
+          tmp.Social.setErrorListener(aChatBox.iframe, function(iframe) {
+            iframe.webNavigation.loadURI("about:socialerror?mode=compactInfo", null, null, null, null);
+          });
           let iframeWindow = aChatBox.iframe.contentWindow;
           aChatBox.addEventListener("DOMContentLoaded", function DOMContentLoaded() {
             aChatBox.removeEventListener("DOMContentLoaded", DOMContentLoaded);
             aChatBox.isActive = !aChatBox.minimized;
             for (let callback of aChatBox._callbacks) {
               if (callback)
                 callback(iframeWindow);
             }
--- a/browser/base/content/test/browser_bug417483.js
+++ b/browser/base/content/test/browser_bug417483.js
@@ -14,13 +14,13 @@ function onPageShow() {
     var tn = frame.document.body.childNodes[0];
     range.setStart(tn , 4);
     range.setEnd(tn , 5);
     sel.addRange(range);
     frame.focus();
     
     document.popupNode = frame.document.body;
     var contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
-    var contextMenu = new nsContextMenu(contentAreaContextMenu, gBrowser);
+    var contextMenu = new nsContextMenu(contentAreaContextMenu);
 
     ok(document.getElementById("frame-sep").hidden, "'frame-sep' should be hidden if the selection contains only spaces");
     finish();
 }
--- a/browser/base/content/test/browser_bug423833.js
+++ b/browser/base/content/test/browser_bug423833.js
@@ -27,17 +27,17 @@ function test1Setup() {
     return;
 
   gBrowser.selectedBrowser.removeEventListener("load", test1Setup, true);
 
   var badFrame = content.frames[1];
   document.popupNode = badFrame.document.firstChild;
 
   var contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
-  var contextMenu = new nsContextMenu(contentAreaContextMenu, gBrowser);
+  var contextMenu = new nsContextMenu(contentAreaContextMenu);
 
   // We'd like to use another load listener here, but error pages don't fire load events
   contextMenu.showOnlyThisFrame();
   intervalID = setInterval(testShowOnlyThisFrame, 3000);
 }
 
 function testShowOnlyThisFrame() {
   if (content.location.href == testPage)
@@ -64,17 +64,17 @@ function test2Setup() {
   gBrowser.removeEventListener("load", test2Setup, true);
 
   // Now let's do the whole thing again, but this time for "Open frame in new tab"
   var badFrame = content.frames[1];
 
   document.popupNode = badFrame.document.firstChild;
 
   var contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
-  var contextMenu = new nsContextMenu(contentAreaContextMenu, gBrowser);
+  var contextMenu = new nsContextMenu(contentAreaContextMenu);
 
   gBrowser.tabContainer.addEventListener("TabOpen", function (event) {
     test2tab = event.target;
     gBrowser.tabContainer.removeEventListener("TabOpen", arguments.callee, false);
   }, false);
   contextMenu.openFrameInTab();
   ok(test2tab, "openFrameInTab() opened a tab");
 
@@ -100,17 +100,17 @@ function testOpenFrameInTab() {
 }
 
 function test3Setup() {
   // One more time, for "Open frame in new window"
   var badFrame = content.frames[1];
   document.popupNode = badFrame.document.firstChild;
 
   var contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
-  var contextMenu = new nsContextMenu(contentAreaContextMenu, gBrowser);
+  var contextMenu = new nsContextMenu(contentAreaContextMenu);
 
   Services.ww.registerNotification(function (aSubject, aTopic, aData) {
     if (aTopic == "domwindowopened")
       test3window = aSubject;
     Services.ww.unregisterNotification(arguments.callee);
   });
 
   contextMenu.openFrame();
--- a/browser/base/content/test/browser_bug424101.js
+++ b/browser/base/content/test/browser_bug424101.js
@@ -11,27 +11,27 @@ function test() {
     let doc = gBrowser.contentDocument;
     let testInput = function(type, expected) {
       let element = doc.createElement("input");
       element.setAttribute("type", type);
       doc.body.appendChild(element);
       document.popupNode = element;
 
       let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
-      let contextMenu = new nsContextMenu(contentAreaContextMenu, gBrowser);
+      let contextMenu = new nsContextMenu(contentAreaContextMenu);
 
       is(contextMenu.shouldDisplay, expected, "context menu behavior for <input type=" + type + "> is wrong");
     };
     let testElement = function(tag, expected) {
       let element = doc.createElement(tag);
       doc.body.appendChild(element);
       document.popupNode = element;
 
       let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
-      let contextMenu = new nsContextMenu(contentAreaContextMenu, gBrowser);
+      let contextMenu = new nsContextMenu(contentAreaContextMenu);
 
       is(contextMenu.shouldDisplay, expected, "context menu behavior for <" + tag + "> is wrong");
     };
 
     testInput("text", true);
     testInput("password", true);
     testInput("image", true);
     testInput("button", true);
--- a/browser/base/content/test/browser_bug734076.js
+++ b/browser/base/content/test/browser_bug734076.js
@@ -97,11 +97,11 @@ function test() {
   }
 
   doNext();
 }
 
 function initContextMenu(aNode) {
   document.popupNode = aNode;
   let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
-  let contextMenu = new nsContextMenu(contentAreaContextMenu, gBrowser);
+  let contextMenu = new nsContextMenu(contentAreaContextMenu);
   return contextMenu;
 }
--- a/browser/base/content/test/browser_plainTextLinks.js
+++ b/browser/base/content/test/browser_plainTextLinks.js
@@ -9,17 +9,17 @@ function setSelection(el1, el2, index1, 
   range.setStart(el1, index1);
   range.setEnd(el2, index2);
   selection.addRange(range);
 }
 
 function initContextMenu(aNode) {
   document.popupNode = aNode;
   let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
-  let contextMenu = new nsContextMenu(contentAreaContextMenu, gBrowser);
+  let contextMenu = new nsContextMenu(contentAreaContextMenu);
   return contextMenu;
 }
 
 function testExpected(expected, msg, aNode) {
   let popupNode = aNode || doc.getElementsByTagName("DIV")[0];
   initContextMenu(popupNode);
   let linkMenuItem = document.getElementById("context-openlinkincurrent");
   is(linkMenuItem.hidden, expected, msg);
--- a/browser/base/content/test/browser_sanitizeDialog.js
+++ b/browser/base/content/test/browser_sanitizeDialog.js
@@ -49,250 +49,289 @@ var gAllTests = [
   },
 
   /**
    * Cancels the dialog, makes sure history not cleared.
    */
   function () {
     // Add history (within the past hour)
     let uris = [];
+    let places = [];
+    let pURI;
     for (let i = 0; i < 30; i++) {
-      uris.push(addHistoryWithMinutesAgo(i));
+      pURI = makeURI("http://" + i + "-minutes-ago.com/");
+      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+      uris.push(pURI);
     }
 
-    let wh = new WindowHelper();
-    wh.onload = function () {
-      this.selectDuration(Sanitizer.TIMESPAN_HOUR);
-      this.checkPrefCheckbox("history", false);
-      this.checkDetails(false);
+    addVisits(places, function() {
+      let wh = new WindowHelper();
+      wh.onload = function () {
+        this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+        this.checkPrefCheckbox("history", false);
+        this.checkDetails(false);
 
-      // Show details
-      this.toggleDetails();
-      this.checkDetails(true);
+        // Show details
+        this.toggleDetails();
+        this.checkDetails(true);
 
-      // Hide details
-      this.toggleDetails();
-      this.checkDetails(false);
-      this.cancelDialog();
+        // Hide details
+        this.toggleDetails();
+        this.checkDetails(false);
+        this.cancelDialog();
 
-      ensureHistoryClearedState(uris, false);
-      blankSlate();
-      ensureHistoryClearedState(uris, true);
-    };
-    wh.open();
+        ensureHistoryClearedState(uris, false);
+        blankSlate();
+        ensureHistoryClearedState(uris, true);
+      };
+      wh.open();
+    });
   },
 
   /**
    * Ensures that the combined history-downloads checkbox clears both history
    * visits and downloads when checked; the dialog respects simple timespan.
    */
   function () {
-    // Add history and downloads (within the past hour).
+    // Add history (within the past hour).
     let uris = [];
+    let places = [];
+    let pURI;
     for (let i = 0; i < 30; i++) {
-      uris.push(addHistoryWithMinutesAgo(i));
+      pURI = makeURI("http://" + i + "-minutes-ago.com/");
+      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+      uris.push(pURI);
     }
-    let downloadIDs = [];
-    for (let i = 0; i < 5; i++) {
-      downloadIDs.push(addDownloadWithMinutesAgo(i));
-    }
-    // Add history and downloads (over an hour ago).
+    // Add history (over an hour ago).
     let olderURIs = [];
     for (let i = 0; i < 5; i++) {
-      olderURIs.push(addHistoryWithMinutesAgo(61 + i));
-    }
-    let olderDownloadIDs = [];
-    for (let i = 0; i < 5; i++) {
-      olderDownloadIDs.push(addDownloadWithMinutesAgo(61 + i));
+      pURI = makeURI("http://" + (61 + i) + "-minutes-ago.com/");
+      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(61 + i)});
+      olderURIs.push(pURI);
     }
-    let totalHistoryVisits = uris.length + olderURIs.length;
 
-    let wh = new WindowHelper();
-    wh.onload = function () {
-      this.selectDuration(Sanitizer.TIMESPAN_HOUR);
-      this.checkPrefCheckbox("history", true);
-      this.acceptDialog();
+    addVisits(places, function() {
+      // Add downloads (within the past hour).
+      let downloadIDs = [];
+      for (let i = 0; i < 5; i++) {
+        downloadIDs.push(addDownloadWithMinutesAgo(i));
+      }
+      // Add downloads (over an hour ago).
+      let olderDownloadIDs = [];
+      for (let i = 0; i < 5; i++) {
+        olderDownloadIDs.push(addDownloadWithMinutesAgo(61 + i));
+      }
+      let totalHistoryVisits = uris.length + olderURIs.length;
+
+      let wh = new WindowHelper();
+      wh.onload = function () {
+        this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+        this.checkPrefCheckbox("history", true);
+        this.acceptDialog();
 
-      intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR,
-                "timeSpan pref should be hour after accepting dialog with " +
-                "hour selected");
-      boolPrefIs("cpd.history", true,
-                 "history pref should be true after accepting dialog with " +
-                 "history checkbox checked");
-      boolPrefIs("cpd.downloads", true,
-                 "downloads pref should be true after accepting dialog with " +
-                 "history checkbox checked");
+        intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR,
+                  "timeSpan pref should be hour after accepting dialog with " +
+                  "hour selected");
+        boolPrefIs("cpd.history", true,
+                   "history pref should be true after accepting dialog with " +
+                   "history checkbox checked");
+        boolPrefIs("cpd.downloads", true,
+                   "downloads pref should be true after accepting dialog with " +
+                   "history checkbox checked");
 
-      // History visits and downloads within one hour should be cleared.
-      ensureHistoryClearedState(uris, true);
-      ensureDownloadsClearedState(downloadIDs, true);
+        // History visits and downloads within one hour should be cleared.
+        ensureHistoryClearedState(uris, true);
+        ensureDownloadsClearedState(downloadIDs, true);
+
+        // Visits and downloads > 1 hour should still exist.
+        ensureHistoryClearedState(olderURIs, false);
+        ensureDownloadsClearedState(olderDownloadIDs, false);
 
-      // Visits and downloads > 1 hour should still exist.
-      ensureHistoryClearedState(olderURIs, false);
-      ensureDownloadsClearedState(olderDownloadIDs, false);
-
-      // OK, done, cleanup after ourselves.
-      blankSlate();
-      ensureHistoryClearedState(olderURIs, true);
-      ensureDownloadsClearedState(olderDownloadIDs, true);
-    };
-    wh.open();
+        // OK, done, cleanup after ourselves.
+        blankSlate();
+        ensureHistoryClearedState(olderURIs, true);
+        ensureDownloadsClearedState(olderDownloadIDs, true);
+      };
+      wh.open();
+    });
   },
 
   /**
    * Ensures that the combined history-downloads checkbox removes neither
    * history visits nor downloads when not checked.
    */
   function () {
     // Add history, downloads, form entries (within the past hour).
     let uris = [];
-    for (let i = 0; i < 5; i++) {
-      uris.push(addHistoryWithMinutesAgo(i));
-    }
-    let downloadIDs = [];
+    let places = [];
+    let pURI;
     for (let i = 0; i < 5; i++) {
-      downloadIDs.push(addDownloadWithMinutesAgo(i));
-    }
-    let formEntries = [];
-    for (let i = 0; i < 5; i++) {
-      formEntries.push(addFormEntryWithMinutesAgo(i));
+      pURI = makeURI("http://" + i + "-minutes-ago.com/");
+      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+      uris.push(pURI);
     }
 
-    let wh = new WindowHelper();
-    wh.onload = function () {
-      is(this.isWarningPanelVisible(), false,
-         "Warning panel should be hidden after previously accepting dialog " +
-         "with a predefined timespan");
-      this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+    addVisits(places, function() {
+      let downloadIDs = [];
+      for (let i = 0; i < 5; i++) {
+        downloadIDs.push(addDownloadWithMinutesAgo(i));
+      }
+      let formEntries = [];
+      for (let i = 0; i < 5; i++) {
+        formEntries.push(addFormEntryWithMinutesAgo(i));
+      }
 
-      // Remove only form entries, leave history (including downloads).
-      this.checkPrefCheckbox("history", false);
-      this.checkPrefCheckbox("formdata", true);
-      this.acceptDialog();
+      let wh = new WindowHelper();
+      wh.onload = function () {
+        is(this.isWarningPanelVisible(), false,
+           "Warning panel should be hidden after previously accepting dialog " +
+           "with a predefined timespan");
+        this.selectDuration(Sanitizer.TIMESPAN_HOUR);
+
+        // Remove only form entries, leave history (including downloads).
+        this.checkPrefCheckbox("history", false);
+        this.checkPrefCheckbox("formdata", true);
+        this.acceptDialog();
 
-      intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR,
-                "timeSpan pref should be hour after accepting dialog with " +
-                "hour selected");
-      boolPrefIs("cpd.history", false,
-                 "history pref should be false after accepting dialog with " +
-                 "history checkbox unchecked");
-      boolPrefIs("cpd.downloads", false,
-                 "downloads pref should be false after accepting dialog with " +
-                 "history checkbox unchecked");
+        intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_HOUR,
+                  "timeSpan pref should be hour after accepting dialog with " +
+                  "hour selected");
+        boolPrefIs("cpd.history", false,
+                   "history pref should be false after accepting dialog with " +
+                   "history checkbox unchecked");
+        boolPrefIs("cpd.downloads", false,
+                   "downloads pref should be false after accepting dialog with " +
+                   "history checkbox unchecked");
 
-      // Of the three only form entries should be cleared.
-      ensureHistoryClearedState(uris, false);
-      ensureDownloadsClearedState(downloadIDs, false);
-      ensureFormEntriesClearedState(formEntries, true);
+        // Of the three only form entries should be cleared.
+        ensureHistoryClearedState(uris, false);
+        ensureDownloadsClearedState(downloadIDs, false);
+        ensureFormEntriesClearedState(formEntries, true);
 
-      // OK, done, cleanup after ourselves.
-      blankSlate();
-      ensureHistoryClearedState(uris, true);
-      ensureDownloadsClearedState(downloadIDs, true);
-    };
-    wh.open();
+        // OK, done, cleanup after ourselves.
+        blankSlate();
+        ensureHistoryClearedState(uris, true);
+        ensureDownloadsClearedState(downloadIDs, true);
+      };
+      wh.open();
+    });
   },
 
   /**
    * Ensures that the "Everything" duration option works.
    */
   function () {
     // Add history.
     let uris = [];
-    uris.push(addHistoryWithMinutesAgo(10));  // within past hour
-    uris.push(addHistoryWithMinutesAgo(70));  // within past two hours
-    uris.push(addHistoryWithMinutesAgo(130)); // within past four hours
-    uris.push(addHistoryWithMinutesAgo(250)); // outside past four hours
-
-    let wh = new WindowHelper();
-    wh.onload = function () {
-      is(this.isWarningPanelVisible(), false,
-         "Warning panel should be hidden after previously accepting dialog " +
-         "with a predefined timespan");
-      this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
-      this.checkPrefCheckbox("history", true);
-      this.checkDetails(true);
+    let places = [];
+    let pURI;
+    // within past hour, within past two hours, within past four hours and 
+    // outside past four hours
+    [10, 70, 130, 250].forEach(function(aValue) {
+      pURI = makeURI("http://" + aValue + "-minutes-ago.com/");
+      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)});
+      uris.push(pURI);
+    });
+    addVisits(places, function() {
+      let wh = new WindowHelper();
+      wh.onload = function () {
+        is(this.isWarningPanelVisible(), false,
+           "Warning panel should be hidden after previously accepting dialog " +
+           "with a predefined timespan");
+        this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+        this.checkPrefCheckbox("history", true);
+        this.checkDetails(true);
 
-      // Hide details
-      this.toggleDetails();
-      this.checkDetails(false);
+        // Hide details
+        this.toggleDetails();
+        this.checkDetails(false);
 
-      // Show details
-      this.toggleDetails();
-      this.checkDetails(true);
+        // Show details
+        this.toggleDetails();
+        this.checkDetails(true);
 
-      this.acceptDialog();
+        this.acceptDialog();
 
-      intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
-                "timeSpan pref should be everything after accepting dialog " +
-                "with everything selected");
-      ensureHistoryClearedState(uris, true);
-    };
-    wh.open();
+        intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
+                  "timeSpan pref should be everything after accepting dialog " +
+                  "with everything selected");
+        ensureHistoryClearedState(uris, true);
+      };
+      wh.open();
+    });
   },
 
   /**
    * Ensures that the "Everything" warning is visible on dialog open after
    * the previous test.
    */
   function () {
     // Add history.
     let uris = [];
-    uris.push(addHistoryWithMinutesAgo(10));  // within past hour
-    uris.push(addHistoryWithMinutesAgo(70));  // within past two hours
-    uris.push(addHistoryWithMinutesAgo(130)); // within past four hours
-    uris.push(addHistoryWithMinutesAgo(250)); // outside past four hours
+    let places = [];
+    let pURI;
+    // within past hour, within past two hours, within past four hours and 
+    // outside past four hours
+    [10, 70, 130, 250].forEach(function(aValue) {
+      pURI = makeURI("http://" + aValue + "-minutes-ago.com/");
+      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)});
+      uris.push(pURI);
+    });
+    addVisits(places, function() {
+      let wh = new WindowHelper();
+      wh.onload = function () {
+        is(this.isWarningPanelVisible(), true,
+           "Warning panel should be visible after previously accepting dialog " +
+           "with clearing everything");
+        this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+        this.checkPrefCheckbox("history", true);
+        this.acceptDialog();
 
-    let wh = new WindowHelper();
-    wh.onload = function () {
-      is(this.isWarningPanelVisible(), true,
-         "Warning panel should be visible after previously accepting dialog " +
-         "with clearing everything");
-      this.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
-      this.checkPrefCheckbox("history", true);
-      this.acceptDialog();
-
-      intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
-                "timeSpan pref should be everything after accepting dialog " +
-                "with everything selected");
-      ensureHistoryClearedState(uris, true);
-    };
-    wh.open();
+        intPrefIs("sanitize.timeSpan", Sanitizer.TIMESPAN_EVERYTHING,
+                  "timeSpan pref should be everything after accepting dialog " +
+                  "with everything selected");
+        ensureHistoryClearedState(uris, true);
+      };
+      wh.open();
+    });
   },
 
   /**
    * The next three tests checks that when a certain history item cannot be
    * cleared then the checkbox should be both disabled and unchecked.
    * In addition, we ensure that this behavior does not modify the preferences.
    */
   function () {
     // Add history.
-    let uris = [ addHistoryWithMinutesAgo(10) ];
-    let formEntries = [ addFormEntryWithMinutesAgo(10) ];
+    let pURI = makeURI("http://" + 10 + "-minutes-ago.com/");
+    addVisits({uri: pURI, visitDate: visitTimeForMinutesAgo(10)}, function() {
+      let uris = [ pURI ];
+      let formEntries = [ addFormEntryWithMinutesAgo(10) ];
 
-    let wh = new WindowHelper();
-    wh.onload = function() {
-      // Check that the relevant checkboxes are enabled
-      var cb = this.win.document.querySelectorAll(
-                 "#itemList > [preference='privacy.cpd.formdata']");
-      ok(cb.length == 1 && !cb[0].disabled, "There is formdata, checkbox to " +
-         "clear formdata should be enabled.");
+      let wh = new WindowHelper();
+      wh.onload = function() {
+        // Check that the relevant checkboxes are enabled
+        var cb = this.win.document.querySelectorAll(
+                   "#itemList > [preference='privacy.cpd.formdata']");
+        ok(cb.length == 1 && !cb[0].disabled, "There is formdata, checkbox to " +
+           "clear formdata should be enabled.");
 
-      var cb = this.win.document.querySelectorAll(
-                 "#itemList > [preference='privacy.cpd.history']");
-      ok(cb.length == 1 && !cb[0].disabled, "There is history, checkbox to " +
-         "clear history should be enabled.");
+        var cb = this.win.document.querySelectorAll(
+                   "#itemList > [preference='privacy.cpd.history']");
+        ok(cb.length == 1 && !cb[0].disabled, "There is history, checkbox to " +
+           "clear history should be enabled.");
 
-      this.checkAllCheckboxes();
-      this.acceptDialog();
+        this.checkAllCheckboxes();
+        this.acceptDialog();
 
-      ensureHistoryClearedState(uris, true);
-      ensureFormEntriesClearedState(formEntries, true);
-    };
-    wh.open();
+        ensureHistoryClearedState(uris, true);
+        ensureFormEntriesClearedState(formEntries, true);
+      };
+      wh.open();
+    });
   },
   function () {
     let wh = new WindowHelper();
     wh.onload = function() {
       boolPrefIs("cpd.history", true,
                  "history pref should be true after accepting dialog with " +
                  "history checkbox checked");
       boolPrefIs("cpd.formdata", true,
@@ -849,36 +888,16 @@ function addFormEntryWithMinutesAgo(aMin
                       timestamp +  " WHERE fieldname = '" + name + "'");
 
   is(formhist.nameExists(name), true,
      "Sanity check: form entry " + name + " should exist after creating it");
   return name;
 }
 
 /**
- * Adds a history visit to history.
- *
- * @param aMinutesAgo
- *        The visit will be visited this many minutes ago
- */
-function addHistoryWithMinutesAgo(aMinutesAgo) {
-  let pURI = makeURI("http://" + aMinutesAgo + "-minutes-ago.com/");
-  PlacesUtils.history.addVisit(pURI,
-                               now_uSec - aMinutesAgo * kUsecPerMin,
-                               null,
-                               Ci.nsINavHistoryService.TRANSITION_LINK,
-                               false,
-                               0);
-  is(PlacesUtils.bhistory.isVisited(pURI), true,
-     "Sanity check: history visit " + pURI.spec +
-     " should exist after creating it");
-  return pURI;
-}
-
-/**
  * Removes all history visits, downloads, and form entries.
  */
 function blankSlate() {
   PlacesUtils.bhistory.removeAllPages();
   dm.cleanUp();
   formhist.removeAllEntries();
 }
 
@@ -1029,16 +1048,26 @@ function ensureHistoryClearedState(aURIs
  *        The pref's expected value
  * @param aMsg
  *        Passed to is()
  */
 function intPrefIs(aPrefName, aExpectedVal, aMsg) {
   is(gPrefService.getIntPref("privacy." + aPrefName), aExpectedVal, aMsg);
 }
 
+/**
+ * Creates a visit time.
+ *
+ * @param aMinutesAgo
+ *        The visit will be visited this many minutes ago
+ */
+function visitTimeForMinutesAgo(aMinutesAgo) {
+  return now_uSec - aMinutesAgo * kUsecPerMin;
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 function test() {
   requestLongerTimeout(2);
   blankSlate();
   waitForExplicitFinish();
   // Kick off all the tests in the gAllTests array.
   waitForAsyncUpdates(doNextTest);
--- a/browser/base/content/test/browser_sanitizeDialog_treeView.js
+++ b/browser/base/content/test/browser_sanitizeDialog_treeView.js
@@ -30,174 +30,205 @@ const formhist = Cc["@mozilla.org/satche
 var gAllTests = [
 
   /**
    * Moves the grippy around, makes sure it works OK.
    */
   function () {
     // Add history (within the past hour) to get some rows in the tree.
     let uris = [];
+    let places = [];
+    let pURI;
     for (let i = 0; i < 30; i++) {
-      uris.push(addHistoryWithMinutesAgo(i));
+      pURI = makeURI("http://" + i + "-minutes-ago.com/");
+      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+      uris.push(pURI);
     }
 
-    // Open the dialog and do our tests.
-    openWindow(function (aWin) {
-      let wh = new WindowHelper(aWin);
-      wh.selectDuration(Sanitizer.TIMESPAN_HOUR);
-      wh.checkGrippy("Grippy should be at last row after selecting HOUR " +
-                     "duration",
-                     wh.getRowCount() - 1);
+    addVisits(places, function() {
+      // Open the dialog and do our tests.
+      openWindow(function (aWin) {
+        let wh = new WindowHelper(aWin);
+        wh.selectDuration(Sanitizer.TIMESPAN_HOUR);
+        wh.checkGrippy("Grippy should be at last row after selecting HOUR " +
+                       "duration",
+                       wh.getRowCount() - 1);
 
-      // Move the grippy around.
-      let row = wh.getGrippyRow();
-      while (row !== 0) {
-        row--;
+        // Move the grippy around.
+        let row = wh.getGrippyRow();
+        while (row !== 0) {
+          row--;
+          wh.moveGrippyBy(-1);
+          wh.checkGrippy("Grippy should be moved up one row", row);
+        }
         wh.moveGrippyBy(-1);
-        wh.checkGrippy("Grippy should be moved up one row", row);
-      }
-      wh.moveGrippyBy(-1);
-      wh.checkGrippy("Grippy should remain at first row after trying to move " +
-                     "it up",
-                     0);
-      while (row !== wh.getRowCount() - 1) {
-        row++;
+        wh.checkGrippy("Grippy should remain at first row after trying to move " +
+                       "it up",
+                       0);
+        while (row !== wh.getRowCount() - 1) {
+          row++;
+          wh.moveGrippyBy(1);
+          wh.checkGrippy("Grippy should be moved down one row", row);
+        }
         wh.moveGrippyBy(1);
-        wh.checkGrippy("Grippy should be moved down one row", row);
-      }
-      wh.moveGrippyBy(1);
-      wh.checkGrippy("Grippy should remain at last row after trying to move " +
-                     "it down",
-                     wh.getRowCount() - 1);
+        wh.checkGrippy("Grippy should remain at last row after trying to move " +
+                       "it down",
+                       wh.getRowCount() - 1);
+
+        // Cancel the dialog, make sure history visits are not cleared.
+        wh.checkPrefCheckbox("history", false);
 
-      // Cancel the dialog, make sure history visits are not cleared.
-      wh.checkPrefCheckbox("history", false);
+        wh.cancelDialog();
+        ensureHistoryClearedState(uris, false);
 
-      wh.cancelDialog();
-      ensureHistoryClearedState(uris, false);
-
-      // OK, done, cleanup after ourselves.
-      blankSlate();
-      ensureHistoryClearedState(uris, true);
+        // OK, done, cleanup after ourselves.
+        blankSlate();
+        ensureHistoryClearedState(uris, true);
+      });
     });
   },
 
   /**
    * Ensures that the combined history-downloads checkbox clears both history
    * visits and downloads when checked; the dialog respects simple timespan.
    */
   function () {
-    // Add history and downloads (within the past hour).
+    // Add history (within the past hour).
     let uris = [];
+    let places = [];
+    let pURI;
     for (let i = 0; i < 30; i++) {
-      uris.push(addHistoryWithMinutesAgo(i));
+      pURI = makeURI("http://" + i + "-minutes-ago.com/");
+      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+      uris.push(pURI);
     }
-    let downloadIDs = [];
-    for (let i = 0; i < 5; i++) {
-      downloadIDs.push(addDownloadWithMinutesAgo(i));
-    }
-    // Add history and downloads (over an hour ago).
+    // Add history (over an hour ago).
     let olderURIs = [];
     for (let i = 0; i < 5; i++) {
-      olderURIs.push(addHistoryWithMinutesAgo(61 + i));
+      pURI = makeURI("http://" + (60 + i) + "-minutes-ago.com/");
+      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(60 + i)});
+      olderURIs.push(pURI);
     }
-    let olderDownloadIDs = [];
-    for (let i = 0; i < 5; i++) {
-      olderDownloadIDs.push(addDownloadWithMinutesAgo(61 + i));
-    }
-    let totalHistoryVisits = uris.length + olderURIs.length;
 
-    // Open the dialog and do our tests.
-    openWindow(function (aWin) {
-      let wh = new WindowHelper(aWin);
-      wh.selectDuration(Sanitizer.TIMESPAN_HOUR);
-      wh.checkGrippy("Grippy should be at proper row after selecting HOUR " +
-                     "duration",
-                     uris.length);
+    addVisits(places, function() {
+      // Add downloads (within the past hour).
+      let downloadIDs = [];
+      for (let i = 0; i < 5; i++) {
+        downloadIDs.push(addDownloadWithMinutesAgo(i));
+      }
+      // Add downloads (over an hour ago).
+      let olderDownloadIDs = [];
+      for (let i = 0; i < 5; i++) {
+        olderDownloadIDs.push(addDownloadWithMinutesAgo(61 + i));
+      }
+      let totalHistoryVisits = uris.length + olderURIs.length;
 
-      // Accept the dialog, make sure history visits and downloads within one
-      // hour are cleared.
-      wh.checkPrefCheckbox("history", true);
-      wh.acceptDialog();
-      ensureHistoryClearedState(uris, true);
-      ensureDownloadsClearedState(downloadIDs, true);
+      // Open the dialog and do our tests.
+      openWindow(function (aWin) {
+        let wh = new WindowHelper(aWin);
+        wh.selectDuration(Sanitizer.TIMESPAN_HOUR);
+        wh.checkGrippy("Grippy should be at proper row after selecting HOUR " +
+                       "duration",
+                       uris.length);
 
-      // Make sure visits and downloads > 1 hour still exist.
-      ensureHistoryClearedState(olderURIs, false);
-      ensureDownloadsClearedState(olderDownloadIDs, false);
+        // Accept the dialog, make sure history visits and downloads within one
+        // hour are cleared.
+        wh.checkPrefCheckbox("history", true);
+        wh.acceptDialog();
+        ensureHistoryClearedState(uris, true);
+        ensureDownloadsClearedState(downloadIDs, true);
 
-      // OK, done, cleanup after ourselves.
-      blankSlate();
-      ensureHistoryClearedState(olderURIs, true);
-      ensureDownloadsClearedState(olderDownloadIDs, true);
+        // Make sure visits and downloads > 1 hour still exist.
+        ensureHistoryClearedState(olderURIs, false);
+        ensureDownloadsClearedState(olderDownloadIDs, false);
+
+        // OK, done, cleanup after ourselves.
+        blankSlate();
+        ensureHistoryClearedState(olderURIs, true);
+        ensureDownloadsClearedState(olderDownloadIDs, true);
+      });
     });
   },
 
   /**
    * Ensures that the combined history-downloads checkbox removes neither
    * history visits nor downloads when not checked.
    */
   function () {
     // Add history, downloads, form entries (within the past hour).
     let uris = [];
-    for (let i = 0; i < 5; i++) {
-      uris.push(addHistoryWithMinutesAgo(i));
-    }
-    let downloadIDs = [];
+    let places = [];
+    let pURI;
     for (let i = 0; i < 5; i++) {
-      downloadIDs.push(addDownloadWithMinutesAgo(i));
-    }
-    let formEntries = [];
-    for (let i = 0; i < 5; i++) {
-      formEntries.push(addFormEntryWithMinutesAgo(i));
+      pURI = makeURI("http://" + i + "-minutes-ago.com/");
+      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(i)});
+      uris.push(pURI);
     }
 
-    // Open the dialog and do our tests.
-    openWindow(function (aWin) {
-      let wh = new WindowHelper(aWin);
-      wh.selectDuration(Sanitizer.TIMESPAN_HOUR);
-      wh.checkGrippy("Grippy should be at last row after selecting HOUR " +
-                     "duration",
-                     wh.getRowCount() - 1);
+    addVisits(places, function() {
+      let downloadIDs = [];
+      for (let i = 0; i < 5; i++) {
+        downloadIDs.push(addDownloadWithMinutesAgo(i));
+      }
+      let formEntries = [];
+      for (let i = 0; i < 5; i++) {
+        formEntries.push(addFormEntryWithMinutesAgo(i));
+      }
 
-      // Remove only form entries, leave history (including downloads).
-      wh.checkPrefCheckbox("history", false);
-      wh.checkPrefCheckbox("formdata", true);
-      wh.acceptDialog();
+      // Open the dialog and do our tests.
+      openWindow(function (aWin) {
+        let wh = new WindowHelper(aWin);
+        wh.selectDuration(Sanitizer.TIMESPAN_HOUR);
+        wh.checkGrippy("Grippy should be at last row after selecting HOUR " +
+                       "duration",
+                       wh.getRowCount() - 1);
 
-      // Of the three only form entries should be cleared.
-      ensureHistoryClearedState(uris, false);
-      ensureDownloadsClearedState(downloadIDs, false);
-      ensureFormEntriesClearedState(formEntries, true);
+        // Remove only form entries, leave history (including downloads).
+        wh.checkPrefCheckbox("history", false);
+        wh.checkPrefCheckbox("formdata", true);
+        wh.acceptDialog();
 
-      // OK, done, cleanup after ourselves.
-      blankSlate();
-      ensureHistoryClearedState(uris, true);
-      ensureDownloadsClearedState(downloadIDs, true);
+        // Of the three only form entries should be cleared.
+        ensureHistoryClearedState(uris, false);
+        ensureDownloadsClearedState(downloadIDs, false);
+        ensureFormEntriesClearedState(formEntries, true);
+
+        // OK, done, cleanup after ourselves.
+        blankSlate();
+        ensureHistoryClearedState(uris, true);
+        ensureDownloadsClearedState(downloadIDs, true);
+      });
     });
   },
 
   /**
    * Ensures that the "Everything" duration option works.
    */
   function () {
     // Add history.
     let uris = [];
-    uris.push(addHistoryWithMinutesAgo(10));  // within past hour
-    uris.push(addHistoryWithMinutesAgo(70));  // within past two hours
-    uris.push(addHistoryWithMinutesAgo(130)); // within past four hours
-    uris.push(addHistoryWithMinutesAgo(250)); // outside past four hours
+    let places = [];
+    let pURI;
+    // within past hour, within past two hours, within past four hours and 
+    // outside past four hours
+    [10, 70, 130, 250].forEach(function(aValue) {
+      pURI = makeURI("http://" + aValue + "-minutes-ago.com/");
+      places.push({uri: pURI, visitDate: visitTimeForMinutesAgo(aValue)});
+      uris.push(pURI);
+    });
+    addVisits(places, function() {
 
-    // Open the dialog and do our tests.
-    openWindow(function (aWin) {
-      let wh = new WindowHelper(aWin);
-      wh.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
-      wh.checkPrefCheckbox("history", true);
-      wh.acceptDialog();
-      ensureHistoryClearedState(uris, true);
+      // Open the dialog and do our tests.
+      openWindow(function (aWin) {
+        let wh = new WindowHelper(aWin);
+        wh.selectDuration(Sanitizer.TIMESPAN_EVERYTHING);
+        wh.checkPrefCheckbox("history", true);
+        wh.acceptDialog();
+        ensureHistoryClearedState(uris, true);
+      });
     });
   }
 ];
 
 // Used as the download database ID for a new download.  Incremented for each
 // new download.  See addDownloadWithMinutesAgo().
 var gDownloadId = 5555551;
 
@@ -458,36 +489,16 @@ function addFormEntryWithMinutesAgo(aMin
                       timestamp +  " WHERE fieldname = '" + name + "'");
 
   is(formhist.nameExists(name), true,
      "Sanity check: form entry " + name + " should exist after creating it");
   return name;
 }
 
 /**
- * Adds a history visit to history.
- *
- * @param aMinutesAgo
- *        The visit will be visited this many minutes ago
- */
-function addHistoryWithMinutesAgo(aMinutesAgo) {
-  let pURI = makeURI("http://" + aMinutesAgo + "-minutes-ago.com/");
-  PlacesUtils.history.addVisit(pURI,
-                               now_uSec - (aMinutesAgo * 60 * 1000 * 1000),
-                               null,
-                               Ci.nsINavHistoryService.TRANSITION_LINK,
-                               false,
-                               0);
-  is(PlacesUtils.bhistory.isVisited(pURI), true,
-     "Sanity check: history visit " + pURI.spec +
-     " should exist after creating it");
-  return pURI;
-}
-
-/**
  * Removes all history visits, downloads, and form entries.
  */
 function blankSlate() {
   PlacesUtils.bhistory.removeAllPages();
   dm.cleanUp();
   formhist.removeAllEntries();
 }
 
@@ -648,16 +659,26 @@ function openWindow(aOnloadCallback) {
   Services.ww.registerNotification(windowObserver);
   Services.ww.openWindow(null,
                          "chrome://browser/content/sanitize.xul",
                          "Sanitize",
                          "chrome,titlebar,dialog,centerscreen,modal",
                          null);
 }
 
+/**
+ * Creates a visit time.
+ *
+ * @param aMinutesAgo
+ *        The visit will be visited this many minutes ago
+ */
+function visitTimeForMinutesAgo(aMinutesAgo) {
+  return now_uSec - (aMinutesAgo * 60 * 1000000);
+}
+
 ///////////////////////////////////////////////////////////////////////////////
 
 function test() {
   blankSlate();
   waitForExplicitFinish();
   // Kick off all the tests in the gAllTests array.
   waitForAsyncUpdates(doNextTest);
 }
--- a/browser/base/content/test/head.js
+++ b/browser/base/content/test/head.js
@@ -126,8 +126,47 @@ function resetBlocklist() {
 
 function whenNewWindowLoaded(aOptions, aCallback) {
   let win = OpenBrowserWindow(aOptions);
   win.addEventListener("load", function onLoad() {
     win.removeEventListener("load", onLoad, false);
     aCallback(win);
   }, false);
 }
+
+function addVisits(aPlaceInfo, aCallback) {
+  let places = [];
+  if (aPlaceInfo instanceof Ci.nsIURI) {
+    places.push({ uri: aPlaceInfo });
+  } else if (Array.isArray(aPlaceInfo)) {
+    places = places.concat(aPlaceInfo);
+  } else {
+    places.push(aPlaceInfo);
+   }
+
+  // Create mozIVisitInfo for each entry.
+  let now = Date.now();
+  for (let i = 0; i < places.length; i++) {
+    if (!places[i].title) {
+      places[i].title = "test visit for " + places[i].uri.spec;
+    }
+    places[i].visits = [{
+      transitionType: places[i].transition === undefined ? Ci.nsINavHistoryService.TRANSITION_LINK
+                                                         : places[i].transition,
+      visitDate: places[i].visitDate || (now++) * 1000,
+      referrerURI: places[i].referrer
+    }];
+  }
+
+  PlacesUtils.asyncHistory.updatePlaces(
+    places,
+    {
+      handleError: function AAV_handleError() {
+        throw("Unexpected error in adding visit.");
+      },
+      handleResult: function () {},
+      handleCompletion: function UP_handleCompletion() {
+        if (aCallback)
+          aCallback();
+      }
+    }
+  );
+}
--- a/browser/base/content/test/social/Makefile.in
+++ b/browser/base/content/test/social/Makefile.in
@@ -15,16 +15,17 @@ include $(DEPTH)/config/autoconf.mk
                  browser_social_toolbar.js \
                  browser_social_shareButton.js \
                  browser_social_sidebar.js \
                  browser_social_flyout.js \
                  browser_social_mozSocial_API.js \
                  browser_social_isVisible.js \
                  browser_social_chatwindow.js \
                  browser_social_multiprovider.js \
+                 browser_social_errorPage.js \
                  social_panel.html \
                  social_share_image.png \
                  social_sidebar.html \
                  social_chat.html \
                  social_flyout.html \
                  social_window.html \
                  social_worker.js \
                  $(NULL)
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/social/browser_social_errorPage.js
@@ -0,0 +1,164 @@
+/* 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/. */
+
+function gc() {
+  Cu.forceGC();
+  let wu =  window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
+                  .getInterface(Components.interfaces.nsIDOMWindowUtils);
+  wu.garbageCollect();
+}
+
+// Support for going on and offline.
+// (via browser/base/content/test/browser_bookmark_titles.js)
+let origProxyType = Services.prefs.getIntPref('network.proxy.type');
+
+function goOffline() {
+  // Simulate a network outage with offline mode. (Localhost is still
+  // accessible in offline mode, so disable the test proxy as well.)
+  if (!Services.io.offline)
+    BrowserOffline.toggleOfflineStatus();
+  Services.prefs.setIntPref('network.proxy.type', 0);
+  // LOAD_FLAGS_BYPASS_CACHE isn't good enough. So clear the cache.
+  Services.cache.evictEntries(Services.cache.STORE_ANYWHERE);
+}
+
+function goOnline(callback) {
+  Services.prefs.setIntPref('network.proxy.type', origProxyType);
+  if (Services.io.offline)
+    BrowserOffline.toggleOfflineStatus();
+  if (callback)
+    callback();
+}
+
+function openPanel(url, panelCallback, loadCallback) {
+  // open a flyout
+  SocialFlyout.open(url, 0, panelCallback);
+  SocialFlyout.panel.firstChild.addEventListener("load", function panelLoad() {
+    SocialFlyout.panel.firstChild.removeEventListener("load", panelLoad, true);
+    loadCallback();
+  }, true);
+}
+
+function openChat(url, panelCallback, loadCallback) {
+  // open a chat window
+  SocialChatBar.openChat(Social.provider, url, panelCallback);
+  SocialChatBar.chatbar.firstChild.addEventListener("DOMContentLoaded", function panelLoad() {
+    SocialChatBar.chatbar.firstChild.removeEventListener("DOMContentLoaded", panelLoad, true);
+    loadCallback();
+  }, true);
+}
+
+function onSidebarLoad(callback) {
+  let sbrowser = document.getElementById("social-sidebar-browser");
+  sbrowser.addEventListener("load", function load() {
+    sbrowser.removeEventListener("load", load, true);
+    callback();
+  }, true);
+}
+
+let manifest = { // normal provider
+  name: "provider 1",
+  origin: "https://example.com",
+  sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
+  workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+  iconURL: "https://example.com/browser/browser/base/content/test/moz.png"
+};
+
+function test() {
+  waitForExplicitFinish();
+  // we don't want the sidebar to auto-load in these tests..
+  Services.prefs.setBoolPref("social.sidebar.open", false);
+  registerCleanupFunction(function() {
+    Services.prefs.clearUserPref("social.sidebar.open");
+  });
+
+  runSocialTestWithProvider(manifest, function (finishcb) {
+    runSocialTests(tests, undefined, goOnline, finishcb);
+  });
+}
+
+var tests = {
+  testSidebar: function(next) {
+    let sbrowser = document.getElementById("social-sidebar-browser");
+    onSidebarLoad(function() {
+      ok(sbrowser.contentDocument.location.href.indexOf("about:socialerror?")==0, "is on social error page");
+      gc();
+      // Add a new load listener, then find and click the "try again" button.
+      onSidebarLoad(function() {
+        // should still be on the error page.
+        ok(sbrowser.contentDocument.location.href.indexOf("about:socialerror?")==0, "is still on social error page");
+        // go online and try again - this should work.
+        goOnline();
+        onSidebarLoad(function() {
+          // should now be on the correct page.
+          is(sbrowser.contentDocument.location.href, manifest.sidebarURL, "is now on social sidebar page");
+          next();
+        });
+        sbrowser.contentDocument.getElementById("btnTryAgain").click();
+      });
+      sbrowser.contentDocument.getElementById("btnTryAgain").click();
+    });
+    // go offline then attempt to load the sidebar - it should fail.
+    goOffline();
+    Services.prefs.setBoolPref("social.sidebar.open", true);
+  },
+
+  testFlyout: function(next) {
+    let panelCallbackCount = 0;
+    let panel = document.getElementById("social-flyout-panel");
+    // go offline and open a flyout.
+    goOffline();
+    openPanel(
+      "https://example.com/browser/browser/base/content/test/social/social_panel.html",
+      function() { // the panel api callback
+        panelCallbackCount++;
+      },
+      function() { // the "load" callback.
+        executeSoon(function() {
+          todo_is(panelCallbackCount, 0, "Bug 833207 - should be no callback when error page loads.");
+          ok(panel.firstChild.contentDocument.location.href.indexOf("about:socialerror?")==0, "is on social error page");
+          // Bug 832943 - the listeners previously stopped working after a GC, so
+          // force a GC now and try again.
+          gc();
+          openPanel(
+            "https://example.com/browser/browser/base/content/test/social/social_panel.html",
+            function() { // the panel api callback
+              panelCallbackCount++;
+            },
+            function() { // the "load" callback.
+              executeSoon(function() {
+                todo_is(panelCallbackCount, 0, "Bug 833207 - should be no callback when error page loads.");
+                ok(panel.firstChild.contentDocument.location.href.indexOf("about:socialerror?")==0, "is on social error page");
+                gc();
+                SocialFlyout.unload();
+                next();
+              });
+            }
+          );
+        });
+      }
+    );
+  },
+
+  testChatWindow: function(next) {
+    let panelCallbackCount = 0;
+    // go offline and open a flyout.
+    goOffline();
+    openChat(
+      "https://example.com/browser/browser/base/content/test/social/social_chat.html",
+      function() { // the panel api callback
+        panelCallbackCount++;
+      },
+      function() { // the "load" callback.
+        executeSoon(function() {
+          todo_is(panelCallbackCount, 0, "Bug 833207 - should be no callback when error page loads.");
+          ok(SocialChatBar.chatbar.selectedChat.iframe.contentDocument.location.href.indexOf("about:socialerror?")==0, "is on social error page");
+          SocialChatBar.chatbar.selectedChat.close();
+          next();
+        });
+      }
+    );
+  }
+}
+
--- a/browser/base/content/web-panels.xul
+++ b/browser/base/content/web-panels.xul
@@ -45,17 +45,17 @@
     <command id="Browser:Reload" oncommand="PanelBrowserReload();"/>
   </commandset>
 
   <popupset id="mainPopupSet">
     <tooltip id="aHTMLTooltip" onpopupshowing="return FillInHTMLTooltip(document.tooltipNode);"/>
     <menupopup id="contentAreaContextMenu" pagemenu="start"
                onpopupshowing="if (event.target != this)
                                  return true;
-                               gContextMenu = new nsContextMenu(this, getPanelBrowser(), event.shiftKey);
+                               gContextMenu = new nsContextMenu(this, event.shiftKey);
                                if (gContextMenu.shouldDisplay)
                                  document.popupNode = this.triggerNode;
                                return gContextMenu.shouldDisplay;"
                onpopuphiding="if (event.target != this)
                                 return;
                               gContextMenu.hiding();
                               gContextMenu = null;">
 #include browser-context.inc
--- a/browser/components/places/tests/browser/browser_410196_paste_into_tags.js
+++ b/browser/components/places/tests/browser/browser_410196_paste_into_tags.js
@@ -1,18 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
-function add_visit(aURI, aReferrer) {
-  return PlacesUtils.history.addVisit(aURI, Date.now() * 1000, aReferrer,
-                                      PlacesUtils.history.TRANSITION_TYPED,
-                                      false, 0);
-}
-
 function add_bookmark(aURI) {
   return PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
                                               aURI, PlacesUtils.bookmarks.DEFAULT_INDEX,
                                               "bookmark/" + aURI.spec);
 }
 
 Components.utils.import("resource://gre/modules/NetUtil.jsm");
 
@@ -33,23 +27,24 @@ function onLibraryReady() {
   ok(PlacesUIUtils, "PlacesUIUtils in scope");
 
   PlacesOrganizer = gLibrary.PlacesOrganizer;
   ok(PlacesOrganizer, "Places organizer in scope");
 
   ContentTree = gLibrary.ContentTree;
   ok(ContentTree, "ContentTree is in scope");
 
-  tests.makeHistVisit();
-  tests.makeTag();
-  tests.focusTag();
-  waitForClipboard(function(aData) !!aData,
-                   tests.copyHistNode,
-                   onClipboardReady,
-                   PlacesUtils.TYPE_X_MOZ_PLACE);
+  tests.makeHistVisit(function() {
+    tests.makeTag();
+    tests.focusTag();
+    waitForClipboard(function(aData) !!aData,
+                     tests.copyHistNode,
+                     onClipboardReady,
+                     PlacesUtils.TYPE_X_MOZ_PLACE);
+  });
 }
 
 function onClipboardReady() {
   tests.focusTag();
   PlacesOrganizer._places.controller.paste();
   tests.historyNode();
   tests.checkForBookmarkInUI();
 
@@ -62,23 +57,27 @@ function onClipboardReady() {
   is(tags.length, 0, "tags are gone");
   PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
 
   waitForClearHistory(finish);
 }
 
 let tests = {
 
-  makeHistVisit: function() {
+  makeHistVisit: function(aCallback) {
     // need to add a history object
     let testURI1 = NetUtil.newURI(MOZURISPEC);
     isnot(testURI1, null, "testURI is not null");
-    let visitId = add_visit(testURI1);
-    ok(visitId > 0, "A visit was added to the history");
-    ok(PlacesUtils.ghistory2.isVisited(testURI1), MOZURISPEC + " is a visited url.");
+    addVisits(
+      {uri: testURI1, transition: PlacesUtils.history.TRANSITION_TYPED},
+      window,
+      function() {
+        ok(PlacesUtils.ghistory2.isVisited(testURI1), MOZURISPEC + " is a visited url.");
+        aCallback();
+      });
   },
 
   makeTag: function() {
     // create an initial tag to work with
     let bmId = add_bookmark(NetUtil.newURI(TEST_URL));
     ok(bmId > 0, "A bookmark was added");
     PlacesUtils.tagging.tagURI(NetUtil.newURI(TEST_URL), ["foo"]);
     let tags = PlacesUtils.tagging.getTagsForURI(NetUtil.newURI(TEST_URL));
--- a/browser/components/places/tests/browser/browser_bookmarksProperties.js
+++ b/browser/components/places/tests/browser/browser_bookmarksProperties.js
@@ -35,27 +35,16 @@ const DIALOG_URL = "chrome://browser/con
 const DIALOG_URL_MINIMAL_UI = "chrome://browser/content/places/bookmarkProperties2.xul";
 
 var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
          getService(Ci.nsIWindowMediator);
 var win = wm.getMostRecentWindow("navigator:browser");
 var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
          getService(Ci.nsIWindowWatcher);
 
-function add_visit(aURI, aDate) {
-  var visitId = PlacesUtils.history
-                           .addVisit(aURI,
-                                     aDate,
-                                     null, // no referrer
-                                     PlacesUtils.history.TRANSITION_TYPED,
-                                     false, // not redirect
-                                     0);
-  return visitId;
-}
-
 function add_bookmark(aURI) {
   var bId = PlacesUtils.bookmarks
                        .insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
                                        aURI,
                                        PlacesUtils.bookmarks.DEFAULT_INDEX,
                                        "bookmark/" + aURI.spec);
   return bId;
 }
@@ -70,18 +59,19 @@ var gCurrentTest = null;
 gTests.push({
   desc: "Bug Description",
   sidebar: SIDEBAR_BOOKMARKS_ID, // See SIDEBAR_ constants above.
   action: ACTION_EDIT, // See ACTION_ constants above.
   itemType: null, // See TYPE_ constants above, required for ACTION_ADD, only for Bookmarks sidebar.
   historyView: SIDEBAR_HISTORY_BYLASTVISITED_VIEW, // See constants above, only for History sidebar.
   window: null, // Will contain handle of dialog window
 
-  setup: function() {
+  setup: function(aCallback) {
     // Setup everything needed for this test, runs before everything else.
+    aCallback();
   },
 
   selectNode: function(tree) {
     // Select the node to edit or to add to, runs when sidebar tree is visible.
   },
 
   run: function() {
     // Actual test, runs when dialog is open.
@@ -105,18 +95,19 @@ gTests.push({
 
 gTests.push({
   desc: "Bug 479348 - Properties on a root should be read-only",
   sidebar: SIDEBAR_BOOKMARKS_ID,
   action: ACTION_EDIT,
   itemType: null,
   window: null,
 
-  setup: function() {
+  setup: function(aCallback) {
     // Nothing to do.
+    aCallback();
   },
 
   selectNode: function(tree) {
     // Select Unfiled Bookmarks root.
     var itemId = PlacesUIUtils.leftPaneQueries["UnfiledBookmarks"];
     tree.selectItems([itemId]);
     this.selectedNode = tree.selectedNode;
   },
@@ -164,25 +155,26 @@ gTests.push({
   desc: "Bug 462662 - Pressing Enter to select tag from autocomplete closes bookmarks properties dialog",
   sidebar: SIDEBAR_BOOKMARKS_ID,
   action: ACTION_EDIT,
   itemType: null,
   window: null,
   _itemId: null,
   _cleanShutdown: false,
 
-  setup: function() {
+  setup: function(aCallback) {
     // Add a bookmark in unsorted bookmarks folder.
     this._itemId = add_bookmark(PlacesUtils._uri(TEST_URL));
     ok(this._itemId > 0, "Correctly added a bookmark");
     // Add a tag to this bookmark.
     PlacesUtils.tagging.tagURI(PlacesUtils._uri(TEST_URL),
                                ["testTag"]);
     var tags = PlacesUtils.tagging.getTagsForURI(PlacesUtils._uri(TEST_URL));
     is(tags[0], "testTag", "Correctly added a tag");
+    aCallback();
   },
 
   selectNode: function(tree) {
     tree.selectItems([this._itemId]);
     is(tree.selectedNode.itemId, this._itemId, "Bookmark has been selected");
   },
 
   run: function() {
@@ -260,18 +252,19 @@ gTests.push({
 gTests.push({
   desc: "Bug 475529 - Add button in new folder dialog not default anymore",
   sidebar: SIDEBAR_BOOKMARKS_ID,
   action: ACTION_ADD,
   itemType: TYPE_FOLDER,
   window: null,
   _itemId: null,
 
-  setup: function() {
+  setup: function(aCallback) {
     // Nothing to do.
+    aCallback();
   },
 
   selectNode: function(tree) {
     // Select Unfiled Bookmarks root.
     var itemId = PlacesUIUtils.leftPaneQueries["UnfiledBookmarks"];
     tree.selectItems([itemId]);
     this.selectedNode = tree.selectedNode;
   },
@@ -318,25 +311,26 @@ gTests.push({
   desc: "Bug 476020 - Pressing Esc while having the tag autocomplete open closes the bookmarks panel",
   sidebar: SIDEBAR_BOOKMARKS_ID,
   action: ACTION_EDIT,
   itemType: null,
   window: null,
   _itemId: null,
   _cleanShutdown: false,
 
-  setup: function() {
+  setup: function(aCallback) {
     // Add a bookmark in unsorted bookmarks folder.
     this._itemId = add_bookmark(PlacesUtils._uri(TEST_URL));
     ok(this._itemId > 0, "Correctly added a bookmark");
     // Add a tag to this bookmark.
     PlacesUtils.tagging.tagURI(PlacesUtils._uri(TEST_URL),
                                ["testTag"]);
     var tags = PlacesUtils.tagging.getTagsForURI(PlacesUtils._uri(TEST_URL));
     is(tags[0], "testTag", "Correctly added a tag");
+    aCallback();
   },
 
   selectNode: function(tree) {
     tree.selectItems([this._itemId]);
     is(tree.selectedNode.itemId, this._itemId, "Bookmark has been selected");
   },
 
   run: function() {
@@ -414,22 +408,23 @@ gTests.push({
 
 gTests.push({
   desc: " Bug 491269 - Test that editing folder name in bookmarks properties dialog does not accept the dialog",
   sidebar: SIDEBAR_HISTORY_ID,
   action: ACTION_ADD,
   historyView: SIDEBAR_HISTORY_BYLASTVISITED_VIEW,
   window: null,
 
-  setup: function() {
+  setup: function(aCallback) {
     // Add a visit.
-    add_visit(PlacesUtils._uri(TEST_URL), Date.now() * 1000);
-    // Sanity check.
-    var gh = PlacesUtils.history.QueryInterface(Ci.nsIGlobalHistory2);
-    ok(gh.isVisited(PlacesUtils._uri(TEST_URL)), TEST_URL + " is a visited url.");
+    addVisits(
+      {uri: PlacesUtils._uri(TEST_URL),
+        transition: PlacesUtils.history.TRANSITION_TYPED},
+      window,
+      aCallback);
   },
 
   selectNode: function(tree) {
     var visitNode = tree.view.nodeForTreeIndex(0);
     tree.selectNode(visitNode);
     is(tree.selectedNode.uri, TEST_URL, "The correct visit has been selected");
     is(tree.selectedNode.itemId, -1, "The selected node is not bookmarked");
   },
@@ -506,18 +501,19 @@ function runNextTest() {
     waitForAsyncUpdates(runNextTest);
     return;
   }
 
   if (gTests.length > 0) {
     // Goto next tests.
     gCurrentTest = gTests.shift();
     info("Start of test: " + gCurrentTest.desc);
-    gCurrentTest.setup();
-    execute_test_in_sidebar();
+    gCurrentTest.setup(function() {
+      execute_test_in_sidebar();
+    });
   }
   else {
     // Finished all tests.
     finish();
   }
 }
 
 /**
--- a/browser/components/places/tests/browser/browser_forgetthissite_single.js
+++ b/browser/components/places/tests/browser/browser_forgetthissite_single.js
@@ -6,21 +6,28 @@
 // selections.
 function test() {
   // initialization
   waitForExplicitFinish();
 
   // Add a history entry.
   let TEST_URIs = ["http://www.mozilla.org/test1", "http://www.mozilla.org/test2"];
   ok(PlacesUtils, "checking PlacesUtils, running in chrome context?");
-  let history = PlacesUtils.history;
+  let places = [];
   TEST_URIs.forEach(function(TEST_URI) {
-    let visitId = history.addVisit(PlacesUtils._uri(TEST_URI), Date.now() * 1000,
-                                   null, PlacesUtils.history.TRANSITION_TYPED, false, 0);
-    ok(visitId > 0, TEST_URI + " successfully marked visited");
+    places.push({uri: PlacesUtils._uri(TEST_URI),
+                 transition: PlacesUtils.history.TRANSITION_TYPED});
+  });
+  addVisits(places, window, function() {
+    testForgetThisSiteVisibility(1, function() {
+      testForgetThisSiteVisibility(2, function() {
+        // Cleanup
+        waitForClearHistory(finish);
+      });
+    });
   });
 
   function testForgetThisSiteVisibility(selectionCount, funcNext) {
     openLibrary(function (organizer) {
           // Select History in the left pane.
           organizer.PlacesOrganizer.selectLeftPaneQuery('History');
           let PO = organizer.PlacesOrganizer;
           let histContainer = PO._places.selectedNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
@@ -57,16 +64,10 @@ function test() {
           // Get cell coordinates
           var x = {}, y = {}, width = {}, height = {};
           tree.treeBoxObject.getCoordsForCellItem(0, tree.columns[0], "text",
                                                   x, y, width, height);
           // Initiate a context menu for the selected cell
           EventUtils.synthesizeMouse(tree.body, x.value + width.value / 2, y.value + height.value / 2, {type: "contextmenu"}, organizer);
     });
   }
+}
 
-  testForgetThisSiteVisibility(1, function() {
-    testForgetThisSiteVisibility(2, function() {
-      // Cleanup
-      waitForClearHistory(finish);
-    });
-  });
-}
--- a/browser/components/places/tests/browser/browser_history_sidebar_search.js
+++ b/browser/components/places/tests/browser/browser_history_sidebar_search.js
@@ -12,26 +12,16 @@ var bh = hs.QueryInterface(Ci.nsIBrowser
 var ios = Cc["@mozilla.org/network/io-service;1"].
           getService(Ci.nsIIOService);
 function uri(spec) {
   return ios.newURI(spec, null, null);
 }
 
 var sidebar = document.getElementById("sidebar");
 
-function add_visit(aURI, aDate) {
-  var visitId = hs.addVisit(aURI,
-                            aDate,
-                            null, // no referrer
-                            hs.TRANSITION_TYPED, // user typed in URL bar
-                            false, // not redirect
-                            0);
-  return visitId;
-}
-
 // Visited pages listed by descending visit date.
 var pages = [
   "http://sidebar.mozilla.org/a",
   "http://sidebar.mozilla.org/b",
   "http://sidebar.mozilla.org/c",
   "http://www.mozilla.org/d",
 ];
 // Number of pages that will be filtered out by the search.
@@ -42,19 +32,25 @@ function test() {
 
   // Cleanup.
   waitForClearHistory(continue_test);
 }
 
 function continue_test() {
   // Add some visited page.
   var time = Date.now();
-  for (var i = 0; i < pages.length; i++) {
-    add_visit(uri(pages[i]), (time - i) * 1000);
+  var pagesLength = pages.length;
+  var places = [];
+  for (var i = 0; i < pagesLength; i++) {
+    places.push({uri: uri(pages[i]), visitDate: (time - i) * 1000,
+                 transition: hs.TRANSITION_TYPED});
   }
+  addVisits(places, window, function() {
+    toggleSidebar("viewHistorySidebar", true);
+  });
 
   sidebar.addEventListener("load", function() {
     sidebar.removeEventListener("load", arguments.callee, true);
     executeSoon(function() {
       // Set "by last visited" in the sidebar (sort by visit date descendind).
       sidebar.contentDocument.getElementById("bylastvisited").doCommand();
       check_sidebar_tree_order(pages.length);
       var searchBox = sidebar.contentDocument.getElementById("search-box");
@@ -66,17 +62,16 @@ function continue_test() {
       searchBox.doCommand();
       check_sidebar_tree_order(pages.length);
 
       // Cleanup.
       toggleSidebar("viewHistorySidebar", false);
       waitForClearHistory(finish);
     });
   }, true);
-  toggleSidebar("viewHistorySidebar", true);
 }
 
 function check_sidebar_tree_order(aExpectedRows) {
   var tree = sidebar.contentDocument.getElementById("historyTree");
   var treeView = tree.view;
   var rc = treeView.rowCount;
   var columns = tree.columns;
   is(columns.count, 1, "There should be only 1 column in the sidebar");
--- a/browser/components/places/tests/browser/browser_library_infoBox.js
+++ b/browser/components/places/tests/browser/browser_library_infoBox.js
@@ -16,111 +16,115 @@ var gLibrary;
 
 gTests.push({
   desc: "Bug 430148 - Remove or hide the more/less button in details pane...",
   run: function() {
     var PO = gLibrary.PlacesOrganizer;
     let ContentTree = gLibrary.ContentTree;
     var infoBoxExpanderWrapper = getAndCheckElmtById("infoBoxExpanderWrapper");
 
-    // add a visit to browser history
-    var bhist = PlacesUtils.history.QueryInterface(Ci.nsIBrowserHistory);
-    PlacesUtils.history.addVisit(PlacesUtils._uri(TEST_URI), Date.now() * 1000,
-                                 null, PlacesUtils.history.TRANSITION_TYPED,
-                                 false, 0);
-    ok(bhist.isVisited(PlacesUtils._uri(TEST_URI)), "Visit has been added.");
+    function addVisitsCallback() {
+      var bhist = PlacesUtils.history.QueryInterface(Ci.nsIBrowserHistory);
+      ok(bhist.isVisited(PlacesUtils._uri(TEST_URI)), "Visit has been added.");
 
-    // open all bookmarks node
-    PO.selectLeftPaneQuery("AllBookmarks");
-    isnot(PO._places.selectedNode, null,
-          "Correctly selected all bookmarks node.");
-    checkInfoBoxSelected(PO);
-    ok(infoBoxExpanderWrapper.hidden,
-       "Expander button is hidden for all bookmarks node.");
-    checkAddInfoFieldsCollapsed(PO);
+      // open all bookmarks node
+      PO.selectLeftPaneQuery("AllBookmarks");
+      isnot(PO._places.selectedNode, null,
+            "Correctly selected all bookmarks node.");
+      checkInfoBoxSelected(PO);
+      ok(infoBoxExpanderWrapper.hidden,
+         "Expander button is hidden for all bookmarks node.");
+      checkAddInfoFieldsCollapsed(PO);
+
+      // open history node
+      PO.selectLeftPaneQuery("History");
+      isnot(PO._places.selectedNode, null, "Correctly selected history node.");
+      checkInfoBoxSelected(PO);
+      ok(infoBoxExpanderWrapper.hidden,
+         "Expander button is hidden for history node.");
+      checkAddInfoFieldsCollapsed(PO);
 
-    // open history node
-    PO.selectLeftPaneQuery("History");
-    isnot(PO._places.selectedNode, null, "Correctly selected history node.");
-    checkInfoBoxSelected(PO);
-    ok(infoBoxExpanderWrapper.hidden,
-       "Expander button is hidden for history node.");
-    checkAddInfoFieldsCollapsed(PO);
+      // open history child node
+      var historyNode = PO._places.selectedNode.
+                        QueryInterface(Ci.nsINavHistoryContainerResultNode);
+      historyNode.containerOpen = true;
+      var childNode = historyNode.getChild(0);
+      isnot(childNode, null, "History node first child is not null.");
+      PO._places.selectNode(childNode);
+      checkInfoBoxSelected(PO);
+      ok(infoBoxExpanderWrapper.hidden,
+         "Expander button is hidden for history child node.");
+      checkAddInfoFieldsCollapsed(PO);
 
-    // open history child node
-    var historyNode = PO._places.selectedNode.
-                      QueryInterface(Ci.nsINavHistoryContainerResultNode);
-    historyNode.containerOpen = true;
-    var childNode = historyNode.getChild(0);
-    isnot(childNode, null, "History node first child is not null.");
-    PO._places.selectNode(childNode);
-    checkInfoBoxSelected(PO);
-    ok(infoBoxExpanderWrapper.hidden,
-       "Expander button is hidden for history child node.");
-    checkAddInfoFieldsCollapsed(PO);
+      // open history item
+      var view = ContentTree.view.treeBoxObject.view;
+      ok(view.rowCount > 0, "History item exists.");
+      view.selection.select(0);
+      ok(infoBoxExpanderWrapper.hidden,
+         "Expander button is hidden for history item.");
+      checkAddInfoFieldsCollapsed(PO);
 
-    // open history item
-    var view = ContentTree.view.treeBoxObject.view;
-    ok(view.rowCount > 0, "History item exists.");
-    view.selection.select(0);
-    ok(infoBoxExpanderWrapper.hidden,
-       "Expander button is hidden for history item.");
-    checkAddInfoFieldsCollapsed(PO);
-
-    historyNode.containerOpen = false;
+      historyNode.containerOpen = false;
 
-    // open bookmarks menu node
-    PO.selectLeftPaneQuery("BookmarksMenu");
-    isnot(PO._places.selectedNode, null,
-          "Correctly selected bookmarks menu node.");
-    checkInfoBoxSelected(PO);
-    ok(infoBoxExpanderWrapper.hidden,
-       "Expander button is hidden for bookmarks menu node.");
-    checkAddInfoFieldsCollapsed(PO);
+      // open bookmarks menu node
+      PO.selectLeftPaneQuery("BookmarksMenu");
+      isnot(PO._places.selectedNode, null,
+            "Correctly selected bookmarks menu node.");
+      checkInfoBoxSelected(PO);
+      ok(infoBoxExpanderWrapper.hidden,
+         "Expander button is hidden for bookmarks menu node.");
+      checkAddInfoFieldsCollapsed(PO);
+
+      // open recently bookmarked node
+      var menuNode = PO._places.selectedNode.
+                     QueryInterface(Ci.nsINavHistoryContainerResultNode);
+      menuNode.containerOpen = true;
+      childNode = menuNode.getChild(0);
+      isnot(childNode, null, "Bookmarks menu child node exists.");
+      var recentlyBookmarkedTitle = PlacesUIUtils.
+                                    getString("recentlyBookmarkedTitle");
+      isnot(recentlyBookmarkedTitle, null,
+            "Correctly got the recently bookmarked title locale string.");
+      is(childNode.title, recentlyBookmarkedTitle,
+         "Correctly selected recently bookmarked node.");
+      PO._places.selectNode(childNode);
+      checkInfoBoxSelected(PO);
+      ok(!infoBoxExpanderWrapper.hidden,
+         "Expander button is not hidden for recently bookmarked node.");
+      checkAddInfoFieldsNotCollapsed(PO);
 
-    // open recently bookmarked node
-    var menuNode = PO._places.selectedNode.
-                   QueryInterface(Ci.nsINavHistoryContainerResultNode);
-    menuNode.containerOpen = true;
-    childNode = menuNode.getChild(0);
-    isnot(childNode, null, "Bookmarks menu child node exists.");
-    var recentlyBookmarkedTitle = PlacesUIUtils.
-                                  getString("recentlyBookmarkedTitle");
-    isnot(recentlyBookmarkedTitle, null,
-          "Correctly got the recently bookmarked title locale string.");
-    is(childNode.title, recentlyBookmarkedTitle,
-       "Correctly selected recently bookmarked node.");
-    PO._places.selectNode(childNode);
-    checkInfoBoxSelected(PO);
-    ok(!infoBoxExpanderWrapper.hidden,
-       "Expander button is not hidden for recently bookmarked node.");
-    checkAddInfoFieldsNotCollapsed(PO);
+      // open first bookmark
+      var view = ContentTree.view.treeBoxObject.view;
+      ok(view.rowCount > 0, "Bookmark item exists.");
+      view.selection.select(0);
+      checkInfoBoxSelected(PO);
+      ok(!infoBoxExpanderWrapper.hidden,
+         "Expander button is not hidden for bookmark item.");
+      checkAddInfoFieldsNotCollapsed(PO);
+      checkAddInfoFields(PO, "bookmark item");
 
-    // open first bookmark
-    var view = ContentTree.view.treeBoxObject.view;
-    ok(view.rowCount > 0, "Bookmark item exists.");
-    view.selection.select(0);
-    checkInfoBoxSelected(PO);
-    ok(!infoBoxExpanderWrapper.hidden,
-       "Expander button is not hidden for bookmark item.");
-    checkAddInfoFieldsNotCollapsed(PO);
-    checkAddInfoFields(PO, "bookmark item");
+      // make sure additional fields are still hidden in second bookmark item
+      ok(view.rowCount > 1, "Second bookmark item exists.");
+      view.selection.select(1);
+      checkInfoBoxSelected(PO);
+      ok(!infoBoxExpanderWrapper.hidden,
+         "Expander button is not hidden for second bookmark item.");
+      checkAddInfoFieldsNotCollapsed(PO);
+      checkAddInfoFields(PO, "second bookmark item");
 
-    // make sure additional fields are still hidden in second bookmark item
-    ok(view.rowCount > 1, "Second bookmark item exists.");
-    view.selection.select(1);
-    checkInfoBoxSelected(PO);
-    ok(!infoBoxExpanderWrapper.hidden,
-       "Expander button is not hidden for second bookmark item.");
-    checkAddInfoFieldsNotCollapsed(PO);
-    checkAddInfoFields(PO, "second bookmark item");
+      menuNode.containerOpen = false;
 
-    menuNode.containerOpen = false;
-
-    waitForClearHistory(nextTest);
+      waitForClearHistory(nextTest);
+    }
+    // add a visit to browser history
+    addVisits(
+      { uri: PlacesUtils._uri(TEST_URI), visitDate: Date.now() * 1000,
+        transition: PlacesUtils.history.TRANSITION_TYPED },
+      window,
+      addVisitsCallback);
   }
 });
 
 function checkInfoBoxSelected(PO) {
   is(getAndCheckElmtById("detailsDeck").selectedIndex, 1,
      "Selected element in detailsDeck is infoBox.");
 }
 
--- a/browser/components/places/tests/browser/browser_library_left_pane_commands.js
+++ b/browser/components/places/tests/browser/browser_library_left_pane_commands.js
@@ -13,63 +13,67 @@ const TEST_URI = "http://www.mozilla.org
 var gTests = [];
 var gLibrary;
 
 //------------------------------------------------------------------------------
 
 gTests.push({
   desc: "Bug 489351 - Date containers under History in Library cannot be deleted/cut",
   run: function() {
-    var bhist = PlacesUtils.history.QueryInterface(Ci.nsIBrowserHistory);
-    // Add a visit.
-    PlacesUtils.history.addVisit(PlacesUtils._uri(TEST_URI), Date.now() * 1000,
-                                 null, PlacesUtils.history.TRANSITION_TYPED,
-                                 false, 0);
-    ok(bhist.isVisited(PlacesUtils._uri(TEST_URI)), "Visit has been added");
+    function addVisitsCallback() {
+      var bhist = PlacesUtils.history.QueryInterface(Ci.nsIBrowserHistory);
+      // Add a visit.
+      ok(bhist.isVisited(PlacesUtils._uri(TEST_URI)), "Visit has been added");
+
+      // Select and open the left pane "History" query.
+      var PO = gLibrary.PlacesOrganizer;
+      PO.selectLeftPaneQuery('History');
+      isnot(PO._places.selectedNode, null, "We correctly selected History");
 
-    // Select and open the left pane "History" query.
-    var PO = gLibrary.PlacesOrganizer;
-    PO.selectLeftPaneQuery('History');
-    isnot(PO._places.selectedNode, null, "We correctly selected History");
+      // Check that both delete and cut commands are disabled.
+      ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
+         "Cut command is disabled");
+      ok(!PO._places.controller.isCommandEnabled("cmd_delete"),
+         "Delete command is disabled");
+      var historyNode = PO._places.selectedNode
+                          .QueryInterface(Ci.nsINavHistoryContainerResultNode);
+      historyNode.containerOpen = true;
 
-    // Check that both delete and cut commands are disabled.
-    ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
-       "Cut command is disabled");
-    ok(!PO._places.controller.isCommandEnabled("cmd_delete"),
-       "Delete command is disabled");
-    var historyNode = PO._places.selectedNode
-                        .QueryInterface(Ci.nsINavHistoryContainerResultNode);
-    historyNode.containerOpen = true;
+      // Check that we have a child container. It is "Today" container.
+      is(historyNode.childCount, 1, "History node has one child");
+      var todayNode = historyNode.getChild(0);
+      var todayNodeExpectedTitle = PlacesUtils.getString("finduri-AgeInDays-is-0");
+      is(todayNode.title, todayNodeExpectedTitle,
+         "History child is the expected container");
 
-    // Check that we have a child container. It is "Today" container.
-    is(historyNode.childCount, 1, "History node has one child");
-    var todayNode = historyNode.getChild(0);
-    var todayNodeExpectedTitle = PlacesUtils.getString("finduri-AgeInDays-is-0");
-    is(todayNode.title, todayNodeExpectedTitle,
-       "History child is the expected container");
+      // Select "Today" container.
+      PO._places.selectNode(todayNode);
+      is(PO._places.selectedNode, todayNode,
+         "We correctly selected Today container");
+      // Check that delete command is enabled but cut command is disabled.
+      ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
+         "Cut command is disabled");
+      ok(PO._places.controller.isCommandEnabled("cmd_delete"),
+         "Delete command is enabled");
 
-    // Select "Today" container.
-    PO._places.selectNode(todayNode);
-    is(PO._places.selectedNode, todayNode,
-       "We correctly selected Today container");
-    // Check that delete command is enabled but cut command is disabled.
-    ok(!PO._places.controller.isCommandEnabled("cmd_cut"),
-       "Cut command is disabled");
-    ok(PO._places.controller.isCommandEnabled("cmd_delete"),
-       "Delete command is enabled");
+      // Execute the delete command and check visit has been removed.
+      PO._places.controller.doCommand("cmd_delete");
+      ok(!bhist.isVisited(PlacesUtils._uri(TEST_URI)), "Visit has been removed");
+
+      // Test live update of "History" query.
+      is(historyNode.childCount, 0, "History node has no more children");
 
-    // Execute the delete command and check visit has been removed.
-    PO._places.controller.doCommand("cmd_delete");
-    ok(!bhist.isVisited(PlacesUtils._uri(TEST_URI)), "Visit has been removed");
-
-    // Test live update of "History" query.
-    is(historyNode.childCount, 0, "History node has no more children");
-
-    historyNode.containerOpen = false;
-    nextTest();
+      historyNode.containerOpen = false;
+      nextTest();
+    }
+    addVisits(
+      {uri: PlacesUtils._uri(TEST_URI), visitDate: Date.now() * 1000,
+        transition: PlacesUtils.history.TRANSITION_TYPED},
+      window,
+      addVisitsCallback);
   }
 });
 
 //------------------------------------------------------------------------------
 
 gTests.push({
   desc: "Bug 490156 - Can't delete smart bookmark containers",
   run: function() {
--- a/browser/components/places/tests/browser/browser_library_panel_leak.js
+++ b/browser/components/places/tests/browser/browser_library_panel_leak.js
@@ -40,14 +40,16 @@ function test() {
     organizer.close();
     // Clean up history.
     waitForClearHistory(finish);
   }
 
   waitForExplicitFinish();
   // Add an history entry.
   ok(PlacesUtils, "checking PlacesUtils, running in chrome context?");
-  PlacesUtils.history.addVisit(PlacesUtils._uri(TEST_URI), Date.now() * 1000,
-                               null, PlacesUtils.history.TRANSITION_TYPED,
-                               false, 0);
-
-  openLibrary(onLibraryReady);
+  addVisits(
+    {uri: PlacesUtils._uri(TEST_URI), visitDate: Date.now() * 1000,
+      transition: PlacesUtils.history.TRANSITION_TYPED},
+    window,
+    function() {
+      openLibrary(onLibraryReady);
+    });
 }
--- a/browser/components/places/tests/browser/browser_library_search.js
+++ b/browser/components/places/tests/browser/browser_library_search.js
@@ -164,22 +164,24 @@ function onLibraryAvailable() {
 
 function test() {
   waitForExplicitFinish();
 
   // Sanity:
   ok(PlacesUtils, "PlacesUtils in context");
 
   // Add visits, a bookmark and a tag.
-  PlacesUtils.history.addVisit(PlacesUtils._uri(TEST_URL),
-                               Date.now() * 1000, null,
-                               PlacesUtils.history.TRANSITION_TYPED, false, 0);
-  PlacesUtils.history.addVisit(PlacesUtils._uri(TEST_DOWNLOAD_URL),
-                               Date.now() * 1000, null,
-                               PlacesUtils.history.TRANSITION_DOWNLOAD, false, 0);
-  PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
-                                       PlacesUtils._uri(TEST_URL),
-                                       PlacesUtils.bookmarks.DEFAULT_INDEX,
-                                       "dummy");
-  PlacesUtils.tagging.tagURI(PlacesUtils._uri(TEST_URL), ["dummyTag"]);
+  addVisits(
+    [{ uri: PlacesUtils._uri(TEST_URL), visitDate: Date.now() * 1000,
+       transition: PlacesUtils.history.TRANSITION_TYPED },
+     { uri: PlacesUtils._uri(TEST_DOWNLOAD_URL), visitDate: Date.now() * 1000,
+       transition: PlacesUtils.history.TRANSITION_DOWNLOAD }],
+    window,
+    function() {
+      PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
+                                           PlacesUtils._uri(TEST_URL),
+                                           PlacesUtils.bookmarks.DEFAULT_INDEX,
+                                           "dummy");
+      PlacesUtils.tagging.tagURI(PlacesUtils._uri(TEST_URL), ["dummyTag"]);
 
-  gLibrary = openLibrary(onLibraryAvailable);
+      gLibrary = openLibrary(onLibraryAvailable);
+    });
 }
--- a/browser/components/places/tests/browser/browser_sidebarpanels_click.js
+++ b/browser/components/places/tests/browser/browser_sidebarpanels_click.js
@@ -22,45 +22,47 @@ function test() {
   }
 
   let sidebar = document.getElementById("sidebar");
   let tests = [];
   let currentTest;
 
   tests.push({
     _itemID: null,
-    init: function() {
+    init: function(aCallback) {
       // Add a bookmark to the Unfiled Bookmarks folder.
       this._itemID = PlacesUtils.bookmarks.insertBookmark(
         PlacesUtils.unfiledBookmarksFolderId, PlacesUtils._uri(TEST_URL),
         PlacesUtils.bookmarks.DEFAULT_INDEX, "test"
       );
+      aCallback();
     },
     prepare: function() {
     },
     selectNode: function(tree) {
       tree.selectItems([this._itemID]);
     },
     cleanup: function(aCallback) {
       PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
       executeSoon(aCallback);
     },
     sidebarName: BOOKMARKS_SIDEBAR_ID,
     treeName: BOOKMARKS_SIDEBAR_TREE_ID,
     desc: "Bookmarks sidebar test"
   });
 
   tests.push({
-    init: function() {
+    init: function(aCallback) {
       // Add a history entry.
       let uri = PlacesUtils._uri(TEST_URL);
-      PlacesUtils.history.addVisit(uri, Date.now() * 1000, null,
-                                   PlacesUtils.history.TRANSITION_TYPED,
-                                   false, 0);
-      ok(PlacesUtils.ghistory2.isVisited(uri), "Item is visited");
+      addVisits(
+        { uri: uri, visitDate: Date.now() * 1000,
+          transition: PlacesUtils.history.TRANSITION_TYPED },
+        window,
+        aCallback);
     },
     prepare: function() {
       sidebar.contentDocument.getElementById("byvisited").doCommand();
     },
     selectNode: function(tree) {
       tree.selectNode(tree.view.nodeForTreeIndex(0));
       is(tree.selectedNode.uri, TEST_URL, "The correct visit has been selected");
       is(tree.selectedNode.itemId, -1, "The selected node is not bookmarked");
@@ -69,17 +71,19 @@ function test() {
       waitForClearHistory(aCallback);
     },
     sidebarName: HISTORY_SIDEBAR_ID,
     treeName: HISTORY_SIDEBAR_TREE_ID,
     desc: "History sidebar test"
   });
 
   function testPlacesPanel(preFunc, postFunc) {
-    currentTest.init();
+    currentTest.init(function() {
+      toggleSidebar(currentTest.sidebarName);
+    });
 
     sidebar.addEventListener("load", function() {
       sidebar.removeEventListener("load", arguments.callee, true);
       executeSoon(function() {
         currentTest.prepare();
 
         if (preFunc)
           preFunc();
@@ -108,17 +112,16 @@ function test() {
         // Now, wait for the observer to catch the alert dialog.
         // If something goes wrong, the test will time out at this stage.
         // Note that for the history sidebar, the URL itself is not opened,
         // and Places will show the load-js-data-url-error prompt as an alert
         // box, which means that the click actually worked, so it's good enough
         // for the purpose of this test.
       });
     }, true);
-    toggleSidebar(currentTest.sidebarName);
   }
 
   function synthesizeClickOnSelectedTreeCell(aTree) {
     let tbo = aTree.treeBoxObject;
     is(tbo.view.selection.count, 1,
        "The test node should be successfully selected");
     // Get selection rowID.
     let min = {}, max = {};
--- a/browser/components/places/tests/browser/head.js
+++ b/browser/components/places/tests/browser/head.js
@@ -77,8 +77,71 @@ function waitForAsyncUpdates(aCallback, 
     handleError: function() {},
     handleCompletion: function(aReason)
     {
       aCallback.apply(scope, args);
     }
   });
   commit.finalize();
 }
+
+/**
+ * Asynchronously adds visits to a page, invoking a callback function when done.
+ *
+ * @param aPlaceInfo
+ *        Can be an nsIURI, in such a case a single LINK visit will be added.
+ *        Otherwise can be an object describing the visit to add, or an array
+ *        of these objects:
+ *          { uri: nsIURI of the page,
+ *            transition: one of the TRANSITION_* from nsINavHistoryService,
+ *            [optional] title: title of the page,
+ *            [optional] visitDate: visit date in microseconds from the epoch
+ *            [optional] referrer: nsIURI of the referrer for this visit
+ *          }
+ * @param [optional] aCallback
+ *        Function to be invoked on completion.
+ * @param [optional] aStack
+ *        The stack frame used to report errors.
+ */
+function addVisits(aPlaceInfo, aWindow, aCallback, aStack) {
+  let stack = aStack || Components.stack.caller;
+  let places = [];
+  if (aPlaceInfo instanceof Ci.nsIURI) {
+    places.push({ uri: aPlaceInfo });
+  }
+  else if (Array.isArray(aPlaceInfo)) {
+    places = places.concat(aPlaceInfo);
+  } else {
+    places.push(aPlaceInfo)
+  }
+
+  // Create mozIVisitInfo for each entry.
+  let now = Date.now();
+  for (let i = 0; i < places.length; i++) {
+    if (!places[i].title) {
+      places[i].title = "test visit for " + places[i].uri.spec;
+    }
+    places[i].visits = [{
+      transitionType: places[i].transition === undefined ? Ci.nsINavHistoryService.TRANSITION_LINK
+                                                         : places[i].transition,
+      visitDate: places[i].visitDate || (now++) * 1000,
+      referrerURI: places[i].referrer
+    }];
+  }
+
+  aWindow.PlacesUtils.asyncHistory.updatePlaces(
+    places,
+    {
+      handleError: function AAV_handleError() {
+        throw("Unexpected error in adding visit.");
+      },
+      handleResult: function () {},
+      handleCompletion: function UP_handleCompletion() {
+        if (aCallback)
+          aCallback();
+      }
+    }
+  );
+}
+
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+  "resource://gre/modules/commonjs/promise/core.js");
+
--- a/browser/components/places/tests/chrome/Makefile.in
+++ b/browser/components/places/tests/chrome/Makefile.in
@@ -6,16 +6,17 @@ DEPTH          = @DEPTH@
 topsrcdir      = @top_srcdir@
 srcdir         = @srcdir@
 VPATH          = @srcdir@
 relativesrcdir = @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MOCHITEST_CHROME_FILES = \
+	head.js \
 	test_treeview_date.xul \
 	test_bug485100-change-case-loses-tag.xul \
 	test_bug427633_no_newfolder_if_noip.xul \
 	test_0_multiple_left_pane.xul \
 	test_0_bug510634.xul \
 	test_bug549192.xul \
 	test_bug549491.xul \
 	test_editBookmarkOverlay_tags_liveUpdate.xul \
new file mode 100644
--- /dev/null
+++ b/browser/components/places/tests/chrome/head.js
@@ -0,0 +1,55 @@
+/**
+ * Asynchronously adds visits to a page, invoking a callback function when done.
+ *
+ * @param aPlaceInfo
+ *        Can be an nsIURI, in such a case a single LINK visit will be added.
+ *        Otherwise can be an object describing the visit to add, or an array
+ *        of these objects:
+ *          { uri: nsIURI of the page,
+ *            transition: one of the TRANSITION_* from nsINavHistoryService,
+ *            [optional] title: title of the page,
+ *            [optional] visitDate: visit date in microseconds from the epoch
+ *            [optional] referrer: nsIURI of the referrer for this visit
+ *          }
+ * @param [optional] aCallback
+ *        Function to be invoked on completion.
+ */
+function addVisits(aPlaceInfo, aCallback) {
+  let places = [];
+  if (aPlaceInfo instanceof Ci.nsIURI) {
+    places.push({ uri: aPlaceInfo });
+  }
+  else if (Array.isArray(aPlaceInfo)) {
+    places = places.concat(aPlaceInfo);
+  } else {
+    places.push(aPlaceInfo)
+  }
+
+  // Create mozIVisitInfo for each entry.
+  let now = Date.now();
+  for (let i = 0; i < places.length; i++) {
+    if (!places[i].title) {
+      places[i].title = "test visit for " + places[i].uri.spec;
+    }
+    places[i].visits = [{
+      transitionType: places[i].transition === undefined ? PlacesUtils.history.TRANSITION_LINK
+                                                         : places[i].transition,
+      visitDate: places[i].visitDate || (now++) * 1000,
+      referrerURI: places[i].referrer
+    }];
+  }
+
+  PlacesUtils.asyncHistory.updatePlaces(
+    places,
+    {
+      handleError: function AAV_handleError() {
+        throw("Unexpected error in adding visit.");
+      },
+      handleResult: function () {},
+      handleCompletion: function UP_handleCompletion() {
+        if (aCallback)
+          aCallback();
+      }
+    }
+  );
+}
--- a/browser/components/places/tests/chrome/test_bug549192.xul
+++ b/browser/components/places/tests/chrome/test_bug549192.xul
@@ -14,16 +14,17 @@
 <?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="549192:  History view not updated after deleting entry"
         onload="runTest();">
 
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+  <script type="application/javascript" src="head.js" />
 
   <body xmlns="http://www.w3.org/1999/xhtml" />
 
   <tree id="tree"
         type="places"
         flatList="true"
         flex="1">
     <treecols>
@@ -46,56 +47,56 @@
       // The mochitest page is added to history.
       waitForClearHistory(continue_test);
     }
 
     function continue_test() {
       // Add some visits.
       let vtime = Date.now() * 1000;
       const ttype = PlacesUtils.history.TRANSITION_TYPED;
-      PlacesUtils.history
-                 .addVisit(Services.io.newURI("http://example.tld/", null, null),
-                           vtime, null, ttype, false, 0);
-      PlacesUtils.history
-                 .addVisit(Services.io.newURI("http://example2.tld/", null, null),
-                           vtime++, null, ttype, false, 0);
-      PlacesUtils.history
-                 .addVisit(Services.io.newURI("http://exmample3.tld/", null, null),
-                           vtime++, null, ttype, false, 0);
 
-      // Make a history query.
-      let query = PlacesUtils.history.getNewQuery();
-      let opts = PlacesUtils.history.getNewQueryOptions();
-      let queryURI = PlacesUtils.history.queriesToQueryString([query], 1, opts);
+      addVisits(
+        [{ uri: Services.io.newURI("http://example.tld/", null, null),
+           visitDate: vtime, transition: ttype },
+         { uri: Services.io.newURI("http://example2.tld/", null, null),
+           visitDate: vtime++, transition: ttype },
+         { uri: Services.io.newURI("http://example3.tld/", null, null),
+           visitDate: vtime++, transition: ttype }],
+         function() {
+           // Make a history query.
+           let query = PlacesUtils.history.getNewQuery();
+           let opts = PlacesUtils.history.getNewQueryOptions();
+           let queryURI = PlacesUtils.history.queriesToQueryString([query], 1, opts);
 
-      // Setup the places tree contents.
-      var tree = document.getElementById("tree");
-      tree.place = queryURI;
+           // Setup the places tree contents.
+           var tree = document.getElementById("tree");
+           tree.place = queryURI;
 
-      // loop through the rows and check formatting
-      let treeView = tree.view;
-      for (let i = 0; i < rc; i++) {
-        selection.select(rc);
-        let node = tree.selectedNode;
-        ok(true, "found " + node.title);
-      }
-      let rc = treeView.rowCount;
-      is(rc, 3, "Rows found.");
-      let selection = treeView.selection;
-      for (let i = 0; i < rc; i++) {
-        selection.select(0);
-        let node = tree.selectedNode;
-        tree.controller.remove("Removing page");
-        ok(treeView.treeIndexForNode(node) == Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE,
-           node.uri + " removed.");
-        ok(treeView.rowCount == rc - i - 1, "Rows count decreased");
-      }
+           // loop through the rows and check formatting
+           let treeView = tree.view;
+           for (let i = 0; i < rc; i++) {
+             selection.select(rc);
+             let node = tree.selectedNode;
+             ok(true, "found " + node.title);
+           }
+           let rc = treeView.rowCount;
+           is(rc, 3, "Rows found.");
+           let selection = treeView.selection;
+           for (let i = 0; i < rc; i++) {
+             selection.select(0);
+             let node = tree.selectedNode;
+             tree.controller.remove("Removing page");
+             ok(treeView.treeIndexForNode(node) == Ci.nsINavHistoryResultTreeViewer.INDEX_INVISIBLE,
+               node.uri + " removed.");
+             ok(treeView.rowCount == rc - i - 1, "Rows count decreased");
+           }
 
-      // Cleanup.
-      waitForClearHistory(SimpleTest.finish);
+           // Cleanup.
+           waitForClearHistory(SimpleTest.finish);
+         });
     }
 
     /**
      * Clears history invoking callback when done.
      */
     function waitForClearHistory(aCallback) {
       const TOPIC_EXPIRATION_FINISHED = "places-expiration-finished";
       let observer = {
--- a/browser/components/places/tests/chrome/test_bug549491.xul
+++ b/browser/components/places/tests/chrome/test_bug549491.xul
@@ -14,16 +14,17 @@
 <?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="549491: 'The root node is never visible' exception when details of the root node are modified "
         onload="runTest();">
 
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+  <script type="application/javascript" src="head.js" />
 
   <body xmlns="http://www.w3.org/1999/xhtml" />
 
   <tree id="tree"
         type="places"
         flatList="true"
         flex="1">
     <treecols>
@@ -46,37 +47,38 @@
     SimpleTest.waitForExplicitFinish();
 
     function runTest() {
       // The mochitest page is added to history.
       waitForClearHistory(continue_test);
     }
 
     function continue_test() {
-      PlacesUtils.history
-                 .addVisit(Services.io.newURI("http://example.tld/", null, null),
-                           Date.now() * 1000, null, PlacesUtils.history.TRANSITION_TYPED, false, 0);
-
-      // Make a history query.
-      let query = PlacesUtils.history.getNewQuery();
-      let opts = PlacesUtils.history.getNewQueryOptions();
-      let queryURI = PlacesUtils.history.queriesToQueryString([query], 1, opts);
+      addVisits(
+        {uri: Services.io.newURI("http://example.tld/", null, null),
+          transition: PlacesUtils.history.TRANSITION_TYPED},
+        function() {
+          // Make a history query.
+          let query = PlacesUtils.history.getNewQuery();
+          let opts = PlacesUtils.history.getNewQueryOptions();
+          let queryURI = PlacesUtils.history.queriesToQueryString([query], 1, opts);
 
-      // Setup the places tree contents.
-      let tree = document.getElementById("tree");
-      tree.place = queryURI;
+          // Setup the places tree contents.
+          let tree = document.getElementById("tree");
+          tree.place = queryURI;
 
-      let rootNode = tree.result.root;
-      let obs = tree.view.QueryInterface(Ci.nsINavHistoryResultObserver);
-      obs.nodeHistoryDetailsChanged(rootNode, rootNode.time, rootNode.accessCount);
-      obs.nodeTitleChanged(rootNode, rootNode.title);
-      ok(true, "No exceptions thrown");
+          let rootNode = tree.result.root;
+          let obs = tree.view.QueryInterface(Ci.nsINavHistoryResultObserver);
+          obs.nodeHistoryDetailsChanged(rootNode, rootNode.time, rootNode.accessCount);
+          obs.nodeTitleChanged(rootNode, rootNode.title);
+          ok(true, "No exceptions thrown");
 
-      // Cleanup.
-      waitForClearHistory(SimpleTest.finish);
+          // Cleanup.
+          waitForClearHistory(SimpleTest.finish);
+        });
     }
 
     /**
      * Clears history invoking callback when done.
      */
     function waitForClearHistory(aCallback) {
       const TOPIC_EXPIRATION_FINISHED = "places-expiration-finished";
       let observer = {
--- a/browser/components/places/tests/chrome/test_treeview_date.xul
+++ b/browser/components/places/tests/chrome/test_treeview_date.xul
@@ -13,16 +13,17 @@
 <?xul-overlay href="chrome://browser/content/places/placesOverlay.xul"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         title="435322: Places tree view's formatting"
         onload="runTest();">
 
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
+  <script type="application/javascript" src="head.js" />
 
   <body xmlns="http://www.w3.org/1999/xhtml" />
 
   <tree id="tree"
         type="places"
         flatList="true"
         flex="1">
     <treecols>
@@ -72,101 +73,102 @@
       }
 
       var midnight = new Date();
       midnight.setHours(0);
       midnight.setMinutes(0);
       midnight.setSeconds(0);
       midnight.setMilliseconds(0);
 
-      // Add a visit 1ms before midnight.
-      hs.addVisit(uri("http://before.midnight.com/"),
-                  (midnight.getTime() - 1) * 1000,
-                  null, hs.TRANSITION_TYPED, false, 0);
-
-      // Add a visit at midnight.
-      hs.addVisit(uri("http://at.midnight.com/"),
-                  (midnight.getTime()) * 1000,
-                  null, hs.TRANSITION_TYPED, false, 0);
-
-      // Add a visit 1ms after midnight.
-      hs.addVisit(uri("http://after.midnight.com/"),
-                  (midnight.getTime() + 1) * 1000,
-                  null, hs.TRANSITION_TYPED, false, 0);
+      function addVisitsCallback() {
+        // add a bookmark to the midnight visit
+        var itemId = bs.insertBookmark(bs.toolbarFolder,
+                                       uri("http://at.midnight.com/"),
+                                       bs.DEFAULT_INDEX,
+                                       "A bookmark at midnight");
 
-      // add a bookmark to the midnight visit
-      var itemId = bs.insertBookmark(bs.toolbarFolder,
-                                     uri("http://at.midnight.com/"),
-                                     bs.DEFAULT_INDEX,
-                                     "A bookmark at midnight");
+        // Make a history query.
+        var query = hs.getNewQuery();
+        var opts = hs.getNewQueryOptions();
+        var queryURI = hs.queriesToQueryString([query], 1, opts);
 
-      // Make a history query.
-      var query = hs.getNewQuery();
-      var opts = hs.getNewQueryOptions();
-      var queryURI = hs.queriesToQueryString([query], 1, opts);
-
-      // Setup the places tree contents.
-      var tree = document.getElementById("tree");
-      tree.place = queryURI;
+        // Setup the places tree contents.
+        var tree = document.getElementById("tree");
+        tree.place = queryURI;
 
-      // loop through the rows and check formatting
-      var treeView = tree.view;
-      var rc = treeView.rowCount;
-      ok(rc >= 3, "Rows found");
-      var columns = tree.columns;
-      ok(columns.count > 0, "Columns found");
-      for (var r = 0; r < rc; r++) {
-        var node = treeView.nodeForTreeIndex(r);
-        ok(node, "Places node found");
-        for (var ci = 0; ci < columns.count; ci++) {
-          var c = columns.getColumnAt(ci);
-          var text = treeView.getCellText(r, c);
-          switch (c.element.getAttribute("anonid")) {
-            case "title":
-              // The title can differ, we did not set any title so we would
-              // expect null, but in such a case the view will generate a title
-              // through PlacesUIUtils.getBestTitle.
-              if (node.title)
-                is(text, node.title, "Title is correct");
-              break;
-            case "url":
-              is(text, node.uri, "Uri is correct");
-              break;
-            case "date":
-              var timeObj = new Date(node.time / 1000);
-              // Default is short date format.
-              var dateFormat = Ci.nsIScriptableDateFormat.dateFormatShort;
-              // For today's visits we don't show date portion.
-              if (node.uri == "http://at.midnight.com/" ||
-                  node.uri == "http://after.midnight.com/")
-                dateFormat = Ci.nsIScriptableDateFormat.dateFormatNone;
-              else if (node.uri == "http://before.midnight.com/")
-                dateFormat = Ci.nsIScriptableDateFormat.dateFormatShort;
-              else {
-                // Avoid to test spurious uris, due to how the test works
-                // a redirecting uri could be put in the tree while we test.
+        // loop through the rows and check formatting
+        var treeView = tree.view;
+        var rc = treeView.rowCount;
+        ok(rc >= 3, "Rows found");
+        var columns = tree.columns;
+        ok(columns.count > 0, "Columns found");
+        for (var r = 0; r < rc; r++) {
+          var node = treeView.nodeForTreeIndex(r);
+          ok(node, "Places node found");
+          for (var ci = 0; ci < columns.count; ci++) {
+            var c = columns.getColumnAt(ci);
+            var text = treeView.getCellText(r, c);
+            switch (c.element.getAttribute("anonid")) {
+              case "title":
+                // The title can differ, we did not set any title so we would
+                // expect null, but in such a case the view will generate a title
+                // through PlacesUIUtils.getBestTitle.
+                if (node.title)
+                  is(text, node.title, "Title is correct");
+                break;
+              case "url":
+                is(text, node.uri, "Uri is correct");
                 break;
-              }
-              var timeStr = ds.FormatDateTime("", dateFormat,
-                    Ci.nsIScriptableDateFormat.timeFormatNoSeconds,
-                    timeObj.getFullYear(), timeObj.getMonth() + 1,
-                    timeObj.getDate(), timeObj.getHours(),
-                    timeObj.getMinutes(), timeObj.getSeconds())
+              case "date":
+                var timeObj = new Date(node.time / 1000);
+                // Default is short date format.
+                var dateFormat = Ci.nsIScriptableDateFormat.dateFormatShort;
+                // For today's visits we don't show date portion.
+                if (node.uri == "http://at.midnight.com/" ||
+                    node.uri == "http://after.midnight.com/")
+                  dateFormat = Ci.nsIScriptableDateFormat.dateFormatNone;
+                else if (node.uri == "http://before.midnight.com/")
+                  dateFormat = Ci.nsIScriptableDateFormat.dateFormatShort;
+                else {
+                  // Avoid to test spurious uris, due to how the test works
+                  // a redirecting uri could be put in the tree while we test.
+                  break;
+                }
+                var timeStr = ds.FormatDateTime("", dateFormat,
+                      Ci.nsIScriptableDateFormat.timeFormatNoSeconds,
+                      timeObj.getFullYear(), timeObj.getMonth() + 1,
+                      timeObj.getDate(), timeObj.getHours(),
+                      timeObj.getMinutes(), timeObj.getSeconds())
 
-              is(text, timeStr, "Date format is correct");
-              break;
-            case "visitCount":
-              is(text, 1, "Visit count is correct");
-              break;         
+                is(text, timeStr, "Date format is correct");
+                break;
+              case "visitCount":
+                is(text, 1, "Visit count is correct");
+                break;
+            }
           }
         }
+        // Cleanup.
+        bs.removeItem(itemId);
+        waitForClearHistory(SimpleTest.finish);
       }
-      // Cleanup.
-      bs.removeItem(itemId);
-      waitForClearHistory(SimpleTest.finish);
+
+      // Add a visit 1ms before midnight, a visit at midnight, and
+      // a visit 1ms after midnight.
+      addVisits(
+        [{uri: uri("http://before.midnight.com/"),
+           visitDate: (midnight.getTime() - 1) * 1000,
+           transition: hs.TRANSITION_TYPED},
+         {uri: uri("http://at.midnight.com/"),
+           visitDate: (midnight.getTime()) * 1000,
+           transition: hs.TRANSITION_TYPED},
+         {uri: uri("http://after.midnight.com/"),
+           visitDate: (midnight.getTime() + 1) * 1000,
+           transition: hs.TRANSITION_TYPED}],
+        addVisitsCallback);
     }
 
     /**
      * Clears history invoking callback when done.
      */
     function waitForClearHistory(aCallback) {
       const TOPIC_EXPIRATION_FINISHED = "places-expiration-finished";
       let observer = {
--- a/browser/components/places/tests/unit/test_clearHistory_shutdown.js
+++ b/browser/components/places/tests/unit/test_clearHistory_shutdown.js
@@ -68,16 +68,20 @@ let notificationsObserver = {
     // Check cache.
     checkCache(URL);
   }
 }
 
 let timeInMicroseconds = Date.now() * 1000;
 
 function run_test() {
+  run_next_test();
+}
+
+add_task(function test_execute() {
   do_test_pending();
 
   print("Initialize browserglue before Places");
 
   // Avoid default bookmarks import.
   let glue = Cc["@mozilla.org/browser/browserglue;1"].
              getService(Ci.nsIObserver);
   glue.observe(null, "initial-migration-will-import-default-bookmarks", null);
@@ -91,24 +95,23 @@ function run_test() {
   Services.prefs.setBoolPref("privacy.clearOnShutdown.formData", true);
   Services.prefs.setBoolPref("privacy.clearOnShutdown.passwords", true);
   Services.prefs.setBoolPref("privacy.clearOnShutdown.sessions", true);
   Services.prefs.setBoolPref("privacy.clearOnShutdown.siteSettings", true);
 
   Services.prefs.setBoolPref("privacy.sanitize.sanitizeOnShutdown", true);
 
   print("Add visits.");
-  URIS.forEach(function(aUrl) {
-    PlacesUtils.history.addVisit(uri(aUrl), timeInMicroseconds++, null,
-                                 PlacesUtils.history.TRANSITION_TYPED,
-                                 false, 0);
-  });
+  for (let aUrl of URIS) {
+    yield promiseAddVisits({uri: uri(aUrl), visitDate: timeInMicroseconds++,
+                            transition: PlacesUtils.history.TRANSITION_TYPED})
+  }
   print("Add cache.");
   storeCache(URL, "testData");
-}
+});
 
 function run_test_continue()
 {
   print("Simulate and wait shutdown.");
   getDistinctNotifications().forEach(
     function (topic)
       Services.obs.addObserver(notificationsObserver, topic, false)
   );
--- a/browser/components/preferences/in-content/privacy.js
+++ b/browser/components/preferences/in-content/privacy.js
@@ -119,16 +119,66 @@ var gPrivacyPane = {
     case "custom":
       selectedIndex = 2;
       break;
     }
     document.getElementById("historyPane").selectedIndex = selectedIndex;
   },
 
   /**
+   * Open up the DNT "learn more" link.
+   */
+  openTrackingInfoSite: function PPP_openTrackingInfoSite()
+  {
+    let thisDocEl = document.documentElement,
+        openerDocEl = window.opener && window.opener.document.documentElement,
+        url = "https://www.mozilla.org/dnt";
+    if (thisDocEl.id == "BrowserPreferences" && !thisDocEl.instantApply)
+      openUILinkIn(url, "window");
+    else
+      openUILinkIn(url, "tab");
+  },
+
+  /**
+   * Update the Tracking preferences based on controls.
+   */
+  setTrackingPrefs: function PPP_setTrackingPrefs()
+  {
+    let dntRadioGroup = document.getElementById("doNotTrackSelection"),
+        dntValuePref = document.getElementById("privacy.donottrackheader.value"),
+        dntEnabledPref = document.getElementById("privacy.donottrackheader.enabled");
+
+    // if the selected radio button says "no preference", set on/off pref to
+    // false and don't change the value pref.
+    if (dntRadioGroup.selectedItem.value == -1) {
+      dntEnabledPref.value = false;
+      return dntValuePref.value;
+    }
+
+    dntEnabledPref.value = true;
+    return dntRadioGroup.selectedItem.value;
+  },
+
+  /**
+   * Obtain the tracking preference value and reflect it in the UI.
+   */
+  getTrackingPrefs: function PPP_getTrackingPrefs()
+  {
+    let dntValuePref = document.getElementById("privacy.donottrackheader.value"),
+        dntEnabledPref = document.getElementById("privacy.donottrackheader.enabled");
+
+    // if DNT is enbaled, select the value from the selected radio
+    // button, otherwise choose the "no preference" radio button
+    if (dntEnabledPref.value)
+      return dntValuePref.value;
+
+    return document.getElementById("dntnopref").value;
+  },
+
+  /**
    * Update the private browsing auto-start pref and the history mode
    * micro-management prefs based on the history mode menulist
    */
   updateHistoryModePrefs: function PPP_updateHistoryModePrefs()
   {
     let pref = document.getElementById("browser.privatebrowsing.autostart");
     switch (document.getElementById("historyMode").value) {
     case "remember":
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -6,16 +6,19 @@
         src="chrome://browser/content/preferences/in-content/privacy.js"/>
 
 <preferences id="privacyPreferences">
 
   <!-- Tracking -->
   <preference id="privacy.donottrackheader.enabled"
               name="privacy.donottrackheader.enabled"
               type="bool"/>
+  <preference id="privacy.donottrackheader.value"
+              name="privacy.donottrackheader.value"
+              type="int"/>
 
   <!-- XXX button prefs -->
   <preference id="pref.privacy.disable_button.cookie_exceptions"
               name="pref.privacy.disable_button.cookie_exceptions"
               type="bool"/>
   <preference id="pref.privacy.disable_button.view_cookies"
               name="pref.privacy.disable_button.view_cookies"
               type="bool"/>
@@ -74,21 +77,30 @@
 <hbox class="heading" data-category="panePrivacy" hidden="true">
   <image class="preference-icon" type="privacy"/>
   <html:h1>&panePrivacy.title;</html:h1>
 </hbox>
 
 <!-- Tracking -->
 <groupbox id="trackingGroup" data-category="panePrivacy" hidden="true">
   <caption label="&tracking.label;"/>
-
-  <checkbox id="privacyDoNotTrackPrefs"
-            label="&doNotTrack.label;"
-            accesskey="&doNotTrack.accesskey;"
-            preference="privacy.donottrackheader.enabled"/>
+  <radiogroup id="doNotTrackSelection" orient="vertical"
+              preference="privacy.donottrackheader.value"
+              onsynctopreference="return gPrivacyPane.setTrackingPrefs()"
+              onsyncfrompreference="return gPrivacyPane.getTrackingPrefs()">
+    <radio id="dntnotrack" value="1" label="&dntTrackingNotOkay.label;"
+            accesskey="&dntTrackingNotOkay.accesskey;" />
+    <radio id="dntdotrack" value="0" label="&dntTrackingOkay.label;"
+            accesskey="&dntTrackingOkay.accesskey;" />
+    <radio id="dntnopref" value="-1" label="&dntTrackingNopref.label;"
+            accesskey="&dntTrackingNopref.accesskey;" />
+  </radiogroup>
+  <label class="text-link" id="doNotTrackInfo"
+          onclick="event.stopPropagation();gPrivacyPane.openTrackingInfoSite();"
+          value="&doNotTrackInfo.label;"/>
 </groupbox>
 
 <!-- History -->
 <groupbox id="historyGroup" data-category="panePrivacy" hidden="true">
   <caption label="&history.label;"/>
   <hbox align="center">
     <label id="historyModeLabel"
            control="historyMode"
--- a/browser/components/preferences/privacy.js
+++ b/browser/components/preferences/privacy.js
@@ -122,16 +122,66 @@ var gPrivacyPane = {
     case "custom":
       selectedIndex = 2;
       break;
     }
     document.getElementById("historyPane").selectedIndex = selectedIndex;
   },
 
   /**
+   * Open up the DNT "learn more" link.
+   */
+  openTrackingInfoSite: function PPP_openTrackingInfoSite()
+  {
+    let thisDocEl = document.documentElement,
+        openerDocEl = window.opener && window.opener.document.documentElement,
+        url = "https://www.mozilla.org/dnt";
+    if (thisDocEl.id == "BrowserPreferences" && !thisDocEl.instantApply)
+      openUILinkIn(url, "window");
+    else
+      openUILinkIn(url, "tab");
+  },
+
+  /**
+   * Update the Tracking preferences based on controls.
+   */
+  setTrackingPrefs: function PPP_setTrackingPrefs()
+  {
+    let dntRadioGroup = document.getElementById("doNotTrackSelection"),
+        dntValuePref = document.getElementById("privacy.donottrackheader.value"),
+        dntEnabledPref = document.getElementById("privacy.donottrackheader.enabled");
+
+    // if the selected radio button says "no preference", set on/off pref to
+    // false and don't change the value pref.
+    if (dntRadioGroup.selectedItem.value == -1) {
+      dntEnabledPref.value = false;
+      return dntValuePref.value;
+    }
+
+    dntEnabledPref.value = true;
+    return dntRadioGroup.selectedItem.value;
+  },
+
+  /**
+   * Obtain the tracking preference value and reflect it in the UI.
+   */
+  getTrackingPrefs: function PPP_getTrackingPrefs()
+  {
+    let dntValuePref = document.getElementById("privacy.donottrackheader.value"),
+        dntEnabledPref = document.getElementById("privacy.donottrackheader.enabled");
+
+    // if DNT is enbaled, select the value from the selected radio
+    // button, otherwise choose the "no preference" radio button
+    if (dntEnabledPref.value)
+      return dntValuePref.value;
+
+    return document.getElementById("dntnopref").value;
+  },
+
+  /**
    * Update the private browsing auto-start pref and the history mode
    * micro-management prefs based on the history mode menulist
    */
   updateHistoryModePrefs: function PPP_updateHistoryModePrefs()
   {
     let pref = document.getElementById("browser.privatebrowsing.autostart");
     switch (document.getElementById("historyMode").value) {
     case "remember":
--- a/browser/components/preferences/privacy.xul
+++ b/browser/components/preferences/privacy.xul
@@ -21,16 +21,19 @@
             helpTopic="prefs-privacy">
 
     <preferences id="privacyPreferences">
   
       <!-- Tracking -->
       <preference id="privacy.donottrackheader.enabled"
                   name="privacy.donottrackheader.enabled"
                   type="bool"/>
+      <preference id="privacy.donottrackheader.value"
+                  name="privacy.donottrackheader.value"
+                  type="int"/>
 
       <!-- XXX button prefs -->
       <preference id="pref.privacy.disable_button.cookie_exceptions"
                   name="pref.privacy.disable_button.cookie_exceptions"
                   type="bool"/>
       <preference id="pref.privacy.disable_button.view_cookies"
                   name="pref.privacy.disable_button.view_cookies"
                   type="bool"/>
@@ -85,21 +88,31 @@
     
     <stringbundle id="bundlePreferences" src="chrome://browser/locale/preferences/preferences.properties"/>
     
     <script type="application/javascript" src="chrome://browser/content/preferences/privacy.js"/>
 
     <!-- Tracking -->
     <groupbox id="trackingGroup">
       <caption label="&tracking.label;"/>
+      <radiogroup id="doNotTrackSelection" orient="vertical"
+                  preference="privacy.donottrackheader.value"
+                  onsynctopreference="return gPrivacyPane.setTrackingPrefs()"
+                  onsyncfrompreference="return gPrivacyPane.getTrackingPrefs()">
+        <radio id="dntnotrack" value="1" label="&dntTrackingNotOkay.label;"
+                accesskey="&dntTrackingNotOkay.accesskey;" />
+        <radio id="dntdotrack" value="0" label="&dntTrackingOkay.label;"
+                accesskey="&dntTrackingOkay.accesskey;" />
+        <radio id="dntnopref" value="-1" label="&dntTrackingNopref.label;"
+                accesskey="&dntTrackingNopref.accesskey;" />
+      </radiogroup>
+      <label class="text-link" id="doNotTrackInfo"
+             onclick="event.stopPropagation();gPrivacyPane.openTrackingInfoSite();"
+             value="&doNotTrackInfo.label;"/>
 
-      <checkbox id="privacyDoNotTrackPrefs"
-                label="&doNotTrack.label;"
-                accesskey="&doNotTrack.accesskey;"
-                preference="privacy.donottrackheader.enabled"/>
     </groupbox>
 
     <!-- History -->
     <groupbox id="historyGroup">
       <caption label="&history.label;"/>
 
       <hbox align="center">
         <label id="historyModeLabel"
--- a/browser/components/preferences/tests/Makefile.in
+++ b/browser/components/preferences/tests/Makefile.in
@@ -7,16 +7,17 @@ topsrcdir	= @top_srcdir@
 srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _BROWSER_FILES = \
+    head.js \
     browser_advanced_update.js \
     browser_bug410900.js \
     browser_bug705422.js \
     browser_permissions.js \
     browser_chunk_permissions.js \
     $(NULL)
 
 ifndef MOZ_PER_WINDOW_PRIVATE_BROWSING
--- a/browser/components/preferences/tests/browser_chunk_permissions.js
+++ b/browser/components/preferences/tests/browser_chunk_permissions.js
@@ -23,37 +23,38 @@ const TEST_PERMS = {
   "geo": PERM_UNKNOWN,
   "indexedDB": PERM_UNKNOWN,
   "popup": PERM_DENY
 };
 
 function test() {
   waitForExplicitFinish();
   registerCleanupFunction(cleanUp);
-  setup();
-  runNextTest();
+  setup(function() {
+    runNextTest();
+  });
 }
 
-function setup() {
+function setup(aCallback) {
   // add test history visit
-  PlacesUtils.history.addVisit(TEST_URI_1, Date.now() * 1000, null,
-    Ci.nsINavHistoryService.TRANSITION_LINK, false, 0);
+  addVisits(TEST_URI_1, function() {
+    // set permissions ourselves to avoid problems with different defaults
+    // from test harness configuration
+    for (let type in TEST_PERMS) {
+      if (type == "password") {
+        Services.logins.setLoginSavingEnabled(TEST_URI_2.prePath, true);
+      } else {
+        // set permissions on a site without history visits to test enumerateServices
+        Services.perms.add(TEST_URI_2, type, TEST_PERMS[type]);
+      }
+    }
 
-  // set permissions ourselves to avoid problems with different defaults
-  // from test harness configuration
-  for (let type in TEST_PERMS) {
-    if (type == "password") {
-      Services.logins.setLoginSavingEnabled(TEST_URI_2.prePath, true);
-    } else {
-      // set permissions on a site without history visits to test enumerateServices
-      Services.perms.add(TEST_URI_2, type, TEST_PERMS[type]);
-    }
-  }
-
-  Services.perms.add(TEST_URI_3, "popup", TEST_PERMS["popup"]);
+    Services.perms.add(TEST_URI_3, "popup", TEST_PERMS["popup"]);
+    aCallback();
+  });
 }
 
 function cleanUp() {
   for (let type in TEST_PERMS) {
     if (type != "password") {
       Services.perms.remove(TEST_URI_1.host, type);
       Services.perms.remove(TEST_URI_2.host, type);
       Services.perms.remove(TEST_URI_3.host, type);
--- a/browser/components/preferences/tests/browser_permissions.js
+++ b/browser/components/preferences/tests/browser_permissions.js
@@ -39,38 +39,37 @@ const NO_GLOBAL_ALLOW = [
 // number of managed permissions in the interface
 const TEST_PERMS_COUNT = 7;
 
 function test() {
   waitForExplicitFinish();
   registerCleanupFunction(cleanUp);
 
   // add test history visit
-  PlacesUtils.history.addVisit(TEST_URI_1, Date.now() * 1000, null,
-    Ci.nsINavHistoryService.TRANSITION_LINK, false, 0);
+  addVisits(TEST_URI_1, function() {
+    // set permissions ourselves to avoid problems with different defaults
+    // from test harness configuration
+    for (let type in TEST_PERMS) {
+      if (type == "password") {
+        Services.logins.setLoginSavingEnabled(TEST_URI_2.prePath, true);
+      } else {
+        // set permissions on a site without history visits to test enumerateServices
+        Services.perms.addFromPrincipal(TEST_PRINCIPAL_2, type, TEST_PERMS[type]);
+      }
+    }
 
-  // set permissions ourselves to avoid problems with different defaults
-  // from test harness configuration
-  for (let type in TEST_PERMS) {
-    if (type == "password") {
-      Services.logins.setLoginSavingEnabled(TEST_URI_2.prePath, true);
-    } else {
-      // set permissions on a site without history visits to test enumerateServices
-      Services.perms.addFromPrincipal(TEST_PRINCIPAL_2, type, TEST_PERMS[type]);
-    }
-  }
+    // open about:permissions
+    gBrowser.selectedTab = gBrowser.addTab("about:permissions");
+  });
 
   function observer() {
     Services.obs.removeObserver(observer, "browser-permissions-initialized", false);
     runNextTest();
   }
   Services.obs.addObserver(observer, "browser-permissions-initialized", false);
-
-  // open about:permissions
-  gBrowser.selectedTab = gBrowser.addTab("about:permissions");
 }
 
 function cleanUp() {
   for (let type in TEST_PERMS) {
     if (type != "password") {
       Services.perms.removeFromPrincipal(TEST_PRINCIPAL_1, type);
       Services.perms.removeFromPrincipal(TEST_PRINCIPAL_2, type);
     }
new file mode 100644
--- /dev/null
+++ b/browser/components/preferences/tests/head.js
@@ -0,0 +1,57 @@
+Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
+
+/**
+ * Asynchronously adds visits to a page, invoking a callback function when done.
+ *
+ * @param aPlaceInfo
+ *        Can be an nsIURI, in such a case a single LINK visit will be added.
+ *        Otherwise can be an object describing the visit to add, or an array
+ *        of these objects:
+ *          { uri: nsIURI of the page,
+ *            transition: one of the TRANSITION_* from nsINavHistoryService,
+ *            [optional] title: title of the page,
+ *            [optional] visitDate: visit date in microseconds from the epoch
+ *            [optional] referrer: nsIURI of the referrer for this visit
+ *          }
+ * @param [optional] aCallback
+ *        Function to be invoked on completion.
+ */
+function addVisits(aPlaceInfo, aCallback) {
+  let places = [];
+  if (aPlaceInfo instanceof Ci.nsIURI) {
+    places.push({ uri: aPlaceInfo });
+  }
+  else if (Array.isArray(aPlaceInfo)) {
+    places = places.concat(aPlaceInfo);
+  } else {
+    places.push(aPlaceInfo)
+  }
+
+  // Create mozIVisitInfo for each entry.
+  let now = Date.now();
+  for (let i = 0; i < places.length; i++) {
+    if (!places[i].title) {
+      places[i].title = "test visit for " + places[i].uri.spec;
+    }
+    places[i].visits = [{
+      transitionType: places[i].transition === undefined ? PlacesUtils.history.TRANSITION_LINK
+                                                         : places[i].transition,
+      visitDate: places[i].visitDate || (now++) * 1000,
+      referrerURI: places[i].referrer
+    }];
+  }
+
+  PlacesUtils.asyncHistory.updatePlaces(
+    places,
+    {
+      handleError: function AAV_handleError() {
+        throw("Unexpected error in adding visit.");
+      },
+      handleResult: function () {},
+      handleCompletion: function UP_handleCompletion() {
+        if (aCallback)
+          aCallback();
+      }
+    }
+  );
+}
\ No newline at end of file
--- a/browser/components/privatebrowsing/test/browser/obsolete/browser_privatebrowsing_forgetthissite.js
+++ b/browser/components/privatebrowsing/test/browser/obsolete/browser_privatebrowsing_forgetthissite.js
@@ -27,20 +27,44 @@ function test() {
   // initialization
   let pb = Cc["@mozilla.org/privatebrowsing;1"].
            getService(Ci.nsIPrivateBrowsingService);
   waitForExplicitFinish();
 
   // Add a history entry.
   const TEST_URI = "http://www.mozilla.org/privatebrowsing";
   ok(PlacesUtils, "checking PlacesUtils, running in chrome context?");
-  let history = PlacesUtils.history;
-  let visitId = history.addVisit(PlacesUtils._uri(TEST_URI), Date.now() * 1000,
-                                 null, PlacesUtils.history.TRANSITION_TYPED, false, 0);
-  ok(visitId > 0, TEST_URI + " successfully marked visited");
+
+  let place = {
+    uri: PlacesUtils._uri(TEST_URI),
+    visits: [{
+      visitDate: Date.now() * 1000,
+      transitionType: PlacesUtils.history.TRANSITION_TYPED
+    }]
+  }
+  PlacesUtils.asyncHistory.updatePlaces(place, {
+    handleError: function () ok(false, "couldn't add visit"),
+    handleResult: function () {},
+    handleCompletion: function () {
+      ok(true, TEST_URI + " successfully marked visited");
+
+      testForgetThisSiteVisibility(true, function() {
+        // Enter private browsing mode
+        pb.privateBrowsingEnabled = true;
+        testForgetThisSiteVisibility(false, function() {
+          // Leave private browsing mode
+          pb.privateBrowsingEnabled = false;
+          testForgetThisSiteVisibility(true, function() {
+            // Cleanup
+            waitForClearHistory(finish);
+          });
+        });
+      });
+    }
+  });
 
   function testForgetThisSiteVisibility(expected, funcNext) {
     function observer(aSubject, aTopic, aData) {
       if (aTopic != "domwindowopened")
         return;
 
       Services.ww.unregisterNotification(observer);
       let organizer = aSubject.QueryInterface(Ci.nsIDOMWindow);
@@ -97,22 +121,9 @@ function test() {
 
     Services.ww.registerNotification(observer);
     Services.ww.openWindow(null,
                            "chrome://browser/content/places/places.xul",
                            "",
                            "chrome,toolbar=yes,dialog=no,resizable",
                            null);
   }
-
-  testForgetThisSiteVisibility(true, function() {
-    // Enter private browsing mode
-    pb.privateBrowsingEnabled = true;
-    testForgetThisSiteVisibility(false, function() {
-      // Leave private browsing mode
-      pb.privateBrowsingEnabled = false;
-      testForgetThisSiteVisibility(true, function() {
-        // Cleanup
-        waitForClearHistory(finish);
-      });
-    });
-  });
 }
--- a/browser/components/thumbnails/test/browser_thumbnails_storage.js
+++ b/browser/components/thumbnails/test/browser_thumbnails_storage.js
@@ -40,26 +40,28 @@ function runTests() {
   }
 
   yield createThumbnail();
 
   // Clear the last 10 minutes of browsing history.
   yield clearHistory(true);
 
   // Retry until the file is gone because Windows locks it sometimes.
-  while (file.exists()) {
+  clearFile(file, URL);
+}
+
+function clearFile(aFile, aURL) {
+  if (aFile.exists())
     // Re-add our URL to the history so that history observer's onDeleteURI()
     // is called again.
-    let time = Date.now() * 1000;
-    let trans = Ci.nsINavHistoryService.TRANSITION_LINK;
-    PlacesUtils.history.addVisit(makeURI(URL), time, null, trans, false, 0);
-
-    // Try again...
-    yield clearHistory(true);
-  }
+    addVisits(makeURI(aURL), function() {
+      // Try again...
+      yield clearHistory(true);
+      clearFile(aFile, aURL);
+    });
 }
 
 function clearHistory(aUseRange) {
   let s = new Sanitizer();
   s.prefDomain = "privacy.cpd.";
 
   let prefs = gPrefService.getBranch(s.prefDomain);
   prefs.setBoolPref("history", true);
--- a/browser/components/thumbnails/test/head.js
+++ b/browser/components/thumbnails/test/head.js
@@ -1,16 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 let tmp = {};
 Cu.import("resource:///modules/PageThumbs.jsm", tmp);
 let PageThumbs = tmp.PageThumbs;
 let PageThumbsStorage = tmp.PageThumbsStorage;
 
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+
 registerCleanupFunction(function () {
   while (gBrowser.tabs.length > 1)
     gBrowser.removeTab(gBrowser.tabs[1]);
 });
 
 /**
  * Provide the default test function to start our test runner.
  */
@@ -150,8 +152,65 @@ function checkCanvasColor(aContext, aRed
 /**
  * Checks if a thumbnail for the given URL exists.
  * @param aURL The url associated to the thumbnail.
  */
 function thumbnailExists(aURL) {
   let file = PageThumbsStorage.getFileForURL(aURL);
   return file.exists() && file.fileSize;
 }
+
+/**
+ * Asynchronously adds visits to a page, invoking a callback function when done.
+ *
+ * @param aPlaceInfo
+ *        Can be an nsIURI, in such a case a single LINK visit will be added.
+ *        Otherwise can be an object describing the visit to add, or an array
+ *        of these objects:
+ *          { uri: nsIURI of the page,
+ *            transition: one of the TRANSITION_* from nsINavHistoryService,
+ *            [optional] title: title of the page,
+ *            [optional] visitDate: visit date in microseconds from the epoch
+ *            [optional] referrer: nsIURI of the referrer for this visit
+ *          }
+ * @param [optional] aCallback
+ *        Function to be invoked on completion.
+ */
+function addVisits(aPlaceInfo, aCallback) {
+  let places = [];
+  if (aPlaceInfo instanceof Ci.nsIURI) {
+    places.push({ uri: aPlaceInfo });
+  }
+  else if (Array.isArray(aPlaceInfo)) {
+    places = places.concat(aPlaceInfo);
+  } else {
+    places.push(aPlaceInfo)
+  }
+
+  // Create mozIVisitInfo for each entry.
+  let now = Date.now();
+  for (let i = 0; i < places.length; i++) {
+    if (!places[i].title) {
+      places[i].title = "test visit for " + places[i].uri.spec;
+    }
+    places[i].visits = [{
+      transitionType: places[i].transition === undefined ? PlacesUtils.history.TRANSITION_LINK
+                                                         : places[i].transition,
+      visitDate: places[i].visitDate || (now++) * 1000,
+      referrerURI: places[i].referrer
+    }];
+  }
+
+  PlacesUtils.asyncHistory.updatePlaces(
+    places,
+    {
+      handleError: function AAV_handleError() {
+        throw("Unexpected error in adding visit.");
+      },
+      handleResult: function () {},
+      handleCompletion: function UP_handleCompletion() {
+        if (aCallback)
+          aCallback();
+      }
+    }
+  );
+}
+
--- a/browser/devtools/inspector/test/browser_inspector_initialization.js
+++ b/browser/devtools/inspector/test/browser_inspector_initialization.js
@@ -87,17 +87,17 @@ function testBreadcrumbs(node)
   let button = b.container.querySelector("button[checked=true]");
   ok(button, "A crumbs is checked=true");
   is(button.getAttribute("tooltiptext"), expectedText, "Crumb refers to the right node");
 }
 
 function _clickOnInspectMenuItem(node) {
   document.popupNode = node;
   var contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
-  var contextMenu = new nsContextMenu(contentAreaContextMenu, gBrowser);
+  var contextMenu = new nsContextMenu(contentAreaContextMenu);
   return contextMenu.inspectNode();
 }
 
 function runContextMenuTest()
 {
   salutation = doc.getElementById("salutation");
   _clickOnInspectMenuItem(salutation).then(testInitialNodeIsSelected);
 }
@@ -137,9 +137,8 @@ function test()
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     doc = content.document;
     waitForFocus(createDocument, content);
   }, true);
 
   content.location = "data:text/html,basic tests for inspector";
 }
-
--- a/browser/locales/en-US/chrome/browser/preferences/privacy.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/privacy.dtd
@@ -1,16 +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/. -->
 
 <!ENTITY tracking.label                 "Tracking">
 
-<!ENTITY doNotTrack.label               "Tell websites I do not want to be tracked">
-<!ENTITY doNotTrack.accesskey           "d">
+<!ENTITY dntTrackingNopref.label        "Do not tell sites anything about my tracking preferences.">
+<!ENTITY dntTrackingNopref.accesskey    "o">
+<!ENTITY dntTrackingNotOkay.label       "Tell sites that I do not want to be tracked.">
+<!ENTITY dntTrackingNotOkay.accesskey   "n">
+<!ENTITY dntTrackingOkay.label          "Tell sites that I want to be tracked.">
+<!ENTITY dntTrackingOkay.accesskey      "t">
+<!ENTITY doNotTrackInfo.label           "Learn More">
 
 <!ENTITY  history.label                 "History">
 
 <!ENTITY  locationBar.label             "Location Bar">
 
 <!ENTITY  locbar.pre.label              "When using the location bar, suggest:">
 <!ENTITY  locbar.pre.accessKey          "u">
 <!ENTITY  locbar.post.label             "">
--- a/browser/modules/Social.jsm
+++ b/browser/modules/Social.jsm
@@ -279,14 +279,86 @@ this.Social = {
     delete this._sharedUrls[url];
     port.postMessage({
       topic: "social.user-unrecommend",
       data: { url: url }
     });
     port.close();
   },
 
-  _sharedUrls: {}
+  _sharedUrls: {},
+
+  setErrorListener: function(iframe, errorHandler) {
+    if (iframe.socialErrorListener)
+      return iframe.socialErrorListener;
+    return new SocialErrorListener(iframe, errorHandler);
+  }
 };
 
 function schedule(callback) {
   Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
 }
+
+
+// Error handling class used to listen for network errors in the social frames
+// and replace them with a social-specific error page
+function SocialErrorListener(iframe, errorHandler) {
+  this.setErrorMessage = errorHandler;
+  this.iframe = iframe;
+  iframe.socialErrorListener = this;
+  iframe.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                                   .getInterface(Ci.nsIWebProgress)
+                                   .addProgressListener(this,
+                                                        Ci.nsIWebProgress.NOTIFY_STATE_REQUEST |
+                                                        Ci.nsIWebProgress.NOTIFY_LOCATION);
+}
+
+SocialErrorListener.prototype = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+                                         Ci.nsISupportsWeakReference,
+                                         Ci.nsISupports]),
+
+  remove: function() {
+    this.iframe.docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                                     .getInterface(Ci.nsIWebProgress)
+                                     .removeProgressListener(this);
+    delete this.iframe.socialErrorListener;
+  },
+
+  onStateChange: function SPL_onStateChange(aWebProgress, aRequest, aState, aStatus) {
+    let failure = false;
+    if ((aState & Ci.nsIWebProgressListener.STATE_STOP)) {
+      if (aRequest instanceof Ci.nsIHttpChannel) {
+        try {
+          // Change the frame to an error page on 4xx (client errors)
+          // and 5xx (server errors)
+          failure = aRequest.responseStatus >= 400 &&
+                    aRequest.responseStatus < 600;
+        } catch (e) {}
+      }
+    }
+
+    // Calling cancel() will raise some OnStateChange notifications by itself,
+    // so avoid doing that more than once
+    if (failure && aStatus != Components.results.NS_BINDING_ABORTED) {
+      aRequest.cancel(Components.results.NS_BINDING_ABORTED);
+      Social.provider.errorState = "content-error";
+      this.setErrorMessage(aWebProgress.QueryInterface(Ci.nsIDocShell)
+                              .chromeEventHandler);
+    }
+  },
+
+  onLocationChange: function SPL_onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+    let failure = aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE;
+    if (failure && Social.provider.errorState != "frameworker-error") {
+      aRequest.cancel(Components.results.NS_BINDING_ABORTED);
+      Social.provider.errorState = "content-error";
+      schedule(function() {
+        this.setErrorMessage(aWebProgress.QueryInterface(Ci.nsIDocShell)
+                              .chromeEventHandler);
+      }.bind(this));
+    }
+  },
+
+  onProgressChange: function SPL_onProgressChange() {},
+  onStatusChange: function SPL_onStatusChange() {},
+  onSecurityChange: function SPL_onSecurityChange() {},
+};
--- a/build/valgrind/cross-architecture.sup
+++ b/build/valgrind/cross-architecture.sup
@@ -242,16 +242,32 @@
    Memcheck:Leak
    fun:malloc
    fun:moz_xmalloc
    fun:_Z22NS_NewComputedDOMStylePN7mozilla3dom7ElementERK18nsAString_internalP12nsIPresShellN18nsComputedDOMStyle9StyleTypeE
    fun:_ZN14nsGlobalWindow22GetComputedStyleHelperEP13nsIDOMElementRK18nsAString_internalbPP25nsIDOMCSSStyleDeclaration
    ...
 }
 {
+   Bug 812423
+   Memcheck:Leak
+   fun:malloc
+   fun:_ZN2js15ArgumentsObject6createI18CopyStackFrameArgsEEPS0_P9JSContextN2JS6HandleIP8JSScriptEENS7_IP10JSFunctionEEjRT_
+   fun:_ZN2js15ArgumentsObject14createExpectedEP9JSContextPNS_10StackFrameE
+   ...
+}
+{
+   Bug 812423
+   Memcheck:Leak
+   fun:malloc
+   fun:_ZN2js15ArgumentsObject6createI13CopyFrameArgsEEPS0_P9JSContextN2JS6HandleIP8JSScriptEENS7_IP10JSFunctionEEjRT_
+   fun:_ZN2js15ArgumentsObject14createExpectedEP9JSContextNS_16AbstractFramePtrE
+   ...
+}
+{
    Bug 823782
    Memcheck:Leak
    fun:malloc
    ...
    fun:_ZN2js6ctypes7LibraryL7DeclareEP9JSContextjPN2JS5ValueE
    ...
 }
 {
--- a/caps/idl/nsIPrincipal.idl
+++ b/caps/idl/nsIPrincipal.idl
@@ -103,17 +103,17 @@ interface nsIPrincipal : nsISerializable
     /**
      * Checks whether this principal is allowed to load the network resource
      * located at the given URI under the same-origin policy. This means that
      * codebase principals are only allowed to load resources from the same
      * domain, the system principal is allowed to load anything, and null
      * principals are not allowed to load anything. This is changed slightly
      * by the optional flag allowIfInheritsPrincipal (which defaults to false)
      * which allows the load of a data: URI (which inherits the principal of
-     * its loader) or a URI with the same principal as its loader (eg. a 
+     * its loader) or a URI with the same principal as its loader (eg. a
      * Blob URI).
      * In these cases, with allowIfInheritsPrincipal set to true, the URI can
      * be loaded by a null principal.
      *
      * If the load is allowed this function does nothing. If the load is not
      * allowed the function throws NS_ERROR_DOM_BAD_URI.
      *
      * NOTE: Other policies might override this, such as the Access-Control
@@ -154,17 +154,17 @@ interface nsIPrincipal : nsISerializable
     readonly attribute AUTF8String extendedOrigin;
 
     /**
      * The base domain of the codebase URI to which this principal pertains
      * (generally the document URI), handling null principals and
      * non-hierarchical schemes correctly.
      */
     readonly attribute ACString baseDomain;
-    
+
     const short APP_STATUS_NOT_INSTALLED = 0;
     const short APP_STATUS_INSTALLED     = 1;
     const short APP_STATUS_PRIVILEGED    = 2;
     const short APP_STATUS_CERTIFIED     = 3;
 
     /**
      * Gets the principal's app status, which indicates whether the principal
      * corresponds to "app code", and if it does, how privileged that code is.
@@ -224,28 +224,28 @@ interface nsIPrincipal : nsISerializable
     /**
      * Returns true iff this principal is a null principal (corresponding to an
      * unknown, hence assumed minimally privileged, security context).
      */
     [infallible] readonly attribute boolean isNullPrincipal;
 };
 
 /**
- * If nsSystemPrincipal is too risky to use, but we want a principal to access 
- * more than one origin, nsExpandedPrincipals letting us define an array of 
+ * If nsSystemPrincipal is too risky to use, but we want a principal to access
+ * more than one origin, nsExpandedPrincipals letting us define an array of
  * principals it subsumes. So script with an nsExpandedPrincipals will gain
- * same origin access when at least one of its principals it contains gained 
+ * same origin access when at least one of its principals it contains gained
  * sameorigin acccess. An nsExpandedPrincipal will be subsumed by the system
  * principal, and by another nsExpandedPrincipal that has all its principals.
  * It is added for jetpack content-scripts to let them interact with the
- * content and a well defined set of other domains, without the risk of 
+ * content and a well defined set of other domains, without the risk of
  * leaking out a system principal to the content. See: Bug 734891
  */
 [uuid(f3e177Df-6a5e-489f-80a7-2dd1481471d8)]
 interface nsIExpandedPrincipal : nsISupports
 {
   /**
    * An array of principals that the expanded principal subsumes.
-   * Note: this list is not reference counted, it is shared, so 
+   * Note: this list is not reference counted, it is shared, so
    * should not be changed and should only be used ephemerally.
    */
   [noscript] readonly attribute PrincipalArray whiteList;
 };
--- a/configure.in
+++ b/configure.in
@@ -427,26 +427,16 @@ else
     # Target the Windows 7 SDK by default
     WINSDK_TARGETVER=601
     WINVER=502
 fi
 
 AC_SUBST(CRTDLLVERSION)
 AC_SUBST(CRTEXPDLLVERSION)
 
-if test -n "$MOZ_METRO"; then
-  case "$target" in
-  *-mingw*)
-    ;;
-  *)
-    AC_MSG_ERROR([Metro builds only valid on the windows platform.]);
-    ;;
-  esac
-fi
-
 MOZ_ARG_WITH_STRING(windows-version,
 [  --with-windows-version=WINSDK_TARGETVER
                           Windows SDK version to target. Lowest version
                           currently allowed is 601 (Win7), highest is 602 (Win8)],
   WINSDK_TARGETVER=$withval)
 
 # Currently only two sdk versions allowed, 601 and 602
 case "$WINSDK_TARGETVER" in
@@ -7906,17 +7896,17 @@ fi
 dnl ========================================================
 dnl =
 dnl = Static Build Options
 dnl =
 dnl ========================================================
 MOZ_ARG_HEADER(Static build options)
 
 # split JS out by default to avoid VS2005 PGO crash (bug 591836).
-if test "$OS_ARCH" = "WINNT" -a "$CPU_ARCH" != "x86_64" ; then
+if test "$OS_ARCH" = "WINNT"; then
   ENABLE_SHARED_JS=1
 fi
 
 MOZ_ARG_ENABLE_BOOL(shared-js,
 [  --enable-shared-js
                           Create a shared JavaScript library.],
     ENABLE_SHARED_JS=1,
     ENABLE_SHARED_JS=)
new file mode 100644
--- /dev/null
+++ b/content/base/crashtests/377960-1.html
@@ -0,0 +1,12 @@
+<html><head>
+
+</head>
+<body onblur="document.activeElement.style.display = 'none'" tabindex="4" onfocus="var x=document.getElementsByTagName('*');var i = Math.floor(Math.random()*x.length);x[i].focus()" style="overflow: scroll;">
+
+
+
+<iframe tabindex="2" style="overflow: scroll; display: inline; position: fixed; direction: rtl;"></iframe>
+
+<select onblur="event.target.setAttribute('tabindex', Math.floor(Math.random()*5)-9)" style="overflow: scroll; display: table; position: absolute; float: right; direction: rtl;"></select>
+
+</body></html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/content/base/crashtests/377960-2.html
@@ -0,0 +1,7 @@
+<html>
+<head><title>Click the &lt;select&gt; to crash.</title></head>
+<body onfocus="document.documentElement.focus(); document.body.style.display = 'none';">
+<iframe style="position: fixed;"></iframe>
+<select style="position: absolute;"></select>
+</body>
+</html>
--- a/content/base/crashtests/crashtests.list
+++ b/content/base/crashtests/crashtests.list
@@ -25,16 +25,18 @@ load 348049-1.xhtml
 load 344882-1.html
 load 345837-1.xhtml
 load 349355-1.html
 load 354645-1.xul
 load 360599-1.html
 load 366200-1.xhtml
 load 371466-1.xhtml
 load 377360-1.xhtml
+load 377960-1.html
+load 377960-2.html
 load 384663-1.html
 load 386000-1.html
 load 386794-1.html
 load 387460-1.html
 load 395469-1.xhtml
 load 395469-2.xhtml
 skip load 399712-1.html # sporadically times out (bug 473680)
 load 398088-1.xul
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -86,16 +86,17 @@ class ImageLoader;
 
 namespace dom {
 class CDATASection;
 class Comment;
 class DocumentFragment;
 class DocumentType;
 class DOMImplementation;
 class Element;
+class GlobalObject;
 class HTMLBodyElement;
 class Link;
 class ProcessingInstruction;
 class UndoManager;
 template<typename> class Sequence;
 } // namespace dom
 } // namespace mozilla
 
@@ -1796,18 +1797,19 @@ public:
     return mCreatingStaticClone;
   }
 
   // WebIDL API
   nsIScriptGlobalObject* GetParentObject() const
   {
     return GetScopeObject();
   }
-  static already_AddRefed<nsIDocument> Constructor(nsISupports* aGlobal,
-                                                   mozilla::ErrorResult& rv);
+  static already_AddRefed<nsIDocument>
+    Constructor(const mozilla::dom::GlobalObject& aGlobal,
+                mozilla::ErrorResult& rv);
   virtual mozilla::dom::DOMImplementation*
     GetImplementation(mozilla::ErrorResult& rv) = 0;
   void GetURL(nsString& retval) const;
   void GetDocumentURI(nsString& retval) const;
   void GetCompatMode(nsString& retval) const;
   void GetCharacterSet(nsAString& retval) const;
   // Skip GetContentType, because our NS_IMETHOD version above works fine here.
   // GetDoctype defined above
--- a/content/base/src/EventSource.cpp
+++ b/content/base/src/EventSource.cpp
@@ -279,22 +279,23 @@ EventSource::Init(nsISupports* aOwner,
 
 /* virtual */ JSObject*
 EventSource::WrapObject(JSContext* aCx, JSObject* aScope, bool* aTriedToWrap)
 {
   return EventSourceBinding::Wrap(aCx, aScope, this, aTriedToWrap);
 }
 
 /* static */ already_AddRefed<EventSource>
-EventSource::Constructor(nsISupports* aOwner, const nsAString& aURL,
+EventSource::Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
                          const EventSourceInit& aEventSourceInitDict,
                          ErrorResult& aRv)
 {
   nsRefPtr<EventSource> eventSource = new EventSource();
-  aRv = eventSource->Init(aOwner, aURL, aEventSourceInitDict.mWithCredentials);
+  aRv = eventSource->Init(aGlobal.Get(), aURL,
+                          aEventSourceInitDict.mWithCredentials);
   return eventSource.forget();
 }
 
 //-----------------------------------------------------------------------------
 // EventSource::nsIObserver
 //-----------------------------------------------------------------------------
 
 NS_IMETHODIMP
--- a/content/base/src/EventSource.h
+++ b/content/base/src/EventSource.h
@@ -70,17 +70,17 @@ public:
 
   // WebIDL
   nsPIDOMWindow*
   GetParentObject() const
   {
     return GetOwner();
   }
   static already_AddRefed<EventSource>
-  Constructor(nsISupports* aOwner, const nsAString& aURL,
+  Constructor(const GlobalObject& aGlobal, const nsAString& aURL,
               const EventSourceInit& aEventSourceInitDict,
               ErrorResult& aRv);
 
   void GetUrl(nsAString& aURL) const
   {
     aURL = mOriginalURL;
   }
   bool WithCredentials() const
--- a/content/base/src/WebSocket.cpp
+++ b/content/base/src/WebSocket.cpp
@@ -482,68 +482,69 @@ WebSocket::WrapObject(JSContext* cx, JSO
 }
 
 //---------------------------------------------------------------------------
 // WebIDL
 //---------------------------------------------------------------------------
 
 // Constructor:
 already_AddRefed<WebSocket>
-WebSocket::Constructor(JSContext* aCx,
-                       nsISupports* aGlobal,
+WebSocket::Constructor(const GlobalObject& aGlobal,
+                       JSContext* aCx,
                        const nsAString& aUrl,
                        ErrorResult& aRv)
 {
   Sequence<nsString> protocols;
-  return WebSocket::Constructor(aCx, aGlobal, aUrl, protocols, aRv);
+  return WebSocket::Constructor(aGlobal, aCx, aUrl, protocols, aRv);
 }
 
 already_AddRefed<WebSocket>
-WebSocket::Constructor(JSContext* aCx,
-                       nsISupports* aGlobal,
+WebSocket::Constructor(const GlobalObject& aGlobal,
+                       JSContext* aCx,
                        const nsAString& aUrl,
                        const nsAString& aProtocol,
                        ErrorResult& aRv)
 {
   Sequence<nsString> protocols;
   protocols.AppendElement(aProtocol);
-  return WebSocket::Constructor(aCx, aGlobal, aUrl, protocols, aRv);
+  return WebSocket::Constructor(aGlobal, aCx, aUrl, protocols, aRv);
 }
 
 already_AddRefed<WebSocket>
-WebSocket::Constructor(JSContext* aCx,
-                       nsISupports* aGlobal,
+WebSocket::Constructor(const GlobalObject& aGlobal,
+                       JSContext* aCx,
                        const nsAString& aUrl,
                        const Sequence<nsString>& aProtocols,
                        ErrorResult& aRv)
 {
   if (!PrefEnabled()) {
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return nullptr;
   }
 
-  nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal = do_QueryInterface(aGlobal);
+  nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
+    do_QueryInterface(aGlobal.Get());
   if (!scriptPrincipal) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   nsCOMPtr<nsIPrincipal> principal = scriptPrincipal->GetPrincipal();
   if (!principal) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
-  nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aGlobal);
+  nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aGlobal.Get());
   if (!sgo) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
-  nsCOMPtr<nsPIDOMWindow> ownerWindow = do_QueryInterface(aGlobal);
+  nsCOMPtr<nsPIDOMWindow> ownerWindow = do_QueryInterface(aGlobal.Get());
   if (!ownerWindow) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   nsTArray<nsString> protocolArray;
 
   for (uint32_t index = 0, len = aProtocols.Length(); index < len; ++index) {
--- a/content/base/src/WebSocket.h
+++ b/content/base/src/WebSocket.h
@@ -93,29 +93,29 @@ public:
 public: // static helpers:
 
   // Determine if preferences allow WebSocket
   static bool PrefEnabled();
 
 public: // WebIDL interface:
 
   // Constructor:
-  static already_AddRefed<WebSocket> Constructor(JSContext *aCx,
-                                                 nsISupports* aGlobal,
+  static already_AddRefed<WebSocket> Constructor(const GlobalObject& aGlobal,
+                                                 JSContext *aCx,
                                                  const nsAString& aUrl,
                                                  ErrorResult& rv);
 
-  static already_AddRefed<WebSocket> Constructor(JSContext *aCx,
-                                                 nsISupports* aGlobal,
+  static already_AddRefed<WebSocket> Constructor(const GlobalObject& aGlobal,
+                                                 JSContext *aCx,
                                                  const nsAString& aUrl,
                                                  const nsAString& aProtocol,
                                                  ErrorResult& rv);
 
-  static already_AddRefed<WebSocket> Constructor(JSContext *aCx,
-                                                 nsISupports* aGlobal,
+  static already_AddRefed<WebSocket> Constructor(const GlobalObject& aGlobal,
+                                                 JSContext *aCx,
                                                  const nsAString& aUrl,
                                                  const Sequence<nsString>& aProtocols,
                                                  ErrorResult& rv);
 
   // webIDL: readonly attribute DOMString url
   void GetUrl(nsAString& aResult);
 
   // webIDL: readonly attribute unsigned short readyState;
--- a/content/base/src/nsDOMMutationObserver.cpp
+++ b/content/base/src/nsDOMMutationObserver.cpp
@@ -543,21 +543,21 @@ nsDOMMutationObserver::TakeRecords(
                          nsTArray<nsRefPtr<nsDOMMutationRecord> >& aRetVal)
 {
   aRetVal.Clear();
   mPendingMutations.SwapElements(aRetVal);
 }
 
 // static
 already_AddRefed<nsDOMMutationObserver>
-nsDOMMutationObserver::Constructor(nsISupports* aGlobal,
+nsDOMMutationObserver::Constructor(const mozilla::dom::GlobalObject& aGlobal,
                                    mozilla::dom::MutationCallback& aCb,
                                    mozilla::ErrorResult& aRv)
 {
-  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal);
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.Get());
   if (!window) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
   MOZ_ASSERT(window->IsInnerWindow());
   nsRefPtr<nsDOMMutationObserver> observer =
     new nsDOMMutationObserver(window.forget(), aCb);
   return observer.forget();
--- a/content/base/src/nsDOMMutationObserver.h
+++ b/content/base/src/nsDOMMutationObserver.h
@@ -350,17 +350,18 @@ public:
     mTransientReceivers.Init();
     SetIsDOMBinding();
   }
   virtual ~nsDOMMutationObserver();
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMMutationObserver)
 
   static already_AddRefed<nsDOMMutationObserver>
-  Constructor(nsISupports* aGlobal, mozilla::dom::MutationCallback& aCb,
+  Constructor(const mozilla::dom::GlobalObject& aGlobal,
+              mozilla::dom::MutationCallback& aCb,
               mozilla::ErrorResult& aRv);
 
   virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope,
                                bool* aTriedToWrap)
   {
     return mozilla::dom::MutationObserverBinding::Wrap(aCx, aScope,
                                                        this, aTriedToWrap);
   }
--- a/content/base/src/nsDOMParser.cpp
+++ b/content/base/src/nsDOMParser.cpp
@@ -348,34 +348,35 @@ nsDOMParser::Init(nsIPrincipal* principa
 
   NS_POSTCONDITION(mPrincipal, "Must have principal");
   NS_POSTCONDITION(mOriginalPrincipal, "Must have original principal");
   NS_POSTCONDITION(mDocumentURI, "Must have document URI");
   return NS_OK;
 }
 
 /*static */already_AddRefed<nsDOMParser>
-nsDOMParser::Constructor(nsISupports* aOwner, nsIPrincipal* aPrincipal,
+nsDOMParser::Constructor(const GlobalObject& aOwner, nsIPrincipal* aPrincipal,
                          nsIURI* aDocumentURI, nsIURI* aBaseURI,
                          ErrorResult& rv)
 {
   if (!nsContentUtils::IsCallerChrome()) {
     rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return nullptr;
   }
-  nsRefPtr<nsDOMParser> domParser = new nsDOMParser(aOwner);
-  rv = domParser->InitInternal(aOwner, aPrincipal, aDocumentURI, aBaseURI);
+  nsRefPtr<nsDOMParser> domParser = new nsDOMParser(aOwner.Get());
+  rv = domParser->InitInternal(aOwner.Get(), aPrincipal, aDocumentURI,
+                               aBaseURI);
   if (rv.Failed()) {
     return nullptr;
   }
   return domParser.forget();
 }
 
 /*static */already_AddRefed<nsDOMParser>
-nsDOMParser::Constructor(nsISupports* aOwner, mozilla::ErrorResult& rv)
+nsDOMParser::Constructor(const GlobalObject& aOwner, ErrorResult& rv)
 {
   nsCOMPtr<nsIPrincipal> prin;
   nsCOMPtr<nsIURI> documentURI;
   nsCOMPtr<nsIURI> baseURI;
   // No arguments; use the subject principal
   nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
   if (!secMan) {
     rv.Throw(NS_ERROR_UNEXPECTED);
@@ -388,18 +389,18 @@ nsDOMParser::Constructor(nsISupports* aO
   }
 
   // We're called from JS; there better be a subject principal, really.
   if (!prin) {
     rv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
-  nsRefPtr<nsDOMParser> domParser = new nsDOMParser(aOwner);
-  rv = domParser->InitInternal(aOwner, prin, documentURI, baseURI);
+  nsRefPtr<nsDOMParser> domParser = new nsDOMParser(aOwner.Get());
+  rv = domParser->InitInternal(aOwner.Get(), prin, documentURI, baseURI);
   if (rv.Failed()) {
     return nullptr;
   }
   return domParser.forget();
 }
 
 nsresult
 nsDOMParser::InitInternal(nsISupports* aOwner, nsIPrincipal* prin,
--- a/content/base/src/nsDOMParser.h
+++ b/content/base/src/nsDOMParser.h
@@ -27,21 +27,22 @@ public:
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(nsDOMParser,
                                                          nsIDOMParser)
 
   // nsIDOMParser
   NS_DECL_NSIDOMPARSER
 
   // WebIDL API
   static already_AddRefed<nsDOMParser>
-  Constructor(nsISupports* aOwner, mozilla::ErrorResult& rv);
+  Constructor(const mozilla::dom::GlobalObject& aOwner,
+              mozilla::ErrorResult& rv);
 
   static already_AddRefed<nsDOMParser>
-  Constructor(nsISupports* aOwner, nsIPrincipal* aPrincipal,
-              nsIURI* aDocumentURI, nsIURI* aBaseURI,
+  Constructor(const mozilla::dom::GlobalObject& aOwner,
+              nsIPrincipal* aPrincipal, nsIURI* aDocumentURI, nsIURI* aBaseURI,
               mozilla::ErrorResult& rv);
 
   already_AddRefed<nsIDocument>
   ParseFromString(const nsAString& aStr, mozilla::dom::SupportedType aType,
                   mozilla::ErrorResult& rv);
 
   already_AddRefed<nsIDocument>
   ParseFromBuffer(const mozilla::dom::Sequence<uint8_t>& aBuf,
--- a/content/base/src/nsDOMSerializer.h
+++ b/content/base/src/nsDOMSerializer.h
@@ -24,19 +24,20 @@ public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMSerializer)
 
   // nsIDOMSerializer
   NS_DECL_NSIDOMSERIALIZER
 
   // WebIDL API
   static already_AddRefed<nsDOMSerializer>
-  Constructor(nsISupports* aOwner, mozilla::ErrorResult& rv)
+  Constructor(const mozilla::dom::GlobalObject& aOwner,
+              mozilla::ErrorResult& rv)
   {
-    nsRefPtr<nsDOMSerializer> domSerializer = new nsDOMSerializer(aOwner);
+    nsRefPtr<nsDOMSerializer> domSerializer = new nsDOMSerializer(aOwner.Get());
     return domSerializer.forget();
   }
 
   void
   SerializeToString(nsINode& aRoot, nsAString& aStr,
                     mozilla::ErrorResult& rv);
 
   void
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -5890,19 +5890,18 @@ nsDocument::EnumerateExternalResources(n
 
 nsSMILAnimationController*
 nsDocument::GetAnimationController()
 {
   // We create the animation controller lazily because most documents won't want
   // one and only SVG documents and the like will call this
   if (mAnimationController)
     return mAnimationController;
-  // Refuse to create an Animation Controller if SMIL is disabled, and also
-  // for data documents.
-  if (!NS_SMILEnabled() || mLoadedAsData || mLoadedAsInteractiveData)
+  // Refuse to create an Animation Controller for data documents.
+  if (mLoadedAsData || mLoadedAsInteractiveData)
     return nullptr;
 
   mAnimationController = new nsSMILAnimationController(this);
 
   // If there's a presContext then check the animation mode and pause if
   // necessary.
   nsIPresShell *shell = GetShell();
   if (mAnimationController && shell) {
@@ -10445,25 +10444,25 @@ nsDocument::DocSizeOfExcludingThis(nsWin
                                        aWindowSizes->mMallocSizeOf);
 
   // Measurement of the following members may be added later if DMD finds it
   // is worthwhile:
   // - many!
 }
 
 already_AddRefed<nsIDocument>
-nsIDocument::Constructor(nsISupports* aGlobal, ErrorResult& rv)
-{
-  nsCOMPtr<nsIScriptGlobalObject> global = do_QueryInterface(aGlobal);
+nsIDocument::Constructor(const GlobalObject& aGlobal, ErrorResult& rv)
+{
+  nsCOMPtr<nsIScriptGlobalObject> global = do_QueryInterface(aGlobal.Get());
   if (!global) {
     rv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
-  nsCOMPtr<nsIScriptObjectPrincipal> prin = do_QueryInterface(aGlobal);
+  nsCOMPtr<nsIScriptObjectPrincipal> prin = do_QueryInterface(aGlobal.Get());
   if (!prin) {
     rv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   nsCOMPtr<nsIURI> uri;
   NS_NewURI(getter_AddRefs(uri), "about:blank");
   if (!uri) {
--- a/content/base/src/nsFormData.cpp
+++ b/content/base/src/nsFormData.cpp
@@ -101,21 +101,21 @@ nsFormData::Append(const nsAString& aNam
 
 /* virtual */ JSObject*
 nsFormData::WrapObject(JSContext* aCx, JSObject* aScope, bool* aTriedToWrap)
 {
   return FormDataBinding::Wrap(aCx, aScope, this, aTriedToWrap);
 }
 
 /* static */ already_AddRefed<nsFormData>
-nsFormData::Constructor(nsISupports* aGlobal,
+nsFormData::Constructor(const GlobalObject& aGlobal,
                         const Optional<nsHTMLFormElement*>& aFormElement,
                         ErrorResult& aRv)
 {
-  nsRefPtr<nsFormData> formData = new nsFormData(aGlobal);
+  nsRefPtr<nsFormData> formData = new nsFormData(aGlobal.Get());
   if (aFormElement.WasPassed()) {
     MOZ_ASSERT(aFormElement.Value());
     aRv = aFormElement.Value()->WalkFormElements(formData);
   }
   return formData.forget();
 }
 
 // -------------------------------------------------------------------------
--- a/content/base/src/nsFormData.h
+++ b/content/base/src/nsFormData.h
@@ -14,16 +14,17 @@
 
 class nsHTMLFormElement;
 class nsIDOMFile;
 
 namespace mozilla {
 class ErrorResult;
 
 namespace dom {
+class GlobalObject;
 template<class> class Optional;
 } // namespace dom
 } // namespace mozilla
 
 class nsFormData : public nsIDOMFormData,
                    public nsIXHRSendable,
                    public nsFormSubmission,
                    public nsWrapperCache
@@ -44,17 +45,17 @@ public:
 
   // WebIDL
   nsISupports*
   GetParentObject() const
   {
     return mOwner;
   }
   static already_AddRefed<nsFormData>
-  Constructor(nsISupports* aGlobal,
+  Constructor(const mozilla::dom::GlobalObject& aGlobal,
               const mozilla::dom::Optional<nsHTMLFormElement*>& aFormElement,
               mozilla::ErrorResult& aRv);
   void Append(const nsAString& aName, const nsAString& aValue);
   void Append(const nsAString& aName, nsIDOMBlob* aBlob);
 
   // nsFormSubmission
   virtual nsresult GetEncodedSubmission(nsIURI* aURI,
                                         nsIInputStream** aPostDataStream);
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -1968,16 +1968,17 @@ GK_ATOM(scrollbar_end_backward, "scrollb
 GK_ATOM(scrollbar_end_forward, "scrollbar-end-forward")
 GK_ATOM(scrollbar_thumb_proportional, "scrollbar-thumb-proportional")
 GK_ATOM(images_in_menus, "images-in-menus")
 GK_ATOM(images_in_buttons, "images-in-buttons")
 GK_ATOM(windows_default_theme, "windows-default-theme")
 GK_ATOM(mac_graphite_theme, "mac-graphite-theme")
 GK_ATOM(mac_lion_theme, "mac-lion-theme")
 GK_ATOM(windows_compositor, "windows-compositor")
+GK_ATOM(windows_glass, "windows-glass")
 GK_ATOM(touch_enabled, "touch-enabled")
 GK_ATOM(maemo_classic, "maemo-classic")
 GK_ATOM(menubar_drag, "menubar-drag")
 
 // windows theme selector metrics
 GK_ATOM(windows_classic, "windows-classic")
 GK_ATOM(windows_theme_aero, "windows-theme-aero")
 GK_ATOM(windows_theme_luna_blue, "windows-theme-luna-blue")
@@ -1995,16 +1996,17 @@ GK_ATOM(_moz_scrollbar_end_forward, "-mo
 GK_ATOM(_moz_scrollbar_thumb_proportional, "-moz-scrollbar-thumb-proportional")
 GK_ATOM(_moz_images_in_menus, "-moz-images-in-menus")
 GK_ATOM(_moz_images_in_buttons, "-moz-images-in-buttons")
 GK_ATOM(_moz_windows_default_theme, "-moz-windows-default-theme")
 GK_ATOM(_moz_mac_graphite_theme, "-moz-mac-graphite-theme")
 GK_ATOM(_moz_mac_lion_theme, "-moz-mac-lion-theme")
 GK_ATOM(_moz_windows_compositor, "-moz-windows-compositor")
 GK_ATOM(_moz_windows_classic, "-moz-windows-classic")
+GK_ATOM(_moz_windows_glass, "-moz-windows-glass")
 GK_ATOM(_moz_windows_theme, "-moz-windows-theme")
 GK_ATOM(_moz_touch_enabled, "-moz-touch-enabled")
 GK_ATOM(_moz_maemo_classic, "-moz-maemo-classic")
 GK_ATOM(_moz_menubar_drag, "-moz-menubar-drag")
 GK_ATOM(_moz_device_pixel_ratio, "-moz-device-pixel-ratio")
 GK_ATOM(_moz_device_orientation, "-moz-device-orientation")
 GK_ATOM(_moz_is_resource_document, "-moz-is-resource-document")
 
--- a/content/base/src/nsXMLHttpRequest.h
+++ b/content/base/src/nsXMLHttpRequest.h
@@ -143,48 +143,49 @@ public:
   }
   nsISupports* GetParentObject()
   {
     return GetOwner();
   }
 
   // The WebIDL constructors.
   static already_AddRefed<nsXMLHttpRequest>
-  Constructor(JSContext* aCx,
-              nsISupports* aGlobal,
+  Constructor(const mozilla::dom::GlobalObject& aGlobal,
+              JSContext* aCx,
               const mozilla::dom::MozXMLHttpRequestParameters& aParams,
               ErrorResult& aRv)
   {
-    nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal);
-    nsCOMPtr<nsIScriptObjectPrincipal> principal = do_QueryInterface(aGlobal);
+    nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.Get());
+    nsCOMPtr<nsIScriptObjectPrincipal> principal =
+      do_QueryInterface(aGlobal.Get());
     if (!window || ! principal) {
       aRv.Throw(NS_ERROR_FAILURE);
       return NULL;
     }
 
     nsRefPtr<nsXMLHttpRequest> req = new nsXMLHttpRequest();
     req->Construct(principal->GetPrincipal(), window);
     req->InitParameters(aParams.mMozAnon, aParams.mMozSystem);
     return req.forget();
   }
 
   static already_AddRefed<nsXMLHttpRequest>
-  Constructor(JSContext* aCx,
-              nsISupports* aGlobal,
+  Constructor(const mozilla::dom::GlobalObject& aGlobal,
+              JSContext* aCx,
               const nsAString& ignored,
               ErrorResult& aRv)
   {
     // Pretend like someone passed null, so we can pick up the default values
     mozilla::dom::MozXMLHttpRequestParameters params;
     if (!params.Init(aCx, nullptr, JS::NullValue())) {
       aRv.Throw(NS_ERROR_UNEXPECTED);
       return nullptr;
     }
 
-    return Constructor(aCx, aGlobal, params, aRv);
+    return Constructor(aGlobal, aCx, params, aRv);
   }
 
   void Construct(nsIPrincipal* aPrincipal,
                  nsPIDOMWindow* aOwnerWindow,
                  nsIURI* aBaseURI = NULL)
   {
     MOZ_ASSERT(aPrincipal);
     MOZ_ASSERT_IF(aOwnerWindow, aOwnerWindow->IsInnerWindow());
--- a/content/canvas/test/webgl/Makefile.in
+++ b/content/canvas/test/webgl/Makefile.in
@@ -9,16 +9,19 @@ srcdir		= @srcdir@
 VPATH		= @srcdir@
 relativesrcdir  = @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 MOCHITEST_FILES = \
   test_webgl_conformance_test_suite.html \
   00_test_list.txt \
   failing_tests_linux.txt \
+  failing_tests_linux_mesa.txt \
+  skipped_tests_linux_mesa.txt \
+  failing_tests_linux_nvidia.txt \
   failing_tests_windows.txt \
   failing_tests_mac.txt \
   failing_tests_mac_mtnlion.txt \
   failing_tests_android.txt \
   skipped_tests_android.txt \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/webgl/failing_tests_linux_mesa.txt
@@ -0,0 +1,5 @@
+conformance/textures/texture-mips.html
+conformance/textures/texture-size-cube-maps.html
+conformance/extensions/oes-texture-float.html
+conformance/glsl/functions/glsl-function-sin.html
+
copy from content/canvas/test/webgl/failing_tests_linux.txt
copy to content/canvas/test/webgl/failing_tests_linux_nvidia.txt
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/webgl/skipped_tests_linux_mesa.txt
@@ -0,0 +1,2 @@
+conformance/misc/type-conversion-test.html
+
--- a/content/canvas/test/webgl/test_webgl_conformance_test_suite.html
+++ b/content/canvas/test/webgl/test_webgl_conformance_test_suite.html
@@ -39,27 +39,68 @@ var OPTIONS = {
     if (request.readyState != 4) {
       throw error;
     }
     return request.responseText;
   };
 
 SimpleTest.waitForExplicitFinish();
 
+function detectDriverType() {
+  const Cc = SpecialPowers.wrap(Components).classes;
+  const Ci = SpecialPowers.wrap(Components).interfaces;
+  var doc = Cc["@mozilla.org/xmlextras/domparser;1"].createInstance(Ci.nsIDOMParser).parseFromString("<html/>", "text/html");
+
+  var canvas = doc.createElement("canvas");
+  canvas.width = 1;
+  canvas.height = 1;
+
+  var type = "";
+  var gl;
+  try {
+    gl = canvas.getContext("experimental-webgl");
+  } catch(e) {}
+
+  if (gl) {
+    var ext = gl.getExtension("WEBGL_debug_renderer_info");
+    // this extension is unconditionally available to chrome. No need to check.
+    var webglRenderer = gl.getParameter(ext.UNMASKED_RENDERER_WEBGL);
+    var webglVendor = gl.getParameter(ext.UNMASKED_VENDOR_WEBGL);
+    if (webglRenderer.contains('llvmpipe')) {
+      type = "mesa";
+    } else if (webglVendor.contains('NVIDIA')) {
+      type = "nvidia";
+    }
+  }
+  return type;
+}
+
 function start() {
 
   var kIsWindows = false;
+  var kIsAndroid = false;
   var kIsLinux = false;
-  var kIsAndroid = false;
+  var kIsLinuxNVidia = false;
+  var kIsLinuxMesa = false;
+
   if (navigator.platform.indexOf("Win") == 0)
     kIsWindows = true;
   else if (navigator.appVersion.indexOf("Android") != -1)
     kIsAndroid = true;
-  else if (navigator.platform.indexOf("Linux") == 0) // must be checked after android, as android also has a 'Linux' platform string
-    kIsLinux = true;
+  else if (navigator.platform.indexOf("Linux") == 0)  {
+    // must be checked after android, as android also has a 'Linux' platform string
+    var type = detectDriverType();
+    if (type == "mesa") {
+      kIsLinuxMesa = true;
+    } else if (type == "nvidia") {
+      kIsLinuxNVidia = true;
+    } else {
+      kIsLinux = true;
+    }
+  }
 
   // Set kMacVersion to the OS X version for Mac, and 0 otherwise.
   var osxmatch = /Mac OS X (\d+.\d+)/.exec(navigator.userAgent);
   var kMacVersion = osxmatch ? parseFloat(osxmatch[1]) : 0;
 
   var kIsWindowsVistaOrHigher = false;
   if (kIsWindows) {
     // code borrowed from browser/modules/test/browser_taskbar_preview.js
@@ -413,16 +454,20 @@ function start() {
   // failing tests. It's easier to do a platform check for Windows than for ANGLE itself.
   // Moreover, we currently also have different tests failing on Mac and on Linux,
   // presumably due to differences in the drivers.
   var failingTestsFilename;
   if (kIsWindows)
     failingTestsFilename = 'failing_tests_windows.txt';
   else if (kIsLinux)
     failingTestsFilename = 'failing_tests_linux.txt';
+  else if (kIsLinuxMesa)
+    failingTestsFilename = 'failing_tests_linux_mesa.txt';
+  else if (kIsLinuxNVidia)
+    failingTestsFilename = 'failing_tests_linux_nvidia.txt';
   else if (kMacVersion == 10.8)
     failingTestsFilename = 'failing_tests_mac_mtnlion.txt';
   else if (kMacVersion)
     failingTestsFilename = 'failing_tests_mac.txt';
   else if (kIsAndroid)
     failingTestsFilename = 'failing_tests_android.txt';
 
   var testsExpectedToFail = loadTextFileSynchronous(failingTestsFilename)
@@ -441,16 +486,22 @@ function start() {
   var testsToSkip = [];
 
   if (kIsAndroid) {
     var testsToSkip = loadTextFileSynchronous('skipped_tests_android.txt')
                       .replace(/\r/g, '') // convert to unix line breaks
                       .split('\n');
   }
 
+  if (kIsLinuxMesa) {
+    var testsToSkip = loadTextFileSynchronous('skipped_tests_linux_mesa.txt')
+                      .replace(/\r/g, '') // convert to unix line breaks
+                      .split('\n');
+  }
+
   runTestSuite();
 }
 
 </script>
 </head>
 <body onload="start();">
 <p id="display"></p>
 <div id="content" style="display: none">
new file mode 100644
--- /dev/null
+++ b/content/html/content/crashtests/383137.xhtml
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<script id="script" xmlns="http://www.w3.org/1999/xhtml">//<![CDATA[
+function doe(){
+document.documentElement.setAttribute('style', '');
+}
+
+setTimeout(doe,100);
+//]]></script>
+
+<style style="overflow: scroll; display: block;">
+style::before {content: counter(section); }
+</style>
+</html>
\ No newline at end of file
--- a/content/html/content/crashtests/crashtests.list
+++ b/content/html/content/crashtests/crashtests.list
@@ -1,16 +1,17 @@
 load 68912-1.html
 load 257818-1.html
 load 307616-1.html
 load 324918-1.xhtml
 load 338649-1.xhtml
 load 339501-1.xhtml
 load 339501-2.xhtml
 load 382568-1.html
+load 383137.xhtml
 load 423371-1.html
 load 451123-1.html
 load 453406-1.html
 load 464197-1.html
 load 465466-1.xhtml
 load 504183-1.html
 load 515829-1.html
 load 515829-2.html
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -1438,68 +1438,83 @@ nsHTMLInputElement::SetValueAsNumber(dou
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
   SetValue(aValueAsNumber);
   return NS_OK;
 }
 
 double
-nsHTMLInputElement::GetMinAsDouble() const
+nsHTMLInputElement::GetMinimum() const
 {
   // Should only be used for <input type='number'/'date'> for the moment.
   MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER || mType == NS_FORM_INPUT_DATE);
 
+  // Once we add support for types that have a default minimum/maximum, take
+  // account of the default minimum here.
+
   if (!HasAttr(kNameSpaceID_None, nsGkAtoms::min)) {
     return MOZ_DOUBLE_NaN();
   }
 
   nsAutoString minStr;
   GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
 
   double min;
   return ConvertStringToNumber(minStr, min) ? min : MOZ_DOUBLE_NaN();
 }
 
 double
-nsHTMLInputElement::GetMaxAsDouble() const
+nsHTMLInputElement::GetMaximum() const
 {
   // Should only be used for <input type='number'/'date'> for the moment.
   MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER || mType == NS_FORM_INPUT_DATE);
 
+  // Once we add support for types that have a default minimum/maximum, take
+  // account of the default maximum here.
+
   if (!HasAttr(kNameSpaceID_None, nsGkAtoms::max)) {
     return MOZ_DOUBLE_NaN();
   }
 
   nsAutoString maxStr;
   GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
 
   double max;
   return ConvertStringToNumber(maxStr, max) ? max : MOZ_DOUBLE_NaN();
 }
 
 double
 nsHTMLInputElement::GetStepBase() const
 {
-  double stepBase = GetMinAsDouble();
-
-  // If @min is not a double, we should use defaultValue.
-  if (MOZ_DOUBLE_IS_NaN(stepBase)) {
-    nsAutoString stringValue;
-    GetAttr(kNameSpaceID_None, nsGkAtoms::value, stringValue);
-
+  MOZ_ASSERT(mType == NS_FORM_INPUT_NUMBER ||
+             mType == NS_FORM_INPUT_DATE,
+             "Check that kDefaultStepBase is correct for this new type");
+
+  double stepBase;
+
+  // Do NOT use GetMinimum here - the spec says to use "the min content
+  // attribute", not "the minimum".
+  nsAutoString minStr;
+  if (GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr) &&
+      ConvertStringToNumber(minStr, stepBase)) {
+    return stepBase;
+  }
+
+  // If @min is not a double, we should use @value.
+  nsAutoString valueStr;
+  if (GetAttr(kNameSpaceID_None, nsGkAtoms::value, valueStr)) {
     nsresult ec;
-    stepBase = stringValue.ToDouble(&ec);
-
-    if (NS_FAILED(ec)) {
-      stepBase = MOZ_DOUBLE_NaN();
+    stepBase = valueStr.ToDouble(&ec);
+    if (NS_SUCCEEDED(ec)) {
+      return stepBase;
     }
   }
 
-  return MOZ_DOUBLE_IS_NaN(stepBase) ? kDefaultStepBase : stepBase;
+  return kDefaultStepBase;
 }
 
 nsresult
 nsHTMLInputElement::ApplyStep(int32_t aStep)
 {
   if (!DoStepDownStepUpApply()) {
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
@@ -1509,35 +1524,35 @@ nsHTMLInputElement::ApplyStep(int32_t aS
     return NS_ERROR_DOM_INVALID_STATE_ERR;
   }
 
   double value = GetValueAsDouble();
   if (MOZ_DOUBLE_IS_NaN(value)) {
     return NS_OK;
   }
 
-  double min = GetMinAsDouble();
-
-  double max = GetMaxAsDouble();
-  if (!MOZ_DOUBLE_IS_NaN(max)) {
+  double minimum = GetMinimum();
+
+  double maximum = GetMaximum();
+  if (!MOZ_DOUBLE_IS_NaN(maximum)) {
     // "max - (max - stepBase) % step" is the nearest valid value to max.
-    max = max - NS_floorModulo(max - GetStepBase(), step);
+    maximum = maximum - NS_floorModulo(maximum - GetStepBase(), step);
   }
 
   // Cases where we are clearly going in the wrong way.
   // We don't use ValidityState because we can be higher than the maximal
   // allowed value and still not suffer from range overflow in the case of
   // of the value specified in @max isn't in the step.
-  if ((value <= min && aStep < 0) ||
-      (value >= max && aStep > 0)) {
+  if ((value <= minimum && aStep < 0) ||
+      (value >= maximum && aStep > 0)) {
     return NS_OK;
   }
 
   if (GetValidityState(VALIDITY_STATE_STEP_MISMATCH) &&
-      value != min && value != max) {
+      value != minimum && value != maximum) {
     if (aStep > 0) {
       value -= NS_floorModulo(value - GetStepBase(), step);
     } else if (aStep < 0) {
       value -= NS_floorModulo(value - GetStepBase(), step);
       value += step;
     }
   }
 
@@ -1553,33 +1568,33 @@ nsHTMLInputElement::ApplyStep(int32_t aS
     if (aStep > 0) {
       value -= NS_floorModulo(value - GetStepBase(), validStep);
       value += validStep;
     } else if (aStep < 0) {
       value -= NS_floorModulo(value - GetStepBase(), validStep);
     }
   }
 
-  // When stepUp() is called and the value is below min, we should clamp on
-  // min unless stepUp() moves us higher than min.
+  // When stepUp() is called and the value is below minimum, we should clamp on
+  // minimum unless stepUp() moves us higher than minimum.
   if (GetValidityState(VALIDITY_STATE_RANGE_UNDERFLOW) && aStep > 0 &&
-      value <= min) {
-    MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(min)); // min can't be NaN if we are here!
-    value = min;
-  // Same goes for stepDown() and max.
+      value <= minimum) {
+    MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(minimum), "Can't be NaN if we are here");
+    value = minimum;
+  // Same goes for stepDown() and maximum.
   } else if (GetValidityState(VALIDITY_STATE_RANGE_OVERFLOW) && aStep < 0 &&
-             value >= max) {
-    MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(max)); // max can't be NaN if we are here!
-    value = max;
+             value >= maximum) {
+    MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(maximum), "Can't be NaN if we are here");
+    value = maximum;
   // If we go down, we want to clamp on min.
-  } else if (aStep < 0 && min == min) {
-    value = std::max(value, min);
+  } else if (aStep < 0 && minimum == minimum) {
+    value = std::max(value, minimum);
   // If we go up, we want to clamp on max.
-  } else if (aStep > 0 && max == max) {
-    value = std::min(value, max);
+  } else if (aStep > 0 && maximum == maximum) {
+    value = std::min(value, maximum);
   }
 
   SetValue(value);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -4641,47 +4656,47 @@ nsHTMLInputElement::HasPatternMismatch()
 bool
 nsHTMLInputElement::IsRangeOverflow() const
 {
   // Ignore type=time until bug 781572 is fixed.
   if (!DoesMinMaxApply() || mType == NS_FORM_INPUT_TIME) {
     return false;
   }
 
-  double max = GetMaxAsDouble();
-  if (MOZ_DOUBLE_IS_NaN(max)) {
+  double maximum = GetMaximum();
+  if (MOZ_DOUBLE_IS_NaN(maximum)) {
     return false;
   }
 
   double value = GetValueAsDouble();
   if (MOZ_DOUBLE_IS_NaN(value)) {
     return false;
   }
 
-  return value > max;
+  return value > maximum;
 }
 
 bool
 nsHTMLInputElement::IsRangeUnderflow() const
 {
   if (!DoesMinMaxApply()) {
     return false;
   }
 
-  double min = GetMinAsDouble();
-  if (MOZ_DOUBLE_IS_NaN(min)) {
+  double minimum = GetMinimum();
+  if (MOZ_DOUBLE_IS_NaN(minimum)) {
     return false;
   }
 
   double value = GetValueAsDouble();
   if (MOZ_DOUBLE_IS_NaN(value)) {
     return false;
   }
 
-  return value < min;
+  return value < minimum;
 }
 
 bool
 nsHTMLInputElement::HasStepMismatch() const
 {
   if (!DoesStepApply()) {
     return false;
   }
@@ -4926,20 +4941,20 @@ nsHTMLInputElement::GetValidationMessage
     }
     case VALIDITY_STATE_RANGE_OVERFLOW:
     {
       nsXPIDLString message;
 
       nsAutoString maxStr;
       if (mType == NS_FORM_INPUT_NUMBER) {
         //We want to show the value as parsed when it's a number
-        double max = GetMaxAsDouble();
-        MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(max));
-
-        maxStr.AppendFloat(max);
+        double maximum = GetMaximum();
+        MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(maximum));
+
+        maxStr.AppendFloat(maximum);
       } else if (mType == NS_FORM_INPUT_DATE) {
         GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
       } else {
         NS_NOTREACHED("Unexpected input type");
       }
 
       const PRUnichar* params[] = { maxStr.get() };
       rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
@@ -4949,20 +4964,20 @@ nsHTMLInputElement::GetValidationMessage
       break;
     }
     case VALIDITY_STATE_RANGE_UNDERFLOW:
     {
       nsXPIDLString message;
 
       nsAutoString minStr;
       if (mType == NS_FORM_INPUT_NUMBER) {
-        double min = GetMinAsDouble();
-        MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(min));
-
-        minStr.AppendFloat(min);
+        double minimum = GetMinimum();
+        MOZ_ASSERT(!MOZ_DOUBLE_IS_NaN(minimum));
+
+        minStr.AppendFloat(minimum);
       } else if (mType == NS_FORM_INPUT_DATE) {
         GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
       } else {
         NS_NOTREACHED("Unexpected input type");
       }
 
       const PRUnichar* params[] = { minStr.get() };
       rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
@@ -4991,19 +5006,19 @@ nsHTMLInputElement::GetValidationMessage
                                    static_cast<uint64_t>(GetStepScaleFactor()));
       }
 
       double stepBase = GetStepBase();
 
       double valueLow = value - NS_floorModulo(value - stepBase, step);
       double valueHigh = value + step - NS_floorModulo(value - stepBase, step);
 
-      double max = GetMaxAsDouble();
-
-      if (MOZ_DOUBLE_IS_NaN(max) || valueHigh <= max) {
+      double maximum = GetMaximum();
+
+      if (MOZ_DOUBLE_IS_NaN(maximum) || valueHigh <= maximum) {
         nsAutoString valueLowStr, valueHighStr;
         ConvertNumberToString(valueLow, valueLowStr);
         ConvertNumberToString(valueHigh, valueHighStr);
 
         const PRUnichar* params[] = { valueLowStr.get(), valueHighStr.get() };
         rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES,
                                                    "FormValidationStepMismatch",
                                                    params, message);
@@ -5465,20 +5480,20 @@ nsHTMLInputElement::UpdateHasRange()
   mHasRange = false;
 
   if (mType != NS_FORM_INPUT_NUMBER && mType != NS_FORM_INPUT_DATE) {
     return;
   }
 
   // <input type=number> has a range if min or max is a valid double.
 
-  double min = GetMinAsDouble();
-  if (!MOZ_DOUBLE_IS_NaN(min)) {
+  double minimum = GetMinimum();
+  if (!MOZ_DOUBLE_IS_NaN(minimum)) {
     mHasRange = true;
     return;
   }
 
-  double max = GetMaxAsDouble();
-  if (!MOZ_DOUBLE_IS_NaN(max)) {
+  double maximum = GetMaximum();
+  if (!MOZ_DOUBLE_IS_NaN(maximum)) {
     mHasRange = true;
     return;
   }
 }
--- a/content/html/content/src/nsHTMLInputElement.h
+++ b/content/html/content/src/nsHTMLInputElement.h
@@ -656,26 +656,34 @@ protected:
   void SetValue(double aValue);
 
   /**
    * Update the HAS_RANGE bit field value.
    */
   void UpdateHasRange();
 
   /**
-   * Returns the min attribute as a double.
-   * Returns NaN if the min attribute isn't a valid floating point number.
+   * Returns the input's "minimum" (as defined by the HTML5 spec) as a double.
+   * Note this takes account of any default minimum that the type may have.
+   * Returns NaN if the min attribute isn't a valid floating point number and
+   * the input's type does not have a default minimum.
+   *
+   * NOTE: Only call this if you know DoesMinMaxApply() returns true.
    */
-  double GetMinAsDouble() const;
+  double GetMinimum() const;
 
   /**
-   * Returns the max attribute as a double.
-   * Returns NaN if the max attribute isn't a valid floating point number.
+   * Returns the input's "maximum" (as defined by the HTML5 spec) as a double.
+   * Note this takes account of any default maximum that the type may have.
+   * Returns NaN if the max attribute isn't a valid floating point number and
+   * the input's type does not have a default maximum.
+   *
+   * NOTE:Only call this if you know DoesMinMaxApply() returns true.
    */
-  double GetMaxAsDouble() const;
+  double GetMaximum() const;
 
    /**
     * Get the step scale value for the current type.
     * See:
     * http://www.whatwg.org/specs/web-apps/current-work/multipage/common-input-element-attributes.html#concept-input-step-scale
     */
   double GetStepScaleFactor() const;
 
--- a/content/media/webaudio/AudioBuffer.cpp
+++ b/content/media/webaudio/AudioBuffer.cpp
@@ -13,16 +13,17 @@
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioBuffer)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mChannels)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
+  tmp->ClearJSChannels();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AudioBuffer)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AudioBuffer)
@@ -47,16 +48,22 @@ AudioBuffer::AudioBuffer(AudioContext* a
 {
   SetIsDOMBinding();
 
   NS_HOLD_JS_OBJECTS(this, AudioBuffer);
 }
 
 AudioBuffer::~AudioBuffer()
 {
+  ClearJSChannels();
+}
+
+void
+AudioBuffer::ClearJSChannels()
+{
   mChannels.Clear();
   NS_DROP_JS_OBJECTS(this, AudioBuffer);
 }
 
 bool
 AudioBuffer::InitializeBuffers(uint32_t aNumberOfChannels, JSContext* aJSContext)
 {
   if (!mChannels.SetCapacity(aNumberOfChannels)) {
--- a/content/media/webaudio/AudioBuffer.h
+++ b/content/media/webaudio/AudioBuffer.h
@@ -68,16 +68,18 @@ public:
   {
     return mChannels.Length();
   }
 
   JSObject* GetChannelData(JSContext* aJSContext, uint32_t aChannel,
                            ErrorResult& aRv) const;
 
 private:
+  void ClearJSChannels();
+
   nsRefPtr<AudioContext> mContext;
   FallibleTArray<JSObject*> mChannels;
   uint32_t mLength;
   float mSampleRate;
 };
 
 }
 }
--- a/content/media/webaudio/AudioContext.cpp
+++ b/content/media/webaudio/AudioContext.cpp
@@ -43,19 +43,19 @@ AudioContext::~AudioContext()
 JSObject*
 AudioContext::WrapObject(JSContext* aCx, JSObject* aScope,
                          bool* aTriedToWrap)
 {
   return AudioContextBinding::Wrap(aCx, aScope, this, aTriedToWrap);
 }
 
 /* static */ already_AddRefed<AudioContext>
-AudioContext::Constructor(nsISupports* aGlobal, ErrorResult& aRv)
+AudioContext::Constructor(const GlobalObject& aGlobal, ErrorResult& aRv)
 {
-  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal);
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aGlobal.Get());
   if (!window) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   AudioContext* object = new AudioContext(window);
   NS_ADDREF(object);
   window->AddAudioContext(object);
--- a/content/media/webaudio/AudioContext.h
+++ b/content/media/webaudio/AudioContext.h
@@ -26,16 +26,17 @@ namespace dom {
 class AudioBuffer;
 class AudioBufferSourceNode;
 class AudioDestinationNode;
 class AudioListener;
 class BiquadFilterNode;
 class DelayNode;
 class DynamicsCompressorNode;
 class GainNode;
+class GlobalObject;
 class PannerNode;
 
 class AudioContext MOZ_FINAL : public nsWrapperCache,
                                public EnableWebAudioCheck
 {
   explicit AudioContext(nsIDOMWindow* aParentWindow);
 
 public:
@@ -50,17 +51,17 @@ public:
   }
 
   void Shutdown() {}
 
   virtual JSObject* WrapObject(JSContext* aCx, JSObject* aScope,
                                bool* aTriedToWrap);
 
   static already_AddRefed<AudioContext>
-  Constructor(nsISupports* aGlobal, ErrorResult& aRv);
+  Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
 
   AudioDestinationNode* Destination() const
   {
     return mDestination;
   }
 
   float SampleRate() const
   {
--- a/content/smil/nsSMILAnimationController.cpp
+++ b/content/smil/nsSMILAnimationController.cpp
@@ -739,16 +739,25 @@ nsSMILAnimationController::AddAnimationT
 
     // We've now made sure that |func|'s inactivity will be reflected as of
     // this sample. We need to clear its HasChanged() flag so that it won't
     // trigger this same clause in future samples (until it changes again).
     func.ClearHasChanged();
   }
 }
 
+static inline bool
+IsTransformAttribute(int32_t aNamespaceID, nsIAtom *aAttributeName)
+{
+  return aNamespaceID == kNameSpaceID_None &&
+         (aAttributeName == nsGkAtoms::transform ||
+          aAttributeName == nsGkAtoms::patternTransform ||
+          aAttributeName == nsGkAtoms::gradientTransform);
+}
+
 // Helper function that, given a nsISMILAnimationElement, looks up its target
 // element & target attribute and populates a nsSMILTargetIdentifier
 // for this target.
 /*static*/ bool
 nsSMILAnimationController::GetTargetIdentifierForAnimation(
     nsISMILAnimationElement* aAnimElem, nsSMILTargetIdentifier& aResult)
 {
   // Look up target (animated) element
@@ -762,16 +771,22 @@ nsSMILAnimationController::GetTargetIden
   // have an XMLNS prefix to indicate the XML namespace.
   nsCOMPtr<nsIAtom> attributeName;
   int32_t attributeNamespaceID;
   if (!aAnimElem->GetTargetAttributeName(&attributeNamespaceID,
                                          getter_AddRefs(attributeName)))
     // Animation has no target attr -- skip it.
     return false;
 
+  // animateTransform can only animate transforms, conversely transforms
+  // can only be animated by animateTransform
+  if (IsTransformAttribute(attributeNamespaceID, attributeName) !=
+      aAnimElem->AsElement().IsSVG(nsGkAtoms::animateTransform))
+    return false;
+
   // Look up target (animated) attribute-type
   nsSMILTargetAttrType attributeType = aAnimElem->GetTargetAttributeType();
 
   // Check if an 'auto' attributeType refers to a CSS property or XML attribute.
   // Note that SMIL requires we search for CSS properties first. So if they
   // overlap, 'auto' = 'CSS'. (SMILANIM 3.1)
   bool isCSS = false;
   if (attributeType == eSMILTargetAttrType_auto) {
--- a/content/svg/content/src/SVGAnimateTransformElement.cpp
+++ b/content/svg/content/src/SVGAnimateTransformElement.cpp
@@ -76,25 +76,11 @@ NS_IMPL_ELEMENT_CLONE_WITH_INIT(SVGAnima
 // nsISMILAnimationElement methods
 
 nsSMILAnimationFunction&
 SVGAnimateTransformElement::AnimationFunction()
 {
   return mAnimationFunction;
 }
 
-bool
-SVGAnimateTransformElement::GetTargetAttributeName(int32_t *aNamespaceID,
-                                                   nsIAtom **aLocalName) const
-{
-  if (SVGAnimationElement::GetTargetAttributeName(aNamespaceID,
-                                                  aLocalName)) {
-    return *aNamespaceID == kNameSpaceID_None &&
-           (*aLocalName == nsGkAtoms::transform ||
-            *aLocalName == nsGkAtoms::patternTransform ||
-            *aLocalName == nsGkAtoms::gradientTransform);
-  }
-  return false;
-}
-
 } // namespace dom
 } // namespace mozilla
 
--- a/content/svg/content/src/SVGAnimateTransformElement.h
+++ b/content/svg/content/src/SVGAnimateTransformElement.h
@@ -45,18 +45,16 @@ public:
   // Element specializations
   bool ParseAttribute(int32_t aNamespaceID,
                         nsIAtom* aAttribute,
                         const nsAString& aValue,
                         nsAttrValue& aResult);
 
   // nsISMILAnimationElement
   virtual nsSMILAnimationFunction& AnimationFunction();
-  virtual bool GetTargetAttributeName(int32_t *aNamespaceID,
-                                      nsIAtom **aLocalName) const;
 
   virtual nsXPCClassInfo* GetClassInfo();
 
   virtual nsIDOMNode* AsDOMNode() { return this; }
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/content/svg/content/src/SVGSVGElement.cpp
+++ b/content/svg/content/src/SVGSVGElement.cpp
@@ -424,132 +424,107 @@ SVGSVGElement::PauseAnimations()
   ErrorResult rv;
   PauseAnimations(rv);
   return rv.ErrorCode();
 }
 
 void
 SVGSVGElement::PauseAnimations(ErrorResult& rv)
 {
-  if (NS_SMILEnabled()) {
-    if (mTimedDocumentRoot) {
-      mTimedDocumentRoot->Pause(nsSMILTimeContainer::PAUSE_SCRIPT);
-    }
-    // else we're not the outermost <svg> or not bound to a tree, so silently fail
-    return;
+  if (mTimedDocumentRoot) {
+    mTimedDocumentRoot->Pause(nsSMILTimeContainer::PAUSE_SCRIPT);
   }
-  NS_NOTYETIMPLEMENTED("SVGSVGElement::PauseAnimations");
-  rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+  // else we're not the outermost <svg> or not bound to a tree, so silently fail
 }
 
 /* void unpauseAnimations (); */
 NS_IMETHODIMP
 SVGSVGElement::UnpauseAnimations()
 {
   ErrorResult rv;
   UnpauseAnimations(rv);
   return rv.ErrorCode();
 }
 
 void
 SVGSVGElement::UnpauseAnimations(ErrorResult& rv)
 {
-  if (NS_SMILEnabled()) {
-    if (mTimedDocumentRoot) {
-      mTimedDocumentRoot->Resume(nsSMILTimeContainer::PAUSE_SCRIPT);
-    }
-    // else we're not the outermost <svg> or not bound to a tree, so silently fail
-    return;
+  if (mTimedDocumentRoot) {
+    mTimedDocumentRoot->Resume(nsSMILTimeContainer::PAUSE_SCRIPT);
   }
-  NS_NOTYETIMPLEMENTED("SVGSVGElement::UnpauseAnimations");
-  rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+  // else we're not the outermost <svg> or not bound to a tree, so silently fail
 }
 
 /* boolean animationsPaused (); */
 NS_IMETHODIMP
 SVGSVGElement::AnimationsPaused(bool *_retval)
 {
   ErrorResult rv;
   *_retval = AnimationsPaused(rv);
   return rv.ErrorCode();
 }
 
 bool
 SVGSVGElement::AnimationsPaused(ErrorResult& rv)
 {
-  if (NS_SMILEnabled()) {
-    nsSMILTimeContainer* root = GetTimedDocumentRoot();
-    return root && root->IsPausedByType(nsSMILTimeContainer::PAUSE_SCRIPT);
-  }
-  NS_NOTYETIMPLEMENTED("SVGSVGElement::AnimationsPaused");
-  rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
-  return false;
+  nsSMILTimeContainer* root = GetTimedDocumentRoot();
+  return root && root->IsPausedByType(nsSMILTimeContainer::PAUSE_SCRIPT);
 }
 
 /* float getCurrentTime (); */
 NS_IMETHODIMP
 SVGSVGElement::GetCurrentTime(float *_retval)
 {
   ErrorResult rv;
   *_retval = GetCurrentTime(rv);
   return rv.ErrorCode();
 }
 
 float
 SVGSVGElement::GetCurrentTime(ErrorResult& rv)
 {
-  if (NS_SMILEnabled()) {
-    nsSMILTimeContainer* root = GetTimedDocumentRoot();
-    if (root) {
-      double fCurrentTimeMs = double(root->GetCurrentTime());
-      return (float)(fCurrentTimeMs / PR_MSEC_PER_SEC);
-    } else {
-      return 0.f;
-    }
+  nsSMILTimeContainer* root = GetTimedDocumentRoot();
+  if (root) {
+    double fCurrentTimeMs = double(root->GetCurrentTime());
+    return (float)(fCurrentTimeMs / PR_MSEC_PER_SEC);
+  } else {
+    return 0.f;
   }
-  NS_NOTYETIMPLEMENTED("SVGSVGElement::GetCurrentTime");
-  rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
-  return 0.f;
 }
 
 /* void setCurrentTime (in float seconds); */
 NS_IMETHODIMP
 SVGSVGElement::SetCurrentTime(float seconds)
 {
   NS_ENSURE_FINITE(seconds, NS_ERROR_ILLEGAL_VALUE);
   ErrorResult rv;
   SetCurrentTime(seconds, rv);
   return rv.ErrorCode();
 }
 
 void
 SVGSVGElement::SetCurrentTime(float seconds, ErrorResult &rv)
 {
-  if (NS_SMILEnabled()) {
-    if (mTimedDocumentRoot) {
-      // Make sure the timegraph is up-to-date
-      FlushAnimations();
-      double fMilliseconds = double(seconds) * PR_MSEC_PER_SEC;
-      // Round to nearest whole number before converting, to avoid precision
-      // errors
-      nsSMILTime lMilliseconds = int64_t(NS_round(fMilliseconds));
-      mTimedDocumentRoot->SetCurrentTime(lMilliseconds);
-      AnimationNeedsResample();
-      // Trigger synchronous sample now, to:
-      //  - Make sure we get an up-to-date paint after this method
-      //  - re-enable event firing (it got disabled during seeking, and it
-      //  doesn't get re-enabled until the first sample after the seek -- so
-      //  let's make that happen now.)
-      FlushAnimations();
-    } // else we're not the outermost <svg> or not bound to a tree, so silently
-      // fail
-    return;
+  if (mTimedDocumentRoot) {
+    // Make sure the timegraph is up-to-date
+    FlushAnimations();
+    double fMilliseconds = double(seconds) * PR_MSEC_PER_SEC;
+    // Round to nearest whole number before converting, to avoid precision
+    // errors
+    nsSMILTime lMilliseconds = int64_t(NS_round(fMilliseconds));
+    mTimedDocumentRoot->SetCurrentTime(lMilliseconds);
+    AnimationNeedsResample();
+    // Trigger synchronous sample now, to:
+    //  - Make sure we get an up-to-date paint after this method
+    //  - re-enable event firing (it got disabled during seeking, and it
+    //  doesn't get re-enabled until the first sample after the seek -- so
+    //  let's make that happen now.)
+    FlushAnimations();
   }
-  NS_NOTYETIMPLEMENTED("SVGSVGElement::SetCurrentTime");
-  rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+  // else we're not the outermost <svg> or not bound to a tree, so silently fail
 }
 
 /* nsIDOMSVGNumber createSVGNumber (); */
 NS_IMETHODIMP
 SVGSVGElement::CreateSVGNumber(nsIDOMSVGNumber **_retval)
 {
   *_retval = CreateSVGNumber().get();
   return NS_OK;
--- a/content/svg/content/src/nsSVGElementFactory.cpp
+++ b/content/svg/content/src/nsSVGElementFactory.cpp
@@ -328,25 +328,23 @@ NS_NewSVGElement(nsIContent** aResult, a
   if (name == nsGkAtoms::pattern)
     return NS_NewSVGPatternElement(aResult, aNodeInfo);
   if (name == nsGkAtoms::mask)
     return NS_NewSVGMaskElement(aResult, aNodeInfo);
   if (name == nsGkAtoms::svgSwitch)
     return NS_NewSVGSwitchElement(aResult, aNodeInfo);
   if (name == nsGkAtoms::view)
     return NS_NewSVGViewElement(aResult, aNodeInfo);
-  if (NS_SMILEnabled()) {
-    if (name == nsGkAtoms::animate)
-      return NS_NewSVGAnimateElement(aResult, aNodeInfo);
-    if (name == nsGkAtoms::animateTransform)
-      return NS_NewSVGAnimateTransformElement(aResult, aNodeInfo);
-    if (name == nsGkAtoms::animateMotion)
-      return NS_NewSVGAnimateMotionElement(aResult, aNodeInfo);
-    if (name == nsGkAtoms::mpath)
-      return NS_NewSVGMPathElement(aResult, aNodeInfo);
-    if (name == nsGkAtoms::set)
-      return NS_NewSVGSetElement(aResult, aNodeInfo);
-  }
+  if (name == nsGkAtoms::animate)
+    return NS_NewSVGAnimateElement(aResult, aNodeInfo);
+  if (name == nsGkAtoms::animateTransform)
+    return NS_NewSVGAnimateTransformElement(aResult, aNodeInfo);
+  if (name == nsGkAtoms::animateMotion)
+    return NS_NewSVGAnimateMotionElement(aResult, aNodeInfo);
+  if (name == nsGkAtoms::mpath)
+    return NS_NewSVGMPathElement(aResult, aNodeInfo);
+  if (name == nsGkAtoms::set)
+    return NS_NewSVGSetElement(aResult, aNodeInfo);
 
   // if we don't know what to create, just create a standard svg element:
   return NS_NewSVGUnknownElement(aResult, aNodeInfo);
 }
 
--- a/content/svg/content/test/Makefile.in
+++ b/content/svg/content/test/Makefile.in
@@ -49,17 +49,16 @@ MOCHITEST_FILES = \
 		test_pathSeg.xhtml \
 		test_pointAtLength.xhtml \
 		test_pointer-events.xhtml \
 		test_pointer-events-2.xhtml \
 		test_pointer-events-3.xhtml \
 		test_pointer-events-4.xhtml \
 		test_scientific.html \
 		scientific-helper.svg \
-		test_SVGAnimatedImageSMILDisabled.html \
 		animated-svg-image-helper.html \
 		animated-svg-image-helper.svg \
 		test_stroke-linecap-hit-testing.xhtml \
 		test_SVG_namespace_ids.html \
 		test_SVGLengthList.xhtml \
 		test_SVGLengthList-2.xhtml \
 		test_SVGMatrix.xhtml \
 		test_SVGNumberList.xhtml \
deleted file mode 100644
--- a/content/svg/content/test/test_SVGAnimatedImageSMILDisabled.html
+++ /dev/null
@@ -1,57 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=623945
--->
-<head>
-  <title>Test for Bug 623945</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=623945">Mozilla Bug 623945</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-</div>
-<pre id="test">
-<script type="application/javascript">
-
-/** Test for Bug 623945 **/
-/* This test makes sure we don't crash when using an animated SVG image with
- * the 'svg.smil.enabled' pref turned off. */
-
-SimpleTest.waitForExplicitFinish();
-
-function setSMILEnabled(enabled) {
-  netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
-  var prefs = Components.classes["@mozilla.org/preferences-service;1"]
-                 .getService(Components.interfaces.nsIPrefBranch);
-  prefs.setBoolPref("svg.smil.enabled", enabled);
-}
-
-function iframeLoaded() {
-  // Woo-hoo, we didn't crash! Declare success!
-  ok(true, "got through the test without crashing");
-
-  // Re-enable SMIL pref for future tests.
-  setSMILEnabled(true);
-  SimpleTest.finish();
-}
-
-function main() {
-  // First, we turn off the SMIL pref:
-  setSMILEnabled(false);
-
-  // Then, we load an HTML document with an animated SVG image.
-  var iframe = document.createElement("iframe");
-  iframe.onload = iframeLoaded;
-  iframe.src = "animated-svg-image-helper.html";
-  document.getElementById("content").appendChild(iframe);
-}
-
-main();
-
-</script>
-</pre>
-</body>
-</html>
--- a/content/xslt/src/xpath/nsXPathEvaluator.cpp
+++ b/content/xslt/src/xpath/nsXPathEvaluator.cpp
@@ -17,16 +17,17 @@
 #include "nsIDocument.h"
 #include "nsIDOMDocument.h"
 #include "nsDOMString.h"
 #include "nsINameSpaceManager.h"
 #include "nsContentUtils.h"
 #include "mozilla/dom/XPathEvaluatorBinding.h"
 
 using namespace mozilla;
+using namespace mozilla::dom;
 
 // txIParseContext implementation
 class nsXPathEvaluatorParseContext : public txIParseContext
 {
 public:
     nsXPathEvaluatorParseContext(nsIDOMXPathNSResolver* aResolver,
                                  nsTArray<int32_t> *aNamespaceIDs,
                                  nsTArray<nsCString> *aContractIDs,
@@ -215,17 +216,17 @@ nsXPathEvaluator::CreateExpression(const
 JSObject*
 nsXPathEvaluator::WrapObject(JSContext* aCx, JSObject* aScope)
 {
     return dom::XPathEvaluatorBinding::Wrap(aCx, aScope, this);
 }
 
 /* static */
 already_AddRefed<nsXPathEvaluator>
-nsXPathEvaluator::Constructor(nsISupports* aGlobal, ErrorResult& rv)
+nsXPathEvaluator::Constructor(const GlobalObject& aGlobal, ErrorResult& rv)
 {
     nsRefPtr<nsXPathEvaluator> newObj = new nsXPathEvaluator(nullptr);
     newObj->Init();
     return newObj.forget();
 }
 
 already_AddRefed<nsIDOMXPathExpression>
 nsXPathEvaluator::CreateExpression(const nsAString& aExpression,
--- a/content/xslt/src/xpath/nsXPathEvaluator.h
+++ b/content/xslt/src/xpath/nsXPathEvaluator.h
@@ -12,16 +12,21 @@
 #include "nsAutoPtr.h"
 #include "nsString.h"
 #include "txResultRecycler.h"
 #include "nsAgg.h"
 #include "nsTArray.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ErrorResult.h"
 
+namespace mozilla {
+namespace dom {
+class GlobalObject;
+}
+}
 class nsINode;
 
 /**
  * A class for evaluating an XPath expression string
  */
 class nsXPathEvaluator MOZ_FINAL : public nsIDOMXPathEvaluator,
                                    public nsIXPathEvaluatorInternal
 {
@@ -43,17 +48,18 @@ public:
                                 nsTArray<nsString> *aNamespaceURIs,
                                 nsTArray<nsCString> *aContractIDs,
                                 nsCOMArray<nsISupports> *aState,
                                 nsIDOMXPathExpression **aResult);
 
     // WebIDL API
     JSObject* WrapObject(JSContext* aCx, JSObject* aScope);
     static already_AddRefed<nsXPathEvaluator>
-        Constructor(nsISupports* aGlobal, mozilla::ErrorResult& rv);
+        Constructor(const mozilla::dom::GlobalObject& aGlobal,
+                    mozilla::ErrorResult& rv);
     already_AddRefed<nsIDOMXPathExpression>
         CreateExpression(const nsAString& aExpression,
                          nsIDOMXPathNSResolver* aResolver,
                          mozilla::ErrorResult& rv);
     already_AddRefed<nsIDOMXPathNSResolver>
         CreateNSResolver(nsINode* aNodeResolver, mozilla::ErrorResult& rv);
     already_AddRefed<nsISupports>
         Evaluate(const nsAString& aExpression, nsINode* aContextNode,
--- a/dom/apps/src/PermissionsInstaller.jsm
+++ b/dom/apps/src/PermissionsInstaller.jsm
@@ -85,18 +85,20 @@ this.PermissionsInstaller = {
                                                        aApp.manifestURL,
                                                        aApp.origin,
                                                        false);
               if (permValue == "unknown" || permValue == "deny") {
                 // All 'deny' permissions should be preserved
                 continue;
               }
               // Remove the deprecated permission
-              // TODO: use PermSettings.remove, see bug 793204
-              this._setPermission(permName, "unknown", aApp);
+              PermissionSettingsModule.removePermission(permName,
+                                                        aApp.manifestURL,
+                                                        aApp.origin,
+                                                        false);
             }
           }
         }
       }
 
       // Check to see if the 'webapp' is app/privileged/certified.
       let appStatus;
       switch (AppsUtils.getAppManifestStatus(aApp.manifest)) {
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -62,16 +62,17 @@ AudioChannelService::Shutdown()
     gAudioChannelService = nullptr;
   }
 }
 
 NS_IMPL_ISUPPORTS0(AudioChannelService)
 
 AudioChannelService::AudioChannelService()
 : mCurrentHigherChannel(AUDIO_CHANNEL_LAST)
+, mCurrentVisibleHigherChannel(AUDIO_CHANNEL_LAST)
 , mActiveContentChildIDsFrozen(false)
 {
   // Creation of the hash table.
   mAgents.Init();
 
   if (XRE_GetProcessType() == GeckoProcessType_Default) {
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     if (obs) {
@@ -209,20 +210,21 @@ AudioChannelService::GetMutedInternal(Au
     MOZ_ASSERT(newType != AUDIO_CHANNEL_INT_NORMAL_HIDDEN);
     muted = ChannelsActiveWithHigherPriorityThan(newType);
   }
 
   return muted;
 }
 
 bool
-AudioChannelService::ContentChannelIsActive()
+AudioChannelService::ContentOrNormalChannelIsActive()
 {
   return !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty() ||
-         !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].IsEmpty();
+         !mChannelCounters[AUDIO_CHANNEL_INT_CONTENT_HIDDEN].IsEmpty() ||
+         !mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].IsEmpty();
 }
 
 void
 AudioChannelService::SendAudioChannelChangedNotification()
 {
   if (XRE_GetProcessType() != GeckoProcessType_Default) {
     return;
   }
@@ -254,55 +256,73 @@ AudioChannelService::SendAudioChannelCha
   else if (!mChannelCounters[AUDIO_CHANNEL_INT_CONTENT].IsEmpty()) {
     higher = AUDIO_CHANNEL_CONTENT;
   }
 
   else if (!mChannelCounters[AUDIO_CHANNEL_INT_NORMAL].IsEmpty()) {
     higher = AUDIO_CHANNEL_NORMAL;
   }
 
+  AudioChannelType visibleHigher = higher;
+
   // Top-Down in the hierarchy for non-visible elements
-  else if (!mChannelCounters[AUDIO_CHANNEL_INT_PUBLICNOTIFICATION_HIDDEN].IsEmpty()) {
-    higher = AUDIO_CHANNEL_PUBLICNOTIFICATION;
-  }
+  if (higher == AUDIO_CHANNEL_LAST) {
+    if (!mChannelCounters[AUDIO_CHANNEL_INT_PUBLICNOTIFICATION_HIDDEN].IsEmpty()) {
+      higher = AUDIO_CHANNEL_PUBLICNOTIFICATION;
+    }
 
-  else if (!mChannelCounters[AUDIO_CHANNEL_INT_RINGER_HIDDEN].IsEmpty()) {
-    higher = AUDIO_CHANNEL_RINGER;
-  }
+    else if (!mChannelCounters[AUDIO_CHANNEL_INT_RINGER_HIDDEN].IsEmpty()) {
+      higher = AUDIO_CHANNEL_RINGER;
+    }
 
-  else if (!mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN].IsEmpty()) {
-    higher = AUDIO_CHANNEL_TELEPHONY;
-  }
+    else if (!mChannelCounters[AUDIO_CHANNEL_INT_TELEPHONY_HIDDEN].IsEmpty()) {
+      higher = AUDIO_CHANNEL_TELEPHONY;
+    }
 
-  else if (!mChannelCounters[AUDIO_CHANNEL_INT_ALARM_HIDDEN].IsEmpty()) {
-    higher = AUDIO_CHANNEL_ALARM;
-  }
+    else if (!mChannelCounters[AUDIO_CHANNEL_INT_ALARM_HIDDEN].IsEmpty()) {
+      higher = AUDIO_CHANNEL_ALARM;
+    }
 
-  else if (!mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN].IsEmpty()) {
-    higher = AUDIO_CHANNEL_NOTIFICATION;
-  }
+    else if (!mChannelCounters[AUDIO_CHANNEL_INT_NOTIFICATION_HIDDEN].IsEmpty()) {
+      higher = AUDIO_CHANNEL_NOTIFICATION;
+    }
 
-  // Content channels play in background if just one is active.
-  else if (!mActiveContentChildIDs.IsEmpty()) {
-    higher = AUDIO_CHANNEL_CONTENT;
+    // Content channels play in background if just one is active.
+    else if (!mActiveContentChildIDs.IsEmpty()) {
+      higher = AUDIO_CHANNEL_CONTENT;
+    }
   }
 
   if (higher != mCurrentHigherChannel) {
     mCurrentHigherChannel = higher;
 
     nsString channelName;
     if (mCurrentHigherChannel != AUDIO_CHANNEL_LAST) {
       channelName.AssignASCII(ChannelName(mCurrentHigherChannel));
     } else {
       channelName.AssignLiteral("none");
     }
 
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     obs->NotifyObservers(nullptr, "audio-channel-changed", channelName.get());
   }
+
+  if (visibleHigher != mCurrentVisibleHigherChannel) {
+    mCurrentVisibleHigherChannel = visibleHigher;
+
+    nsString channelName;
+    if (mCurrentVisibleHigherChannel != AUDIO_CHANNEL_LAST) {
+      channelName.AssignASCII(ChannelName(mCurrentVisibleHigherChannel));
+    } else {
+      channelName.AssignLiteral("none");
+    }
+
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    obs->NotifyObservers(nullptr, "visible-audio-channel-changed", channelName.get());
+  }
 }
 
 PLDHashOperator
 AudioChannelService::NotifyEnumerator(AudioChannelAgent* aAgent,
                                       AudioChannelAgentData* aData, void* aUnused)
 {
   MOZ_ASSERT(aAgent);
   aAgent->NotifyAudioChannelStateChanged();
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -52,17 +52,17 @@ public:
    * Return true if this type should be muted.
    */
   virtual bool GetMuted(AudioChannelAgent* aAgent, bool aElementHidden);
 
   /**
    * Return true if there is a content channel active in this process
    * or one of its subprocesses.
    */
-  virtual bool ContentChannelIsActive();
+  virtual bool ContentOrNormalChannelIsActive();
 
 protected:
   void Notify();
 
   /**
    * Send the audio-channel-changed notification if needed.
    */
   void SendAudioChannelChangedNotification();
@@ -122,16 +122,17 @@ protected:
   NotifyEnumerator(AudioChannelAgent* aAgent,
                    AudioChannelAgentData* aData, void *aUnused);
 
   nsClassHashtable< nsPtrHashKey<AudioChannelAgent>, AudioChannelAgentData > mAgents;
 
   nsTArray<uint64_t> mChannelCounters[AUDIO_CHANNEL_INT_LAST];
 
   AudioChannelType mCurrentHigherChannel;
+  AudioChannelType mCurrentVisibleHigherChannel;
 
   nsTArray<uint64_t> mActiveContentChildIDs;
   bool mActiveContentChildIDsFrozen;
 
   // This is needed for IPC comunication between
   // AudioChannelServiceChild and this class.
   friend class ContentParent;
   friend class ContentChild;
--- a/dom/base/URL.cpp
+++ b/dom/base/URL.cpp
@@ -1,101 +1,103 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/. */
-
-#include "URL.h"
-
-#include "nsGlobalWindow.h"
-#include "nsIDOMFile.h"
-#include "nsIDOMMediaStream.h"
-#include "nsIDocument.h"
-#include "nsIPrincipal.h"
-#include "nsContentUtils.h"
-#include "nsHostObjectProtocolHandler.h"
-
-namespace mozilla {
-namespace dom {
-
-void
-URL::CreateObjectURL(nsISupports* aGlobal, nsIDOMBlob* aBlob,
-                     const objectURLOptions& aOptions,
-                     nsAString& aResult,
-                     ErrorResult& aError)
-{
-  CreateObjectURLInternal(aGlobal, aBlob, NS_LITERAL_CSTRING(BLOBURI_SCHEME),
-                          aOptions, aResult, aError);
-}
-
-void
-URL::CreateObjectURL(nsISupports* aGlobal, nsIDOMMediaStream* aStream,
-                     const mozilla::dom::objectURLOptions& aOptions,
-                     nsAString& aResult,
-                     ErrorResult& aError)
-{
-  CreateObjectURLInternal(aGlobal, aStream, NS_LITERAL_CSTRING(MEDIASTREAMURI_SCHEME),
-                          aOptions, aResult, aError);
-}
-
-void
-URL::CreateObjectURLInternal(nsISupports* aGlobal, nsISupports* aObject,
-                             const nsACString& aScheme,
-                             const mozilla::dom::objectURLOptions& aOptions,
-                             nsAString& aResult,
-                             ErrorResult& aError)
-{
-  nsCOMPtr<nsPIDOMWindow> w = do_QueryInterface(aGlobal);
-  nsGlobalWindow* window = static_cast<nsGlobalWindow*>(w.get());
-  NS_PRECONDITION(!window || window->IsInnerWindow(),
-                  "Should be inner window");
-
-  if (!window || !window->GetExtantDoc()) {
-    aError.Throw(NS_ERROR_INVALID_POINTER);
-    return;
-  }
-
-  nsIDocument* doc = window->GetExtantDoc();
-
-  nsCString url;
-  nsresult rv = nsHostObjectProtocolHandler::AddDataEntry(aScheme, aObject,
-    doc->NodePrincipal(), url);
-  if (NS_FAILED(rv)) {
-    aError.Throw(rv);
-    return;
-  }
-
-  doc->RegisterHostObjectUri(url);
-  CopyASCIItoUTF16(url, aResult);
-}
-
-void
-URL::RevokeObjectURL(nsISupports* aGlobal, const nsAString& aURL)
-{
-  nsCOMPtr<nsPIDOMWindow> w = do_QueryInterface(aGlobal);
-  nsGlobalWindow* window = static_cast<nsGlobalWindow*>(w.get());
-  NS_PRECONDITION(!window || window->IsInnerWindow(),
-                  "Should be inner window");
-  if (!window)
-    return;
-
-  NS_LossyConvertUTF16toASCII asciiurl(aURL);
-
-  nsIPrincipal* winPrincipal = window->GetPrincipal();
-  if (!winPrincipal) {
-    return;
-  }
-
-  nsIPrincipal* principal =
-    nsHostObjectProtocolHandler::GetDataEntryPrincipal(asciiurl);
-  bool subsumes;
-  if (principal && winPrincipal &&
-      NS_SUCCEEDED(winPrincipal->Subsumes(principal, &subsumes)) &&
-      subsumes) {
-    if (window->GetExtantDoc()) {
-      window->GetExtantDoc()->UnregisterHostObjectUri(asciiurl);
-    }
-    nsHostObjectProtocolHandler::RemoveDataEntry(asciiurl);
-  }
-}
-
-}
-}
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "URL.h"
+
+#include "nsGlobalWindow.h"
+#include "nsIDOMFile.h"
+#include "nsIDOMMediaStream.h"
+#include "nsIDocument.h"
+#include "nsIPrincipal.h"
+#include "nsContentUtils.h"
+#include "nsHostObjectProtocolHandler.h"
+
+namespace mozilla {
+namespace dom {
+
+void
+URL::CreateObjectURL(const GlobalObject& aGlobal, nsIDOMBlob* aBlob,
+                     const objectURLOptions& aOptions,
+                     nsString& aResult,
+                     ErrorResult& aError)
+{
+  CreateObjectURLInternal(aGlobal.Get(), aBlob,
+                          NS_LITERAL_CSTRING(BLOBURI_SCHEME), aOptions, aResult,
+                          aError);
+}
+
+void
+URL::CreateObjectURL(const GlobalObject& aGlobal, nsIDOMMediaStream* aStream,
+                     const mozilla::dom::objectURLOptions& aOptions,
+                     nsString& aResult,
+                     ErrorResult& aError)
+{
+  CreateObjectURLInternal(aGlobal.Get(), aStream,
+                          NS_LITERAL_CSTRING(MEDIASTREAMURI_SCHEME), aOptions,
+                          aResult, aError);
+}
+
+void
+URL::CreateObjectURLInternal(nsISupports* aGlobal, nsISupports* aObject,
+                             const nsACString& aScheme,
+                             const mozilla::dom::objectURLOptions& aOptions,
+                             nsString& aResult,
+                             ErrorResult& aError)
+{
+  nsCOMPtr<nsPIDOMWindow> w = do_QueryInterface(aGlobal);
+  nsGlobalWindow* window = static_cast<nsGlobalWindow*>(w.get());
+  NS_PRECONDITION(!window || window->IsInnerWindow(),
+                  "Should be inner window");
+
+  if (!window || !window->GetExtantDoc()) {
+    aError.Throw(NS_ERROR_INVALID_POINTER);
+    return;
+  }
+
+  nsIDocument* doc = window->GetExtantDoc();
+
+  nsCString url;
+  nsresult rv = nsHostObjectProtocolHandler::AddDataEntry(aScheme, aObject,
+    doc->NodePrincipal(), url);
+  if (NS_FAILED(rv)) {
+    aError.Throw(rv);
+    return;
+  }
+
+  doc->RegisterHostObjectUri(url);
+  CopyASCIItoUTF16(url, aResult);
+}
+
+void
+URL::RevokeObjectURL(const GlobalObject& aGlobal, const nsAString& aURL)
+{
+  nsCOMPtr<nsPIDOMWindow> w = do_QueryInterface(aGlobal.Get());
+  nsGlobalWindow* window = static_cast<nsGlobalWindow*>(w.get());
+  NS_PRECONDITION(!window || window->IsInnerWindow(),
+                  "Should be inner window");
+  if (!window)
+    return;
+
+  NS_LossyConvertUTF16toASCII asciiurl(aURL);
+
+  nsIPrincipal* winPrincipal = window->GetPrincipal();
+  if (!winPrincipal) {
+    return;
+  }
+
+  nsIPrincipal* principal =
+    nsHostObjectProtocolHandler::GetDataEntryPrincipal(asciiurl);
+  bool subsumes;
+  if (principal && winPrincipal &&
+      NS_SUCCEEDED(winPrincipal->Subsumes(principal, &subsumes)) &&
+      subsumes) {
+    if (window->GetExtantDoc()) {
+      window->GetExtantDoc()->UnregisterHostObjectUri(asciiurl);
+    }
+    nsHostObjectProtocolHandler::RemoveDataEntry(asciiurl);
+  }
+}
+
+}
+}
--- a/dom/base/URL.h
+++ b/dom/base/URL.h
@@ -1,42 +1,44 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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 URL_h___
-#define URL_h___
-
-#include "nscore.h"
-#include "mozilla/dom/URLBinding.h"
-
-class nsIDOMBlob;
-class nsIDOMMediaStream;
-
-namespace mozilla {
-namespace dom {
-
-class URL MOZ_FINAL
-{
-public:
-  // WebIDL methods
-  static void CreateObjectURL(nsISupports* aGlobal, nsIDOMBlob* aBlob,
-                              const objectURLOptions& aOptions,
-                              nsAString& aResult,
-                              ErrorResult& aError);
-  static void CreateObjectURL(nsISupports* aGlobal, nsIDOMMediaStream* aStream,
-                              const mozilla::dom::objectURLOptions& aOptions,
-                              nsAString& aResult,
-                              mozilla::ErrorResult& aError);
-  static void RevokeObjectURL(nsISupports* aGlobal, const nsAString& aURL);
-
-private:
-  static void CreateObjectURLInternal(nsISupports* aGlobal, nsISupports* aObject,
-                                      const nsACString& aScheme,
-                                      const mozilla::dom::objectURLOptions& aOptions,
-                                      nsAString& aResult,
-                                      mozilla::ErrorResult& aError);
-};
-
-}
-}
-
-#endif /* URL_h___ */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 URL_h___
+#define URL_h___
+
+#include "nscore.h"
+#include "mozilla/dom/URLBinding.h"
+
+class nsIDOMBlob;
+class nsIDOMMediaStream;
+
+namespace mozilla {
+namespace dom {
+
+class URL MOZ_FINAL
+{
+public:
+  // WebIDL methods
+  static void CreateObjectURL(const GlobalObject& aGlobal, nsIDOMBlob* aBlob,
+                              const objectURLOptions& aOptions,
+                              nsString& aResult,
+                              ErrorResult& aError);
+  static void CreateObjectURL(const GlobalObject& aGlobal,
+                              nsIDOMMediaStream* aStream,
+                              const mozilla::dom::objectURLOptions& aOptions,
+                              nsString& aResult,
+                              mozilla::ErrorResult& aError);
+  static void RevokeObjectURL(const GlobalObject& aGlobal,
+                              const nsAString& aURL);
+
+private:
+  static void CreateObjectURLInternal(nsISupports* aGlobal, nsISupports* aObject,
+                                      const nsACString& aScheme,
+                                      const mozilla::dom::objectURLOptions& aOptions,
+                                      nsString& aResult,
+                                      mozilla::ErrorResult& aError);
+};
+
+}
+}
+
+#endif /* URL_h___ */
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -459,17 +459,16 @@ using mozilla::dom::workers::ResolveWork
 #endif
 #include "USSDReceivedEvent.h"
 #include "DataErrorEvent.h"
 #include "mozilla/dom/network/Utils.h"
 
 #ifdef MOZ_B2G_RIL
 #include "Telephony.h"
 #include "TelephonyCall.h"
-#include "CallEvent.h"
 #include "nsIDOMVoicemail.h"
 #include "nsIDOMVoicemailEvent.h"
 #include "nsIDOMIccManager.h"
 #include "StkCommandEvent.h"
 #include "nsIDOMMozCellBroadcast.h"
 #include "nsIDOMMozCellBroadcastEvent.h"
 #include "CFStateChangeEvent.h"
 #endif // MOZ_B2G_RIL
@@ -1495,18 +1494,16 @@ static nsDOMClassInfoData sClassInfoData
   NS_DEFINE_CLASSINFO_DATA(MediaQueryList, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
 #ifdef MOZ_B2G_RIL
   NS_DEFINE_CLASSINFO_DATA(Telephony, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(TelephonyCall, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
-  NS_DEFINE_CLASSINFO_DATA(CallEvent, nsDOMGenericSH,
-                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(MozVoicemail, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(MozVoicemailEvent, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(MozIccManager, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(MozStkCommandEvent, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
@@ -3906,21 +3903,16 @@ nsDOMClassInfo::Init()
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(TelephonyCall, nsIDOMTelephonyCall)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMTelephonyCall)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
   DOM_CLASSINFO_MAP_END
 
-  DOM_CLASSINFO_MAP_BEGIN(CallEvent, nsIDOMCallEvent)
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMCallEvent)
-    DOM_CLASSINFO_MAP_ENTRY(nsIDOMEvent)
-  DOM_CLASSINFO_MAP_END
-
   DOM_CLASSINFO_MAP_BEGIN(MozVoicemail, nsIDOMMozVoicemail)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozVoicemail)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(MozVoicemailEvent, nsIDOMMozVoicemailEvent)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMMozVoicemailEvent)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMEvent)
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -426,17 +426,16 @@ DOMCI_CLASS(MozCSSKeyframesRule)
 
 DOMCI_CLASS(CSSPageRule)
 
 DOMCI_CLASS(MediaQueryList)
 
 #ifdef MOZ_B2G_RIL
 DOMCI_CLASS(Telephony)
 DOMCI_CLASS(TelephonyCall)
-DOMCI_CLASS(CallEvent)
 DOMCI_CLASS(MozVoicemail)
 DOMCI_CLASS(MozVoicemailEvent)
 DOMCI_CLASS(MozIccManager)
 DOMCI_CLASS(MozStkCommandEvent)
 #endif
 
 #ifdef MOZ_B2G_FM
 DOMCI_CLASS(FMRadio)
--- a/dom/base/nsIScriptContext.h
+++ b/dom/base/nsIScriptContext.h
@@ -8,16 +8,17 @@
 
 #include "nscore.h"
 #include "nsStringGlue.h"
 #include "nsISupports.h"
 #include "nsCOMPtr.h"
 #include "nsIProgrammingLanguage.h"
 #include "jsfriendapi.h"
 #include "jspubtd.h"
+#include "js/GCAPI.h"
 
 class nsIScriptGlobalObject;
 class nsIScriptSecurityManager;
 class nsIPrincipal;
 class nsIAtom;
 class nsIArray;
 class nsIVariant;
 class nsIObjectInputStream;
@@ -196,17 +197,17 @@ public:
   virtual bool IsContextInitialized() = 0;
 
   /**
    * For garbage collected systems, do a synchronous collection pass.
    * May be a no-op on other systems
    *
    * @return NS_OK if the method is successful
    */
-  virtual void GC(js::gcreason::Reason aReason) = 0;
+  virtual void GC(JS::gcreason::Reason aReason) = 0;
 
   /**
    * Inform the context that a script was evaluated.
    * A GC may be done if "necessary."
    * This call is necessary if script evaluation is done
    * without using the EvaluateScript method.
    * @param aTerminated If true then call termination function if it was 
    *    previously set. Within DOM this will always be true, but outside 
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -141,17 +141,17 @@ static nsITimer *sCCTimer;
 static nsITimer *sFullGCTimer;
 static nsITimer *sInterSliceGCTimer;
 
 static PRTime sLastCCEndTime;
 
 static bool sCCLockedOut;
 static PRTime sCCLockedOutTime;
 
-static js::GCSliceCallback sPrevGCSliceCallback;
+static JS::GCSliceCallback sPrevGCSliceCallback;
 static js::AnalysisPurgeCallback sPrevAnalysisPurgeCallback;
 
 // The number of currently pending document loads. This count isn't
 // guaranteed to always reflect reality and can't easily as we don't
 // have an easy place to know when a load ends or is interrupted in
 // all cases. This counter also gets reset if we end up GC'ing while
 // we're waiting for a slow page to load. IOW, this count may be 0
 // even when there are pending loads.
@@ -230,17 +230,17 @@ public:
 
 NS_IMPL_ISUPPORTS1(nsJSEnvironmentObserver, nsIObserver)
 
 NS_IMETHODIMP
 nsJSEnvironmentObserver::Observe(nsISupports* aSubject, const char* aTopic,
                                  const PRUnichar* aData)
 {
   if (sGCOnMemoryPressure && !nsCRT::strcmp(aTopic, "memory-pressure")) {
-    nsJSContext::GarbageCollectNow(js::gcreason::MEM_PRESSURE,
+    nsJSContext::GarbageCollectNow(JS::gcreason::MEM_PRESSURE,
                                    nsJSContext::NonIncrementalGC,
                                    nsJSContext::NonCompartmentGC,
                                    nsJSContext::ShrinkingGC);
     nsJSContext::CycleCollectNow();
   } else if (!nsCRT::strcmp(aTopic, "quit-application")) {
     sShuttingDown = true;
     KillTimers();
   }
@@ -1176,17 +1176,17 @@ nsJSContext::DestroyJSContext()
   // Clear our entry in the JSContext, bugzilla bug 66413
   ::JS_SetContextPrivate(mContext, nullptr);
 
   // Unregister our "javascript.options.*" pref-changed callback.
   Preferences::UnregisterCallback(JSOptionChangedCallback,
                                   js_options_dot_str, this);
 
   if (mGCOnDestruction) {
-    PokeGC(js::gcreason::NSJSCONTEXT_DESTROY);
+    PokeGC(JS::gcreason::NSJSCONTEXT_DESTROY);
   }
         
   // Let xpconnect destroy the JSContext when it thinks the time is right.
   nsIXPConnect *xpc = nsContentUtils::XPConnect();
   if (xpc) {
     xpc->ReleaseJSContext(mContext, true);
   } else {
     ::JS_DestroyContextNoGC(mContext);
@@ -2531,23 +2531,23 @@ nsJSContext::ScriptExecuted()
 }
 
 void
 FullGCTimerFired(nsITimer* aTimer, void* aClosure)
 {
   NS_RELEASE(sFullGCTimer);
 
   uintptr_t reason = reinterpret_cast<uintptr_t>(aClosure);
-  nsJSContext::GarbageCollectNow(static_cast<js::gcreason::Reason>(reason),
+  nsJSContext::GarbageCollectNow(static_cast<JS::gcreason::Reason>(reason),
                                  nsJSContext::IncrementalGC);
 }
 
 //static
 void
-nsJSContext::GarbageCollectNow(js::gcreason::Reason aReason,
+nsJSContext::GarbageCollectNow(JS::gcreason::Reason aReason,
                                IsIncremental aIncremental,
                                IsCompartment aCompartment,
                                IsShrinking aShrinking,
                                int64_t aSliceMillis)
 {
   SAMPLE_LABEL("GC", "GarbageCollectNow");
 
   MOZ_ASSERT_IF(aSliceMillis, aIncremental == IncrementalGC);
@@ -2565,83 +2565,83 @@ nsJSContext::GarbageCollectNow(js::gcrea
   sLoadingInProgress = false;
 
   if (!nsContentUtils::XPConnect() || !nsJSRuntime::sRuntime) {
     return;
   }
 
   if (sCCLockedOut && aIncremental == IncrementalGC) {
     // We're in the middle of incremental GC. Do another slice.
-    js::PrepareForIncrementalGC(nsJSRuntime::sRuntime);
-    js::IncrementalGC(nsJSRuntime::sRuntime, aReason, aSliceMillis);
+    JS::PrepareForIncrementalGC(nsJSRuntime::sRuntime);
+    JS::IncrementalGC(nsJSRuntime::sRuntime, aReason, aSliceMillis);
     return;
   }
 
-  // Use compartment GC when we're not asked to do a shrinking GC nor
+  // Use zone GC when we're not asked to do a shrinking GC nor
   // global GC and compartment GC has been called less than
   // NS_MAX_COMPARTMENT_GC_COUNT times after the previous global GC.
   if (!sDisableExplicitCompartmentGC &&
       aShrinking != ShrinkingGC && aCompartment != NonCompartmentGC &&
       sCompartmentGCCount < NS_MAX_COMPARTMENT_GC_COUNT) {
-    js::PrepareForFullGC(nsJSRuntime::sRuntime);
+    JS::PrepareForFullGC(nsJSRuntime::sRuntime);
     for (nsJSContext* cx = sContextList; cx; cx = cx->mNext) {
       if (!cx->mActive && cx->mContext) {
         if (JSObject* global = cx->GetNativeGlobal()) {
-          js::SkipCompartmentForGC(js::GetObjectCompartment(global));
+          JS::SkipZoneForGC(js::GetObjectZone(global));
         }
       }
       cx->mActive = false;
     }
-    if (js::IsGCScheduled(nsJSRuntime::sRuntime)) {
+    if (JS::IsGCScheduled(nsJSRuntime::sRuntime)) {
       if (aIncremental == IncrementalGC) {
-        js::IncrementalGC(nsJSRuntime::sRuntime, aReason, aSliceMillis);
+        JS::IncrementalGC(nsJSRuntime::sRuntime, aReason, aSliceMillis);
       } else {
-        js::GCForReason(nsJSRuntime::sRuntime, aReason);
+        JS::GCForReason(nsJSRuntime::sRuntime, aReason);
       }
     }
     return;
   }
 
   for (nsJSContext* cx = sContextList; cx; cx = cx->mNext) {
     cx->mActive = false;
   }
-  js::PrepareForFullGC(nsJSRuntime::sRuntime);
+  JS::PrepareForFullGC(nsJSRuntime::sRuntime);
   if (aIncremental == IncrementalGC) {
-    js::IncrementalGC(nsJSRuntime::sRuntime, aReason, aSliceMillis);
+    JS::IncrementalGC(nsJSRuntime::sRuntime, aReason, aSliceMillis);
   } else {
-    js::GCForReason(nsJSRuntime::sRuntime, aReason);
+    JS::GCForReason(nsJSRuntime::sRuntime, aReason);
   }
 }
 
 //static
 void
 nsJSContext::ShrinkGCBuffersNow()
 {
   SAMPLE_LABEL("GC", "ShrinkGCBuffersNow");
 
   KillShrinkGCBuffersTimer();
 
-  JS_ShrinkGCBuffers(nsJSRuntime::sRuntime);
+  JS::ShrinkGCBuffers(nsJSRuntime::sRuntime);
 }
 
 // Return true if any JSContext has a "global object" with a gray
 // parent. The intent is to look for JS Object windows. We don't merge
 // system compartments, so we don't use them to trigger merging CCs.
 static bool
 AnyGrayGlobalParent()
 {
   if (!nsJSRuntime::sRuntime) {
     return false;
   }
   JSContext *iter = nullptr;
   JSContext *cx;
   while ((cx = JS_ContextIterator(nsJSRuntime::sRuntime, &iter))) {
     if (JSObject *global = JS_GetGlobalObject(cx)) {
       if (JSObject *parent = js::GetObjectParent(global)) {
-        if (js::GCThingIsMarkedGray(parent) &&
+        if (JS::GCThingIsMarkedGray(parent) &&
             !js::IsSystemCompartment(js::GetGCThingCompartment(parent))) {
           return true;
         }
       }
     }
   }
   return false;
 }
@@ -2681,18 +2681,18 @@ DoMergingCC(bool aForced)
 
 }
 
 static void
 FinishAnyIncrementalGC()
 {
   if (sCCLockedOut) {
     // We're in the middle of an incremental GC, so finish it.
-    js::PrepareForIncrementalGC(nsJSRuntime::sRuntime);
-    js::FinishIncrementalGC(nsJSRuntime::sRuntime, js::gcreason::CC_FORCED);
+    JS::PrepareForIncrementalGC(nsJSRuntime::sRuntime);
+    JS::FinishIncrementalGC(nsJSRuntime::sRuntime, JS::gcreason::CC_FORCED);
   }
 }
 
 static void
 FireForgetSkippable(uint32_t aSuspected, bool aRemoveChildless)
 {
   PRTime startTime = PR_Now();
   FinishAnyIncrementalGC();
@@ -2765,17 +2765,17 @@ nsJSContext::CycleCollectNow(nsICycleCol
   bool mergingCC = DoMergingCC(aForced);
   nsCycleCollectorResults ccResults;
   nsCycleCollector_collect(mergingCC, &ccResults, aListener);
   sCCollectedWaitingForGC += ccResults.mFreedRefCounted + ccResults.mFreedGCed;
 
   // If we collected a substantial amount of cycles, poke the GC since more objects
   // might be unreachable now.
   if (sCCollectedWaitingForGC > 250) {
-    PokeGC(js::gcreason::CC_WAITING);
+    PokeGC(JS::gcreason::CC_WAITING);
   }
 
   PRTime endCCTime = PR_Now();
 
   // Log information about the CC via telemetry, JSON and the console.
   uint32_t ccNowDuration = TimeBetween(start, endCCTime);
   Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_FINISH_IGC, finishedIGC);
   Telemetry::Accumulate(Telemetry::CYCLE_COLLECTOR_SYNC_SKIPPABLE, ranSyncForgetSkippable);
@@ -2883,31 +2883,31 @@ nsJSContext::CycleCollectNow(nsICycleCol
   sNeedsFullCC = false;
 }
 
 // static
 void
 InterSliceGCTimerFired(nsITimer *aTimer, void *aClosure)
 {
   NS_RELEASE(sInterSliceGCTimer);
-  nsJSContext::GarbageCollectNow(js::gcreason::INTER_SLICE_GC,
+  nsJSContext::GarbageCollectNow(JS::gcreason::INTER_SLICE_GC,
                                  nsJSContext::IncrementalGC,
                                  nsJSContext::CompartmentGC,
                                  nsJSContext::NonShrinkingGC,
                                  NS_INTERSLICE_GC_BUDGET);
 }
 
 // static
 void
 GCTimerFired(nsITimer *aTimer, void *aClosure)
 {
   NS_RELEASE(sGCTimer);
 
   uintptr_t reason = reinterpret_cast<uintptr_t>(aClosure);
-  nsJSContext::GarbageCollectNow(static_cast<js::gcreason::Reason>(reason),
+  nsJSContext::GarbageCollectNow(static_cast<JS::gcreason::Reason>(reason),
                                  nsJSContext::IncrementalGC,
                                  nsJSContext::CompartmentGC);
 }
 
 void
 ShrinkGCBuffersTimerFired(nsITimer *aTimer, void *aClosure)
 {
   NS_RELEASE(sShrinkGCBuffersTimer);
@@ -3014,22 +3014,22 @@ nsJSContext::LoadEnd()
   // need to be), so make sure we don't make it wrap backwards here.
   if (sPendingLoadCount > 0) {
     --sPendingLoadCount;
     return;
   }
 
   // Its probably a good idea to GC soon since we have finished loading.
   sLoadingInProgress = false;
-  PokeGC(js::gcreason::LOAD_END);
+  PokeGC(JS::gcreason::LOAD_END);
 }
 
 // static
 void
-nsJSContext::PokeGC(js::gcreason::Reason aReason, int aDelay)
+nsJSContext::PokeGC(JS::gcreason::Reason aReason, int aDelay)
 {
   if (sGCTimer || sShuttingDown) {
     // There's already a timer for GC'ing, just return
     return;
   }
 
   CallCreateInstance("@mozilla.org/timer;1", &sGCTimer);
 
@@ -3140,17 +3140,17 @@ nsJSContext::KillCCTimer()
   if (sCCTimer) {
     sCCTimer->Cancel();
 
     NS_RELEASE(sCCTimer);
   }
 }
 
 void
-nsJSContext::GC(js::gcreason::Reason aReason)
+nsJSContext::GC(JS::gcreason::Reason aReason)
 {
   mActive = true;
   PokeGC(aReason);
 }
 
 class NotifyGCEndRunnable : public nsRunnable
 {
   nsString mMessage;
@@ -3174,21 +3174,21 @@ NotifyGCEndRunnable::Run()
   const jschar oomMsg[3] = { '{', '}', 0 };
   const jschar *toSend = mMessage.get() ? mMessage.get() : oomMsg;
   observerService->NotifyObservers(nullptr, "garbage-collection-statistics", toSend);
 
   return NS_OK;
 }
 
 static void
-DOMGCSliceCallback(JSRuntime *aRt, js::GCProgress aProgress, const js::GCDescription &aDesc)
+DOMGCSliceCallback(JSRuntime *aRt, JS::GCProgress aProgress, const JS::GCDescription &aDesc)
 {
   NS_ASSERTION(NS_IsMainThread(), "GCs must run on the main thread");
 
-  if (aProgress == js::GC_CYCLE_END) {
+  if (aProgress == JS::GC_CYCLE_END) {
     PRTime delta = GetCollectionTimeDelta();
 
     if (sPostGCEventsToConsole) {
       NS_NAMED_LITERAL_STRING(kFmt, "GC(T+%.1f) ");
       nsString prefix, gcstats;
       gcstats.Adopt(aDesc.formatMessage(aRt));
       prefix.Adopt(nsTextFormatter::smprintf(kFmt.get(),
                                              double(delta) / PR_USEC_PER_SEC));
@@ -3203,49 +3203,49 @@ DOMGCSliceCallback(JSRuntime *aRt, js::G
       nsString json;
       json.Adopt(aDesc.formatJSON(aRt, PR_Now()));
       nsRefPtr<NotifyGCEndRunnable> notify = new NotifyGCEndRunnable(json);
       NS_DispatchToMainThread(notify);
     }
   }
 
   // Prevent cycle collections and shrinking during incremental GC.
-  if (aProgress == js::GC_CYCLE_BEGIN) {
+  if (aProgress == JS::GC_CYCLE_BEGIN) {
     sCCLockedOut = true;
     nsJSContext::KillShrinkGCBuffersTimer();
-  } else if (aProgress == js::GC_CYCLE_END) {
+  } else if (aProgress == JS::GC_CYCLE_END) {
     sCCLockedOut = false;
   }
 
   // The GC has more work to do, so schedule another GC slice.
-  if (aProgress == js::GC_SLICE_END) {
+  if (aProgress == JS::GC_SLICE_END) {
     nsJSContext::KillInterSliceGCTimer();
     if (!sShuttingDown) {
       CallCreateInstance("@mozilla.org/timer;1", &sInterSliceGCTimer);
       sInterSliceGCTimer->InitWithFuncCallback(InterSliceGCTimerFired,
                                                NULL,
                                                NS_INTERSLICE_GC_DELAY,
                                                nsITimer::TYPE_ONE_SHOT);
     }
   }
 
-  if (aProgress == js::GC_CYCLE_END) {
+  if (aProgress == JS::GC_CYCLE_END) {
     // May need to kill the inter-slice GC timer
     nsJSContext::KillInterSliceGCTimer();
 
     sCCollectedWaitingForGC = 0;
     sCleanupsSinceLastGC = 0;
     sNeedsFullCC = true;
     nsJSContext::MaybePokeCC();
 
     if (aDesc.isCompartment) {
       ++sCompartmentGCCount;
       if (!sFullGCTimer && !sShuttingDown) {
         CallCreateInstance("@mozilla.org/timer;1", &sFullGCTimer);
-        js::gcreason::Reason reason = js::gcreason::FULL_GC_TIMER;
+        JS::gcreason::Reason reason = JS::gcreason::FULL_GC_TIMER;
         sFullGCTimer->InitWithFuncCallback(FullGCTimerFired,
                                            reinterpret_cast<void *>(reason),
                                            NS_FULL_GC_DELAY,
                                            nsITimer::TYPE_ONE_SHOT);
       }
     } else {
       sCompartmentGCCount = 0;
       nsJSContext::KillFullGCTimer();
@@ -3562,17 +3562,17 @@ nsJSRuntime::Init()
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = sRuntimeService->GetRuntime(&sRuntime);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Let's make sure that our main thread is the same as the xpcom main thread.
   NS_ASSERTION(NS_IsMainThread(), "bad");
 
-  sPrevGCSliceCallback = js::SetGCSliceCallback(sRuntime, DOMGCSliceCallback);
+  sPrevGCSliceCallback = JS::SetGCSliceCallback(sRuntime, DOMGCSliceCallback);
   sPrevAnalysisPurgeCallback = js::SetAnalysisPurgeCallback(sRuntime, DOMAnalysisPurgeCallback);
 
   // Set up the structured clone callbacks.
   static JSStructuredCloneCallbacks cloneCallbacks = {
     NS_DOMReadStructuredClone,
     NS_DOMWriteStructuredClone,
     NS_DOMStructuredCloneError
   };
--- a/dom/base/nsJSEnvironment.h
+++ b/dom/base/nsJSEnvironment.h
@@ -120,40 +120,40 @@ public:
     NonShrinkingGC
   };
 
   enum IsIncremental {
     IncrementalGC,
     NonIncrementalGC
   };
 
-  static void GarbageCollectNow(js::gcreason::Reason reason,
+  static void GarbageCollectNow(JS::gcreason::Reason reason,
                                 IsIncremental aIncremental = NonIncrementalGC,
                                 IsCompartment aCompartment = NonCompartmentGC,
                                 IsShrinking aShrinking = NonShrinkingGC,
                                 int64_t aSliceMillis = 0);
   static void ShrinkGCBuffersNow();
   // If aExtraForgetSkippableCalls is -1, forgetSkippable won't be
   // called even if the previous collection was GC.
   static void CycleCollectNow(nsICycleCollectorListener *aListener = nullptr,
                               int32_t aExtraForgetSkippableCalls = 0,
                               bool aForced = true);
 
-  static void PokeGC(js::gcreason::Reason aReason, int aDelay = 0);
+  static void PokeGC(JS::gcreason::Reason aReason, int aDelay = 0);
   static void KillGCTimer();
 
   static void PokeShrinkGCBuffers();
   static void KillShrinkGCBuffersTimer();
 
   static void MaybePokeCC();
   static void KillCCTimer();
   static void KillFullGCTimer();
   static void KillInterSliceGCTimer();
 
-  virtual void GC(js::gcreason::Reason aReason);
+  virtual void GC(JS::gcreason::Reason aReason);
 
   static uint32_t CleanupsSinceLastGC();
 
   nsIScriptGlobalObject* GetCachedGlobalObject()
   {
     // Verify that we have a global so that this
     // does always return a null when GetGlobalObject() is null.
     JSObject* global = JS_GetGlobalObject(mContext);
--- a/dom/bindings/BindingDeclarations.h
+++ b/dom/bindings/BindingDeclarations.h
@@ -11,16 +11,17 @@
  * BindingUtils.h
  */
 #ifndef mozilla_dom_BindingDeclarations_h__
 #define mozilla_dom_BindingDeclarations_h__
 
 #include "nsStringGlue.h"
 #include "jsapi.h"
 #include "mozilla/Util.h"
+#include "nsCOMPtr.h"
 
 namespace mozilla {
 namespace dom {
 
 struct MainThreadDictionaryBase
 {
 protected:
   JSContext* ParseJSON(const nsAString& aJSON,
@@ -29,12 +30,60 @@ protected:
                        JS::Value& aVal);
 };
 
 struct EnumEntry {
   const char* value;
   size_t length;
 };
 
+class NS_STACK_CLASS GlobalObject
+{
+public:
+  GlobalObject(JSContext* aCx, JSObject* aObject);
+
+  nsISupports* Get() const
+  {
+    return mGlobalObject;
+  }
+
+  bool Failed() const
+  {
+    return !Get();
+  }
+
+private:
+  js::RootedObject mGlobalJSObject;
+  nsISupports* mGlobalObject;
+  nsCOMPtr<nsISupports> mGlobalObjectRef;
+};
+
+class NS_STACK_CLASS WorkerGlobalObject
+{
+public:
+  WorkerGlobalObject(JSContext* aCx, JSObject* aObject);
+
+  JSObject* Get() const
+  {
+    return mGlobalJSObject;
+  }
+  // The context that this returns is not guaranteed to be in the compartment of
+  // the object returned from Get(), in fact it's generally in the caller's
+  // compartment.
+  JSContext* GetContext() const
+  {
+    return mCx;
+  }
+
+  bool Failed() const
+  {
+    return !Get();
+  }
+
+private:
+  js::RootedObject mGlobalJSObject;
+  JSContext* mCx;
+};
+
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_BindingDeclarations_h__
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -12,16 +12,17 @@
 #include "BindingUtils.h"
 
 #include "AccessCheck.h"
 #include "nsContentUtils.h"
 #include "nsIXPConnect.h"
 #include "WrapperFactory.h"
 #include "xpcprivate.h"
 #include "XPCQuickStubs.h"
+#include "XPCWrapper.h"
 #include "XrayWrapper.h"
 
 namespace mozilla {
 namespace dom {
 
 JSErrorFormatString ErrorFormatString[] = {
 #define MSG_DEF(_name, _argc, _str) \
   { _str, _argc, JSEXN_TYPEERR },
@@ -1433,10 +1434,58 @@ ReparentWrapper(JSContext* aCx, JSObject
 
   if (newParent && !JS_SetParent(aCx, aObj, newParent)) {
     MOZ_CRASH();
   }
 
   return NS_OK;
 }
 
+template<bool mainThread>
+inline JSObject*
+GetGlobalObject(JSContext* aCx, JSObject* aObject,
+                Maybe<JSAutoCompartment>& aAutoCompartment)
+{
+  if (js::IsWrapper(aObject)) {
+    aObject = XPCWrapper::Unwrap(aCx, aObject, false);
+    if (!aObject) {
+      Throw<mainThread>(aCx, NS_ERROR_XPC_SECURITY_MANAGER_VETO);
+      return nullptr;
+    }
+    aAutoCompartment.construct(aCx, aObject);
+  }
+
+  return JS_GetGlobalForObject(aCx, aObject);
+}
+
+GlobalObject::GlobalObject(JSContext* aCx, JSObject* aObject)
+  : mGlobalJSObject(aCx)
+{
+  Maybe<JSAutoCompartment> ac;
+  mGlobalJSObject = GetGlobalObject<true>(aCx, aObject, ac);
+  if (!mGlobalJSObject) {
+    return;
+  }
+
+  JS::Value val;
+  val.setObject(*mGlobalJSObject);
+
+  // Switch this to UnwrapDOMObjectToISupports once our global objects are
+  // using new bindings.
+  nsresult rv = xpc_qsUnwrapArg<nsISupports>(aCx, val, &mGlobalObject,
+                                             static_cast<nsISupports**>(getter_AddRefs(mGlobalObjectRef)),
+                                             &val);
+  if (NS_FAILED(rv)) {
+    mGlobalObject = nullptr;
+    Throw<true>(aCx, NS_ERROR_XPC_BAD_CONVERT_JS);
+  }
+}
+
+WorkerGlobalObject::WorkerGlobalObject(JSContext* aCx, JSObject* aObject)
+  : mGlobalJSObject(aCx),
+    mCx(aCx)
+{
+  Maybe<JSAutoCompartment> ac;
+  mGlobalJSObject = GetGlobalObject<false>(aCx, aObject, ac);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -697,23 +697,46 @@ HandleNewBindingWrappingFailure(JSContex
     return false;
   }
 
   qsObjectHelper helper(value, GetWrapperCache(value));
   return NativeInterface2JSObjectAndThrowIfFailed(cx, scope, vp, helper,
                                                   nullptr, true);
 }
 
-// Helper for smart pointers (nsAutoPtr/nsRefPtr/nsCOMPtr).
-template <template <typename> class SmartPtr, class T>
-MOZ_ALWAYS_INLINE bool
-HandleNewBindingWrappingFailure(JSContext* cx, JSObject* scope,
-                                const SmartPtr<T>& value, JS::Value* vp)
+// Helper for calling HandleNewBindingWrappingFailure with smart pointers
+// (nsAutoPtr/nsRefPtr/nsCOMPtr) or references.
+HAS_MEMBER(get)
+
+template <class T, bool isSmartPtr=HasgetMember<T>::Value>
+struct HandleNewBindingWrappingFailureHelper
 {
-  return HandleNewBindingWrappingFailure(cx, scope, value.get(), vp);
+  static inline bool Wrap(JSContext* cx, JSObject* scope, const T& value,
+                          JS::Value* vp)
+  {
+    return HandleNewBindingWrappingFailure(cx, scope, value.get(), vp);
+  }
+};
+
+template <class T>
+struct HandleNewBindingWrappingFailureHelper<T, false>
+{
+  static inline bool Wrap(JSContext* cx, JSObject* scope, T& value,
+                          JS::Value* vp)
+  {
+    return HandleNewBindingWrappingFailure(cx, scope, &value, vp);
+  }
+};
+
+template<class T>
+inline bool
+HandleNewBindingWrappingFailure(JSContext* cx, JSObject* scope, T& value,
+                                JS::Value* vp)
+{
+  return HandleNewBindingWrappingFailureHelper<T>::Wrap(cx, scope, value, vp);
 }
 
 template<bool Fatal>
 inline bool
 EnumValueNotFound(JSContext* cx, const jschar* chars, size_t length,
                   const char* type)
 {
   return false;
@@ -1113,18 +1136,16 @@ WrapCallThisObject(JSContext* cx, JSObje
     return nullptr;
   }
 
   return obj;
 }
 
 // Helper for calling WrapNewBindingObject with smart pointers
 // (nsAutoPtr/nsRefPtr/nsCOMPtr) or references.
-HAS_MEMBER(get)
-
 template <class T, bool isSmartPtr=HasgetMember<T>::Value>
 struct WrapNewBindingObjectHelper
 {
   static inline bool Wrap(JSContext* cx, JSObject* scope, const T& value,
                           JS::Value* vp)
   {
     return WrapNewBindingObject(cx, scope, value.get(), vp);
   }
@@ -1143,16 +1164,51 @@ struct WrapNewBindingObjectHelper<T, fal
 template<class T>
 inline bool
 WrapNewBindingObject(JSContext* cx, JSObject* scope, T& value,
                      JS::Value* vp)
 {
   return WrapNewBindingObjectHelper<T>::Wrap(cx, scope, value, vp);
 }
 
+template <class T>
+inline JSObject*
+GetCallbackFromCallbackObject(T* aObj)
+{
+  return aObj->Callback();
+}
+
+// Helper for getting the callback JSObject* of a smart ptr around a
+// CallbackObject or a reference to a CallbackObject or something like
+// that.
+template <class T, bool isSmartPtr=HasgetMember<T>::Value>
+struct GetCallbackFromCallbackObjectHelper
+{
+  static inline JSObject* Get(const T& aObj)
+  {
+    return GetCallbackFromCallbackObject(aObj.get());
+  }
+};
+
+template <class T>
+struct GetCallbackFromCallbackObjectHelper<T, false>
+{
+  static inline JSObject* Get(T& aObj)
+  {
+    return GetCallbackFromCallbackObject(&aObj);
+  }
+};
+
+template<class T>
+inline JSObject*
+GetCallbackFromCallbackObject(T& aObj)
+{
+  return GetCallbackFromCallbackObjectHelper<T>::Get(aObj);
+}
+
 static inline bool
 InternJSString(JSContext* cx, jsid& id, const char* chars)
 {
   if (JSString *str = ::JS_InternString(cx, chars)) {
     id = INTERNED_STRING_TO_JSID(cx, str);
     return true;
   }
   return false;
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -269,16 +269,19 @@ DOMInterfaces = {
 
 'Event': [
 {
     'workers': True,
 }],
 
 'EventListener': [
 {
+    'nativeType': 'nsIDOMEventListener'
+},
+{
     'workers': True,
 }],
 
 'EventTarget': [
 {
     'hasInstanceInterface': 'nsIDOMEventTarget',
     'hasXPConnectImpls': True,
     'concrete': False,
@@ -300,16 +303,17 @@ DOMInterfaces = {
 
 'FileReaderSync': {
     'workers': True,
 },
 
 'DOMTransaction': [
 {
     'nativeType': 'nsIUndoManagerTransaction',
+    'headerFile': 'nsIUndoManagerTransaction.h',
 }],
 
 'UndoManager': [
 {
     'implicitJSContext' : [ 'undo', 'redo', 'transact' ]
 }],
 
 'FormData': [
@@ -530,16 +534,21 @@ DOMInterfaces = {
     'hasInstanceInterface': 'nsIDOMNode',
     'resultNotAddRefed': [ 'ownerDocument', 'parentNode', 'parentElement',
                            'childNodes', 'firstChild', 'lastChild',
                            'previousSibling', 'nextSibling', 'insertBefore',
                            'appendChild', 'replaceChild', 'removeChild',
                            'attributes' ]
 },
 
+'NodeFilter': {
+    'nativeType': 'nsIDOMNodeFilter',
+    'headerFile': 'nsIDOMNodeFilter.h',
+},
+
 'NodeList': {
     'nativeType': 'nsINodeList',
     'resultNotAddRefed': [ 'item' ]
 },
 
 'PaintRequest': {
     'nativeType': 'nsPaintRequest',
     'prefable': True
@@ -1034,22 +1043,16 @@ DOMInterfaces = {
         },
 
 'TestNonWrapperCacheInterface' : {
         'headerFile': 'TestBindingHeader.h',
         'register': False,
         'wrapperCache': False
         },
 
-'TestCallbackInterface': {
-        'nativeType': 'mozilla::dom::TestCallbackInterface',
-        'headerFile': 'TestBindingHeader.h',
-        'register': False
-        },
-
 'IndirectlyImplementedInterface': {
         'headerFile': 'TestBindingHeader.h',
         'register': False,
         'castable': False,
         'concrete': False
         },
 
 'OnlyForUseInConstructor' : {
--- a/dom/bindings/CallbackFunction.h
+++ b/dom/bindings/CallbackFunction.h
@@ -9,154 +9,55 @@
  *
  * This class implements common functionality like lifetime
  * management, initialization with the callable, and setup of the call
  * environment.  Subclasses corresponding to particular callback
  * function types should provide a Call() method that actually does
  * the call.
  */
 
-#pragma once
+#ifndef mozilla_dom_CallbackFunction_h
+#define mozilla_dom_CallbackFunction_h
 
-#include "nsISupports.h"
-#include "nsISupportsImpl.h"
-#include "nsCycleCollectionParticipant.h"
-#include "jsapi.h"
-#include "jswrapper.h"
-#include "mozilla/Assertions.h"
-#include "mozilla/Util.h"
-#include "nsContentUtils.h"
-#include "nsWrapperCache.h"
-#include "nsJSEnvironment.h"
-#include "xpcpublic.h"
-#include "nsLayoutStatics.h"
+#include "mozilla/dom/CallbackObject.h"
 
 namespace mozilla {
 namespace dom {
 
-class CallbackFunction : public nsISupports
+class CallbackFunction : public CallbackObject
 {
 public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CallbackFunction)
-
   /**
    * Create a CallbackFunction.  aCallable is the callable we're wrapping.
    * aOwner is the object that will be receiving this CallbackFunction as a
    * method argument, if any.  We need this so we can store our callable in the
    * same compartment as our owner.  If *aInited is set to false, an exception
    * has been thrown.
    */
   CallbackFunction(JSContext* cx, JSObject* aOwner, JSObject* aCallable,
                    bool* aInited)
-    : mCallable(nullptr)
+    : CallbackObject(cx, aOwner, aCallable, aInited)
   {
     MOZ_ASSERT(JS_ObjectIsCallable(cx, aCallable));
-    // If aOwner is not null, enter the compartment of aOwner's
-    // underlying object.
-    if (aOwner) {
-      aOwner = js::UnwrapObject(aOwner);
-      JSAutoCompartment ac(cx, aOwner);
-      if (!JS_WrapObject(cx, &aCallable)) {
-        *aInited = false;
-        return;
-      }
-    }
-
-    // Set mCallable before we hold, on the off chance that a GC could somehow
-    // happen in there... (which would be pretty odd, granted).
-    mCallable = aCallable;
-    // Make sure we'll be able to drop as needed
-    nsLayoutStatics::AddRef();
-    NS_HOLD_JS_OBJECTS(this, CallbackFunction);
-    *aInited = true;
-  }
-
-  virtual ~CallbackFunction()
-  {
-    DropCallback();
   }
 
   JSObject* Callable() const
   {
-    xpc_UnmarkGrayObject(mCallable);
-    return mCallable;
+    return Callback();
   }
 
   bool HasGrayCallable() const
   {
     // Play it safe in case this gets called after unlink.
-    return mCallable && xpc_IsGrayGCThing(mCallable);
+    return mCallback && xpc_IsGrayGCThing(mCallback);
   }
 
 protected:
   explicit CallbackFunction(CallbackFunction* aCallbackFunction)
-    : mCallable(aCallbackFunction->mCallable)
-  {
-    // Set mCallable before we hold, on the off chance that a GC could somehow
-    // happen in there... (which would be pretty odd, granted).
-    // Make sure we'll be able to drop as needed
-    nsLayoutStatics::AddRef();
-    NS_HOLD_JS_OBJECTS(this, CallbackFunction);
-  }
-
-  void DropCallback()
-  {
-    if (mCallable) {
-      mCallable = nullptr;
-      NS_DROP_JS_OBJECTS(this, CallbackFunction);
-      nsLayoutStatics::Release();
-    }
-  }
-
-  JSObject* mCallable;
-
-  class NS_STACK_CLASS CallSetup
+    : CallbackObject(aCallbackFunction)
   {
-    /**
-     * A class that performs whatever setup we need to safely make a
-     * call while this class is on the stack, After the constructor
-     * returns, the call is safe to make if GetContext() returns
-     * non-null.
-     */
-  public:
-    CallSetup(JSObject* const aCallable);
-    ~CallSetup();
-
-    JSContext* GetContext() const
-    {
-      return mCx;
-    }
-
-  private:
-    // We better not get copy-constructed
-    CallSetup(const CallSetup&) MOZ_DELETE;
-
-    // Members which can go away whenever
-    JSContext* mCx;
-    nsCOMPtr<nsIScriptContext> mCtx;
-
-    // And now members whose construction/destruction order we need to control.
-
-    // Put our nsAutoMicrotask first, so it gets destroyed after everything else
-    // is gone
-    nsAutoMicroTask mMt;
-
-    // Can't construct an XPCAutoRequest until we have a JSContext, so
-    // this needs to be a Maybe.
-    Maybe<XPCAutoRequest> mAr;
-
-    // Can't construct a TerminationFuncHolder without an nsJSContext.  But we
-    // generally want its destructor to come after the destructor of mCxPusher.
-    Maybe<nsJSContext::TerminationFuncHolder> mTerminationFuncHolder;
-
-    nsCxPusher mCxPusher;
-
-    // Can't construct a JSAutoCompartment without a JSContext either.  Also,
-    // Put mAc after mCxPusher so that we exit the compartment before we pop the
-    // JSContext.  Though in practice we'll often manually order those two
-    // things.
-    Maybe<JSAutoCompartment> mAc;
-  };
+  }
 };
 
 } // namespace dom
 } // namespace mozilla
+
+#endif // mozilla_dom_CallbackFunction_h
new file mode 100644
--- /dev/null
+++ b/dom/bindings/CallbackInterface.cpp
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#include "mozilla/dom/CallbackInterface.h"
+#include "jsapi.h"
+#include "mozilla/dom/BindingUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+bool
+CallbackInterface::GetCallableProperty(JSContext* cx, const char* aPropName,
+				       JS::Value* aCallable)
+{
+  if (!JS_GetProperty(cx, mCallback, aPropName, aCallable)) {
+    return false;
+  }
+  if (!aCallable->isObject() ||
+      !JS_ObjectIsCallable(cx, &aCallable->toObject())) {
+    ThrowErrorMessage(cx, MSG_NOT_CALLABLE);
+    return false;
+  }
+
+  return true;
+}
+
+} // namespace dom
+} // namespace mozilla
copy from dom/bindings/CallbackFunction.h
copy to dom/bindings/CallbackInterface.h
--- a/dom/bindings/CallbackFunction.h
+++ b/dom/bindings/CallbackInterface.h
@@ -1,162 +1,49 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
 /**
- * A common base class for representing WebIDL callback function types in C++.
+ * A common base class for representing WebIDL callback interface types in C++.
  *
- * This class implements common functionality like lifetime
- * management, initialization with the callable, and setup of the call
- * environment.  Subclasses corresponding to particular callback
- * function types should provide a Call() method that actually does
- * the call.
+ * This class implements common functionality like lifetime management,
+ * initialization with the callback object, and setup of the call environment.
+ * Subclasses corresponding to particular callback interface types should
+ * provide methods that actually do the various necessary calls.
  */
 
-#pragma once
+#ifndef mozilla_dom_CallbackInterface_h
+#define mozilla_dom_CallbackInterface_h
 
-#include "nsISupports.h"
-#include "nsISupportsImpl.h"
-#include "nsCycleCollectionParticipant.h"
-#include "jsapi.h"
-#include "jswrapper.h"
-#include "mozilla/Assertions.h"
-#include "mozilla/Util.h"
-#include "nsContentUtils.h"
-#include "nsWrapperCache.h"
-#include "nsJSEnvironment.h"
-#include "xpcpublic.h"
-#include "nsLayoutStatics.h"
+#include "mozilla/dom/CallbackObject.h"
 
 namespace mozilla {
 namespace dom {
 
-class CallbackFunction : public nsISupports
+class CallbackInterface : public CallbackObject
 {
 public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CallbackFunction)
-
   /**
-   * Create a CallbackFunction.  aCallable is the callable we're wrapping.
-   * aOwner is the object that will be receiving this CallbackFunction as a
-   * method argument, if any.  We need this so we can store our callable in the
-   * same compartment as our owner.  If *aInited is set to false, an exception
-   * has been thrown.
+   * Create a CallbackInterface.  aCallback is the callback object we're
+   * wrapping.  aOwner is the object that will be receiving this
+   * CallbackInterface as a method argument, if any.  We need this so we can
+   * store our callable in the same compartment as our owner.  If *aInited is
+   * set to false, an exception has been thrown.
    */
-  CallbackFunction(JSContext* cx, JSObject* aOwner, JSObject* aCallable,
+  CallbackInterface(JSContext* cx, JSObject* aOwner, JSObject* aCallback,
                    bool* aInited)
-    : mCallable(nullptr)
+    : CallbackObject(cx, aOwner, aCallback, aInited)
   {
-    MOZ_ASSERT(JS_ObjectIsCallable(cx, aCallable));
-    // If aOwner is not null, enter the compartment of aOwner's
-    // underlying object.
-    if (aOwner) {
-      aOwner = js::UnwrapObject(aOwner);
-      JSAutoCompartment ac(cx, aOwner);
-      if (!JS_WrapObject(cx, &aCallable)) {
-        *aInited = false;
-        return;
-      }
-    }
-
-    // Set mCallable before we hold, on the off chance that a GC could somehow
-    // happen in there... (which would be pretty odd, granted).
-    mCallable = aCallable;
-    // Make sure we'll be able to drop as needed
-    nsLayoutStatics::AddRef();
-    NS_HOLD_JS_OBJECTS(this, CallbackFunction);
-    *aInited = true;
-  }
-
-  virtual ~CallbackFunction()
-  {
-    DropCallback();
-  }
-
-  JSObject* Callable() const
-  {
-    xpc_UnmarkGrayObject(mCallable);
-    return mCallable;
-  }
-
-  bool HasGrayCallable() const
-  {
-    // Play it safe in case this gets called after unlink.
-    return mCallable && xpc_IsGrayGCThing(mCallable);
   }
 
 protected:
-  explicit CallbackFunction(CallbackFunction* aCallbackFunction)
-    : mCallable(aCallbackFunction->mCallable)
-  {
-    // Set mCallable before we hold, on the off chance that a GC could somehow
-    // happen in there... (which would be pretty odd, granted).
-    // Make sure we'll be able to drop as needed
-    nsLayoutStatics::AddRef();
-    NS_HOLD_JS_OBJECTS(this, CallbackFunction);
-  }
-
-  void DropCallback()
-  {
-    if (mCallable) {
-      mCallable = nullptr;
-      NS_DROP_JS_OBJECTS(this, CallbackFunction);
-      nsLayoutStatics::Release();
-    }
-  }
-
-  JSObject* mCallable;
-
-  class NS_STACK_CLASS CallSetup
-  {
-    /**
-     * A class that performs whatever setup we need to safely make a
-     * call while this class is on the stack, After the constructor
-     * returns, the call is safe to make if GetContext() returns
-     * non-null.
-     */
-  public:
-    CallSetup(JSObject* const aCallable);
-    ~CallSetup();
+  bool GetCallableProperty(JSContext* cx, const char* aPropName,
+                           JS::Value* aCallable);
 
-    JSContext* GetContext() const
-    {
-      return mCx;
-    }
-
-  private:
-    // We better not get copy-constructed
-    CallSetup(const CallSetup&) MOZ_DELETE;
-
-    // Members which can go away whenever
-    JSContext* mCx;
-    nsCOMPtr<nsIScriptContext> mCtx;
-
-    // And now members whose construction/destruction order we need to control.
-
-    // Put our nsAutoMicrotask first, so it gets destroyed after everything else
-    // is gone
-    nsAutoMicroTask mMt;
-
-    // Can't construct an XPCAutoRequest until we have a JSContext, so
-    // this needs to be a Maybe.
-    Maybe<XPCAutoRequest> mAr;
-
-    // Can't construct a TerminationFuncHolder without an nsJSContext.  But we
-    // generally want its destructor to come after the destructor of mCxPusher.
-    Maybe<nsJSContext::TerminationFuncHolder> mTerminationFuncHolder;
-
-    nsCxPusher mCxPusher;
-
-    // Can't construct a JSAutoCompartment without a JSContext either.  Also,
-    // Put mAc after mCxPusher so that we exit the compartment before we pop the
-    // JSContext.  Though in practice we'll often manually order those two
-    // things.
-    Maybe<JSAutoCompartment> mAc;
-  };
 };
 
 } // namespace dom
 } // namespace mozilla
+
+#endif // mozilla_dom_CallbackFunction_h
rename from dom/bindings/CallbackFunction.cpp
rename to dom/bindings/CallbackObject.cpp
--- a/dom/bindings/CallbackFunction.cpp
+++ b/dom/bindings/CallbackObject.cpp
@@ -1,59 +1,61 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
-#include "mozilla/dom/CallbackFunction.h"
+#include "mozilla/dom/CallbackObject.h"
 #include "jsfriendapi.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIXPConnect.h"
 #include "nsIScriptContext.h"
 #include "nsPIDOMWindow.h"
 #include "nsJSUtils.h"
 #include "nsIScriptSecurityManager.h"
 
 namespace mozilla {
 namespace dom {
 
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CallbackFunction)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CallbackObject)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
-NS_IMPL_CYCLE_COLLECTING_ADDREF(CallbackFunction)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(CallbackFunction)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(CallbackObject)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(CallbackObject)
 
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CallbackFunction)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CallbackObject)
   tmp->DropCallback();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CallbackFunction)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CallbackObject)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CallbackFunction)
-  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallable)
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CallbackObject)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallback)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
-CallbackFunction::CallSetup::CallSetup(JSObject* const aCallable)
+CallbackObject::CallSetup::CallSetup(JSObject* const aCallback)
   : mCx(nullptr)
 {
-  xpc_UnmarkGrayObject(aCallable);
+  xpc_UnmarkGrayObject(aCallback);
 
-  // We need to produce a useful JSContext here.  Ideally one that the callable
+  // We need to produce a useful JSContext here.  Ideally one that the callback
   // is in some sense associated with, so that we can sort of treat it as a
-  // "script entry point".
+  // "script entry point".  Though once we actually have script entry points,
+  // we'll need to do the script entry point bits once we have an actual
+  // callable.
 
-  // First, find the real underlying callable.
-  JSObject* realCallable = js::UnwrapObject(aCallable);
+  // First, find the real underlying callback.
+  JSObject* realCallback = js::UnwrapObject(aCallback);
 
-  // Now get the nsIScriptGlobalObject for this callable.
+  // Now get the nsIScriptGlobalObject for this callback.
   JSContext* cx = nullptr;
   nsIScriptContext* ctx = nullptr;
-  nsIScriptGlobalObject* sgo = nsJSUtils::GetStaticScriptGlobal(realCallable);
+  nsIScriptGlobalObject* sgo = nsJSUtils::GetStaticScriptGlobal(realCallback);
   if (sgo) {
     // Make sure that if this is a window it's the current inner, since the
     // nsIScriptContext and hence JSContext are associated with the outer
     // window.  Which means that if someone holds on to a function from a
     // now-unloaded document we'd have the new document as the script entry
     // point...
     nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(sgo);
     if (win) {
@@ -93,42 +95,42 @@ CallbackFunction::CallSetup::CallSetup(J
   // have an nsIScriptContext.
   // XXXbz Why, if, say CheckFunctionAccess fails?  I know that's how
   // nsJSContext::CallEventHandler works, but is it required?
   // FIXME: Bug 807369.
   mCtx = ctx;
 
   // Check that it's ok to run this callback at all.
   // FIXME: Bug 807371: we want a less silly check here.
-  // Make sure to unwrap aCallable before passing it in, because
+  // Make sure to unwrap aCallback before passing it in, because
   // getting principals from wrappers is silly.
   nsresult rv = nsContentUtils::GetSecurityManager()->
-    CheckFunctionAccess(cx, js::UnwrapObject(aCallable), nullptr);
+    CheckFunctionAccess(cx, js::UnwrapObject(aCallback), nullptr);
 
   // Construct a termination func holder even if we're not planning to
   // run any script.  We need this because we're going to call
   // ScriptEvaluated even if we don't run the script...  See XXX
   // comment above.
   if (ctx) {
     mTerminationFuncHolder.construct(static_cast<nsJSContext*>(ctx));
   }
 
   if (NS_FAILED(rv)) {
     // Security check failed.  We're done here.
     return;
   }
 
-  // Enter the compartment of our callable, so we can actually call it.
-  mAc.construct(cx, aCallable);
+  // Enter the compartment of our callback, so we can actually work with it.
+  mAc.construct(cx, aCallback);
 
   // And now we're ready to go.
   mCx = cx;
 }
 
-CallbackFunction::CallSetup::~CallSetup()
+CallbackObject::CallSetup::~CallSetup()
 {
   // First things first: if we have a JSContext, report any pending
   // errors on it.
   if (mCx) {
     nsJSUtils::ReportPendingException(mCx);
   }
 
   // If we have an mCtx, we need to call ScriptEvaluated() on it.  But we have
copy from dom/bindings/CallbackFunction.h
copy to dom/bindings/CallbackObject.h
--- a/dom/bindings/CallbackFunction.h
+++ b/dom/bindings/CallbackObject.h
@@ -1,25 +1,26 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
 /**
- * A common base class for representing WebIDL callback function types in C++.
+ * A common base class for representing WebIDL callback function and
+ * callback interface types in C++.
  *
  * This class implements common functionality like lifetime
- * management, initialization with the callable, and setup of the call
- * environment.  Subclasses corresponding to particular callback
- * function types should provide a Call() method that actually does
- * the call.
+ * management, initialization with the JS object, and setup of the
+ * call environment.  Subclasses are responsible for providing methods
+ * that do the call into JS as needed.
  */
 
-#pragma once
+#ifndef mozilla_dom_CallbackObject_h
+#define mozilla_dom_CallbackObject_h
 
 #include "nsISupports.h"
 #include "nsISupportsImpl.h"
 #include "nsCycleCollectionParticipant.h"
 #include "jsapi.h"
 #include "jswrapper.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Util.h"
@@ -27,92 +28,85 @@
 #include "nsWrapperCache.h"
 #include "nsJSEnvironment.h"
 #include "xpcpublic.h"
 #include "nsLayoutStatics.h"
 
 namespace mozilla {
 namespace dom {
 
-class CallbackFunction : public nsISupports
+class CallbackObject : public nsISupports
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CallbackFunction)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CallbackObject)
 
   /**
-   * Create a CallbackFunction.  aCallable is the callable we're wrapping.
-   * aOwner is the object that will be receiving this CallbackFunction as a
-   * method argument, if any.  We need this so we can store our callable in the
+   * Create a CallbackObject.  aCallback is the callback object we're wrapping.
+   * aOwner is the object that will be receiving this CallbackObject as a method
+   * argument, if any.  We need this so we can store our callback object in the
    * same compartment as our owner.  If *aInited is set to false, an exception
    * has been thrown.
    */
-  CallbackFunction(JSContext* cx, JSObject* aOwner, JSObject* aCallable,
-                   bool* aInited)
-    : mCallable(nullptr)
+  CallbackObject(JSContext* cx, JSObject* aOwner, JSObject* aCallback,
+                 bool* aInited)
+    : mCallback(nullptr)
   {
-    MOZ_ASSERT(JS_ObjectIsCallable(cx, aCallable));
     // If aOwner is not null, enter the compartment of aOwner's
     // underlying object.
     if (aOwner) {
       aOwner = js::UnwrapObject(aOwner);
       JSAutoCompartment ac(cx, aOwner);
-      if (!JS_WrapObject(cx, &aCallable)) {
+      if (!JS_WrapObject(cx, &aCallback)) {
         *aInited = false;
         return;
       }
     }
 
-    // Set mCallable before we hold, on the off chance that a GC could somehow
+    // Set mCallback before we hold, on the off chance that a GC could somehow
     // happen in there... (which would be pretty odd, granted).
-    mCallable = aCallable;
+    mCallback = aCallback;
     // Make sure we'll be able to drop as needed
     nsLayoutStatics::AddRef();
-    NS_HOLD_JS_OBJECTS(this, CallbackFunction);
+    NS_HOLD_JS_OBJECTS(this, CallbackObject);
     *aInited = true;
   }
 
-  virtual ~CallbackFunction()
+  virtual ~CallbackObject()
   {
     DropCallback();
   }
 
-  JSObject* Callable() const
+  JSObject* Callback() const
   {
-    xpc_UnmarkGrayObject(mCallable);
-    return mCallable;
-  }
-
-  bool HasGrayCallable() const
-  {
-    // Play it safe in case this gets called after unlink.
-    return mCallable && xpc_IsGrayGCThing(mCallable);
+    xpc_UnmarkGrayObject(mCallback);
+    return mCallback;
   }
 
 protected:
-  explicit CallbackFunction(CallbackFunction* aCallbackFunction)
-    : mCallable(aCallbackFunction->mCallable)
+  explicit CallbackObject(CallbackObject* aCallbackFunction)
+    : mCallback(aCallbackFunction->mCallback)
   {
-    // Set mCallable before we hold, on the off chance that a GC could somehow
+    // Set mCallback before we hold, on the off chance that a GC could somehow
     // happen in there... (which would be pretty odd, granted).
     // Make sure we'll be able to drop as needed
     nsLayoutStatics::AddRef();
-    NS_HOLD_JS_OBJECTS(this, CallbackFunction);
+    NS_HOLD_JS_OBJECTS(this, CallbackObject);
   }
 
   void DropCallback()
   {
-    if (mCallable) {
-      mCallable = nullptr;
-      NS_DROP_JS_OBJECTS(this, CallbackFunction);
+    if (mCallback) {
+      mCallback = nullptr;
+      NS_DROP_JS_OBJECTS(this, CallbackObject);
       nsLayoutStatics::Release();
     }
   }
 
-  JSObject* mCallable;
+  JSObject* mCallback;
 
   class NS_STACK_CLASS CallSetup
   {
     /**
      * A class that performs whatever setup we need to safely make a
      * call while this class is on the stack, After the constructor
      * returns, the call is safe to make if GetContext() returns
      * non-null.
@@ -155,8 +149,10 @@ protected:
     // JSContext.  Though in practice we'll often manually order those two
     // things.
     Maybe<JSAutoCompartment> mAc;
   };
 };
 
 } // namespace dom
 } // namespace mozilla
+
+#endif // mozilla_dom_CallbackObject_h
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -453,18 +453,19 @@ def callForEachType(descriptors, diction
     for callback in callbacks:
         for t in getTypesFromCallback(callback):
             func(t)
 
 class CGHeaders(CGWrapper):
     """
     Generates the appropriate include statements.
     """
-    def __init__(self, descriptors, dictionaries, callbacks, declareIncludes,
-                 defineIncludes, child, config=None):
+    def __init__(self, descriptors, dictionaries, callbacks,
+                 callbackDescriptors, declareIncludes, defineIncludes, child,
+                 config=None):
         """
         Builds a set of includes to cover |descriptors|.
 
         Also includes the files in |declareIncludes| in the header
         file and the files in |defineIncludes| in the .cpp.
         """
 
         # Determine the filenames for which we need headers.
@@ -515,33 +516,43 @@ class CGHeaders(CGWrapper):
                 bindingHeaders.add(self.getDeclarationFilename(unrolled.inner))
             elif unrolled.isCallback():
                 # Callbacks are both a type and an object
                 bindingHeaders.add(self.getDeclarationFilename(t.unroll()))
             elif unrolled.isFloat() and not unrolled.isUnrestricted():
                 # Restricted floats are tested for finiteness
                 bindingHeaders.add("mozilla/FloatingPoint.h")
 
-        callForEachType(descriptors, dictionaries, callbacks, addHeadersForType)
+        callForEachType(descriptors + callbackDescriptors, dictionaries,
+                        callbacks, addHeadersForType)
 
         declareIncludes = set(declareIncludes)
         for d in dictionaries:
             if d.parent:
                 declareIncludes.add(self.getDeclarationFilename(d.parent))
             bindingHeaders.add(self.getDeclarationFilename(d))
 
         for c in callbacks:
             bindingHeaders.add(self.getDeclarationFilename(c))
 
+        for c in callbackDescriptors:
+            bindingHeaders.add(self.getDeclarationFilename(c.interface))
+
         if len(callbacks) != 0:
             # We need CallbackFunction to serve as our parent class
             declareIncludes.add("mozilla/dom/CallbackFunction.h")
             # And we need BindingUtils.h so we can wrap "this" objects
             declareIncludes.add("mozilla/dom/BindingUtils.h")
 
+        if len(callbackDescriptors) != 0:
+            # We need CallbackInterface to serve as our parent class
+            declareIncludes.add("mozilla/dom/CallbackInterface.h")
+            # And we need BindingUtils.h so we can wrap "this" objects
+            declareIncludes.add("mozilla/dom/BindingUtils.h")
+
         # Let the machinery do its thing.
         def _includeString(includes):
             return ''.join(['#include "%s"\n' % i for i in includes]) + '\n'
         CGWrapper.__init__(self, child,
                            declarePre=_includeString(sorted(declareIncludes)),
                            definePre=_includeString(sorted(set(defineIncludes) |
                                                            bindingIncludes |
                                                            bindingHeaders |
@@ -926,39 +937,22 @@ class CGClassConstructHook(CGAbstractSta
             return ""
         return CGAbstractStaticMethod.define(self)
 
     def definition_body(self):
         return self.generate_code()
 
     def generate_code(self):
         preamble = """
-  JSObject* obj = JS_GetGlobalForObject(cx, JSVAL_TO_OBJECT(JS_CALLEE(cx, vp)));
+  JSObject* obj = JSVAL_TO_OBJECT(JS_CALLEE(cx, vp));
 """
-        if self.descriptor.workers:
-            preArgs = ["cx", "obj"]
-        else:
-            preamble += """
-  nsISupports* global;
-  xpc_qsSelfRef globalRef;
-  {
-    nsresult rv;
-    JS::Value val = OBJECT_TO_JSVAL(obj);
-    rv = xpc_qsUnwrapArg<nsISupports>(cx, val, &global, &globalRef.ptr, &val);
-    if (NS_FAILED(rv)) {
-      return ThrowErrorMessage(cx, MSG_GLOBAL_NOT_NATIVE);
-    }
-  }
-"""
-            preArgs = ["global"]
-
         name = self._ctor.identifier.name
         nativeName = MakeNativeName(self.descriptor.binaryNames.get(name, name))
-        callGenerator = CGMethodCall(preArgs, nativeName, True,
-                                     self.descriptor, self._ctor)
+        callGenerator = CGMethodCall(nativeName, True, self.descriptor,
+                                     self._ctor)
         return preamble + callGenerator.define();
 
 class CGClassConstructHookHolder(CGGeneric):
     def __init__(self, descriptor):
         if descriptor.interface.ctor():
             constructHook = CONSTRUCT_HOOK_NAME
         else:
             constructHook = "ThrowingConstructor"
@@ -2503,16 +2497,44 @@ for (uint32_t i = 0; i < length; ++i) {
 
         return templateBody.define(), declType, holderType, False
 
     if type.isGeckoInterface():
         assert not isEnforceRange and not isClamp
 
         descriptor = descriptorProvider.getDescriptor(
             type.unroll().inner.identifier.name)
+
+        if (descriptor.interface.isCallback() and
+            descriptor.interface.identifier.name != "NodeFilter" and
+            descriptor.interface.identifier.name != "EventListener" and
+            descriptor.interface.identifier.name != "DOMTransaction"):
+            if descriptor.workers:
+                if type.nullable():
+                    declType = CGGeneric("JSObject*")
+                else:
+                    declType = CGGeneric("NonNull<JSObject>")
+                conversion = "  ${declName} = &${val}.toObject();\n"
+            else:
+                name = descriptor.interface.identifier.name
+                if type.nullable():
+                    declType = CGGeneric("nsRefPtr<%s>" % name);
+                else:
+                    declType = CGGeneric("OwningNonNull<%s>" % name)
+                conversion = (
+                    "  bool inited;\n"
+                    "  ${declName} = new %s(cx, ${obj}, &${val}.toObject(), &inited);\n"
+                    "  if (!inited) {\n"
+                    "%s\n"
+                    "  }\n" % (name, CGIndenter(exceptionCodeIndented).define()))
+            template = wrapObjectTemplate(conversion, type,
+                                          "${declName} = nullptr",
+                                          failureCode)
+            return (template, declType, None, isOptional)
+
         # This is an interface that we implement as a concrete class
         # or an XPCOM interface.
 
         # Allow null pointers for nullable types and old-binding classes
         argIsPointer = type.nullable() or type.unroll().inner.isExternal()
 
         # Sequences and non-worker callbacks have to hold a strong ref to the
         # thing being passed down.
@@ -3317,17 +3339,21 @@ if (!returnArray) {
 %s
     }
   }
 }\n""" % (result, exceptionCodeIndented.define(),
           innerTemplate,
           CGIndenter(exceptionCodeIndented, 4).define())) +
                 setValue("JS::ObjectValue(*returnArray)"), False)
 
-    if type.isGeckoInterface():
+    if (type.isGeckoInterface() and
+        (not type.isCallbackInterface() or
+         type.unroll().inner.identifier.name == "EventListener" or
+         type.unroll().inner.identifier.name == "NodeFilter" or
+         type.unroll().inner.identifier.name == "DOMTransaction")):
         descriptor = descriptorProvider.getDescriptor(type.unroll().inner.identifier.name)
         if type.nullable():
             wrappingCode = ("if (!%s) {\n" % (result) +
                             CGIndenter(CGGeneric(setValue("JSVAL_NULL"))).define() + "\n" +
                             "}\n")
         else:
             wrappingCode = ""
 
@@ -3384,32 +3410,34 @@ if (!%(resultStr)s) {
 %(exceptionCode)s
 }
 """ % { "result" : result,
         "resultStr" : result + "_str",
         "strings" : type.inner.identifier.name + "Values::strings",
         "exceptionCode" : exceptionCode } +
         setValue("JS::StringValue(%s_str)" % result), False)
 
-    if type.isCallback():
-        assert not type.isInterface()
-        # XXXbz we're going to assume that callback types are always
-        # nullable and always have [TreatNonCallableAsNull] for now.
+    if type.isCallback() or type.isCallbackInterface():
         # See comments in WrapNewBindingObject explaining why we need
         # to wrap here.
         # NB: setValue(..., True) calls JS_WrapValue(), so is fallible
         if descriptorProvider.workers:
             return (setValue("JS::ObjectOrNullValue(%s)" % result, True), False)
 
-        wrapCode = (("if (%(result)s) {\n" +
-                     CGIndenter(CGGeneric(setValue(
-                            "JS::ObjectValue(*%(result)s->Callable())", True))).define() + "\n"
-                     "} else {\n" +
-                     CGIndenter(CGGeneric(setValue("JS::NullValue()"))).define() + "\n"
-                     "}") % { "result": result })
+        wrapCode = setValue(
+            "JS::ObjectValue(*GetCallbackFromCallbackObject(%(result)s))",
+            True)
+        if type.nullable():
+            wrapCode = (
+                "if (%(result)s) {\n" +
+                CGIndenter(CGGeneric(wrapCode)).define() + "\n"
+                "} else {\n" +
+                CGIndenter(CGGeneric(setValue("JS::NullValue()"))).define() + "\n"
+                "}")
+        wrapCode = wrapCode % { "result": result }
         return wrapCode, False
 
     if type.tag() == IDLType.Tags.any:
         # See comments in WrapNewBindingObject explaining why we need
         # to wrap here.
         # NB: setValue(..., True) calls JS_WrapValue(), so is fallible
         return (setValue(result, True), False)
 
@@ -3605,17 +3633,17 @@ def getRetvalDeclarationForType(returnTy
                     returnType)
 
 def isResultAlreadyAddRefed(descriptor, extendedAttributes):
     # Default to already_AddRefed on the main thread, raw pointer in workers
     return not descriptor.workers and not 'resultNotAddRefed' in extendedAttributes
 
 def needCx(returnType, arguments, extendedAttributes, descriptorProvider):
     return (typeNeedsCx(returnType, descriptorProvider, True) or
-            any(typeNeedsCx(a.type, descriptorProvider) for (a, _) in arguments) or
+            any(typeNeedsCx(a.type, descriptorProvider) for a in arguments) or
             'implicitJSContext' in extendedAttributes)
 
 class CGCallGenerator(CGThing):
     """
     A class to generate an actual call to a C++ object.  Assumes that the C++
     object is stored in a variable whose name is given by the |object| argument.
 
     errorReport should be a CGThing for an error report or None if no
@@ -3644,29 +3672,21 @@ class CGCallGenerator(CGThing):
             args.append(CGGeneric(name))
 
         # Return values that go in outparams go here
         if resultOutParam:
             args.append(CGGeneric("result"))
         if isFallible:
             args.append(CGGeneric("rv"))
 
-        needsCx = needCx(returnType, arguments, extendedAttributes,
-                         descriptorProvider)
-
-        if not "cx" in argsPre and needsCx:
-            args.prepend(CGGeneric("cx"))
-
         # Build up our actual call
         self.cgRoot = CGList([], "\n")
 
         call = CGGeneric(nativeMethodName)
-        if static:
-            call = CGWrapper(call, pre="%s::" % descriptorProvider.nativeType)
-        else: 
+        if not static:
             call = CGWrapper(call, pre="%s->" % object)
         call = CGList([call, CGWrapper(args, pre="(", post=");")])
         if result is not None:
             result = CGWrapper(result, post=" result;")
             self.cgRoot.prepend(result)
             if not resultOutParam:
                 call = CGWrapper(call, pre="result = ")
 
@@ -3704,54 +3724,73 @@ class CGPerSignatureCall(CGThing):
     The idlNode parameter can be either a method or an attr. We can query
     |idlNode.identifier| in both cases, so we can be agnostic between the two.
     """
     # XXXbz For now each entry in the argument list is either an
     # IDLArgument or a FakeArgument, but longer-term we may want to
     # have ways of flagging things like JSContext* or optional_argc in
     # there.
 
-    def __init__(self, returnType, argsPre, arguments, nativeMethodName, static,
-                 descriptor, idlNode, argConversionStartsAt=0,
-                 getter=False, setter=False):
+    def __init__(self, returnType, arguments, nativeMethodName, static,
+                 descriptor, idlNode, argConversionStartsAt=0, getter=False,
+                 setter=False):
         assert idlNode.isMethod() == (not getter and not setter)
         assert idlNode.isAttr() == (getter or setter)
 
         CGThing.__init__(self)
         self.returnType = returnType
         self.descriptor = descriptor
         self.idlNode = idlNode
         self.extendedAttributes = descriptor.getExtendedAttributes(idlNode,
                                                                    getter=getter,
                                                                    setter=setter)
-        self.argsPre = argsPre
         self.arguments = arguments
         self.argCount = len(arguments)
         if self.argCount > argConversionStartsAt:
             # Insert our argv in there
             cgThings = [CGGeneric(self.getArgvDecl())]
         else:
             cgThings = []
         lenientFloatCode = None
         if idlNode.getExtendedAttribute('LenientFloat') is not None:
             if setter:
                 lenientFloatCode = "return true;"
             elif idlNode.isMethod():
                 lenientFloatCode = ("*vp = JSVAL_VOID;\n"
                                     "return true;")
+
+        argsPre = []
+        if static:
+            nativeMethodName = "%s::%s" % (descriptor.nativeType,
+                                           nativeMethodName)
+            globalObjectType = "GlobalObject"
+            if descriptor.workers:
+                globalObjectType = "Worker" + globalObjectType
+            cgThings.append(CGGeneric("""%s global(cx, obj);
+if (global.Failed()) {
+  return false;
+}
+""" % globalObjectType))
+            argsPre.append("global")
+
+        needsCx = needCx(returnType, arguments, self.extendedAttributes,
+                         descriptor)
+        if needsCx and not (static and descriptor.workers):
+            argsPre.append("cx")
+
         cgThings.extend([CGArgumentConverter(arguments[i], i, self.getArgv(),
                                              self.getArgc(), self.descriptor,
                                              invalidEnumValueFatal=not setter,
                                              allowTreatNonCallableAsNull=setter,
                                              lenientFloatCode=lenientFloatCode) for
                          i in range(argConversionStartsAt, self.argCount)])
 
         cgThings.append(CGCallGenerator(
                     self.getErrorReport() if self.isFallible() else None,
-                    self.getArguments(), self.argsPre, returnType,
+                    self.getArguments(), argsPre, returnType,
                     self.extendedAttributes, descriptor, nativeMethodName,
                     static))
         self.cgRoot = CGList(cgThings, "\n")
 
     def getArgv(self):
         return "argv" if self.argCount > 0 else ""
     def getArgvDecl(self):
         return "\nJS::Value* argv = JS_ARGV(cx, vp);\n"
@@ -3840,32 +3879,32 @@ class CGCase(CGList):
         self.append(CGIndenter(bodyList));
         self.append(CGGeneric("}"))
 
 class CGMethodCall(CGThing):
     """
     A class to generate selection of a method signature from a set of
     signatures and generation of a call to that signature.
     """
-    def __init__(self, argsPre, nativeMethodName, static, descriptor, method):
+    def __init__(self, nativeMethodName, static, descriptor, method):
         CGThing.__init__(self)
 
         methodName = '"%s.%s"' % (descriptor.interface.identifier.name, method.identifier.name)
 
         def requiredArgCount(signature):
             arguments = signature[1]
             if len(arguments) == 0:
                 return 0
             requiredArgs = len(arguments)
             while requiredArgs and arguments[requiredArgs-1].optional:
                 requiredArgs -= 1
             return requiredArgs
 
         def getPerSignatureCall(signature, argConversionStartsAt=0):
-            return CGPerSignatureCall(signature[0], argsPre, signature[1],
+            return CGPerSignatureCall(signature[0], signature[1],
                                       nativeMethodName, static, descriptor,
                                       method, argConversionStartsAt)
             
 
         signatures = method.signatures()
         if len(signatures) == 1:
             # Special case: we can just do a per-signature method call
             # here for our one signature and not worry about switching
@@ -4143,23 +4182,19 @@ class CGMethodCall(CGThing):
         return self.cgRoot.define()
 
 class CGGetterCall(CGPerSignatureCall):
     """
     A class to generate a native object getter call for a particular IDL
     getter.
     """
     def __init__(self, returnType, nativeMethodName, descriptor, attr):
-        if attr.isStatic():
-            argsPre = [ "global" ]
-        else:
-            argsPre = []
-        CGPerSignatureCall.__init__(self, returnType, argsPre, [],
-                                    nativeMethodName, attr.isStatic(),
-                                    descriptor, attr, getter=True)
+        CGPerSignatureCall.__init__(self, returnType, [], nativeMethodName,
+                                    attr.isStatic(), descriptor, attr,
+                                    getter=True)
 
 class FakeArgument():
     """
     A class that quacks like an IDLArgument.  This is used to make
     setters look like method calls or for special operations.
     """
     def __init__(self, type, interfaceMember, name="arg"):
         self.type = type
@@ -4176,22 +4211,17 @@ class FakeArgument():
         self.identifier = FakeIdentifier()
 
 class CGSetterCall(CGPerSignatureCall):
     """
     A class to generate a native object setter call for a particular IDL
     setter.
     """
     def __init__(self, argType, nativeMethodName, descriptor, attr):
-        if attr.isStatic():
-            argsPre = [ "global" ]
-        else:
-            argsPre = []
-        CGPerSignatureCall.__init__(self, None, argsPre,
-                                    [FakeArgument(argType, attr)],
+        CGPerSignatureCall.__init__(self, None, [FakeArgument(argType, attr)],
                                     nativeMethodName, attr.isStatic(),
                                     descriptor, attr, setter=True)
     def wrap_return_value(self):
         # We have no return value
         return "\nreturn true;"
     def getArgc(self):
         return "1"
     def getArgvDecl(self):
@@ -4250,47 +4280,21 @@ class CGAbstractStaticBindingMethod(CGAb
     global object.  Subclasses are expected to override the generate_code
     function to do the rest of the work.  This function should return a
     CGThing which is already properly indented.
     """
     def __init__(self, descriptor, name, args):
         CGAbstractStaticMethod.__init__(self, descriptor, name, "JSBool", args)
 
     def definition_body(self):
-        isMainThread = toStringBool(not self.descriptor.workers)
         unwrap = CGGeneric("""js::RootedObject obj(cx, JS_THIS_OBJECT(cx, vp));
 if (!obj) {
   return false;
 }
-
-// We have to be careful to leave "obj" in its existing compartment, even
-// while we grab our global from the real underlying object, because we
-// use it for unwrapping the other arguments later.
-nsISupports* global;
-xpc_qsSelfRef globalRef;
-{
-  JS::Value val;
-  Maybe<JSAutoCompartment> ac;
-  if (js::IsWrapper(obj)) {
-    JSObject* realObj = XPCWrapper::Unwrap(cx, obj, false);
-    if (!realObj) {
-      return Throw<%s>(cx, NS_ERROR_XPC_SECURITY_MANAGER_VETO);
-    }
-    ac.construct(cx, realObj);
-    val.setObject(*JS_GetGlobalForObject(cx, realObj));
-  } else {
-    val.setObject(*JS_GetGlobalForObject(cx, obj));
-  }
-
-  nsresult rv = xpc_qsUnwrapArg<nsISupports>(cx, val, &global, &globalRef.ptr,
-                                             &val);
-  if (NS_FAILED(rv)) {
-    return Throw<%s>(cx, NS_ERROR_XPC_BAD_CONVERT_JS);
-  }
-}""" % (isMainThread, isMainThread))
+""")
         return CGList([ CGIndenter(unwrap),
                         self.generate_code() ], "\n\n").define()
 
     def generate_code(self):
         assert(False) # Override me
 
 def MakeNativeName(name):
     return name[0].upper() + name[1:]
@@ -4322,18 +4326,18 @@ class CGSpecializedMethod(CGAbstractStat
         args = [Argument('JSContext*', 'cx'), Argument('JSHandleObject', 'obj'),
                 Argument('%s*' % descriptor.nativeType, 'self'),
                 Argument('unsigned', 'argc'), Argument('JS::Value*', 'vp')]
         CGAbstractStaticMethod.__init__(self, descriptor, name, 'bool', args)
 
     def definition_body(self):
         nativeName = CGSpecializedMethod.makeNativeName(self.descriptor,
                                                         self.method)
-        return CGMethodCall([], nativeName, self.method.isStatic(),
-                            self.descriptor, self.method).define()
+        return CGMethodCall(nativeName, self.method.isStatic(), self.descriptor,
+                            self.method).define()
 
     @staticmethod
     def makeNativeName(descriptor, method):
         name = method.identifier.name
         return MakeNativeName(descriptor.binaryNames.get(name, name))
 
 class CppKeywords():
     """
@@ -4365,18 +4369,17 @@ class CGStaticMethod(CGAbstractStaticBin
         name = method.identifier.name
         args = [Argument('JSContext*', 'cx'), Argument('unsigned', 'argc'),
                 Argument('JS::Value*', 'vp')]
         CGAbstractStaticBindingMethod.__init__(self, descriptor, name, args)
 
     def generate_code(self):
         nativeName = CGSpecializedMethod.makeNativeName(self.descriptor,
                                                         self.method)
-        return CGMethodCall([ "global" ], nativeName, True, self.descriptor,
-                            self.method)
+        return CGMethodCall(nativeName, True, self.descriptor, self.method)
 
 class CGGenericGetter(CGAbstractBindingMethod):
     """
     A class for generating the C++ code for an IDL attribute getter.
     """
     def __init__(self, descriptor, lenientThis=False):
         args = [Argument('JSContext*', 'cx'), Argument('unsigned', 'argc'),
                 Argument('JS::Value*', 'vp')]
@@ -5675,17 +5678,17 @@ class CGProxySpecialOperation(CGPerSigna
         operation = descriptor.operations[operation]
         assert len(operation.signatures()) == 1
         signature = operation.signatures()[0]
 
         (returnType, arguments) = signature
 
         # We pass len(arguments) as the final argument so that the
         # CGPerSignatureCall won't do any argument conversion of its own.
-        CGPerSignatureCall.__init__(self, returnType, "", arguments, nativeName,
+        CGPerSignatureCall.__init__(self, returnType, arguments, nativeName,
                                     False, descriptor, operation,
                                     len(arguments))
 
         if operation.isSetter() or operation.isCreator():
             # arguments[0] is the index or name of the item that we're setting.
             argument = arguments[1]
             template = getJSToNativeConversionTemplate(argument.type, descriptor,
                                                        treatNullAs=argument.treatNullAs,
@@ -6876,16 +6879,18 @@ class CGBindingRoot(CGThing):
     declare or define to generate header or cpp code (respectively).
     """
     def __init__(self, config, prefix, webIDLFile):
         descriptors = config.getDescriptors(webIDLFile=webIDLFile,
                                             hasInterfaceOrInterfacePrototypeObject=True,
                                             skipGen=False)
         dictionaries = config.getDictionaries(webIDLFile)
         callbacks = config.getCallbacks(webIDLFile)
+        callbackDescriptors = config.getDescriptors(webIDLFile=webIDLFile,
+                                                    isCallback=True)
 
         forwardDeclares = [CGClassForwardDeclare('XPCWrappedNativeScope')]
 
         descriptorsForForwardDeclaration = list(descriptors)
         ifaces = []
         workerIfaces = []
         for dictionary in dictionaries:
             dictionaryIfaces = [ type.unroll().inner
@@ -6897,16 +6902,24 @@ class CGBindingRoot(CGThing):
         for callback in callbacks:
             callbackIfaces = [ t.unroll().inner
                                for t in getTypesFromCallback(callback)
                                if t.unroll().isGeckoInterface() ]
             workerIfaces.extend(callbackIfaces)
             if not callback.isWorkerOnly():
                 ifaces.extend(callbackIfaces)
 
+        for callbackDescriptor in callbackDescriptors:
+            callbackDescriptorIfaces = [
+                t.unroll().inner
+                for t in getTypesFromDescriptor(callbackDescriptor)
+                if t.unroll().isGeckoInterface() ]
+            workerIfaces.extend(callbackDescriptorIfaces)
+            ifaces.extend(callbackDescriptorIfaces)
+
         # Put in all the non-worker descriptors
         descriptorsForForwardDeclaration.extend(
             config.getDescriptor(iface.identifier.name, False) for
             iface in ifaces)
         # And now the worker ones.  But these may not exist, so we
         # have to be more careful.
         for iface in workerIfaces:
             try:
@@ -6934,16 +6947,22 @@ class CGBindingRoot(CGThing):
         # Now add the forward declarations we need for our union types
         # and callback functions.
         for callback in callbacks:
             forwardDeclares.extend(
                 declareNativeType("mozilla::dom::" + str(t.unroll()))
                 for t in getTypesFromCallback(callback)
                 if t.unroll().isUnion() or t.unroll().isCallback())
 
+        for callbackDescriptor in callbackDescriptors:
+            forwardDeclares.extend(
+                declareNativeType("mozilla::dom::" + str(t.unroll()))
+                for t in getTypesFromDescriptor(callbackDescriptor)
+                if t.unroll().isUnion() or t.unroll().isCallback())
+
         # Forward declarations for callback functions used in dictionaries.
         for dictionary in dictionaries:
             forwardDeclares.extend(
                 declareNativeType("mozilla::dom::" + str(t.unroll()))
                 for t in getTypesFromDictionary(dictionary)
                 if t.unroll().isCallback())
 
         forwardDeclares = CGList(forwardDeclares)
@@ -7009,16 +7028,19 @@ class CGBindingRoot(CGThing):
         # since we don't have a sane setup yet for invoking callbacks in workers
         # and managing their lifetimes.
         cgthings.extend(CGCallbackFunction(c, config.getDescriptorProvider(False))
                         for c in callbacks)
 
         # Do codegen for all the descriptors
         cgthings.extend([CGDescriptor(x) for x in descriptors])
 
+        # Do codegen for all the callback interfaces
+        cgthings.extend([CGCallbackInterface(x) for x in callbackDescriptors])
+
         # And make sure we have the right number of newlines at the end
         curr = CGWrapper(CGList(cgthings, "\n\n"), post="\n\n")
 
         # Wrap all of that in our namespaces.
         curr = CGNamespace.build(['mozilla', 'dom'],
                                  CGWrapper(curr, pre="\n"))
 
         curr = CGList([forwardDeclares,
@@ -7026,16 +7048,17 @@ class CGBindingRoot(CGThing):
                                  defineOnly=True),
                        traitsClasses, curr],
                       "\n")
 
         # Add header includes.
         curr = CGHeaders(descriptors,
                          dictionaries,
                          callbacks,
+                         callbackDescriptors,
                          ['mozilla/dom/BindingDeclarations.h',
                           'mozilla/ErrorResult.h',
                           'mozilla/dom/DOMJSClass.h',
                           'mozilla/dom/DOMJSProxyHandler.h'],
                          ['mozilla/dom/BindingUtils.h',
                           'mozilla/dom/NonRefcountedDOMObject.h',
                           'mozilla/dom/Nullable.h',
                           'PrimitiveConversions.h',
@@ -7235,25 +7258,27 @@ class CGNativeMember(ClassMethod):
             type = CGWrapper(CGGeneric(elementDecl), pre="nsTArray< ", post=" >")
             if nullable:
                 type = CGWrapper(type, pre="Nullable< ", post=" >")
             args.append(Argument("%s&" % type.define(), "retval"))
         # And the ErrorResult
         if not 'infallible' in self.extendedAttrs:
             # Use aRv so it won't conflict with local vars named "rv"
             args.append(Argument("ErrorResult&", "aRv"))
+        # And jscontext bits.
+        if (self.passCxAsNeeded and
+            needCx(returnType, argList, self.extendedAttrs,
+                   self.descriptor)):
+            args.insert(0, Argument("JSContext*", "cx"))
         # And if we're static, a global
         if self.member.isStatic():
-            args.insert(0, Argument("nsISupports*", "global"))
-        # And jscontext bits.  needCx expects a list of tuples, in each of which
-        # the first element is the actual argument
-        if (self.passCxAsNeeded and
-            needCx(returnType, ((a, "") for a in argList), self.extendedAttrs,
-                   self.descriptor)):
-            args.insert(0, Argument("JSContext*", "cx"))
+            globalObjectType = "GlobalObject"
+            if self.descriptor.workers:
+                globalObjectType = "Worker" + globalObjectType
+            args.insert(0, Argument("const %s&" % globalObjectType, "global"))
         return args
 
     def doGetArgType(self, type, optional, isMember):
         """
         The main work of getArgType.  Returns a string type decl, whether this
         is a const ref, as well as whether the type should be wrapped in
         Nullable as needed.
         """
@@ -7269,17 +7294,21 @@ class CGNativeMember(ClassMethod):
                              pre="Sequence< ", post=" >")
             return decl.define(), True, True
 
         if type.isUnion():
             if type.nullable():
                 type = type.inner
             return str(type), True, True
 
-        if type.isGeckoInterface():
+        if (type.isGeckoInterface() and
+            (not type.isCallbackInterface() or
+             type.unroll().inner.identifier.name == "NodeFilter" or
+             type.unroll().inner.identifier.name == "EventListener" or
+             type.unroll().inner.identifier.name == "DOMTransaction")):
             iface = type.unroll().inner
             argIsPointer = type.nullable() or iface.isExternal()
             forceOwningType = iface.isCallback() or isMember
             if argIsPointer:
                 if (optional or isMember) and forceOwningType:
                     typeDecl = "nsRefPtr<%s>"
                 else:
                     typeDecl = "%s*"
@@ -7314,28 +7343,33 @@ class CGNativeMember(ClassMethod):
                 declType = "nsString"
             else:
                 declType = "nsAString"
             return declType, True, False
 
         if type.isEnum():
             return type.inner.identifier.name, False, True
 
-        if type.isCallback():
+        if type.isCallback() or type.isCallbackInterface():
+            forceOwningType = optional or isMember
             if type.nullable():
-                if optional:
+                if forceOwningType:
                     declType = "nsRefPtr<%s>"
                 else:
                     declType = "%s*"
             else:
-                if optional:
+                if forceOwningType:
                     declType = "OwningNonNull<%s>"
                 else:
                     declType = "%s&"
-            return declType % type.unroll().identifier.name, False, False
+            if type.isCallback():
+                name = type.unroll().identifier.name
+            else:
+                name = type.unroll().inner.identifier.name
+            return declType % name, False, False
 
         if type.isAny():
             return "JS::Value", False, False
 
         if type.isObject():
             if type.nullable() or self.jsObjectsArePtr:
                 declType = "%s*"
             elif optional:
@@ -7594,17 +7628,17 @@ class CGExampleRoot(CGThing):
                               pre="\n", post="\n")
 
         self.root = CGNamespace.build(["mozilla", "dom"], self.root);
 
         self.root = CGList([CGClassForwardDeclare("JSContext", isStruct=True),
                             self.root], "\n")
 
         # Throw in our #includes
-        self.root = CGHeaders([], [], [],
+        self.root = CGHeaders([], [], [], [],
                               [ "nsWrapperCache.h",
                                 "nsCycleCollectionParticipant.h",
                                 "mozilla/Attributes.h",
                                 "mozilla/ErrorResult.h" ],
                               [ "%s.h" % interfaceName,
                                 "mozilla/dom/%sBinding.h" % interfaceName,
                                 "nsContentUtils.h" ], self.root);
 
@@ -7621,36 +7655,40 @@ class CGExampleRoot(CGThing):
 """)
 
     def declare(self):
         return self.root.declare()
 
     def define(self):
         return self.root.define()
 
-class CGCallbackFunction(CGClass):
-    def __init__(self, callback, descriptorProvider):
-        if callback.isWorkerOnly() and not descriptorProvider.workers:
-            self.generatable = False
-            return
-        name = callback.identifier.name
+class CGCallback(CGClass):
+    def __init__(self, idlObject, descriptorProvider, baseName, methods,
+                 getters=[], setters=[]):
+        self.baseName = baseName
+        name = idlObject.identifier.name
         if descriptorProvider.workers:
             name += "Workers"
         try:
-            # For our public Call() method we want most of the same args and the
-            # same return type as what CallCallback generates.  So we want to
-            # take advantage of all its CGNativeMember infrastructure, but that
-            # infrastructure can't deal with templates and most especially
-            # template arguments.  So just cheat and have CallCallback compute
-            # all those things for us.
-            callCallback = CallCallback(callback, descriptorProvider)
+            # For our public methods that needThisHandling we want most of the
+            # same args and the same return type as what CallbackMember
+            # generates.  So we want to take advantage of all its
+            # CGNativeMember infrastructure, but that infrastructure can't deal
+            # with templates and most especially template arguments.  So just
+            # cheat and have CallbackMember compute all those things for us.
+            realMethods = []
+            for method in methods:
+                if not method.needThisHandling:
+                    realMethods.append(method)
+                else:
+                    realMethods.extend(self.getMethodImpls(method))
             CGClass.__init__(self, name,
-                             bases=[ClassBase("CallbackFunction")],
+                             bases=[ClassBase(baseName)],
                              constructors=self.getConstructors(),
-                             methods=self.getCallImpls(callCallback))
+                             methods=realMethods+getters+setters)
             self.generatable = True
         except NoSuchDescriptorError, err:
             if not descriptorProvider.workers:
                 raise err
             self.generatable = False
 
     def define(self):
         if not self.generatable:
@@ -7661,199 +7699,234 @@ class CGCallbackFunction(CGClass):
         if not self.generatable:
             return ""
         return CGClass.declare(self)
 
     def getConstructors(self):
         return [ClassConstructor(
             [Argument("JSContext*", "cx"),
              Argument("JSObject*", "aOwner"),
-             Argument("JSObject*", "aCallable"),
+             Argument("JSObject*", "aCallback"),
              Argument("bool*", "aInited")],
             bodyInHeader=True,
             visibility="public",
             baseConstructors=[
-                "CallbackFunction(cx, aOwner, aCallable, aInited)"
-                ],
-            body=""),
-            ClassConstructor(
-            [Argument("CallbackFunction*", "aOther")],
-            bodyInHeader=True,
-            visibility="public",
-            explicit=True,
-            baseConstructors=[
-                "CallbackFunction(aOther)"
+                "%s(cx, aOwner, aCallback, aInited)" % self.baseName
                 ],
             body="")]
 
-    def getCallImpls(self, callCallback):
-        args = list(callCallback.args)
+    def getMethodImpls(self, method):
+        assert method.needThisHandling
+        args = list(method.args)
         # Strip out the JSContext*/JSObject* args
         # that got added.
         assert args[0].name == "cx" and args[0].argType == "JSContext*"
         assert args[1].name == "aThisObj" and args[1].argType == "JSObject*"
         args = args[2:]
         # Record the names of all the arguments, so we can use them when we call
         # the private method.
         argnames = [arg.name for arg in args]
         argnamesWithThis = ["s.GetContext()", "thisObjJS"] + argnames
         argnamesWithoutThis = ["s.GetContext()", "nullptr"] + argnames
         # And now insert our template argument.
         argsWithoutThis = list(args)
         args.insert(0, Argument("const T&",  "thisObj"))
 
-        setupCall = ("CallSetup s(mCallable);\n"
+        setupCall = ("CallSetup s(mCallback);\n"
                      "if (!s.GetContext()) {\n"
                      "  aRv.Throw(NS_ERROR_UNEXPECTED);\n"
                      "  return${errorReturn};\n"
                      "}\n")
 
         bodyWithThis = string.Template(
             setupCall+
-            "JSObject* thisObjJS = WrapCallThisObject(s.GetContext(), mCallable, thisObj);\n"
+            "JSObject* thisObjJS = WrapCallThisObject(s.GetContext(), mCallback, thisObj);\n"
             "if (!thisObjJS) {\n"
             "  aRv.Throw(NS_ERROR_FAILURE);\n"
             "  return${errorReturn};\n"
             "}\n"
-            "return Call(${callArgs});").substitute({
-                "errorReturn" : callCallback.getDefaultRetval(),
-                "callArgs" : ", ".join(argnamesWithThis)
+            "return ${methodName}(${callArgs});").substitute({
+                "errorReturn" : method.getDefaultRetval(),
+                "callArgs" : ", ".join(argnamesWithThis),
+                "methodName": method.name,
                 })
         bodyWithoutThis = string.Template(
             setupCall +
-            "return Call(${callArgs});").substitute({
-                "errorReturn" : callCallback.getDefaultRetval(),
-                "callArgs" : ", ".join(argnamesWithoutThis)
+            "return ${methodName}(${callArgs});").substitute({
+                "errorReturn" : method.getDefaultRetval(),
+                "callArgs" : ", ".join(argnamesWithoutThis),
+                "methodName": method.name,
                 })
-        return [ClassMethod("Call", callCallback.returnType, args,
+        return [ClassMethod(method.name, method.returnType, args,
                             bodyInHeader=True,
                             templateArgs=["typename T"],
                             body=bodyWithThis),
-                ClassMethod("Call", callCallback.returnType, argsWithoutThis,
+                ClassMethod(method.name, method.returnType, argsWithoutThis,
                             bodyInHeader=True,
                             body=bodyWithoutThis),
-                callCallback]
+                method]
+
+class CGCallbackFunction(CGCallback):
+    def __init__(self, callback, descriptorProvider):
+        if callback.isWorkerOnly() and not descriptorProvider.workers:
+            self.generatable = False
+            return
+        CGCallback.__init__(self, callback, descriptorProvider,
+                            "CallbackFunction",
+                            methods=[CallCallback(callback, descriptorProvider)])
+
+    def getConstructors(self):
+        return CGCallback.getConstructors(self) + [
+            ClassConstructor(
+            [Argument("CallbackFunction*", "aOther")],
+            bodyInHeader=True,
+            visibility="public",
+            explicit=True,
+            baseConstructors=[
+                "CallbackFunction(aOther)"
+                ],
+            body="")]
+
+class CGCallbackInterface(CGCallback):
+    def __init__(self, descriptor):
+        iface = descriptor.interface
+        attrs = [m for m in iface.members if m.isAttr() and not m.isStatic()]
+        getters = [CallbackGetter(a, descriptor) for a in attrs]
+        setters = [CallbackSetter(a, descriptor) for a in attrs
+                   if not a.readonly]
+        methods = [m for m in iface.members
+                   if m.isMethod() and not m.isStatic()]
+        methods = [CallbackOperation(m, sig, descriptor) for m in methods
+                   for sig in m.signatures()]
+        CGCallback.__init__(self, iface, descriptor, "CallbackInterface",
+                            methods, getters=getters, setters=setters)
 
 class FakeMember():
     def __init__(self):
         self.treatUndefinedAs = self.treatNullAs = "Default"
     def isStatic(self):
         return False
     def isAttr(self):
         return False
     def getExtendedAttribute(self, name):
         # Claim to be a [Creator] so we can avoid the "mark this
         # resultNotAddRefed" comments CGNativeMember codegen would
         # otherwise stick in.
         if name == "Creator":
             return True
         return None
 
-class CallCallback(CGNativeMember):
-    def __init__(self, callback, descriptorProvider):
-        sig = callback.signatures()[0]
+class CallbackMember(CGNativeMember):
+    def __init__(self, sig, name, descriptorProvider, needThisHandling):
+        """
+        needThisHandling is True if we need to be able to accept a specified
+        thisObj, False otherwise.
+        """
         self.retvalType = sig[0]
-        self.callback = callback
+        self.originalSig = sig
         args = sig[1]
         self.argCount = len(args)
         if self.argCount > 0:
             # Check for variadic arguments
             lastArg = args[self.argCount-1]
             if lastArg.variadic:
                 self.argCountStr = (
                     "(%d - 1) + %s.Length()" % (self.argCount,
                                                 lastArg.identifier.name))
             else:
                 self.argCountStr = "%d" % self.argCount
+        self.needThisHandling = needThisHandling
+        # If needThisHandling, we generate ourselves as private and the caller
+        # will handle generating public versions that handle the "this" stuff.
+        visibility = "private" if needThisHandling else "public"
+        # We don't care, for callback codegen, whether our original member was
+        # a method or attribure or whatnot.  Just always pass FakeMember()
+        # here.
         CGNativeMember.__init__(self, descriptorProvider, FakeMember(),
-                                "Call", (self.retvalType, args),
+                                name, (self.retvalType, args),
                                 extendedAttrs={},
                                 passCxAsNeeded=False,
-                                visibility="private",
+                                visibility=visibility,
                                 jsObjectsArePtr=True)
         # We have to do all the generation of our body now, because
         # the caller relies on us throwing if we can't manage it.
         self.exceptionCode=("aRv.Throw(NS_ERROR_UNEXPECTED);\n"
                             "return%s;" % self.getDefaultRetval())
         self.body = self.getImpl()
 
     def getImpl(self):
         replacements = {
+            "declRval": self.getRvalDecl(),
             "errorReturn" : self.getDefaultRetval(),
             "returnResult": self.getResultConversion(),
             "convertArgs": self.getArgConversions(),
+            "doCall": self.getCall(),
+            "setupCall": self.getCallSetup(),
             }
         if self.argCount > 0:
             replacements["argCount"] = self.argCountStr
             replacements["argvDecl"] = string.Template(
                 "JS::AutoValueVector argv(cx);\n"
                 "if (!argv.resize(${argCount})) {\n"
                 "  aRv.Throw(NS_ERROR_OUT_OF_MEMORY);\n"
                 "  return${errorReturn};\n"
                 "}\n"
                 ).substitute(replacements)
-            replacements["argv"] = "argv.begin()"
-            replacements["argc"] = "argc"
         else:
             # Avoid weird 0-sized arrays
             replacements["argvDecl"] = ""
-            replacements["argv"] = "nullptr"
-            replacements["argc"] = "0"
 
         return string.Template(
-            "JS::Value rval = JSVAL_VOID;\n"
-            "${argvDecl}" # Newlines and semicolons are in the value
+            # Newlines and semicolons are in the values
+            "${setupCall}"
+            "${declRval}"
+            "${argvDecl}"
             "${convertArgs}"
-            "if (!JS_CallFunctionValue(cx, aThisObj, JS::ObjectValue(*mCallable),\n"
-            "                          ${argc}, ${argv}, &rval)) {\n"
-            "  aRv.Throw(NS_ERROR_UNEXPECTED);\n"
-            "  return${errorReturn};\n"
-            "}\n"
+            "${doCall}"
             "${returnResult}").substitute(replacements)
 
     def getResultConversion(self):
         replacements = {
             "val": "rval",
             "valPtr": "&rval",
             "holderName" : "rvalHolder",
             "declName" : "rvalDecl",
             # We actually want to pass in a null scope object here, because
-            # wrapping things into our current compartment (that of mCallable)
+            # wrapping things into our current compartment (that of mCallback)
             # is what we want.
             "obj": "nullptr"
             }
 
         convertType = instantiateJSToNativeConversionTemplate(
             getJSToNativeConversionTemplate(self.retvalType,
                                             self.descriptor,
                                             exceptionCode=self.exceptionCode),
             replacements)
         assignRetval = string.Template(
             self.getRetvalInfo(self.retvalType,
                                False)[2]).substitute(replacements)
         return convertType.define() + "\n" + assignRetval
 
     def getArgConversions(self):
-        # Just reget the arglist from self.callback, because our superclasses
+        # Just reget the arglist from self.originalSig, because our superclasses
         # just have way to many members they like to clobber, so I can't find a
         # safe member name to store it in.
         argConversions = [self.getArgConversion(i, arg) for (i, arg)
-                          in enumerate(self.callback.signatures()[0][1])]
+                          in enumerate(self.originalSig[1])]
         # Do them back to front, so our argc modifications will work
         # correctly, because we examine trailing arguments first.
         argConversions.reverse();
         # Wrap each one in a scope so that any locals it has don't leak out, and
         # also so that we can just "break;" for our successCode.
         argConversions = [CGWrapper(CGIndenter(CGGeneric(c)),
                                     pre="do {\n",
                                     post="\n} while (0);")
                           for c in argConversions]
         if self.argCount > 0:
-            argConversions.insert(0,
-                                  CGGeneric("unsigned argc = %s;" % self.argCountStr));
+            argConversions.insert(0, self.getArgcDecl())
         # And slap them together.
         return CGList(argConversions, "\n\n").define() + "\n\n"
 
     def getArgConversion(self, i, arg):
         argval = arg.identifier.name
 
         if arg.variadic:
             argval = argval + "[idx]"
@@ -7876,17 +7949,17 @@ class CallCallback(CGNativeMember):
             arg.type, self.descriptor,
             {
                 'result' : result,
                 'successCode' : "continue;" if arg.variadic else "break;",
                 'jsvalRef' : "argv[%s]" % jsvalIndex,
                 'jsvalPtr' : "&argv[%s]" % jsvalIndex,
                 # XXXbz we don't have anything better to use for 'obj',
                 # really...
-                'obj' : 'mCallable',
+                'obj' : 'mCallback',
                 'isCreator': False,
                 'exceptionCode' : self.exceptionCode
                 })
         if arg.variadic:
             conversion = string.Template(
                 "for (uint32_t idx = 0; idx < ${arg}.Length(); ++idx) {\n" +
                 CGIndenter(CGGeneric(conversion)).define() + "\n"
                 "}\n"
@@ -7906,21 +7979,187 @@ class CallCallback(CGNativeMember):
     def getDefaultRetval(self):
         default = self.getRetvalInfo(self.retvalType, False)[1]
         if len(default) != 0:
             default = " " + default
         return default
 
     def getArgs(self, returnType, argList):
         args = CGNativeMember.getArgs(self, returnType, argList)
+        if not self.needThisHandling:
+            return args
         # We want to allow the caller to pass in a "this" object, as
         # well as a JSContext.
         return [Argument("JSContext*", "cx"),
                 Argument("JSObject*", "aThisObj")] + args
 
+    def getCallSetup(self):
+        if self.needThisHandling:
+            # It's been done for us already
+            return ""
+        return string.Template(
+            "CallSetup s(mCallback);\n"
+            "JSContext* cx = s.GetContext();\n"
+            "if (!cx) {\n"
+            "  aRv.Throw(NS_ERROR_UNEXPECTED);\n"
+            "  return${errorReturn};\n"
+            "}\n").substitute({
+                "errorReturn" : self.getDefaultRetval(),
+                })
+
+    def getArgcDecl(self):
+        return CGGeneric("unsigned argc = %s;" % self.argCountStr);
+
+    @staticmethod
+    def ensureASCIIName(idlObject):
+        type = "attribute" if idlObject.isAttr() else "operation"
+        if re.match("[^\x20-\x7E]", idlObject.identifier.name):
+            raise SyntaxError('Callback %s name "%s" contains non-ASCII '
+                              "characters.  We can't handle that.  %s" %
+                              (type, idlObject.identifier.name,
+                               idlObject.location))
+        if re.match('"', idlObject.identifier.name):
+            raise SyntaxError("Callback %s name '%s' contains "
+                              "double-quote character.  We can't handle "
+                              "that.  %s" %
+                              (type, idlObject.identifier.name,
+                               idlObject.location))
+
+class CallbackMethod(CallbackMember):
+    def __init__(self, sig, name, descriptorProvider, needThisHandling):
+        CallbackMember.__init__(self, sig, name, descriptorProvider,
+                                needThisHandling)
+    def getRvalDecl(self):
+        return "JS::Value rval = JSVAL_VOID;\n"
+
+    def getCall(self):
+        replacements = {
+            "errorReturn" : self.getDefaultRetval(),
+            "thisObj": self.getThisObj(),
+            "getCallable": self.getCallableDecl()
+            }
+        if self.argCount > 0:
+            replacements["argv"] = "argv.begin()"
+            replacements["argc"] = "argc"
+        else:
+            replacements["argv"] = "nullptr"
+            replacements["argc"] = "0"
+        return string.Template("${getCallable}"
+                "if (!JS_CallFunctionValue(cx, ${thisObj}, callable,\n"
+                "                          ${argc}, ${argv}, &rval)) {\n"
+                "  aRv.Throw(NS_ERROR_UNEXPECTED);\n"
+                "  return${errorReturn};\n"
+                "}\n").substitute(replacements)
+
+class CallCallback(CallbackMethod):
+    def __init__(self, callback, descriptorProvider):
+        CallbackMethod.__init__(self, callback.signatures()[0], "Call",
+                                descriptorProvider, needThisHandling=True)
+
+    def getThisObj(self):
+        return "aThisObj"
+
+    def getCallableDecl(self):
+        return "JS::Value callable = JS::ObjectValue(*mCallback);\n"
+
+class CallbackOperation(CallbackMethod):
+    def __init__(self, method, signature, descriptor):
+        self.singleOperation = descriptor.interface.isSingleOperationInterface()
+        self.ensureASCIIName(method)
+        self.methodName = method.identifier.name
+        CallbackMethod.__init__(self, signature,
+                                MakeNativeName(self.methodName),
+                                descriptor,
+                                self.singleOperation)
+
+    def getThisObj(self):
+        if not self.singleOperation:
+            return "mCallback"
+        # This relies on getCallableDecl declaring a boolean
+        # isCallable in the case when we're a single-operation
+        # interface.
+        return "isCallable ? aThisObj : mCallback"
+
+    def getCallableDecl(self):
+        replacements = {
+            "errorReturn" : self.getDefaultRetval(),
+            "methodName": self.methodName
+            }
+        getCallableFromProp = string.Template(
+                'if (!GetCallableProperty(cx, "${methodName}", &callable)) {\n'
+                '  aRv.Throw(NS_ERROR_UNEXPECTED);\n'
+                '  return${errorReturn};\n'
+                '}\n').substitute(replacements)
+        if not self.singleOperation:
+            return 'JS::Value callable;\n' + getCallableFromProp
+        return (
+            'bool isCallable = JS_ObjectIsCallable(cx, mCallback);\n'
+            'JS::Value callable;\n'
+            'if (isCallable) {\n'
+            '  callable = JS::ObjectValue(*mCallback);\n'
+            '} else {\n'
+            '%s'
+            '}\n' % CGIndenter(CGGeneric(getCallableFromProp)).define())
+
+class CallbackGetter(CallbackMember):
+    def __init__(self, attr, descriptor):
+        self.ensureASCIIName(attr)
+        self.attrName = attr.identifier.name
+        CallbackMember.__init__(self,
+                                (attr.type, []),
+                                # We're always fallible
+                                "Get" + MakeNativeName(attr.identifier.name),
+                                descriptor,
+                                needThisHandling=False)
+
+    def getRvalDecl(self):
+        return "JS::Value rval = JSVAL_VOID;\n"
+
+    def getCall(self):
+        replacements = {
+            "errorReturn" : self.getDefaultRetval(),
+            "attrName": self.attrName
+            }
+        return string.Template(
+            'if (!JS_GetProperty(cx, mCallback, "${attrName}", &rval)) {\n'
+            '  aRv.Throw(NS_ERROR_UNEXPECTED);\n'
+            '  return${errorReturn};\n'
+            '}\n').substitute(replacements);
+
+class CallbackSetter(CallbackMember):
+    def __init__(self, attr, descriptor):
+        self.ensureASCIIName(attr)
+        self.attrName = attr.identifier.name
+        CallbackMember.__init__(self,
+                                (BuiltinTypes[IDLBuiltinType.Types.void],
+                                 [FakeArgument(attr.type, attr)]),
+                                "Set" + MakeNativeName(attr.identifier.name),
+                                descriptor,
+                                needThisHandling=False)
+
+    def getRvalDecl(self):
+        # We don't need an rval
+        return ""
+
+    def getCall(self):
+        replacements = {
+            "errorReturn" : self.getDefaultRetval(),
+            "attrName": self.attrName,
+            "argv": "argv.begin()",
+            }
+        return string.Template(
+            'MOZ_ASSERT(argv.length() == 1);\n'
+            'if (!JS_SetProperty(cx, mCallback, "${attrName}", ${argv})) {\n'
+            '  aRv.Throw(NS_ERROR_UNEXPECTED);\n'
+            '  return${errorReturn};\n'
+            '}\n').substitute(replacements)
+
+    def getArgcDecl(self):
+        return None
+
 class GlobalGenRoots():
     """
     Roots for global codegen.
 
     To generate code, call the method associated with the target, and then
     call the appropriate define/declare method.
     """
 
@@ -8005,17 +8244,17 @@ struct PrototypeIDMap;
         curr = CGWrapper(curr, post='\n')
 
         # Add the includes
         defineIncludes = [CGHeaders.getDeclarationFilename(desc.interface)
                           for desc in config.getDescriptors(hasInterfaceObject=True,
                                                             workers=False,
                                                             register=True)]
         defineIncludes.append('nsScriptNameSpaceManager.h')
-        curr = CGHeaders([], [], [], [], defineIncludes, curr)
+        curr = CGHeaders([], [], [], [], [], defineIncludes, curr)
 
         # Add include guards.
         curr = CGIncludeGuard('RegisterBindings', curr)
 
         # Done.
         return curr
 
     @staticmethod
@@ -8057,17 +8296,17 @@ struct PrototypeIDMap;
                 # set the top of the namespace stack to the list of the new
                 # namespace
                 stack[i + j + 1:] = [list]
 
             stack[len(elements)].append(clazz)
 
         curr = CGList([stack[0], curr], "\n")
 
-        curr = CGHeaders([], [], [], includes, implincludes, curr)
+        curr = CGHeaders([], [], [], [], includes, implincludes, curr)
 
         # Add include guards.
         curr = CGIncludeGuard('UnionTypes', curr)
 
         # Done.
         return curr
 
     @staticmethod
@@ -8079,15 +8318,15 @@ struct PrototypeIDMap;
                                              config)
 
         # Wrap all of that in our namespaces.
         curr = CGNamespace.build(['mozilla', 'dom'], unions)
 
         curr = CGWrapper(curr, post='\n')
 
         headers.update(["nsDebug.h", "mozilla/dom/UnionTypes.h", "nsDOMQS.h", "XPCWrapper.h"])
-        curr = CGHeaders([], [], [], headers, [], curr)
+        curr = CGHeaders([], [], [], [], headers, [], curr)
 
         # Add include guards.
         curr = CGIncludeGuard('UnionConversions', curr)
 
         # Done.
         return curr
--- a/dom/bindings/Configuration.py
+++ b/dom/bindings/Configuration.py
@@ -1,13 +1,14 @@
 # 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/.
 
 from WebIDL import IDLInterface, IDLExternalInterface
+import os
 
 autogenerated_comment = "/* THIS FILE IS AUTOGENERATED - DO NOT EDIT */\n"
 
 class Configuration:
     """
     Represents global configuration state based on IDL parse data and
     the configuration file.
     """
@@ -150,33 +151,45 @@ class Descriptor(DescriptorProvider):