Merge from mozilla-central.
authorDavid Anderson <danderson@mozilla.com>
Fri, 10 Aug 2012 14:50:20 -0700
changeset 112966 e95f1a182bc2de73e1f4df727a7b80e2c5f3f8c8
parent 112965 b3ece996df7eab4d7cf2228e855f117ad0693360 (current diff)
parent 107519 b5ae446888f54480e2d20e59dc1b8723799a5cbb (diff)
child 112967 cb2c22f6bb92b895308c346cdd999d5fd77c581b
push id1708
push userakeybl@mozilla.com
push dateMon, 19 Nov 2012 21:10:21 +0000
treeherdermozilla-beta@27b14fe50103 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone17.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.
accessible/src/atk/AccessibleWrap.cpp
accessible/src/atk/ApplicationAccessibleWrap.cpp
accessible/src/base/AccEvent.cpp
accessible/src/base/nsAccDocManager.cpp
accessible/src/base/nsAccessNode.cpp
accessible/src/base/nsAccessNode.h
accessible/src/base/nsAccessibilityService.cpp
accessible/src/base/nsAccessibilityService.h
accessible/src/msaa/nsAccessNodeWrap.cpp
browser/base/content/test/browser_shareButton.js
content/base/public/nsContentUtils.h
content/base/public/nsIDocument.h
content/base/public/nsINode.h
content/base/src/Makefile.in
content/base/src/nsDocument.cpp
content/base/src/nsDocument.h
content/base/src/nsGenericElement.cpp
content/base/src/nsGkAtomList.h
content/base/src/nsObjectLoadingContent.cpp
content/base/test/Makefile.in
content/canvas/src/WebGLContext.h
content/canvas/src/WebGLContextGL.cpp
content/canvas/src/WebGLContextValidate.cpp
content/canvas/test/test_canvas.html
content/html/content/src/nsGenericHTMLElement.cpp
content/html/content/src/nsGenericHTMLElement.h
content/html/content/src/nsHTMLObjectElement.cpp
content/html/content/src/nsHTMLSharedObjectElement.cpp
dom/Makefile.in
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/indexedDB/IDBDatabase.cpp
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/PContent.ipdl
dom/plugins/ipc/PluginInstanceParent.cpp
editor/libeditor/base/nsEditRules.h
editor/libeditor/base/nsEditor.cpp
editor/libeditor/base/nsEditor.h
editor/libeditor/base/nsEditorUtils.h
editor/libeditor/html/nsHTMLAbsPosition.cpp
editor/libeditor/html/nsHTMLDataTransfer.cpp
editor/libeditor/html/nsHTMLEditRules.cpp
editor/libeditor/html/nsHTMLEditRules.h
editor/libeditor/html/nsHTMLEditor.cpp
editor/libeditor/html/nsHTMLEditorStyle.cpp
editor/libeditor/html/nsTableEditor.cpp
editor/libeditor/text/nsPlaintextEditor.cpp
editor/libeditor/text/nsTextEditRules.cpp
editor/libeditor/text/nsTextEditRules.h
extensions/spellcheck/src/mozInlineSpellChecker.cpp
extensions/spellcheck/src/mozInlineSpellChecker.h
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/FoldConstants.cpp
js/src/jsapi.cpp
js/src/jsapi.h
js/src/jsatom.cpp
js/src/jsatom.h
js/src/jsatominlines.h
js/src/jscntxt.cpp
js/src/jscompartment.cpp
js/src/jsdbgapi.cpp
js/src/jsfriendapi.cpp
js/src/jsgc.h
js/src/jsgcinlines.h
js/src/jsinfer.cpp
js/src/jsinfer.h
js/src/jsinterp.cpp
js/src/jsiter.cpp
js/src/jsobj.cpp
js/src/jsreflect.cpp
js/src/jsstr.cpp
js/src/jsxml.cpp
js/src/shell/js.cpp
js/src/tests/ecma_3/RegExp/regress-289669.js
js/src/tests/ecma_3/RegExp/regress-311414.js
js/src/tests/js1_5/Array/regress-99120-01.js
js/src/tests/js1_5/Array/regress-99120-02.js
js/src/tests/js1_5/Regress/regress-169559.js
js/src/tests/js1_5/Regress/regress-313967-01.js
js/src/tests/js1_5/Regress/regress-313967-02.js
js/src/tests/js1_5/Regress/regress-347306-01.js
js/src/tests/js1_5/String/regress-157334-01.js
js/src/tests/js1_5/String/regress-314890.js
js/src/tests/js1_5/String/regress-322772.js
js/src/tests/js1_5/String/regress-56940-01.js
js/src/tests/js1_5/String/regress-56940-02.js
js/src/tests/js1_5/extensions/regress-335700.js
js/src/tests/js1_5/extensions/regress-347306-02.js
js/src/vm/Debugger.cpp
js/src/vm/RegExpObject.cpp
layout/build/nsLayoutModule.cpp
layout/generic/nsGfxScrollFrame.cpp
layout/reftests/svg/reftest.list
layout/style/nsCSSParser.cpp
layout/style/nsCSSPseudoClasses.cpp
layout/style/nsCSSRuleProcessor.cpp
layout/svg/base/src/nsSVGPatternFrame.cpp
mobile/android/base/GeckoApp.java
mobile/android/base/Makefile.in
mobile/android/base/db/AndroidBrowserDB.java
mobile/android/chrome/content/browser.js
modules/libpref/src/Preferences.cpp
modules/libpref/src/init/all.js
netwerk/base/src/nsFileStreams.h
netwerk/protocol/http/nsHttpConnectionMgr.cpp
netwerk/protocol/http/nsHttpConnectionMgr.h
toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
toolkit/xre/nsAppRunner.cpp
toolkit/xre/nsEmbedFunctions.cpp
widget/android/nsAppShell.cpp
widget/android/nsAppShell.h
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -43,39 +43,16 @@ XPCOMUtils.defineLazyGetter(this, 'Debug
   Cu.import('resource://gre/modules/devtools/dbg-server.jsm');
   return DebuggerServer;
 });
 
 function getContentWindow() {
   return shell.contentBrowser.contentWindow;
 }
 
-// FIXME Bug 707625
-// Please don't add more permissions here.
-// XXX never grant 'content-camera' to non-gaia apps
-function addPermissions(urls) {
-  let permissions = [
-    'indexedDB-unlimited', 'webapps-manage', 'offline-app', 'pin-app',
-    'content-camera', 'wifi-manage', 'desktop-notification',
-    'geolocation', 'device-storage', 'alarms'
-  ];
-
-  urls.forEach(function(url) {
-    url = url.trim();
-    if (url) {
-      let uri = Services.io.newURI(url, null, null);
-      let allow = Ci.nsIPermissionManager.ALLOW_ACTION;
-
-      permissions.forEach(function(permission) {
-        Services.perms.add(uri, permission, allow);
-      });
-    }
-  });
-}
-
 var shell = {
 
   get CrashSubmit() {
     delete this.CrashSubmit;
     Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
     return this.CrashSubmit;
   },
 
@@ -155,23 +132,16 @@ var shell = {
     // level modified from this point.
     // try catch block must be used since the emulator fails here. bug 746429
     try {
       Services.audioManager.masterVolume = 0.5;
     } catch(e) {
       dump('Error setting master volume: ' + e + '\n');
     }
 
-    let domains = "";
-    try {
-      domains = Services.prefs.getCharPref('b2g.privileged.domains');
-    } catch(e) {}
-
-    addPermissions(domains.split(","));
-
     CustomEventManager.init();
     WebappsHelper.init();
 
     // XXX could factor out into a settings->pref map.  Not worth it yet.
     SettingsListener.observe("debug.fps.enabled", false, function(value) {
       Services.prefs.setBoolPref("layers.acceleration.draw-fps", value);
     });
     SettingsListener.observe("debug.paint-flashing.enabled", false, function(value) {
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -151,17 +151,17 @@ endif
                  browser_bug710878.js \
                  browser_bug719271.js \
                  browser_bug724239.js \
                  browser_bug735471.js \
                  browser_bug743421.js \
                  browser_bug749738.js \
                  browser_bug763468.js \
                  browser_bug767836.js \
-                 browser_shareButton.js \
+                 browser_social_shareButton.js \
                  browser_canonizeURL.js \
                  browser_customize.js \
                  browser_findbarClose.js \
                  browser_homeDrop.js \
                  browser_keywordBookmarklets.js \
                  browser_contextSearchTabPosition.js \
                  browser_ctrlTab.js \
                  browser_customize_popupNotification.js \
--- a/browser/base/content/test/browser_social_mozSocial_API.js
+++ b/browser/base/content/test/browser_social_mozSocial_API.js
@@ -1,33 +1,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
-
 function test() {
-  // XXX Bug 775779
-  if (Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2).isDebugBuild) {
-    ok(true, "can't run social sidebar test in debug builds because they falsely report leaks");
-    return;
-  }
   waitForExplicitFinish();
 
   let manifest = { // normal provider
     name: "provider 1",
     origin: "http://example.com",
     sidebarURL: "http://example.com/browser/browser/base/content/test/social_sidebar.html",
     workerURL: "http://example.com/browser/browser/base/content/test/social_worker.js",
     iconURL: "chrome://branding/content/icon48.png"
   };
-  runSocialTestWithProvider(manifest, function () {
-    runSocialTests(tests, undefined, undefined, function () {
-      SocialService.removeProvider(Social.provider.origin, finish);
-    });
+  runSocialTestWithProvider(manifest, function (finishcb) {
+    runSocialTests(tests, undefined, undefined, finishcb);
   });
 }
 
 var tests = {
   testStatusIcons: function(next) {
     let iconsReady = false;
     let gotSidebarMessage = false;
 
rename from browser/base/content/test/browser_shareButton.js
rename to browser/base/content/test/browser_social_shareButton.js
--- a/browser/base/content/test/browser_shareButton.js
+++ b/browser/base/content/test/browser_social_shareButton.js
@@ -1,53 +1,51 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 let prefName = "social.enabled",
     shareButton,
     sharePopup,
     okButton,
-    undoButton;
+    undoButton,
+    gFinishCB;
 
 function test() {
   waitForExplicitFinish();
 
   // Need to load a non-empty page for the social share button to appear
   let tab = gBrowser.selectedTab = gBrowser.addTab("about:", {skipAnimation: true});
   tab.linkedBrowser.addEventListener("load", function tabLoad(event) {
     tab.linkedBrowser.removeEventListener("load", tabLoad, true);
     executeSoon(tabLoaded);
   }, true);
 
-  // Enable the service to start
-  Services.prefs.setBoolPref(prefName, true);
-
   registerCleanupFunction(function() {
-    Services.prefs.clearUserPref(prefName);
     gBrowser.removeTab(tab);
   });
 }
 
 function tabLoaded() {
   ok(Social, "Social module loaded");
 
-  // If the UI is already active, run the test immediately, otherwise wait
-  // for initialization.
-  if (Social.provider) {
-    executeSoon(testInitial);
-  } else {
-    Services.obs.addObserver(function obs() {
-      Services.obs.removeObserver(obs, "test-social-ui-ready");
-      executeSoon(testInitial);
-    }, "test-social-ui-ready", false);
-  }
+  let manifest = { // normal provider
+    name: "provider 1",
+    origin: "https://example.com",
+    sidebarURL: "https://example.com/browser/browser/base/content/test/social_sidebar.html",
+    workerURL: "https://example.com/browser/browser/base/content/test/social_worker.js",
+    iconURL: "chrome://branding/content/icon48.png"
+  };
+  runSocialTestWithProvider(manifest, function (finishcb) {
+    gFinishCB = finishcb;
+    testInitial();
+  });
 }
 
-function testInitial() {
+function testInitial(finishcb) {
   ok(Social.provider, "Social provider is active");
   ok(Social.provider.enabled, "Social provider is enabled");
   ok(Social.provider.port, "Social provider has a port to its FrameWorker");
 
   shareButton = SocialShareButton.shareButton;
   sharePopup = SocialShareButton.sharePopup;
   ok(shareButton, "share button exists");
   ok(sharePopup, "share popup exists");
@@ -111,29 +109,43 @@ function checkShortcutWorked(keyTarget) 
     ok(true, "popup was shown after second use of keyboard shortcut");
     executeSoon(checkOKButton);
   });
   EventUtils.synthesizeKey("l", {accelKey: true, shiftKey: true}, keyTarget);
 }
 
 function checkOKButton() {
   is(document.activeElement, okButton, "ok button should be focused by default");
-  checkNextInTabOrder(undoButton, function () {
-    checkNextInTabOrder(okButton, testCloseBySpace);
-  });
+
+  // This rest of particular test doesn't really apply on Mac, since buttons
+  // aren't focusable by default.
+  if (navigator.platform.indexOf("Mac") != -1) {
+    executeSoon(testCloseBySpace);
+    return;
+  }
+
+  let displayName = document.getElementById("socialUserDisplayName");
+
+  // Linux has the buttons in the [unshare] [ok] order, so displayName will come first.
+  if (navigator.platform.indexOf("Linux") != -1) {
+    checkNextInTabOrder(displayName, function () {
+      checkNextInTabOrder(undoButton, function () {
+        checkNextInTabOrder(okButton, testCloseBySpace);
+      });
+    });
+  } else {
+    checkNextInTabOrder(undoButton, function () {
+      checkNextInTabOrder(displayName, function () {
+        checkNextInTabOrder(okButton, testCloseBySpace);
+      });
+    });
+  }
 }
 
 function checkNextInTabOrder(element, next) {
-  // This particular test doesn't really apply on Mac, since buttons aren't
-  // focusable by default.
-  if (navigator.platform.indexOf("Mac") != -1) {
-    executeSoon(next);
-    return;
-  }
-
   function listener() {
     element.removeEventListener("focus", listener);
     is(document.activeElement, element, element.id + " should be next in tab order");
     executeSoon(next);
   }
   element.addEventListener("focus", listener);
   // Register a cleanup function to remove the listener in case this test fails
   registerCleanupFunction(function () {
@@ -150,10 +162,10 @@ function testCloseBySpace() {
     executeSoon(testDisable);
   });
   EventUtils.synthesizeKey("VK_SPACE", {});
 }
 
 function testDisable() {
   Services.prefs.setBoolPref(prefName, false);
   is(shareButton.hidden, true, "Share button should be hidden when pref is disabled");
-  finish();
+  gFinishCB();
 }
--- a/browser/base/content/test/browser_social_sidebar.js
+++ b/browser/base/content/test/browser_social_sidebar.js
@@ -1,34 +1,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
-
 function test() {
-  // XXX Bug 775779
-  if (Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2).isDebugBuild) {
-    ok(true, "can't run social sidebar test in debug builds because they falsely report leaks");
-    return;
-  }
-
   waitForExplicitFinish();
 
   let manifest = { // normal provider
     name: "provider 1",
     origin: "https://example.com",
     sidebarURL: "https://example.com/browser/browser/base/content/test/social_sidebar.html",
     workerURL: "https://example.com/browser/browser/base/content/test/social_worker.js",
     iconURL: "chrome://branding/content/icon48.png"
   };
   runSocialTestWithProvider(manifest, doTest);
 }
 
-function doTest() {
+function doTest(finishcb) {
   ok(SocialSidebar.canShow, "social sidebar should be able to be shown");
   ok(SocialSidebar.enabled, "social sidebar should be on by default");
 
   let command = document.getElementById("Social:ToggleSidebar");
   let sidebar = document.getElementById("social-sidebar-box");
   let browser = sidebar.firstChild;
 
   function checkShown(shouldBeShown) {
@@ -51,18 +43,18 @@ function doTest() {
 
     checkShown(false);
 
     browser.addEventListener("sidebarshow", function sidebarshow() {
       browser.removeEventListener("sidebarshow", sidebarshow);
 
       checkShown(true);
 
-      // Remove the test provider
-      SocialService.removeProvider(Social.provider.origin, finish);
+      // Finish the test
+      finishcb();
     });
 
     // Toggle it back on
     info("Toggling sidebar back on");
     Social.toggleSidebar();
   });
 
   // Now toggle it off
--- a/browser/base/content/test/browser_social_toolbar.js
+++ b/browser/base/content/test/browser_social_toolbar.js
@@ -1,27 +1,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
-
 function test() {
   waitForExplicitFinish();
 
   let manifest = { // normal provider
     name: "provider 1",
     origin: "https://example1.com",
     workerURL: "https://example1.com/worker.js",
     iconURL: "chrome://branding/content/icon48.png"
   };
-  runSocialTestWithProvider(manifest, function () {
-    runSocialTests(tests, undefined, undefined, function () {
-      SocialService.removeProvider(Social.provider.origin, finish);
-    });
+  runSocialTestWithProvider(manifest, function (finishcb) {
+    runSocialTests(tests, undefined, undefined, finishcb);
   });
 }
 
 var tests = {
   testProfileSet: function(next) {
     let profile = {
       portrait: "https://example.com/portrait.jpg",
       userName: "trickster",
--- a/browser/base/content/test/head.js
+++ b/browser/base/content/test/head.js
@@ -84,43 +84,38 @@ function waitForCondition(condition, nex
       moveOn();
     }
     tries++;
   }, 100);
   var moveOn = function() { clearInterval(interval); nextTest(); };
 }
 
 function runSocialTestWithProvider(manifest, callback) {
+  let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
+
+  info("runSocialTestWithProvider: " + manifest.toSource());
+
   let oldProvider;
-  function saveOldProviderAndStartTestWith(provider) {
+  SocialService.addProvider(manifest, function(provider) {
+    info("runSocialTestWithProvider: provider added");
     oldProvider = Social.provider;
     Social.provider = provider;
 
     // Now that we've set the UI's provider, enable the social functionality
     Services.prefs.setBoolPref("social.enabled", true);
 
     registerCleanupFunction(function () {
       Social.provider = oldProvider;
       Services.prefs.clearUserPref("social.enabled");
     });
 
-    callback();
-  }
-
-  SocialService.addProvider(manifest, function(provider) {
-    // If the UI is already active, run the test immediately, otherwise wait
-    // for initialization.
-    if (Social.provider) {
-      saveOldProviderAndStartTestWith(provider);
-    } else {
-      Services.obs.addObserver(function obs() {
-        Services.obs.removeObserver(obs, "test-social-ui-ready");
-        saveOldProviderAndStartTestWith(provider);
-      }, "test-social-ui-ready", false);
+    function finishSocialTest() {
+      SocialService.removeProvider(provider.origin, finish);
     }
+    callback(finishSocialTest);
   });
 }
 
 
 function runSocialTests(tests, cbPreTest, cbPostTest, cbFinish) {
   let testIter = Iterator(tests);
 
   if (cbPreTest === undefined) {
--- a/browser/modules/Social.jsm
+++ b/browser/modules/Social.jsm
@@ -26,17 +26,16 @@ let Social = {
     }
 
     // Eventually this might want to retrieve a specific provider, but for now
     // just use the first available.
     SocialService.getProviderList(function (providers) {
       if (providers.length)
         this.provider = providers[0];
       callback();
-      Services.obs.notifyObservers(null, "test-social-ui-ready", "");
     }.bind(this));
   },
 
   get uiVisible() {
     return this.provider && this.provider.enabled && this.provider.port;
   },
 
   set enabled(val) {
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -351,16 +351,17 @@ class Automation(object):
     os.mkdir(profileDir)
 
     # Set up permissions database
     locations = self.readLocations()
     self.setupPermissionsDatabase(profileDir,
       {'allowXULXBL':[(l.host, 'noxul' not in l.options) for l in locations]});
 
     part = """\
+user_pref("social.skipLoadingProviders", true);
 user_pref("browser.console.showInPanel", true);
 user_pref("browser.dom.window.dump.enabled", true);
 user_pref("browser.firstrun.show.localepicker", false);
 user_pref("browser.firstrun.show.uidiscovery", false);
 user_pref("browser.startup.page", 0); // use about:blank, not browser.startup.homepage
 user_pref("browser.ui.layout.tablet", 0); // force tablet UI off
 user_pref("dom.allow_scripts_to_close_windows", true);
 user_pref("dom.disable_open_during_load", false);
--- a/build/macosx/common
+++ b/build/macosx/common
@@ -1,12 +1,17 @@
 if [ -d "$topsrcdir/clang" ]; then
     # mozilla-central based build
     export CC=$topsrcdir/clang/bin/clang
     export CXX=$topsrcdir/clang/bin/clang++
 elif [ -d "$topsrcdir/../clang" ]; then
     # comm-central based build
     export CC=$topsrcdir/../clang/bin/clang
     export CXX=$topsrcdir/../clang/bin/clang++
+else
+    # SeaMonkey does not have clang installed so need
+    # to set CC and CXX here.
+    export CC=/usr/bin/gcc-4.2
+    export CXX=/usr/bin/g++-4.2
 fi
 
 ac_add_options --enable-stdcxx-compat
 ac_add_options --with-ccache
--- a/build/mobile/b2gautomation.py
+++ b/build/mobile/b2gautomation.py
@@ -224,17 +224,20 @@ class B2GRemoteAutomation(Automation):
 
         # Set up port forwarding again for Marionette, since any that
         # existed previously got wiped out by the reboot.
         if not self._is_emulator:
             self._devicemanager.checkCmd(['forward',
                                           'tcp:%s' % self.marionette.port,
                                           'tcp:%s' % self.marionette.port])
 
-        time.sleep(5)
+        if self._is_emulator:
+            self.marionette.emulator.wait_for_port()
+        else:
+            time.sleep(5)
 
         # start a marionette session
         session = self.marionette.start_session()
         if 'b2g' not in session:
             raise Exception("bad session value %s returned by start_session" % session)
 
         # Start the tests by navigating to the mochitest url, by setting it
         # as the 'src' attribute to the homescreen mozbrowser element
@@ -269,17 +272,17 @@ class B2GRemoteAutomation(Automation):
             proc.daemon = True
             proc.start()
 
         def _save_stdout_proc(self, cmd, queue):
             self.stdout_proc = StdOutProc(cmd, queue)
             self.stdout_proc.run()
             if hasattr(self.stdout_proc, 'processOutput'):
                 self.stdout_proc.processOutput()
-            self.stdout_proc.waitForFinish(timeout=10)
+            self.stdout_proc.waitForFinish()
             self.stdout_proc = None
 
         @property
         def pid(self):
             # a dummy value to make the automation happy
             return 0
 
         @property
--- a/build/mobile/robocop/Assert.java.in
+++ b/build/mobile/robocop/Assert.java.in
@@ -5,18 +5,18 @@
 
 package @ANDROID_PACKAGE_NAME@;
 
 public interface Assert {
     void dumpLog(String message);
     void dumpLog(String message, Throwable t);
     void setLogFile(String filename);
     void setTestName(String testName);
+    void endTest();
 
-    void finalize();
     void ok(boolean condition, String name, String diag);
     void is(Object a, Object b, String name);
     void isnot(Object a, Object b, String name);
     void todo(boolean condition, String name, String diag);
     void todo_is(Object a, Object b, String name);
     void todo_isnot(Object a, Object b, String name);
     void info(String name, String message);
 
--- a/build/mobile/robocop/FennecMochitestAssert.java.in
+++ b/build/mobile/robocop/FennecMochitestAssert.java.in
@@ -103,18 +103,17 @@ public class FennecMochitestAssert imple
         } else {
             mPassed++;
         }
         if (isError) {
             junit.framework.Assert.fail(message);
         }
     }
 
-    public void finalize() {
-        // It appears that we call finalize during cleanup, this might be an invalid assertion.
+    public void endTest() {
         String message;
 
         if (mLogTestName != "") {
             long diff = SystemClock.uptimeMillis() - mStartTime;
             message = Integer.toString(mLineNumber++) + " INFO TEST-END | " + mLogTestName;
             message += " | finished in " + diff + "ms";
             dumpLog(message);
             mLogTestName = "";
--- a/build/mobile/robocop/FennecTalosAssert.java.in
+++ b/build/mobile/robocop/FennecTalosAssert.java.in
@@ -26,17 +26,17 @@ public class FennecTalosAssert implement
      *  Set the filename used for dumpLog.
      */
     public void setLogFile(String filename) {
         FennecNativeDriver.setLogFile(filename);
     }
 
     public void setTestName(String testName) { }
 
-    public void finalize() { }
+    public void endTest() { }
 
     public void ok(boolean condition, String name, String diag) {
         if (!condition) {
             dumpLog("__FAIL" + name + ": " + diag + "__FAIL");
         }
     }
 
     public void is(Object a, Object b, String name) {
--- a/build/unix/elfhack/Makefile.in
+++ b/build/unix/elfhack/Makefile.in
@@ -33,66 +33,73 @@ CPU := arm
 else
 CPU := $(TARGET_CPU)
 endif
 endif
 
 CSRCS := \
   inject/$(CPU).c \
   inject/$(CPU)-noinit.c \
-  test.c \
+  test-ctors.c \
+  test-array.c \
   $(NULL)
 
 ifndef CROSS_COMPILE
 CSRCS += dummy.c
 endif
 
 WRAP_LDFLAGS=
 
 # need this to suppress errors due to /usr/include/linux/byteorder/swab.h
 # on mozilla buildbots
 OS_CXXFLAGS := $(filter-out -pedantic,$(OS_CXXFLAGS))
 
 include $(topsrcdir)/config/rules.mk
 
-test$(DLL_SUFFIX): test.$(OBJ_SUFFIX) elfhack $(CSRCS:.c=.$(OBJ_SUFFIX))
-	$(MKSHLIB) $(LDFLAGS) $<
+test-array$(DLL_SUFFIX) test-ctors$(DLL_SUFFIX): %$(DLL_SUFFIX): %.$(OBJ_SUFFIX) elfhack $(filter inject/%,$(CSRCS:.c=.$(OBJ_SUFFIX)))
+	$(MKSHLIB) $(LDFLAGS) $< -nostartfiles
 	@echo ===
 	@echo === If you get failures below, please file a bug describing the error
 	@echo === and your environment \(compiler and linker versions\), and use
 	@echo === --disable-elf-hack until this is fixed.
 	@echo ===
+	# Fail if the library doesn't have $(DT_TYPE) .dynamic info
+	$(TOOLCHAIN_PREFIX)readelf -d $@ | grep '($(DT_TYPE))'
 	@rm -f $@.bak
 	$(CURDIR)/elfhack -b -f $@
 	# Fail if the backup file doesn't exist
 	[ -f "$@.bak" ]
 	# Fail if the new library doesn't contain less relocations
-	[ $$(objdump -R $@.bak | wc -l) -gt $$(objdump -R $@ | wc -l) ]
+	[ $$($(TOOLCHAIN_PREFIX)objdump -R $@.bak | wc -l) -gt $$(objdump -R $@ | wc -l) ]
 
-.PRECIOUS: test$(DLL_SUFFIX)
+test-array$(DLL_SUFFIX): DT_TYPE=INIT_ARRAY
+test-ctors$(DLL_SUFFIX): DT_TYPE=INIT
 
-GARBAGE += test$(DLL_SUFFIX) test$(DLL_SUFFIX).bak
+.PRECIOUS: test-array$(DLL_SUFFIX) test-ctors$(DLL_SUFFIX)
 
-libs:: test$(DLL_SUFFIX)
+GARBAGE += test-array$(DLL_SUFFIX) test-ctors$(DLL_SUFFIX) test-array$(DLL_SUFFIX).bak test-ctors$(DLL_SUFFIX).bak
+
+libs:: test-array$(DLL_SUFFIX) test-ctors$(DLL_SUFFIX)
 
 ifndef CROSS_COMPILE
-dummy: dummy.$(OBJ_SUFFIX) test$(DLL_SUFFIX)
+dummy: dummy.$(OBJ_SUFFIX)
 	$(CC) -o $@ $^ $(LDFLAGS)
 
 libs:: dummy
 	# Will either crash or return exit code 1 if elfhack is broken
-	LD_LIBRARY_PATH=$(CURDIR) $(CURDIR)/dummy
+	LD_PRELOAD=$(CURDIR)/test-array$(DLL_SUFFIX) $(CURDIR)/dummy
+	LD_PRELOAD=$(CURDIR)/test-ctors$(DLL_SUFFIX) $(CURDIR)/dummy
 
 GARBAGE += dummy
 endif
 
 inject:
 	$(NSINSTALL) -D $@
 
-inject/%.c: inject.c | inject
+inject/%.c: inject.c $(call mkdir_deps,inject)
 	cp $< $@
 
 GARBAGE_DIRS += inject
 
 inject/%.$(OBJ_SUFFIX): DEFINES += -DBITS=$(if $(HAVE_64BIT_OS),64,32)
 inject/%.$(OBJ_SUFFIX): CFLAGS := -O2 -fno-stack-protector $(filter -m% -I%,$(CFLAGS))
 inject/$(CPU)-noinit.$(OBJ_SUFFIX): DEFINES += -DNOINIT
 test.$(OBJ_SUFFIX): CFLAGS := -O0
--- a/build/unix/elfhack/dummy.c
+++ b/build/unix/elfhack/dummy.c
@@ -1,9 +1,9 @@
 /* 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/. */
 
-extern __attribute__((visibility("default"))) int print_status();
+extern __attribute__((visibility("default"), weak)) int print_status();
 
 int main() {
     return print_status();
 }
--- a/build/unix/elfhack/elf.cpp
+++ b/build/unix/elfhack/elf.cpp
@@ -342,17 +342,17 @@ ElfDynamic_Section *Elf::getDynSection()
     for (std::vector<ElfSegment *>::iterator seg = segments.begin(); seg != segments.end(); seg++)
         if (((*seg)->getType() == PT_DYNAMIC) && ((*seg)->getFirstSection() != NULL) &&
             (*seg)->getFirstSection()->getType() == SHT_DYNAMIC)
             return (ElfDynamic_Section *)(*seg)->getFirstSection();
 
     return NULL;
 }
 
-void Elf::write(std::ofstream &file)
+void Elf::normalize()
 {
     // fixup section headers sh_name; TODO: that should be done by sections
     // themselves
     for (ElfSection *section = ehdr; section != NULL; section = section->getNext()) {
         if (section->getIndex() == 0)
             continue;
         else
             ehdr->e_shnum = section->getIndex() + 1;
@@ -382,16 +382,21 @@ void Elf::write(std::ofstream &file)
         phdr_section->getNext()->markDirty();
     }
     // fixup shdr before writing
     if (ehdr->e_shnum != shdr_section->getSize() / shdr_section->getEntSize())
         shdr_section->getShdr().sh_size = ehdr->e_shnum * Elf_Shdr::size(ehdr->e_ident[EI_CLASS]);
     ehdr->e_shoff = shdr_section->getOffset();
     ehdr->e_entry = eh_entry.getValue();
     ehdr->e_shstrndx = eh_shstrndx->getIndex();
+}
+
+void Elf::write(std::ofstream &file)
+{
+    normalize();
     for (ElfSection *section = ehdr;
          section != NULL; section = section->getNext()) {
         file.seekp(section->getOffset());
         if (section == phdr_section) {
             for (std::vector<ElfSegment *>::iterator seg = segments.begin(); seg != segments.end(); seg++) {
                 Elf_Phdr phdr;
                 phdr.p_type = (*seg)->getType();
                 phdr.p_flags = (*seg)->getFlags();
@@ -637,44 +642,35 @@ ElfValue *ElfDynamic_Section::getValueFo
 }
 
 ElfSection *ElfDynamic_Section::getSectionForType(unsigned int tag)
 {
     ElfValue *value = getValueForType(tag);
     return value ? value->getSection() : NULL;
 }
 
-void ElfDynamic_Section::setValueForType(unsigned int tag, ElfValue *val)
+bool ElfDynamic_Section::setValueForType(unsigned int tag, ElfValue *val)
 {
     unsigned int i;
-    for (i = 0; (i < shdr.sh_size / shdr.sh_entsize) && (dyns[i].tag != DT_NULL); i++)
+    unsigned int shnum = shdr.sh_size / shdr.sh_entsize;
+    for (i = 0; (i < shnum) && (dyns[i].tag != DT_NULL); i++)
         if (dyns[i].tag == tag) {
             delete dyns[i].value;
             dyns[i].value = val;
-            return;
+            return true;
         }
-    // This should never happen, as the last entry is always tagged DT_NULL
-    assert(i < shdr.sh_size / shdr.sh_entsize);
     // If we get here, this means we didn't match for the given tag
-    dyns[i].tag = tag;
-    dyns[i++].value = val;
+    // Most of the time, there are a few DT_NULL entries, that we can
+    // use to add our value, but if we are on the last entry, we can't.
+    if (i >= shnum - 1)
+        return false;
 
-    // If we were on the last entry, we need to grow the section.
-    // Most of the time, though, there are a few DT_NULL entries.
-    if (i < shdr.sh_size / shdr.sh_entsize)
-        return;
-
-    Elf_DynValue value;
-    value.tag = DT_NULL;
-    value.value = NULL;
-    dyns.push_back(value);
-    // Resize the section accordingly
-    shdr.sh_size += shdr.sh_entsize;
-    if (getNext() != NULL)
-        getNext()->markDirty();
+    dyns[i].tag = tag;
+    dyns[i].value = val;
+    return true;
 }
 
 ElfDynamic_Section::ElfDynamic_Section(Elf_Shdr &s, std::ifstream *file, Elf *parent)
 : ElfSection(s, file, parent)
 {
     int pos = file->tellg();
     dyns.resize(s.sh_size / s.sh_entsize);
     file->seekg(shdr.sh_offset);
--- a/build/unix/elfhack/elfhack.cpp
+++ b/build/unix/elfhack/elfhack.cpp
@@ -33,16 +33,18 @@ public:
     typedef wrapped<Elf64_Addr> Type64;
 
     template <class endian, typename R, typename T>
     static inline void swap(T &t, R &r) {
         r.value = endian::swap(t.value);
     }
 };
 
+typedef serializable<Elf_Addr_Traits> Elf_Addr;
+
 class Elf_RelHack_Traits {
 public:
     typedef Elf32_Rel Type32;
     typedef Elf32_Rel Type64;
 
     template <class endian, typename R, typename T>
     static inline void swap(T &t, R &r) {
         r.r_offset = endian::swap(t.r_offset);
@@ -76,35 +78,34 @@ public:
         shdr.sh_size = rels.size() * shdr.sh_entsize;
     }
 private:
     std::vector<Elf_RelHack> rels;
 };
 
 class ElfRelHackCode_Section: public ElfSection {
 public:
-    ElfRelHackCode_Section(Elf_Shdr &s, Elf &e)
-    : ElfSection(s, NULL, NULL), parent(e) {
+    ElfRelHackCode_Section(Elf_Shdr &s, Elf &e, unsigned int init)
+    : ElfSection(s, NULL, NULL), parent(e), init(init) {
         std::string file(rundir);
-        init = parent.getDynSection()->getSectionForType(DT_INIT);
         file += "/inject/";
         switch (parent.getMachine()) {
         case EM_386:
             file += "x86";
             break;
         case EM_X86_64:
             file += "x86_64";
             break;
         case EM_ARM:
             file += "arm";
             break;
         default:
             throw std::runtime_error("unsupported architecture");
         }
-        if (init == NULL)
+        if (!init)
             file += "-noinit";
         file += ".o";
         std::ifstream inject(file.c_str(), std::ios::in|std::ios::binary);
         elf = new Elf(inject);
         if (elf->getType() != ET_REL)
             throw std::runtime_error("object for injected code is not ET_REL");
         if (elf->getMachine() != parent.getMachine())
             throw std::runtime_error("architecture of object for injected code doesn't match");
@@ -219,42 +220,42 @@ private:
         }
     };
 
     class arm_thm_jump24_relocation {
     public:
         Elf32_Addr operator()(unsigned int base_addr, Elf32_Off offset,
                               Elf32_Word addend, unsigned int addr)
         {
-            /* Follows description of b.w instructions as per
+            /* Follows description of b.w and bl instructions as per
                ARM Architecture Reference Manual ARMĀ® v7-A and ARMĀ® v7-R edition, A8.6.16
-               We limit ourselves to Encoding T3.
+               We limit ourselves to Encoding T4 of b.w and Encoding T1 of bl.
                We don't care about sign_extend because the only case where this is
                going to be used only jumps forward. */
             Elf32_Addr tmp = (Elf32_Addr) (addr - offset - base_addr);
             unsigned int word0 = addend & 0xffff,
                          word1 = addend >> 16;
 
-            if (((word0 & 0xf800) != 0xf000) || ((word1 & 0xd000) != 0x9000))
-                throw std::runtime_error("R_ARM_THM_JUMP24 relocation only supported for B.W <label>");
+            if (((word0 & 0xf800) != 0xf000) || ((word1 & 0x9000) != 0x9000))
+                throw std::runtime_error("R_ARM_THM_JUMP24/R_ARM_THM_CALL relocation only supported for B.W <label> and BL <label>");
 
             unsigned int s = (word0 & (1 << 10)) >> 10;
             unsigned int j1 = (word1 & (1 << 13)) >> 13;
             unsigned int j2 = (word1 & (1 << 11)) >> 11;
             unsigned int i1 = j1 ^ s ? 0 : 1;
             unsigned int i2 = j2 ^ s ? 0 : 1;
 
             tmp += ((s << 24) | (i1 << 23) | (i2 << 22) | ((word0 & 0x3ff) << 12) | ((word1 & 0x7ff) << 1));
 
             s = (tmp & (1 << 24)) >> 24;
             j1 = ((tmp & (1 << 23)) >> 23) ^ !s;
             j2 = ((tmp & (1 << 22)) >> 22) ^ !s;
 
-            return 0xf000 | (s << 10) | ((tmp & (0x3ff << 12)) >> 12) | 
-                   (0x9000 << 16) | (j1 << 29) | (j2 << 27) | ((tmp & 0xffe) << 15);
+            return 0xf000 | (s << 10) | ((tmp & (0x3ff << 12)) >> 12) |
+                   ((word1 & 0xd000) << 16) | (j1 << 29) | (j2 << 27) | ((tmp & 0xffe) << 15);
         }
     };
 
     class gotoff_relocation {
     public:
         Elf32_Addr operator()(unsigned int base_addr, Elf32_Off offset,
                               Elf32_Word addend, unsigned int addr)
         {
@@ -294,17 +295,17 @@ private:
             if (symtab->syms[ELF32_R_SYM(r->r_info)].value.getSection() == NULL) {
                 if (strcmp(name, "relhack") == 0) {
                     addr = getNext()->getAddr();
                 } else if (strcmp(name, "elf_header") == 0) {
                     // TODO: change this ungly hack to something better
                     ElfSection *ehdr = parent.getSection(1)->getPrevious()->getPrevious();
                     addr = ehdr->getAddr();
                 } else if (strcmp(name, "original_init") == 0) {
-                    addr = init->getAddr();
+                    addr = init;
                 } else if (strcmp(name, "_GLOBAL_OFFSET_TABLE_") == 0) {
                     // We actually don't need a GOT, but need it as a reference for
                     // GOTOFF relocations. We'll just use the start of the ELF file
                     addr = 0;
                 } else if (strcmp(name, "") == 0) {
                     // This is for R_ARM_V4BX, until we find something better
                     addr = -1;
                 } else {
@@ -323,16 +324,17 @@ private:
             case REL(386, GOTPC):
             case REL(ARM, GOTPC):
             case REL(ARM, REL32):
                 apply_relocation<pc32_relocation>(the_code, buf, &*r, addr);
                 break;
             case REL(ARM, PLT32):
                 apply_relocation<arm_plt32_relocation>(the_code, buf, &*r, addr);
                 break;
+            case REL(ARM, THM_PC22 /* THM_CALL */):
             case REL(ARM, THM_JUMP24):
                 apply_relocation<arm_thm_jump24_relocation>(the_code, buf, &*r, addr);
                 break;
             case REL(386, GOTOFF):
             case REL(ARM, GOTOFF):
                 apply_relocation<gotoff_relocation>(the_code, buf, &*r, addr);
                 break;
             case REL(ARM, V4BX):
@@ -341,22 +343,46 @@ private:
             default:
                 throw std::runtime_error("Unsupported relocation type");
             }
         }
     }
 
     Elf *elf, &parent;
     std::vector<ElfSection *> code;
-    ElfSection *init;
+    unsigned int init;
     int entry_point;
 };
 
+unsigned int get_addend(Elf_Rel *rel, Elf *elf) {
+    ElfLocation loc(rel->r_offset, elf);
+    Elf_Addr addr(loc.getBuffer(), Elf_Addr::size(elf->getClass()), elf->getClass(), elf->getData());
+    return addr.value;
+}
+
+unsigned int get_addend(Elf_Rela *rel, Elf *elf) {
+    return rel->r_addend;
+}
+
+void set_relative_reloc(Elf_Rel *rel, Elf *elf, unsigned int value) {
+    ElfLocation loc(rel->r_offset, elf);
+    Elf_Addr addr;
+    addr.value = value;
+    addr.serialize(const_cast<char *>(loc.getBuffer()), Elf_Addr::size(elf->getClass()), elf->getClass(), elf->getData());
+}
+
+void set_relative_reloc(Elf_Rela *rel, Elf *elf, unsigned int value) {
+    // ld puts the value of relocated relocations both in the addend and
+    // at r_offset. For consistency, keep it that way.
+    set_relative_reloc((Elf_Rel *)rel, elf, value);
+    rel->r_addend = value;
+}
+
 template <typename Rel_Type>
-int do_relocation_section(Elf *elf, unsigned int rel_type, unsigned int rel_type2)
+int do_relocation_section(Elf *elf, unsigned int rel_type, unsigned int rel_type2, bool force)
 {
     ElfDynamic_Section *dyn = elf->getDynSection();
     if (dyn ==NULL) {
         fprintf(stderr, "Couldn't find SHT_DYNAMIC section\n");
         return -1;
     }
 
     ElfSegment *relro = elf->getSegmentByType(PT_GNU_RELRO);
@@ -365,71 +391,104 @@ int do_relocation_section(Elf *elf, unsi
     assert(section->getType() == Rel_Type::sh_type);
 
     Elf32_Shdr relhack32_section =
         { 0, SHT_PROGBITS, SHF_ALLOC, 0, (Elf32_Off)-1, 0, SHN_UNDEF, 0,
           Elf_RelHack::size(elf->getClass()), Elf_RelHack::size(elf->getClass()) }; // TODO: sh_addralign should be an alignment, not size
     Elf32_Shdr relhackcode32_section =
         { 0, SHT_PROGBITS, SHF_ALLOC | SHF_EXECINSTR, 0, (Elf32_Off)-1, 0,
           SHN_UNDEF, 0, 1, 0 };
+
+    unsigned int entry_sz = Elf_Addr::size(elf->getClass());
+
+    // The injected code needs to be executed before any init code in the
+    // binary. There are three possible cases:
+    // - The binary has no init code at all. In this case, we will add a
+    //   DT_INIT entry pointing to the injected code.
+    // - The binary has a DT_INIT entry. In this case, we will interpose:
+    //   we change DT_INIT to point to the injected code, and have the
+    //   injected code call the original DT_INIT entry point.
+    // - The binary has no DT_INIT entry, but has a DT_INIT_ARRAY. In this
+    //   case, we interpose as well, by replacing the first entry in the
+    //   array to point to the injected code, and have the injected code
+    //   call the original first entry.
+    // The binary may have .ctors instead of DT_INIT_ARRAY, for its init
+    // functions, but this falls into the second case above, since .ctors
+    // are actually run by DT_INIT code.
+    ElfValue *value = dyn->getValueForType(DT_INIT);
+    unsigned int original_init = value ? value->getValue() : 0;
+    ElfSection *init_array = NULL;
+    if (!value || !value->getValue()) {
+        value = dyn->getValueForType(DT_INIT_ARRAYSZ);
+        if (value && value->getValue() >= entry_sz)
+            init_array = dyn->getSectionForType(DT_INIT_ARRAY);
+    }
+
     Elf_Shdr relhack_section(relhack32_section);
     Elf_Shdr relhackcode_section(relhackcode32_section);
     ElfRelHack_Section *relhack = new ElfRelHack_Section(relhack_section);
-    ElfRelHackCode_Section *relhackcode = new ElfRelHackCode_Section(relhackcode_section, *elf);
 
     ElfSymtab_Section *symtab = (ElfSymtab_Section *) section->getLink();
     Elf_SymValue *sym = symtab->lookup("__cxa_pure_virtual");
 
     std::vector<Rel_Type> new_rels;
     Elf_RelHack relhack_entry;
     relhack_entry.r_offset = relhack_entry.r_info = 0;
-    int entry_sz = (elf->getClass() == ELFCLASS32) ? 4 : 8;
+    size_t init_array_reloc = 0;
     for (typename std::vector<Rel_Type>::iterator i = section->rels.begin();
          i != section->rels.end(); i++) {
         // We don't need to keep R_*_NONE relocations
         if (!ELF32_R_TYPE(i->r_info))
             continue;
-        ElfSection *section = elf->getSectionAt(i->r_offset);
+        ElfLocation loc(i->r_offset, elf);
         // __cxa_pure_virtual is a function used in vtables to point at pure
         // virtual methods. The __cxa_pure_virtual function usually abort()s.
         // These functions are however normally never called. In the case
         // where they would, jumping to the NULL address instead of calling
         // __cxa_pure_virtual is going to work just as well. So we can remove
         // relocations for the __cxa_pure_virtual symbol and NULL out the
         // content at the offset pointed by the relocation.
         if (sym) {
             if (sym->defined) {
                 // If we are statically linked to libstdc++, the
                 // __cxa_pure_virtual symbol is defined in our lib, and we
                 // have relative relocations (rel_type) for it.
                 if (ELF32_R_TYPE(i->r_info) == rel_type) {
-                    serializable<Elf_Addr_Traits> addr(&section->getData()[i->r_offset - section->getAddr()], entry_sz, elf->getClass(), elf->getData());
+                    Elf_Addr addr(loc.getBuffer(), entry_sz, elf->getClass(), elf->getData());
                     if (addr.value == sym->value.getValue()) {
-                        memset((char *)&section->getData()[i->r_offset - section->getAddr()], 0, entry_sz);
+                        memset((char *)loc.getBuffer(), 0, entry_sz);
                         continue;
                     }
                 }
             } else {
                 // If we are dynamically linked to libstdc++, the
                 // __cxa_pure_virtual symbol is undefined in our lib, and we
                 // have absolute relocations (rel_type2) for it.
                 if ((ELF32_R_TYPE(i->r_info) == rel_type2) &&
                     (sym == &symtab->syms[ELF32_R_SYM(i->r_info)])) {
-                    memset((char *)&section->getData()[i->r_offset - section->getAddr()], 0, entry_sz);
+                    memset((char *)loc.getBuffer(), 0, entry_sz);
                     continue;
                 }
             }
         }
-        // Don't pack relocations happening in non writable sections.
-        // Our injected code is likely not to be allowed to write there.
-        if (!(section->getFlags() & SHF_WRITE) || (ELF32_R_TYPE(i->r_info) != rel_type) ||
-            (relro && (i->r_offset >= relro->getAddr()) &&
-                      (i->r_offset < relro->getAddr() + relro->getMemSize())))
+        // Keep track of the relocation associated with the first init_array entry.
+        if (init_array && i->r_offset == init_array->getAddr()) {
+            if (init_array_reloc) {
+                fprintf(stderr, "Found multiple relocations for the first init_array entry. Skipping\n");
+                return -1;
+            }
             new_rels.push_back(*i);
-        else {
+            init_array_reloc = new_rels.size();
+        } else if (!(loc.getSection()->getFlags() & SHF_WRITE) || (ELF32_R_TYPE(i->r_info) != rel_type) ||
+                   (relro && (i->r_offset >= relro->getAddr()) &&
+                   (i->r_offset < relro->getAddr() + relro->getMemSize()))) {
+            // Don't pack relocations happening in non writable sections.
+            // Our injected code is likely not to be allowed to write there.
+            new_rels.push_back(*i);
+        } else {
             // TODO: check that i->r_addend == *i->r_offset
             if (i->r_offset == relhack_entry.r_offset + relhack_entry.r_info * entry_sz) {
                 relhack_entry.r_info++;
             } else {
                 if (relhack_entry.r_offset)
                     relhack->push_back(relhack_entry);
                 relhack_entry.r_offset = i->r_offset;
                 relhack_entry.r_info = 1;
@@ -439,31 +498,64 @@ int do_relocation_section(Elf *elf, unsi
     if (relhack_entry.r_offset)
         relhack->push_back(relhack_entry);
     // Last entry must be NULL
     relhack_entry.r_offset = relhack_entry.r_info = 0;
     relhack->push_back(relhack_entry);
 
     unsigned int old_end = section->getOffset() + section->getSize();
 
+    if (init_array) {
+        if (! init_array_reloc) {
+            fprintf(stderr, "Didn't find relocation for DT_INIT_ARRAY's first entry. Skipping\n");
+            return -1;
+        }
+        Rel_Type *rel = &new_rels[init_array_reloc - 1];
+        unsigned int addend = get_addend(rel, elf);
+        // Use relocated value of DT_INIT_ARRAY's first entry for the
+        // function to be called by the injected code.
+        if (ELF32_R_TYPE(rel->r_info) == rel_type) {
+            original_init = addend;
+        } else if (ELF32_R_TYPE(rel->r_info) == rel_type2) {
+            ElfSymtab_Section *symtab = (ElfSymtab_Section *)section->getLink();
+            original_init = symtab->syms[ELF32_R_SYM(rel->r_info)].value.getValue() + addend;
+        } else {
+            fprintf(stderr, "Unsupported relocation type for DT_INIT_ARRAY's first entry. Skipping\n");
+            return -1;
+        }
+    }
+
+    ElfRelHackCode_Section *relhackcode = new ElfRelHackCode_Section(relhackcode_section, *elf, original_init);
     relhackcode->insertBefore(section);
     relhack->insertAfter(relhackcode);
 
     section->rels.assign(new_rels.begin(), new_rels.end());
     section->shrink(new_rels.size() * section->getEntSize());
+    if (section->getOffset() + section->getSize() >= old_end) {
+        fprintf(stderr, "No gain. Skipping\n");
+        return -1;
+    }
+    // Ensure Elf sections will be at their final location.
+    elf->normalize();
     ElfLocation *init = new ElfLocation(relhackcode, relhackcode->getEntryPoint());
-    dyn->setValueForType(DT_INIT, init);
+    if (init_array) {
+        // Adjust the first DT_INIT_ARRAY entry to point at the injected code
+        // by transforming its relocation into a relative one pointing to the
+        // address of the injected code.
+        Rel_Type *rel = &section->rels[init_array_reloc - 1];
+        rel->r_info = ELF32_R_INFO(0, rel_type); // Set as a relative relocation
+        set_relative_reloc(&section->rels[init_array_reloc - 1], elf, init->getValue());
+    } else if (!dyn->setValueForType(DT_INIT, init)) {
+        fprintf(stderr, "Can't grow .dynamic section to set DT_INIT. Skipping\n");
+        return -1;
+    }
     // TODO: adjust the value according to the remaining number of relative relocations
     if (dyn->getValueForType(Rel_Type::d_tag_count))
         dyn->setValueForType(Rel_Type::d_tag_count, new ElfPlainValue(0));
 
-    if (section->getOffset() + section->getSize() >= old_end) {
-        fprintf(stderr, "No gain. Skipping\n");
-        return -1;
-    }
     return 0;
 }
 
 static inline int backup_file(const char *name)
 {
     std::string fname(name);
     fname += ".bak";
     return rename(name, fname.c_str());
@@ -489,23 +581,23 @@ void do_file(const char *name, bool back
             delete elf;
             return;
         }
     }
 
     int exit = -1;
     switch (elf->getMachine()) {
     case EM_386:
-        exit = do_relocation_section<Elf_Rel>(elf, R_386_RELATIVE, R_386_32);
+        exit = do_relocation_section<Elf_Rel>(elf, R_386_RELATIVE, R_386_32, force);
         break;
     case EM_X86_64:
-        exit = do_relocation_section<Elf_Rela>(elf, R_X86_64_RELATIVE, R_X86_64_64);
+        exit = do_relocation_section<Elf_Rela>(elf, R_X86_64_RELATIVE, R_X86_64_64, force);
         break;
     case EM_ARM:
-        exit = do_relocation_section<Elf_Rel>(elf, R_ARM_RELATIVE, R_ARM_ABS32);
+        exit = do_relocation_section<Elf_Rel>(elf, R_ARM_RELATIVE, R_ARM_ABS32, force);
         break;
     }
     if (exit == 0) {
         if (!force && (elf->getSize() >= size)) {
             fprintf(stderr, "No gain. Skipping\n");
         } else if (backup && backup_file(name) != 0) {
             fprintf(stderr, "Couln't create backup file\n");
         } else {
--- a/build/unix/elfhack/elfxx.h
+++ b/build/unix/elfhack/elfxx.h
@@ -130,16 +130,17 @@ class ElfLocation: public ElfValue {
     unsigned int offset;
 public:
     enum position { ABSOLUTE, RELATIVE };
     ElfLocation(): section(NULL), offset(0) {};
     ElfLocation(ElfSection *section, unsigned int off, enum position pos = RELATIVE);
     ElfLocation(unsigned int location, Elf *elf);
     unsigned int getValue();
     ElfSection *getSection() { return section; }
+    const char *getBuffer();
 };
 
 class ElfSize: public ElfValue {
     ElfSection *section;
 public:
     ElfSize(ElfSection *s): section(s) {};
     unsigned int getValue();
     ElfSection *getSection() { return section; }
@@ -159,28 +160,42 @@ public:
     serializable() {};
     serializable(const typename T::Type32 &p): T::Type32(p) {};
 
 private:
     template <typename R>
     void init(const char *buf, size_t len, char ei_data)
     {
         R e;
-        assert(len <= sizeof(e));
+        assert(len >= sizeof(e));
         memcpy(&e, buf, sizeof(e));
         if (ei_data == ELFDATA2LSB) {
             T::template swap<little_endian>(e, *this);
             return;
         } else if (ei_data == ELFDATA2MSB) {
             T::template swap<big_endian>(e, *this);
             return;
         }
         throw std::runtime_error("Unsupported ELF data encoding");
     }
 
+    template <typename R>
+    void serialize(const char *buf, size_t len, char ei_data)
+    {
+        assert(len >= sizeof(R));
+        if (ei_data == ELFDATA2LSB) {
+            T::template swap<little_endian>(*this, *(R *)buf);
+            return;
+        } else if (ei_data == ELFDATA2MSB) {
+            T::template swap<big_endian>(*this, *(R *)buf);
+            return;
+        }
+        throw std::runtime_error("Unsupported ELF data encoding");
+    }
+
 public:
     serializable(const char *buf, size_t len, char ei_class, char ei_data)
     {
         if (ei_class == ELFCLASS32) {
             init<typename T::Type32>(buf, len, ei_data);
             return;
         } else if (ei_class == ELFCLASS64) {
             init<typename T::Type64>(buf, len, ei_data);
@@ -189,65 +204,55 @@ public:
         throw std::runtime_error("Unsupported ELF class");
     }
 
     serializable(std::ifstream &file, char ei_class, char ei_data)
     {
         if (ei_class == ELFCLASS32) {
             typename T::Type32 e;
             file.read((char *)&e, sizeof(e));
-            if (ei_data == ELFDATA2LSB) {
-                T::template swap<little_endian>(e, *this);
-                return;
-            } else if (ei_data == ELFDATA2MSB) {
-                T::template swap<big_endian>(e, *this);
-                return;
-            }
+            init<typename T::Type32>((char *)&e, sizeof(e), ei_data);
+            return;
         } else if (ei_class == ELFCLASS64) {
             typename T::Type64 e;
             file.read((char *)&e, sizeof(e));
-            if (ei_data == ELFDATA2LSB) {
-                T::template swap<little_endian>(e, *this);
-                return;
-            } else if (ei_data == ELFDATA2MSB) {
-                T::template swap<big_endian>(e, *this);
-                return;
-            }
+            init<typename T::Type64>((char *)&e, sizeof(e), ei_data);
+            return;
         }
         throw std::runtime_error("Unsupported ELF class or data encoding");
     }
 
     void serialize(std::ofstream &file, char ei_class, char ei_data)
     {
         if (ei_class == ELFCLASS32) {
             typename T::Type32 e;
-            if (ei_data == ELFDATA2LSB) {
-                T::template swap<little_endian>(*this, e);
-                file.write((char *)&e, sizeof(e));
-                return;
-            } else if (ei_data == ELFDATA2MSB) {
-                T::template swap<big_endian>(*this, e);
-                file.write((char *)&e, sizeof(e));
-                return;
-            }
+            serialize<typename T::Type32>((char *)&e, sizeof(e), ei_data);
+            file.write((char *)&e, sizeof(e));
+            return;
         } else if (ei_class == ELFCLASS64) {
             typename T::Type64 e;
-            if (ei_data == ELFDATA2LSB) {
-                T::template swap<little_endian>(*this, e);
-                file.write((char *)&e, sizeof(e));
-                return;
-            } else if (ei_data == ELFDATA2MSB) {
-                T::template swap<big_endian>(*this, e);
-                file.write((char *)&e, sizeof(e));
-                return;
-            }
+            serialize<typename T::Type64>((char *)&e, sizeof(e), ei_data);
+            file.write((char *)&e, sizeof(e));
+            return;
         }
         throw std::runtime_error("Unsupported ELF class or data encoding");
     }
 
+    void serialize(char *buf, size_t len, char ei_class, char ei_data)
+    {
+        if (ei_class == ELFCLASS32) {
+            serialize<typename T::Type32>(buf, len, ei_data);
+            return;
+        } else if (ei_class == ELFCLASS64) {
+            serialize<typename T::Type64>(buf, len, ei_data);
+            return;
+        }
+        throw std::runtime_error("Unsupported ELF class");
+    }
+
     static inline unsigned int size(char ei_class)
     {
         if (ei_class == ELFCLASS32)
             return sizeof(typename T::Type32);
         else if (ei_class == ELFCLASS64)
             return sizeof(typename T::Type64);
         return 0;
     }
@@ -264,16 +269,17 @@ public:
     ElfSection *getSection(int index);
 
     ElfSection *getSectionAt(unsigned int offset);
 
     ElfSegment *getSegmentByType(unsigned int type);
 
     ElfDynamic_Section *getDynSection();
 
+    void normalize();
     void write(std::ofstream &file);
 
     char getClass();
     char getData();
     char getType();
     char getMachine();
     unsigned int getSize();
 private:
@@ -500,17 +506,17 @@ class ElfDynamic_Section: public ElfSect
 public:
     ElfDynamic_Section(Elf_Shdr &s, std::ifstream *file, Elf *parent);
     ~ElfDynamic_Section();
 
     void serialize(std::ofstream &file, char ei_class, char ei_data);
 
     ElfValue *getValueForType(unsigned int tag);
     ElfSection *getSectionForType(unsigned int tag);
-    void setValueForType(unsigned int tag, ElfValue *val);
+    bool setValueForType(unsigned int tag, ElfValue *val);
 private:
     std::vector<Elf_DynValue> dyns;
 };
 
 typedef serializable<Elf_Sym_Traits> Elf_Sym;
 
 struct Elf_SymValue {
     const char *name;
@@ -656,15 +662,19 @@ inline ElfLocation::ElfLocation(unsigned
     section = elf->getSectionAt(location);
     offset = location - (section ? section->getAddr() : 0);
 }
 
 inline unsigned int ElfLocation::getValue() {
     return (section ? section->getAddr() : 0) + offset;
 }
 
+inline const char *ElfLocation::getBuffer() {
+    return section ? section->getData() + offset : NULL;
+}
+
 inline unsigned int ElfSize::getValue() {
     return section->getSize();
 }
 
 inline unsigned int ElfEntSize::getValue() {
     return section->getEntSize();
 }
new file mode 100644
--- /dev/null
+++ b/build/unix/elfhack/test-array.c
@@ -0,0 +1,8 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "test.c"
+
+__attribute__((section(".init_array"), used))
+static void (*init_array[])() = { end_test, test };
new file mode 100644
--- /dev/null
+++ b/build/unix/elfhack/test-ctors.c
@@ -0,0 +1,17 @@
+/* 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 "test.c"
+
+/* Recent binutils would put .ctors content into a .init_array section */
+__attribute__((section(".manual_ctors"), used))
+static void (*ctors[])() = { (void (*)())-1, end_test, test, NULL };
+
+__attribute__((section(".init")))
+void _init() {
+    void (**func)() = &ctors[sizeof(ctors) / sizeof(void (*)()) - 1];
+    while (*(--func) != (void (*)())-1) {
+        (*func)();
+    }
+}
--- a/build/unix/elfhack/test.c
+++ b/build/unix/elfhack/test.c
@@ -120,24 +120,24 @@ int print_status() {
 }
 
 /* On ARM, this creates a .tbss section before .init_array, which
  * elfhack could then pick instead of .init_array.
  * Also, when .tbss is big enough, elfhack may wrongfully consider
  * following sections as part of the PT_TLS segment. */
 __thread int foo[1024];
 
-__attribute__((constructor)) void end_test() {
+void end_test() {
     static int count = 0;
     /* Only exit when both constructors have been called */
     if (++count == 2)
         ret = 0;
 }
 
-__attribute__((constructor)) void test() {
+void test() {
     int i = 0, j = 0;
 #define DEF_(a,i,w) \
     if (a[i++] != str_ ## w) return;
 #define DEF(w) DEF_(strings,i,w)
 #include "test.c"
 #include "test.c"
 #include "test.c"
 #undef DEF
new file mode 100644
--- /dev/null
+++ b/content/base/public/DirectionalityUtils.h
@@ -0,0 +1,55 @@
+/* -*- 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 DirectionalityUtils_h___
+#define DirectionalityUtils_h___
+
+class nsIContent;
+class nsIDocument;
+class nsINode;
+
+namespace mozilla {
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+namespace mozilla {
+
+namespace directionality {
+
+enum Directionality {
+  eDir_NotSet = 0,
+  eDir_RTL    = 1,
+  eDir_LTR    = 2
+};
+
+/**
+ * Set the directionality of an element according to the algorithm defined at
+ * http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-directionality,
+ * not including elements with auto direction.
+ *
+ * @return the directionality that the element was set to
+ */
+Directionality RecomputeDirectionality(mozilla::dom::Element* aElement,
+                                       bool aNotify = true);
+
+/**
+ * Set the directionality of any descendants of a node that do not themselves
+ * have a dir attribute.
+ * For performance reasons we walk down the descendant tree in the rare case
+ * of setting the dir attribute, rather than walking up the ancestor tree in
+ * the much more common case of getting the element's directionality.
+ */
+void SetDirectionalityOnDescendants(mozilla::dom::Element* aElement, 
+                                    Directionality aDir,
+                                    bool aNotify = true);
+
+} // end namespace directionality
+
+} // end namespace mozilla
+
+#endif /* DirectionalityUtils_h___ */
--- a/content/base/public/Element.h
+++ b/content/base/public/Element.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_Element_h__
 #define mozilla_dom_Element_h__
 
 #include "mozilla/dom/FragmentOrElement.h" // for base class
 #include "nsChangeHint.h"                  // for enum
 #include "nsEventStates.h"                 // for member
+#include "mozilla/dom/DirectionalityUtils.h"
 
 class nsEventStateManager;
 class nsFocusManager;
 class nsGlobalWindow;
 class nsICSSDeclaration;
 class nsISMILAttr;
 
 // Element-specific flags
@@ -211,16 +212,64 @@ public:
 
   /**
    * Returns an atom holding the name of the "class" attribute on this
    * content node (if applicable).  Returns null if there is no
    * "class" attribute for this type of content node.
    */
   virtual nsIAtom *GetClassAttributeName() const = 0;
 
+  inline mozilla::directionality::Directionality GetDirectionality() const {
+    if (HasFlag(NODE_HAS_DIRECTION_RTL)) {
+      return mozilla::directionality::eDir_RTL;
+    }
+
+    if (HasFlag(NODE_HAS_DIRECTION_LTR)) {
+      return mozilla::directionality::eDir_LTR;
+    }
+
+    return mozilla::directionality::eDir_NotSet;
+  }
+
+  inline void SetDirectionality(mozilla::directionality::Directionality aDir,
+                                bool aNotify) {
+    UnsetFlags(NODE_ALL_DIRECTION_FLAGS);
+    if (!aNotify) {
+      RemoveStatesSilently(DIRECTION_STATES);
+    }
+
+    switch (aDir) {
+      case (mozilla::directionality::eDir_RTL):
+        SetFlags(NODE_HAS_DIRECTION_RTL);
+        if (!aNotify) {
+          AddStatesSilently(NS_EVENT_STATE_RTL);
+        }
+        break;
+
+      case(mozilla::directionality::eDir_LTR):
+        SetFlags(NODE_HAS_DIRECTION_LTR);
+        if (!aNotify) {
+          AddStatesSilently(NS_EVENT_STATE_LTR);
+        }
+        break;
+
+      default:
+        break;
+    }
+
+    /* 
+     * Only call UpdateState if we need to notify, because we call
+     * SetDirectionality for every element, and UpdateState is very very slow
+     * for some elements.
+     */
+    if (aNotify) {
+      UpdateState(true);
+    }
+  }
+
 protected:
   /**
    * Method to get the _intrinsic_ content state of this element.  This is the
    * state that is independent of the element's presentation.  To get the full
    * content state, use State().  See nsEventStates.h for
    * the possible bits that could be set here.
    */
   virtual nsEventStates IntrinsicState() const;
--- a/content/base/public/Makefile.in
+++ b/content/base/public/Makefile.in
@@ -43,16 +43,17 @@ nsReferencedElement.h \
 nsTreeSanitizer.h \
 nsXMLNameSpaceMap.h \
 nsIXFormsUtilityService.h \
 $(NULL)
 
 EXPORTS_NAMESPACES = mozilla/dom mozilla
 
 EXPORTS_mozilla/dom = \
+		DirectionalityUtils.h \
 		Element.h \
 		FragmentOrElement.h \
 		FromParser.h \
 		$(NULL)
 
 EXPORTS_mozilla = \
 		CORSMode.h \
 		$(NULL)
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -16,17 +16,17 @@
 
 #if defined(SOLARIS)
 #include <ieeefp.h>
 #endif
 
 //A trick to handle IEEE floating point exceptions on FreeBSD - E.D.
 #ifdef __FreeBSD__
 #include <ieeefp.h>
-#ifdef __alpha__
+#if !defined(__i386__) && !defined(__x86_64__)
 static fp_except_t allmask = FP_X_INV|FP_X_OFL|FP_X_UFL|FP_X_DZ|FP_X_IMP;
 #else
 static fp_except_t allmask = FP_X_INV|FP_X_OFL|FP_X_UFL|FP_X_DZ|FP_X_IMP|FP_X_DNML;
 #endif
 static fp_except_t oldmask = fpsetmask(~allmask);
 #endif
 
 #include "nsAString.h"
--- a/content/base/public/nsIDocument.h
+++ b/content/base/public/nsIDocument.h
@@ -18,16 +18,17 @@
 #include "nsILoadContext.h"              // for member (in nsCOMPtr)
 #include "nsILoadGroup.h"                // for member (in nsCOMPtr)
 #include "nsINode.h"                     // for base class
 #include "nsIScriptGlobalObject.h"       // for member (in nsCOMPtr)
 #include "nsIStructuredCloneContainer.h" // for member (in nsCOMPtr)
 #include "nsPIDOMWindow.h"               // for use in inline functions
 #include "nsPropertyTable.h"             // for member
 #include "nsTHashtable.h"                // for member
+#include "mozilla/dom/DirectionalityUtils.h"
 
 class imgIRequest;
 class nsAString;
 class nsBindingManager;
 class nsCSSStyleSheet;
 class nsDOMNavigationTiming;
 class nsEventStates;
 class nsFrameLoader;
@@ -392,16 +393,20 @@ public:
    * callers are expected to take action as needed if they want this
    * change to actually change anything immediately.
    * @see nsBidiUtils.h
    */
   void SetBidiOptions(PRUint32 aBidiOptions)
   {
     mBidiOptions = aBidiOptions;
   }
+
+  inline mozilla::directionality::Directionality GetDocumentDirectionality() {
+    return mDirectionality;
+  }
   
   /**
    * Access HTTP header data (this may also get set from other
    * sources, like HTML META tags).
    */
   virtual void GetHeaderData(nsIAtom* aHeaderField, nsAString& aData) const = 0;
   virtual void SetHeaderData(nsIAtom* aheaderField, const nsAString& aData) = 0;
 
@@ -1848,16 +1853,19 @@ protected:
   // If mIsStaticDocument is true, mOriginalDocument points to the original
   // document.
   nsCOMPtr<nsIDocument> mOriginalDocument;
 
   // The bidi options for this document.  What this bitfield means is
   // defined in nsBidiUtils.h
   PRUint32 mBidiOptions;
 
+  // The root directionality of this document.
+  mozilla::directionality::Directionality mDirectionality;
+
   nsCString mContentLanguage;
 private:
   nsCString mContentType;
 protected:
 
   // The document's security info
   nsCOMPtr<nsISupports> mSecurityInfo;
 
--- a/content/base/public/nsINode.h
+++ b/content/base/public/nsINode.h
@@ -140,18 +140,26 @@ enum {
   NODE_HAS_ACCESSKEY           = 0x00020000U,
 
   // Set if the node is handling a click.
   NODE_HANDLING_CLICK          = 0x00040000U,
 
   // Set if the node has had :hover selectors matched against it
   NODE_HAS_RELEVANT_HOVER_RULES = 0x00080000U,
 
+  // Set if the node has right-to-left directionality
+  NODE_HAS_DIRECTION_RTL        = 0x00100000U,
+
+  // Set if the node has left-to-right directionality
+  NODE_HAS_DIRECTION_LTR        = 0x00200000U,
+
+  NODE_ALL_DIRECTION_FLAGS      = NODE_HAS_DIRECTION_LTR | NODE_HAS_DIRECTION_RTL,
+
   // Remaining bits are node type specific.
-  NODE_TYPE_SPECIFIC_BITS_OFFSET =        20
+  NODE_TYPE_SPECIFIC_BITS_OFFSET =        22
 };
 
 /**
  * Class used to detect unexpected mutations. To use the class create an
  * nsMutationGuard on the stack before unexpected mutations could occur.
  * You can then at any time call Mutated to check if any unexpected mutations
  * have occurred.
  *
@@ -1274,16 +1282,18 @@ private:
     // Set if element has pointer locked
     ElementHasPointerLock,
     // Set if the node may have DOMMutationObserver attached to it.
     NodeMayHaveDOMMutationObserver,
     // Set if node is Content
     NodeIsContent,
     // Set if the node has animations or transitions
     ElementHasAnimations,
+    // Set if node has a dir attribute with a valid value (ltr or rtl)
+    NodeHasValidDirAttribute,
     // Guard value
     BooleanFlagCount
   };
 
   void SetBoolFlag(BooleanFlag name, bool value) {
     PR_STATIC_ASSERT(BooleanFlagCount <= 8*sizeof(mBoolFlags));
     mBoolFlags = (mBoolFlags & ~(1 << name)) | (value << name);
   }
@@ -1341,16 +1351,19 @@ public:
   void SetMayHaveDOMMutationObserver()
     { SetBoolFlag(NodeMayHaveDOMMutationObserver, true); }
   bool HasListenerManager() { return HasFlag(NODE_HAS_LISTENERMANAGER); }
   bool HasPointerLock() const { return GetBoolFlag(ElementHasPointerLock); }
   void SetPointerLock() { SetBoolFlag(ElementHasPointerLock); }
   void ClearPointerLock() { ClearBoolFlag(ElementHasPointerLock); }
   bool MayHaveAnimations() { return GetBoolFlag(ElementHasAnimations); }
   void SetMayHaveAnimations() { SetBoolFlag(ElementHasAnimations); }
+  void SetHasValidDir() { SetBoolFlag(NodeHasValidDirAttribute); }
+  void ClearHasValidDir() { ClearBoolFlag(NodeHasValidDirAttribute); }
+  bool HasValidDir() const { return GetBoolFlag(NodeHasValidDirAttribute); }
 protected:
   void SetParentIsContent(bool aValue) { SetBoolFlag(ParentIsContent, aValue); }
   void SetInDocument() { SetBoolFlag(IsInDocument); }
   void SetNodeIsContent() { SetBoolFlag(NodeIsContent); }
   void ClearInDocument() { ClearBoolFlag(IsInDocument); }
   void SetIsElement() { SetBoolFlag(NodeIsElement); }
   void SetHasID() { SetBoolFlag(ElementHasID); }
   void ClearHasID() { ClearBoolFlag(ElementHasID); }
new file mode 100644
--- /dev/null
+++ b/content/base/src/DirectionalityUtils.cpp
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 sw=2 et tw=78: */
+/* 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/DirectionalityUtils.h"
+#include "nsINode.h"
+#include "nsIContent.h"
+#include "nsIDocument.h"
+#include "mozilla/dom/Element.h"
+#include "nsIDOMNodeFilter.h"
+#include "nsTreeWalker.h"
+#include "nsIDOMHTMLDocument.h"
+
+
+namespace mozilla {
+
+namespace directionality {
+
+typedef mozilla::dom::Element Element;
+
+Directionality
+RecomputeDirectionality(Element* aElement, bool aNotify)
+{
+  Directionality dir = eDir_LTR;
+
+  if (aElement->HasValidDir()) {
+    dir = aElement->GetDirectionality();
+  } else {
+    Element* parent = aElement->GetElementParent();
+    if (parent) {
+      // If the element doesn't have an explicit dir attribute with a valid
+      // value, the directionality is the same as the parent element (but
+      // don't propagate the parent directionality if it isn't set yet).
+      Directionality parentDir = parent->GetDirectionality();
+      if (parentDir != eDir_NotSet) {
+        dir = parentDir;
+      }
+    } else {
+      // If there is no parent element, the directionality is the same as the
+      // document direction.
+      Directionality documentDir =
+        aElement->OwnerDoc()->GetDocumentDirectionality();
+      if (documentDir != eDir_NotSet) {
+        dir = documentDir;
+      }
+    }
+    
+    aElement->SetDirectionality(dir, aNotify);
+  }
+  return dir;
+}
+
+void
+SetDirectionalityOnDescendants(Element* aElement, Directionality aDir,
+                               bool aNotify)
+{
+  for (nsIContent* child = aElement->GetFirstChild(); child; ) {
+    if (!child->IsElement()) {
+      child = child->GetNextNode(aElement);
+      continue;
+    }
+
+    Element* element = child->AsElement();
+    if (element->HasValidDir()) {
+      child = child->GetNextNonChildNode(aElement);
+      continue;
+    }
+    element->SetDirectionality(aDir, aNotify);
+    child = child->GetNextNode(aElement);
+  }
+}
+
+} // end namespace directionality
+
+} // end namespace mozilla
+
--- a/content/base/src/Makefile.in
+++ b/content/base/src/Makefile.in
@@ -48,16 +48,17 @@ EXPORTS_NAMESPACES = mozilla/dom
 EXPORTS_mozilla/dom = \
   Link.h \
   $(NULL)
 
 LOCAL_INCLUDES = \
 		$(NULL)
 
 CPPSRCS		= \
+		DirectionalityUtils.cpp \
 		nsAtomListUtils.cpp \
 		nsAttrAndChildArray.cpp \
 		nsAttrValue.cpp \
 		nsAttrValueOrString.cpp \
 		nsCCUncollectableMarker.cpp \
 		nsChannelPolicy.cpp \
 		nsCommentNode.cpp \
 		nsContentAreaDragDrop.cpp \
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -86,16 +86,17 @@
 // for radio group stuff
 #include "nsIDOMHTMLInputElement.h"
 #include "nsIRadioVisitor.h"
 #include "nsIFormControl.h"
 
 #include "nsXMLEventsManager.h"
 
 #include "nsBidiUtils.h"
+#include "mozilla/dom/DirectionalityUtils.h"
 
 #include "nsIDOMUserDataHandler.h"
 #include "nsIDOMXPathEvaluator.h"
 #include "nsIXPathEvaluatorInternal.h"
 #include "nsIParserService.h"
 #include "nsContentCreatorFunctions.h"
 
 #include "nsIScriptContext.h"
@@ -165,16 +166,17 @@
 
 #include "mozilla/Preferences.h"
 
 #include "imgILoader.h"
 #include "nsWrapperCacheInlines.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
+using namespace mozilla::directionality;
 
 typedef nsTArray<Link*> LinkArray;
 
 // Reference to the document which requested DOM full-screen mode.
 nsWeakPtr nsDocument::sFullScreenDoc = nullptr;
 
 // Reference to the root document of the branch containing the document
 // which requested DOM full-screen mode.
@@ -1505,16 +1507,17 @@ nsIDocument::nsIDocument()
     mRemovedFromDocShell(false),
     // mAllowDNSPrefetch starts true, so that we can always reliably && it
     // with various values that might disable it.  Since we never prefetch
     // unless we get a window, and in that case the docshell value will get
     // &&-ed in, this is safe.
     mAllowDNSPrefetch(true),
     mIsBeingUsedAsImage(false),
     mHasLinksToUpdate(false),
+    mDirectionality(eDir_LTR),
     mPartID(0)
 {
   SetInDocument();
 }
 
 // NOTE! nsDocument::operator new() zeroes out all members, so don't
 // bother initializing members to 0.
 
@@ -5578,16 +5581,25 @@ nsDocument::SetDir(const nsAString& aDir
         if (shell) {
           nsPresContext *context = shell->GetPresContext();
           NS_ENSURE_TRUE(context, NS_ERROR_UNEXPECTED);
           context->SetBidi(options, true);
         } else {
           // No presentation; just set it on ourselves
           SetBidiOptions(options);
         }
+        Directionality dir = elt->mValue == IBMBIDI_TEXTDIRECTION_RTL ?
+                               eDir_RTL : eDir_LTR;
+        SetDocumentDirectionality(dir);
+        // Set the directionality of the root element and its descendants, if any
+        Element* rootElement = GetRootElement();
+        if (rootElement) {
+          rootElement->SetDirectionality(dir, true);
+          SetDirectionalityOnDescendants(rootElement, dir);
+        }
       }
 
       break;
     }
   }
 
   return NS_OK;
 }
--- a/content/base/src/nsDocument.h
+++ b/content/base/src/nsDocument.h
@@ -1043,16 +1043,22 @@ protected:
   void DestroyElementMaps();
 
   // Refreshes the hrefs of all the links in the document.
   void RefreshLinkHrefs();
 
   nsIContent* GetFirstBaseNodeWithHref();
   nsresult SetFirstBaseNodeWithHref(nsIContent *node);
 
+  inline void
+  SetDocumentDirectionality(mozilla::directionality::Directionality aDir)
+  {
+    mDirectionality = aDir;
+  }
+
   // Get the first <title> element with the given IsNodeOfType type, or
   // return null if there isn't one
   nsIContent* GetTitleContent(PRUint32 aNodeType);
   // Find the first "title" element in the given IsNodeOfType type and
   // append the concatenation of its text node children to aTitle. Do
   // nothing if there is no such element.
   void GetTitleFromElement(PRUint32 aNodeType, nsAString& aTitle);
 
--- a/content/base/src/nsGenericElement.cpp
+++ b/content/base/src/nsGenericElement.cpp
@@ -46,16 +46,17 @@
 #include "nsDOMTokenList.h"
 #include "nsXBLPrototypeBinding.h"
 #include "nsDOMError.h"
 #include "nsDOMString.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIDOMMutationEvent.h"
 #include "nsMutationEvent.h"
 #include "nsNodeUtils.h"
+#include "mozilla/dom/DirectionalityUtils.h"
 #include "nsDocument.h"
 #include "nsAttrValueOrString.h"
 #ifdef MOZ_XUL
 #include "nsXULElement.h"
 #endif /* MOZ_XUL */
 #include "nsFrameManager.h"
 #include "nsFrameSelection.h"
 #ifdef DEBUG
@@ -123,16 +124,17 @@
 #include "mozilla/Telemetry.h"
 
 #include "mozilla/CORSMode.h"
 
 #include "nsStyledElement.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
+using namespace mozilla::directionality;
 
 nsEventStates
 Element::IntrinsicState() const
 {
   return IsEditable() ? NS_EVENT_STATE_MOZ_READWRITE :
                         NS_EVENT_STATE_MOZ_READONLY;
 }
 
@@ -1353,16 +1355,23 @@ nsGenericElement::BindToTree(nsIDocument
                NODE_NEEDS_FRAME | NODE_DESCENDANTS_NEED_FRAMES |
                // And the restyle bits
                ELEMENT_ALL_RESTYLE_FLAGS);
   } else {
     // If we're not in the doc, update our subtree pointer.
     SetSubtreeRootPointer(aParent->SubtreeRoot());
   }
 
+  // This has to be here, rather than in nsGenericHTMLElement::BindToTree, 
+  //  because it has to happen after updating the parent pointer, but before
+  //  recursively binding the kids.
+  if (IsHTML()) {
+    RecomputeDirectionality(this, false);
+  }
+
   // If NODE_FORCE_XBL_BINDINGS was set we might have anonymous children
   // that also need to be told that they are moving.
   nsresult rv;
   if (hadForceXBL) {
     nsBindingManager* bmgr = OwnerDoc()->BindingManager();
 
     // First check if we have a binding...
     nsXBLBinding* contBinding =
@@ -1538,16 +1547,23 @@ nsGenericElement::UnbindFromTree(bool aD
 #endif
   {
     nsDOMSlots *slots = GetExistingDOMSlots();
     if (slots) {
       slots->mBindingParent = nullptr;
     }
   }
 
+  // This has to be here, rather than in nsGenericHTMLElement::UnbindFromTree, 
+  //  because it has to happen after unsetting the parent pointer, but before
+  //  recursively unbinding the kids.
+  if (IsHTML()) {
+    RecomputeDirectionality(this, false);
+  }
+
   if (aDeep) {
     // Do the kids. Don't call GetChildCount() here since that'll force
     // XUL to generate template children, which there is no need for since
     // all we're going to do is unbind them anyway.
     PRUint32 i, n = mAttrsAndChildren.ChildCount();
 
     for (i = 0; i < n; ++i) {
       // Note that we pass false for aNullParent here, since we don't want
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -265,16 +265,17 @@ GK_ATOM(details, "details")
 GK_ATOM(deviceAspectRatio, "device-aspect-ratio")
 GK_ATOM(deviceHeight, "device-height")
 GK_ATOM(deviceWidth, "device-width")
 GK_ATOM(dfn, "dfn")
 GK_ATOM(dialog, "dialog")
 GK_ATOM(difference, "difference")
 GK_ATOM(digit, "digit")
 GK_ATOM(dir, "dir")
+GK_ATOM(directionality, "directionality")
 GK_ATOM(disableOutputEscaping, "disable-output-escaping")
 GK_ATOM(disabled, "disabled")
 GK_ATOM(display, "display")
 GK_ATOM(distinct, "distinct")
 GK_ATOM(div, "div")
 GK_ATOM(dl, "dl")
 GK_ATOM(doctypePublic, "doctype-public")
 GK_ATOM(doctypeSystem, "doctype-system")
--- a/content/base/src/nsObjectLoadingContent.cpp
+++ b/content/base/src/nsObjectLoadingContent.cpp
@@ -76,16 +76,26 @@ static NS_DEFINE_CID(kAppShellCID, NS_AP
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo* gObjectLog = PR_NewLogModule("objlc");
 #endif
 
 #define LOG(args) PR_LOG(gObjectLog, PR_LOG_DEBUG, args)
 #define LOG_ENABLED() PR_LOG_TEST(gObjectLog, PR_LOG_DEBUG)
 
+static bool
+InActiveDocument(nsIContent *aContent)
+{
+  if (!aContent->IsInDoc()) {
+    return false;
+  }
+  nsIDocument *doc = aContent->OwnerDoc();
+  return (doc && doc->IsActive());
+}
+
 ///
 /// Runnables and helper classes
 ///
 
 class nsAsyncInstantiateEvent : public nsRunnable {
 public:
   nsAsyncInstantiateEvent(nsObjectLoadingContent *aContent)
   : mContent(aContent) {}
@@ -132,17 +142,17 @@ NS_IMETHODIMP
 InDocCheckEvent::Run()
 {
   nsObjectLoadingContent *objLC =
     static_cast<nsObjectLoadingContent *>(mContent.get());
 
   nsCOMPtr<nsIContent> content =
     do_QueryInterface(static_cast<nsIImageLoadingContent *>(objLC));
 
-  if (!content->IsInDoc()) {
+  if (!InActiveDocument(content)) {
     nsObjectLoadingContent *objLC =
       static_cast<nsObjectLoadingContent *>(mContent.get());
     objLC->UnloadObject();
   }
   return NS_OK;
 }
 
 /**
@@ -551,49 +561,51 @@ IsPluginEnabledForType(const nsCString& 
 
 bool
 nsObjectLoadingContent::IsSupportedDocument(const nsCString& aMimeType)
 {
   nsCOMPtr<nsIContent> thisContent =
     do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
   NS_ASSERTION(thisContent, "must be a content");
 
-  nsresult rv;
   nsCOMPtr<nsIWebNavigationInfo> info(
-    do_GetService(NS_WEBNAVIGATION_INFO_CONTRACTID, &rv));
-  PRUint32 supported;
-  if (info) {
-    nsCOMPtr<nsIWebNavigation> webNav;
-    nsIDocument* currentDoc = thisContent->GetCurrentDoc();
-    if (currentDoc) {
-      webNav = do_GetInterface(currentDoc->GetScriptGlobalObject());
-    }
-    rv = info->IsTypeSupported(aMimeType, webNav, &supported);
+    do_GetService(NS_WEBNAVIGATION_INFO_CONTRACTID));
+  if (!info) {
+    return false;
   }
 
-  if (NS_SUCCEEDED(rv)) {
-    if (supported == nsIWebNavigationInfo::UNSUPPORTED) {
-      // Try a stream converter
-      // NOTE: We treat any type we can convert from as a supported type. If a
-      // type is not actually supported, the URI loader will detect that and
-      // return an error, and we'll fallback.
-      nsCOMPtr<nsIStreamConverterService> convServ =
-        do_GetService("@mozilla.org/streamConverters;1");
-      bool canConvert = false;
-      if (convServ) {
-        rv = convServ->CanConvert(aMimeType.get(), "*/*", &canConvert);
-      }
-      return NS_SUCCEEDED(rv) && canConvert;
-    }
+  nsCOMPtr<nsIWebNavigation> webNav;
+  nsIDocument* currentDoc = thisContent->GetCurrentDoc();
+  if (currentDoc) {
+    webNav = do_GetInterface(currentDoc->GetScriptGlobalObject());
+  }
+  
+  PRUint32 supported;
+  nsresult rv = info->IsTypeSupported(aMimeType, webNav, &supported);
 
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  if (supported != nsIWebNavigationInfo::UNSUPPORTED) {
     // Don't want to support plugins as documents
     return supported != nsIWebNavigationInfo::PLUGIN;
   }
 
-  return false;
+  // Try a stream converter
+  // NOTE: We treat any type we can convert from as a supported type. If a
+  // type is not actually supported, the URI loader will detect that and
+  // return an error, and we'll fallback.
+  nsCOMPtr<nsIStreamConverterService> convServ =
+    do_GetService("@mozilla.org/streamConverters;1");
+  bool canConvert = false;
+  if (convServ) {
+    rv = convServ->CanConvert(aMimeType.get(), "*/*", &canConvert);
+  }
+  return NS_SUCCEEDED(rv) && canConvert;
 }
 
 nsresult
 nsObjectLoadingContent::BindToTree(nsIDocument* aDocument,
                                    nsIContent* /*aParent*/,
                                    nsIContent* /*aBindingParent*/,
                                    bool /*aCompileEventHandlers*/)
 {
@@ -607,28 +619,29 @@ void
 nsObjectLoadingContent::UnbindFromTree(bool /*aDeep*/, bool /*aNullParent*/)
 {
   nsCOMPtr<nsIContent> thisContent =
     do_QueryInterface(static_cast<nsIObjectLoadingContent*>(this));
   MOZ_ASSERT(thisContent);
   nsIDocument* ownerDoc = thisContent->OwnerDoc();
   ownerDoc->RemovePlugin(this);
 
-  if (mType == eType_Plugin) {
+  if (mType == eType_Plugin && mInstanceOwner) {
     // we'll let the plugin continue to run at least until we get back to
     // the event loop. If we get back to the event loop and the node
     // has still not been added back to the document then we tear down the
     // plugin
     nsCOMPtr<nsIRunnable> event = new InDocCheckEvent(this);
 
     nsCOMPtr<nsIAppShell> appShell = do_GetService(kAppShellCID);
     if (appShell) {
       appShell->RunInStableState(event);
     }
   } else {
+    // Reset state and clear pending events
     /// XXX(johns): The implementation for GenericFrame notes that ideally we
     ///             would keep the docshell around, but trash the frameloader
     UnloadObject();
   }
 
 }
 
 nsObjectLoadingContent::nsObjectLoadingContent()
@@ -689,17 +702,17 @@ nsObjectLoadingContent::InstantiatePlugi
 
   nsCOMPtr<nsIContent> thisContent =
     do_QueryInterface(static_cast<nsIImageLoadingContent *>(this));
   // Flush layout so that the plugin is initialized with the latest information.
   nsIDocument* doc = thisContent->GetCurrentDoc();
   if (!doc) {
     return NS_ERROR_FAILURE;
   }
-  if (!doc->IsActive()) {
+  if (!InActiveDocument(thisContent)) {
     NS_ERROR("Shouldn't be calling "
              "InstantiatePluginInstance in an inactive document");
     return NS_ERROR_FAILURE;
   }
   doc->FlushPendingNotifications(Flush_Layout);
 
   nsresult rv = NS_ERROR_FAILURE;
   nsRefPtr<nsPluginHost> pluginHost =
@@ -1466,29 +1479,29 @@ nsObjectLoadingContent::LoadObject(bool 
                                    nsIRequest *aLoadingChannel)
 {
   nsCOMPtr<nsIContent> thisContent =
     do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
   NS_ASSERTION(thisContent, "must be a content");
   nsIDocument* doc = thisContent->OwnerDoc();
   nsresult rv = NS_OK;
 
+  // Sanity check
+  if (!InActiveDocument(thisContent)) {
+    NS_NOTREACHED("LoadObject called while not bound to an active document");
+    return NS_ERROR_UNEXPECTED;
+  }
+
   // XXX(johns): In these cases, we refuse to touch our content and just
   //   remain unloaded, as per legacy behavior. It would make more sense to
   //   load fallback content initially and refuse to ever change state again.
   if (doc->IsBeingUsedAsImage() || doc->IsLoadedAsData()) {
     return NS_OK;
   }
 
-  // Sanity check
-  if (!thisContent->IsInDoc()) {
-    NS_NOTREACHED("LoadObject called while not bound to a document");
-    return NS_ERROR_UNEXPECTED;
-  }
-
   LOG(("OBJLC [%p]: LoadObject called, notify %u, forceload %u, channel %p",
        this, aNotify, aForceLoad, aLoadingChannel));
 
   // We can't re-use an already open channel, but aForceLoad may make us try
   // to load a plugin without any changes in channel state.
   if (aForceLoad && mChannelLoaded) {
     CloseChannel();
     mChannelLoaded = false;
@@ -1965,17 +1978,17 @@ nsObjectLoadingContent::NotifyStateChang
   if (!doc) {
     return; // Nothing to do
   }
 
   nsEventStates newState = ObjectState();
 
   if (newState != aOldState) {
     // This will trigger frame construction
-    NS_ASSERTION(thisContent->IsInDoc(), "Something is confused");
+    NS_ASSERTION(InActiveDocument(thisContent), "Something is confused");
     nsEventStates changedBits = aOldState ^ newState;
 
     {
       nsAutoScriptBlocker scriptBlocker;
       doc->ContentStateChanged(thisContent, changedBits);
     }
     if (aSync) {
       // Make sure that frames are actually constructed immediately.
@@ -2146,20 +2159,20 @@ nsObjectLoadingContent::PluginCrashed(ns
 
 NS_IMETHODIMP
 nsObjectLoadingContent::SyncStartPluginInstance()
 {
   NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
                "Must be able to run script in order to instantiate a plugin instance!");
 
   // Don't even attempt to start an instance unless the content is in
-  // the document.
+  // the document and active
   nsCOMPtr<nsIContent> thisContent =
     do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
-  if (!thisContent->IsInDoc()) {
+  if (!InActiveDocument(thisContent)) {
     return NS_ERROR_FAILURE;
   }
 
   nsCOMPtr<nsIURI> kungFuURIGrip(mURI);
   nsCString contentType(mContentType);
   return InstantiatePluginInstance();
 }
 
--- a/content/base/test/Makefile.in
+++ b/content/base/test/Makefile.in
@@ -376,16 +376,18 @@ MOCHITEST_FILES_B = \
 		file_csp_redirects_page.sjs \
 		file_csp_redirects_main.html \
 		file_csp_redirects_resource.sjs \
 		test_bug346485.html \
 		test_bug560780.html \
 		test_bug562652.html \
 		test_bug562137.html \
 		file_bug562137.txt \
+		test_bug562169-1.html \
+		test_bug562169-2.html \
 		test_bug548193.html \
 		file_bug548193.sjs \
 		test_html_colors_quirks.html \
 		test_html_colors_standards.html \
 		test_bug300992.html \
 		test_websocket_hello.html \
 		file_websocket_hello_wsh.py \
 		test_websocket_basic.html \
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_bug562169-1.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=562169
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 562169</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=562169">Mozilla Bug 562169</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+ <div dir="rtl" id="z"></div>  
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 562169 **/
+/** Test that adding an child to an element with dir="rtl" makes the
+      child have rtl directionality, and removing the child makes it
+     go back to ltr directionality **/
+
+function checkSelector(element, expectedDir, expectedChild)
+{
+    ok(element.querySelector(":dir("+expectedDir+")") == expectedChild,
+       "direction should be " + expectedDir);
+}
+
+var x = document.createElement("div");
+var y = document.createElement("div");
+x.appendChild(y);
+checkSelector(x, "ltr", y);
+$(z).appendChild(x);
+checkSelector(x, "rtl", y);
+$(z).removeChild(x);
+checkSelector(x, "ltr", y);
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/content/base/test/test_bug562169-2.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=562169
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 562169</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=562169">Mozilla Bug 562169</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 562169 **/
+/** Test that a newly created element has ltr directionality **/
+
+ok(document.createElement("div").mozMatchesSelector(":dir(ltr)"),
+   "Element should be ltr on creation");
+
+</script>
+</pre>
+</body>
+</html>
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -1094,16 +1094,17 @@ protected:
     WebGLContextOptions mOptions;
 
     bool mInvalidated;
     bool mResetLayer;
     bool mOptionsFrozen;
     bool mMinCapability;
     bool mDisableExtensions;
     bool mHasRobustness;
+    bool mIsMesa;
 
     template<typename WebGLObjectType>
     void DeleteWebGLObjectsArray(nsTArray<WebGLObjectType>& array);
 
     WebGLuint mActiveTexture;
     WebGLenum mWebGLError;
 
     // whether shader validation is supported
@@ -2319,16 +2320,34 @@ public:
         for (uint32_t i = 0; i < mAttachedShaders.Length(); ++i) {
             if (mAttachedShaders[i] && !mAttachedShaders[i]->CompileStatus()) {
                 return true;
             }
         }
         return false;
     }
 
+    size_t UpperBoundNumSamplerUniforms() {
+        size_t numSamplerUniforms = 0;
+        for (size_t i = 0; i < mAttachedShaders.Length(); ++i) {
+            const WebGLShader *shader = mAttachedShaders[i];
+            if (!shader)
+                continue;
+            for (size_t j = 0; j < shader->mUniformInfos.Length(); ++j) {
+                WebGLUniformInfo u = shader->mUniformInfos[j];
+                if (u.type == SH_SAMPLER_2D ||
+                    u.type == SH_SAMPLER_CUBE)
+                {
+                    numSamplerUniforms += u.arraySize;
+                }
+            }
+        }
+        return numSamplerUniforms;
+    }
+
     bool NextGeneration()
     {
         if (!(mGeneration + 1).isValid())
             return false; // must exit without changing mGeneration
         ++mGeneration;
         return true;
     }
 
--- a/content/canvas/src/WebGLContextGL.cpp
+++ b/content/canvas/src/WebGLContextGL.cpp
@@ -3701,16 +3701,27 @@ WebGLContext::LinkProgram(WebGLProgram *
 
     if (!program->HasBothShaderTypesAttached()) {
         GenerateWarning("linkProgram: this program doesn't have both a vertex shader"
                         " and a fragment shader");
         program->SetLinkStatus(false);
         return;
     }
 
+    // bug 777028
+    // Mesa can't handle more than 16 samplers per program, counting each array entry.
+    if (mIsMesa) {
+        if (program->UpperBoundNumSamplerUniforms() > 16) {
+            GenerateWarning("Programs with more than 16 samplers are disallowed on Mesa drivers "
+                            "to avoid a Mesa crasher.");
+            program->SetLinkStatus(false);
+            return;
+        }
+    }
+
     GLint ok;
     if (gl->WorkAroundDriverBugs() &&
         program->HasBadShaderAttached())
     {
         // it's a common driver bug, caught by program-test.html, that linkProgram doesn't
         // correctly preserve the state of an in-use program that has been attached a bad shader
         // see bug 777883
         ok = false;
@@ -5024,17 +5035,16 @@ WebGLContext::CompileShader(WebGLShader 
         shader->mAttributes.Clear();
         shader->mUniforms.Clear();
         shader->mUniformInfos.Clear();
 
         nsAutoArrayPtr<char> attribute_name(new char[attrib_max_length+1]);
         nsAutoArrayPtr<char> uniform_name(new char[uniform_max_length+1]);
         nsAutoArrayPtr<char> mapped_name(new char[mapped_max_length+1]);
 
-
         for (int i = 0; i < num_uniforms; i++) {
             int length, size;
             ShDataType type;
             ShGetActiveUniform(compiler, i,
                                 &length, &size, &type,
                                 uniform_name,
                                 mapped_name);
             if (useShaderSourceTranslation) {
--- a/content/canvas/src/WebGLContextValidate.cpp
+++ b/content/canvas/src/WebGLContextValidate.cpp
@@ -766,16 +766,19 @@ WebGLContext::InitAndValidateGL()
     if (mShaderValidation) {
         if (!ShInitialize()) {
             GenerateWarning("GLSL translator initialization failed!");
             return false;
         }
     }
 #endif
 
+    // Mesa can only be detected with the GL_VERSION string, of the form "2.1 Mesa 7.11.0"
+    mIsMesa = strstr((const char *)(gl->fGetString(LOCAL_GL_VERSION)), "Mesa");
+
     // notice that the point of calling GetAndClearError here is not only to check for error,
     // it is also to reset the error flags so that a subsequent WebGL getError call will give the correct result.
     error = gl->GetAndClearError();
     if (error != LOCAL_GL_NO_ERROR) {
         GenerateWarning("GL error 0x%x occurred during WebGL context initialization!", error);
         return false;
     }
 
--- a/content/canvas/test/test_canvas.html
+++ b/content/canvas/test/test_canvas.html
@@ -15,17 +15,18 @@ function IsD2DEnabled() {
     try {
         enabled = Cc["@mozilla.org/gfx/info;1"].getService(Components.interfaces.nsIGfxInfo).D2DEnabled;
     } catch(e) {}
     
     return enabled;
 }
 
 function IsLinux() {
-    return navigator.platform.indexOf("Linux") == 0;
+    return navigator.platform.indexOf("Linux") == 0 &&
+           navigator.appVersion.indexOf("Android") == -1;
 }
 
 function IsMacOSX10_5orOlder() {
     var is105orOlder = false;
 
     if (navigator.platform.indexOf("Mac") == 0) {
         var version = Cc["@mozilla.org/system-info;1"]
                         .getService(Components.interfaces.nsIPropertyBag2)
--- a/content/events/public/nsEventStates.h
+++ b/content/events/public/nsEventStates.h
@@ -243,21 +243,27 @@ private:
 // Content is in the sub-suboptimal region.
 #define NS_EVENT_STATE_SUB_SUB_OPTIMUM NS_DEFINE_EVENT_STATE_MACRO(39)
 // Handler for click to play plugin (vulnerable w/update)
 #define NS_EVENT_STATE_VULNERABLE_UPDATABLE NS_DEFINE_EVENT_STATE_MACRO(40)
 // Handler for click to play plugin (vulnerable w/no update)
 #define NS_EVENT_STATE_VULNERABLE_NO_UPDATE NS_DEFINE_EVENT_STATE_MACRO(41)
 // Platform does not support plugin content (some mobile platforms)
 #define NS_EVENT_STATE_TYPE_UNSUPPORTED_PLATFORM NS_DEFINE_EVENT_STATE_MACRO(42)
+// Element is ltr (for :dir pseudo-class)
+#define NS_EVENT_STATE_LTR NS_DEFINE_EVENT_STATE_MACRO(43)
+// Element is rtl (for :dir pseudo-class)
+#define NS_EVENT_STATE_RTL NS_DEFINE_EVENT_STATE_MACRO(44)
 
 /**
  * NOTE: do not go over 63 without updating nsEventStates::InternalType!
  */
 
+#define DIRECTION_STATES (NS_EVENT_STATE_LTR | NS_EVENT_STATE_RTL)
+
 #define ESM_MANAGED_STATES (NS_EVENT_STATE_ACTIVE | NS_EVENT_STATE_FOCUS |     \
                             NS_EVENT_STATE_HOVER | NS_EVENT_STATE_DRAGOVER |   \
                             NS_EVENT_STATE_URLTARGET | NS_EVENT_STATE_FOCUSRING | \
                             NS_EVENT_STATE_FULL_SCREEN | NS_EVENT_STATE_FULL_SCREEN_ANCESTOR)
 
 #define INTRINSIC_STATES (~ESM_MANAGED_STATES)
 
 #endif // nsEventStates_h__
--- a/content/html/content/src/nsGenericHTMLElement.cpp
+++ b/content/html/content/src/nsGenericHTMLElement.cpp
@@ -48,16 +48,17 @@
 #include "nsScriptLoader.h"
 #include "nsRuleData.h"
 
 #include "nsPresState.h"
 #include "nsILayoutHistoryState.h"
 
 #include "nsHTMLParts.h"
 #include "nsContentUtils.h"
+#include "mozilla/dom/DirectionalityUtils.h"
 #include "nsString.h"
 #include "nsUnicharUtils.h"
 #include "nsGkAtoms.h"
 #include "nsEventStateManager.h"
 #include "nsIDOMEvent.h"
 #include "nsDOMCSSDeclaration.h"
 #include "nsITextControlFrame.h"
 #include "nsIForm.h"
@@ -91,16 +92,17 @@
 #include "HTMLPropertiesCollection.h"
 #include "nsVariant.h"
 #include "nsDOMSettableTokenList.h"
 #include "nsThreadUtils.h"
 #include "nsTextFragment.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
+using namespace mozilla::directionality;
 
 class nsINodeInfo;
 class nsIDOMNodeList;
 class nsRuleWalker;
 
 // XXX todo: add in missing out-of-memory checks
 
 //----------------------------------------------------------------------
@@ -1682,16 +1684,34 @@ nsGenericHTMLElement::UpdateEditableStat
   if (value != eInherit) {
     DoSetEditableFlag(!!value, aNotify);
     return;
   }
 
   nsStyledElement::UpdateEditableState(aNotify);
 }
 
+nsEventStates
+nsGenericHTMLElement::IntrinsicState() const
+{
+  nsEventStates state = nsGenericHTMLElementBase::IntrinsicState();
+
+  if (GetDirectionality() == eDir_RTL) {
+    state |= NS_EVENT_STATE_RTL;
+    state &= ~NS_EVENT_STATE_LTR;
+  } else { // at least for HTML, directionality is exclusively LTR or RTL
+    NS_ASSERTION(GetDirectionality() == eDir_LTR,
+                 "HTML element's directionality must be either RTL or LTR");
+    state |= NS_EVENT_STATE_LTR;
+    state &= ~NS_EVENT_STATE_RTL;
+  }
+
+  return state;
+}
+
 nsresult
 nsGenericHTMLElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                                  nsIContent* aBindingParent,
                                  bool aCompileEventHandlers)
 {
   nsresult rv = nsGenericHTMLElementBase::BindToTree(aDocument, aParent,
                                                      aBindingParent,
                                                      aCompileEventHandlers);
@@ -1884,16 +1904,30 @@ nsGenericHTMLElement::AfterSetAttr(PRInt
       NS_ABORT_IF_FALSE(aValue->Type() == nsAttrValue::eString,
         "Expected string value for script body");
       nsresult rv = AddScriptEventListener(aName, aValue->GetStringValue());
       NS_ENSURE_SUCCESS(rv, rv);
     }
     else if (aNotify && aName == nsGkAtoms::spellcheck) {
       SyncEditorsOnSubtree(this);
     }
+    else if (aName == nsGkAtoms::dir) {
+      Directionality dir;
+      if (aValue &&
+          (aValue->Equals(nsGkAtoms::ltr, eIgnoreCase) ||
+           aValue->Equals(nsGkAtoms::rtl, eIgnoreCase))) {
+        SetHasValidDir();
+        dir = aValue->Equals(nsGkAtoms::rtl, eIgnoreCase) ? eDir_RTL : eDir_LTR;
+        SetDirectionality(dir, aNotify);
+      } else {
+        ClearHasValidDir();
+        dir = RecomputeDirectionality(this, aNotify);
+      }
+      SetDirectionalityOnDescendants(this, dir, aNotify);
+    }
   }
 
   return nsGenericHTMLElementBase::AfterSetAttr(aNamespaceID, aName,
                                                 aValue, aNotify);
 }
 
 nsEventListenerManager*
 nsGenericHTMLElement::GetEventListenerManagerForAttr(nsIAtom* aAttrName,
--- a/content/html/content/src/nsGenericHTMLElement.h
+++ b/content/html/content/src/nsGenericHTMLElement.h
@@ -44,16 +44,18 @@ typedef nsMappedAttributeElement nsGener
 class nsGenericHTMLElement : public nsGenericHTMLElementBase
 {
 public:
   nsGenericHTMLElement(already_AddRefed<nsINodeInfo> aNodeInfo)
     : nsGenericHTMLElementBase(aNodeInfo)
   {
     NS_ASSERTION(mNodeInfo->NamespaceID() == kNameSpaceID_XHTML,
                  "Unexpected namespace");
+    AddStatesSilently(NS_EVENT_STATE_LTR);
+    SetFlags(NODE_HAS_DIRECTION_LTR);
   }
 
   /** Typesafe, non-refcounting cast from nsIContent.  Cheaper than QI. **/
   static nsGenericHTMLElement* FromContent(nsIContent *aContent)
   {
     if (aContent->IsHTML())
       return static_cast<nsGenericHTMLElement*>(aContent);
     return nullptr;
@@ -197,16 +199,18 @@ public:
   nsresult PostHandleEventForAnchors(nsEventChainPostVisitor& aVisitor);
   bool IsHTMLLink(nsIURI** aURI) const;
 
   // HTML element methods
   void Compact() { mAttrsAndChildren.Compact(); }
 
   virtual void UpdateEditableState(bool aNotify);
 
+  virtual nsEventStates IntrinsicState() const;
+
   // Helper for setting our editable flag and notifying
   void DoSetEditableFlag(bool aEditable, bool aNotify) {
     SetEditableFlag(aEditable);
     UpdateState(aNotify);
   }
 
   virtual bool ParseAttribute(PRInt32 aNamespaceID,
                                 nsIAtom* aAttribute,
--- a/content/html/content/src/nsHTMLObjectElement.cpp
+++ b/content/html/content/src/nsHTMLObjectElement.cpp
@@ -512,16 +512,22 @@ nsMapRuleToAttributesFunc
 nsHTMLObjectElement::GetAttributeMappingFunction() const
 {
   return &MapAttributesIntoRule;
 }
 
 void
 nsHTMLObjectElement::StartObjectLoad(bool aNotify)
 {
+  // BindToTree can call us asynchronously, and we may be removed from the tree
+  // in the interim
+  if (!IsInDoc() || !OwnerDoc()->IsActive()) {
+    return;
+  }
+
   LoadObject(aNotify);
   SetIsNetworkCreated(false);
 }
 
 nsEventStates
 nsHTMLObjectElement::IntrinsicState() const
 {
   return nsGenericHTMLFormElement::IntrinsicState() | ObjectState();
--- a/content/html/content/src/nsHTMLSharedObjectElement.cpp
+++ b/content/html/content/src/nsHTMLSharedObjectElement.cpp
@@ -460,16 +460,22 @@ nsHTMLSharedObjectElement::GetAttributeM
   }
 
   return &MapAttributesIntoRule;
 }
 
 void
 nsHTMLSharedObjectElement::StartObjectLoad(bool aNotify)
 {
+  // BindToTree can call us asynchronously, and we may be removed from the tree
+  // in the interim
+  if (!IsInDoc() || !OwnerDoc()->IsActive()) {
+    return;
+  }
+
   LoadObject(aNotify);
   SetIsNetworkCreated(false);
 }
 
 nsEventStates
 nsHTMLSharedObjectElement::IntrinsicState() const
 {
   return nsGenericHTMLElement::IntrinsicState() | ObjectState();
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -86,31 +86,30 @@ public:
   {
     MOZ_COUNT_CTOR(ControlMessage);
   }
   // All these run on the graph thread
   virtual ~ControlMessage()
   {
     MOZ_COUNT_DTOR(ControlMessage);
   }
-  // Executed before we know what the action time for this message will be.
-  // Call NoteStreamAffected on the stream whose output will be
-  // modified by this message. Default implementation calls
-  // NoteStreamAffected(mStream).
-  virtual void UpdateAffectedStream();
-  // Executed after we know what the action time for this message will be.
-  virtual void Process() {}
+  // Do the action of this message on the MediaStreamGraph thread. Any actions
+  // affecting graph processing should take effect at mStateComputedTime.
+  // All stream data for times < mStateComputedTime has already been
+  // computed.
+  virtual void Run() = 0;
   // When we're shutting down the application, most messages are ignored but
   // some cleanup messages should still be processed (on the main thread).
-  virtual void ProcessDuringShutdown() {}
+  virtual void RunDuringShutdown() {}
+  MediaStream* GetStream() { return mStream; }
 
 protected:
-  // We do not hold a reference to mStream. The main thread will be holding
-  // a reference to the stream while this message is in flight. The last message
-  // referencing a stream is the Destroy message for that stream.
+  // We do not hold a reference to mStream. The graph will be holding
+  // a reference to the stream until the Destroy message is processed. The
+  // last message referencing a stream is the Destroy message for that stream.
   MediaStream* mStream;
 };
 
 }
 
 /**
  * The implementation of a media stream graph. This class is private to this
  * file. It's not in the anonymous namespace because MediaStream needs to
@@ -197,39 +196,34 @@ public:
   void PrepareUpdatesToMainThreadState();
   // The following methods are the various stages of RunThread processing.
   /**
    * Compute a new current time for the graph and advance all on-graph-thread
    * state to the new current time.
    */
   void UpdateCurrentTime();
   /**
-   * Update mLastActionTime to the time at which the current set of messages
-   * will take effect.
-   */
-  void ChooseActionTime();
-  /**
    * Update the consumption state of aStream to reflect whether its data
    * is needed or not.
    */
   void UpdateConsumptionState(SourceMediaStream* aStream);
   /**
    * Extract any state updates pending in aStream, and apply them.
    */
   void ExtractPendingInput(SourceMediaStream* aStream,
                            GraphTime aDesiredUpToTime,
                            bool* aEnsureNextIteration);
   /**
    * Update "have enough data" flags in aStream.
    */
   void UpdateBufferSufficiencyState(SourceMediaStream* aStream);
   /**
-   * Compute the blocking states of streams from mBlockingDecisionsMadeUntilTime
+   * Compute the blocking states of streams from mStateComputedTime
    * until the desired future time aEndBlockingDecisions.
-   * Updates mBlockingDecisionsMadeUntilTime and sets MediaStream::mBlocked
+   * Updates mStateComputedTime and sets MediaStream::mBlocked
    * for all streams.
    */
   void RecomputeBlocking(GraphTime aEndBlockingDecisions);
   // The following methods are used to help RecomputeBlocking.
   /**
    * Mark a stream blocked at time aTime. If this results in decisions that need
    * to be revisited at some point in the future, *aEnd will be reduced to the
    * first time in the future to recompute those decisions.
@@ -262,17 +256,17 @@ public:
    */
   StreamTime GraphTimeToStreamTime(MediaStream* aStream, GraphTime aTime);
   enum {
     INCLUDE_TRAILING_BLOCKED_INTERVAL = 0x01
   };
   /**
    * Given a stream time aTime, convert it to a graph time taking into
    * account the time during which aStream is scheduled to be blocked.
-   * aTime must be <= mBlockingDecisionsMadeUntilTime since blocking decisions
+   * aTime must be <= mStateComputedTime since blocking decisions
    * are only known up to that point.
    * If aTime is exactly at the start of a blocked interval, then the blocked
    * interval is included in the time returned if and only if
    * aFlags includes INCLUDE_TRAILING_BLOCKED_INTERVAL.
    */
   GraphTime StreamTimeToGraphTime(MediaStream* aStream, StreamTime aTime,
                                   PRUint32 aFlags = 0);
   /**
@@ -314,78 +308,49 @@ public:
   bool IsEmpty() { return mStreams.IsEmpty(); }
 
   // For use by control messages
   /**
    * Identify which graph update index we are currently processing.
    */
   PRInt64 GetProcessingGraphUpdateIndex() { return mProcessingGraphUpdateIndex; }
   /**
-   * Marks aStream as affected by a change in its output at desired time aTime
-   * (in the timeline of aStream). The change may not actually happen at this time,
-   * it may be delayed until later if there is buffered data we can't change.
-   */
-  void NoteStreamAffected(MediaStream* aStream, double aTime);
-  /**
-   * Marks aStream as affected by a change in its output at the earliest
-   * possible time.
-   */
-  void NoteStreamAffected(MediaStream* aStream);
-  /**
    * Add aStream to the graph and initializes its graph-specific state.
    */
   void AddStream(MediaStream* aStream);
   /**
    * Remove aStream from the graph. Ensures that pending messages about the
    * stream back to the main thread are flushed.
    */
   void RemoveStream(MediaStream* aStream);
 
-  /**
-   * Compute the earliest time at which an action be allowed to occur on any
-   * stream. Actions cannot be earlier than the previous action time, and
-   * cannot affect already-committed blocking decisions (and associated
-   * buffered audio).
-   */
-  GraphTime GetEarliestActionTime()
-  {
-    return NS_MAX(mCurrentTime, NS_MAX(mLastActionTime, mBlockingDecisionsMadeUntilTime));
-  }
-
   // Data members
 
   /**
    * Media graph thread.
    * Readonly after initialization on the main thread.
    */
   nsCOMPtr<nsIThread> mThread;
 
   // The following state is managed on the graph thread only, unless
   // mLifecycleState > LIFECYCLE_RUNNING in which case the graph thread
   // is not running and this state can be used from the main thread.
 
   nsTArray<nsRefPtr<MediaStream> > mStreams;
   /**
-   * The time the last action was deemed to have occurred. This could be
-   * later than mCurrentTime if actions have to be delayed during data
-   * buffering, or before mCurrentTime if mCurrentTime has advanced since
-   * the last action happened. In ControlMessage::Process calls,
-   * mLastActionTime has always been updated to be >= mCurrentTime.
-   */
-  GraphTime mLastActionTime;
-  /**
    * The current graph time for the current iteration of the RunThread control
    * loop.
    */
   GraphTime mCurrentTime;
   /**
-   * Blocking decisions have been made up to this time. We also buffer audio
-   * up to this time.
+   * Blocking decisions and all stream contents have been computed up to this
+   * time. The next batch of updates from the main thread will be processed
+   * at this time. Always >= mCurrentTime.
    */
-  GraphTime mBlockingDecisionsMadeUntilTime;
+  GraphTime mStateComputedTime;
   /**
    * This is only used for logging.
    */
   TimeStamp mInitialTimeStamp;
   /**
    * The real timestamp of the latest run of UpdateCurrentTime.
    */
   TimeStamp mCurrentTimeStamp;
@@ -556,45 +521,19 @@ MediaStreamGraphImpl::FinishStream(Media
   aStream->mFinished = true;
   // Force at least one more iteration of the control loop, since we rely
   // on UpdateCurrentTime to notify our listeners once the stream end
   // has been reached.
   EnsureNextIteration();
 }
 
 void
-MediaStreamGraphImpl::NoteStreamAffected(MediaStream* aStream, double aTime)
-{
-  NS_ASSERTION(aTime >= 0, "Bad time");
-  GraphTime t =
-      NS_MAX(GetEarliestActionTime(),
-          StreamTimeToGraphTime(aStream, SecondsToMediaTime(aTime),
-                                INCLUDE_TRAILING_BLOCKED_INTERVAL));
-  aStream->mMessageAffectedTime = NS_MIN(aStream->mMessageAffectedTime, t);
-}
-
-void
-MediaStreamGraphImpl::NoteStreamAffected(MediaStream* aStream)
-{
-  GraphTime t = GetEarliestActionTime();
-  aStream->mMessageAffectedTime = NS_MIN(aStream->mMessageAffectedTime, t);
-}
-
-void
-ControlMessage::UpdateAffectedStream()
-{
-  NS_ASSERTION(mStream, "Must have stream for default UpdateAffectedStream");
-  mStream->GraphImpl()->NoteStreamAffected(mStream);
-}
-
-void
 MediaStreamGraphImpl::AddStream(MediaStream* aStream)
 {
   aStream->mBufferStartTime = mCurrentTime;
-  aStream->mMessageAffectedTime = GetEarliestActionTime();
   *mStreams.AppendElement() = already_AddRefed<MediaStream>(aStream);
   LOG(PR_LOG_DEBUG, ("Adding media stream %p to the graph", aStream));
 }
 
 void
 MediaStreamGraphImpl::RemoveStream(MediaStream* aStream)
 {
   // Remove references in mStreamUpdates before we allow aStream to die.
@@ -611,22 +550,16 @@ MediaStreamGraphImpl::RemoveStream(Media
 
   // This unrefs the stream, probably destroying it
   mStreams.RemoveElement(aStream);
 
   LOG(PR_LOG_DEBUG, ("Removing media stream %p from the graph", aStream));
 }
 
 void
-MediaStreamGraphImpl::ChooseActionTime()
-{
-  mLastActionTime = GetEarliestActionTime();
-}
-
-void
 MediaStreamGraphImpl::UpdateConsumptionState(SourceMediaStream* aStream)
 {
   bool isConsumed = !aStream->mAudioOutputs.IsEmpty() ||
     !aStream->mVideoOutputs.IsEmpty();
   MediaStreamListener::Consumption state = isConsumed ? MediaStreamListener::CONSUMED
     : MediaStreamListener::NOT_CONSUMED;
   if (state != aStream->mLastConsumptionState) {
     aStream->mLastConsumptionState = state;
@@ -648,18 +581,18 @@ MediaStreamGraphImpl::ExtractPendingInpu
     if (aStream->mPullEnabled) {
       for (PRUint32 j = 0; j < aStream->mListeners.Length(); ++j) {
         MediaStreamListener* l = aStream->mListeners[j];
         {
           // Compute how much stream time we'll need assuming we don't block
           // the stream at all between mBlockingDecisionsMadeUntilTime and
           // aDesiredUpToTime.
           StreamTime t =
-            GraphTimeToStreamTime(aStream, mBlockingDecisionsMadeUntilTime) +
-            (aDesiredUpToTime - mBlockingDecisionsMadeUntilTime);
+            GraphTimeToStreamTime(aStream, mStateComputedTime) +
+            (aDesiredUpToTime - mStateComputedTime);
           MutexAutoUnlock unlock(aStream->mMutex);
           l->NotifyPull(this, t);
           *aEnsureNextIteration = true;
         }
       }
     }
     finished = aStream->mUpdateFinished;
     for (PRInt32 i = aStream->mUpdateTracks.Length() - 1; i >= 0; --i) {
@@ -739,17 +672,17 @@ MediaStreamGraphImpl::UpdateBufferSuffic
   }
 }
 
 
 StreamTime
 MediaStreamGraphImpl::GraphTimeToStreamTime(MediaStream* aStream,
                                             GraphTime aTime)
 {
-  NS_ASSERTION(aTime <= mBlockingDecisionsMadeUntilTime,
+  NS_ASSERTION(aTime <= mStateComputedTime,
                "Don't ask about times where we haven't made blocking decisions yet");
   if (aTime <= mCurrentTime) {
     return NS_MAX<StreamTime>(0, aTime - aStream->mBufferStartTime);
   }
   GraphTime t = mCurrentTime;
   StreamTime s = t - aStream->mBufferStartTime;
   while (t < aTime) {
     GraphTime end;
@@ -776,19 +709,19 @@ MediaStreamGraphImpl::StreamTimeToGraphT
 
   MediaTime streamAmount = aTime - bufferElapsedToCurrentTime;
   NS_ASSERTION(streamAmount >= 0, "Can't answer queries before current time");
 
   GraphTime t = mCurrentTime;
   while (t < GRAPH_TIME_MAX) {
     bool blocked;
     GraphTime end;
-    if (t < mBlockingDecisionsMadeUntilTime) {
+    if (t < mStateComputedTime) {
       blocked = aStream->mBlocked.GetAt(t, &end);
-      end = NS_MIN(end, mBlockingDecisionsMadeUntilTime);
+      end = NS_MIN(end, mStateComputedTime);
     } else {
       blocked = false;
       end = GRAPH_TIME_MAX;
     }
     if (blocked) {
       t = end;
     } else {
       if (streamAmount == 0) {
@@ -820,34 +753,34 @@ MediaStreamGraphImpl::GetAudioPosition(M
 
 void
 MediaStreamGraphImpl::UpdateCurrentTime()
 {
   GraphTime prevCurrentTime = mCurrentTime;
   TimeStamp now = TimeStamp::Now();
   GraphTime nextCurrentTime =
     SecondsToMediaTime((now - mCurrentTimeStamp).ToSeconds()) + mCurrentTime;
-  if (mBlockingDecisionsMadeUntilTime < nextCurrentTime) {
+  if (mStateComputedTime < nextCurrentTime) {
     LOG(PR_LOG_WARNING, ("Media graph global underrun detected"));
-    LOG(PR_LOG_DEBUG, ("Advancing mBlockingDecisionsMadeUntilTime from %f to %f",
-                       MediaTimeToSeconds(mBlockingDecisionsMadeUntilTime),
+    LOG(PR_LOG_DEBUG, ("Advancing mStateComputedTime from %f to %f",
+                       MediaTimeToSeconds(mStateComputedTime),
                        MediaTimeToSeconds(nextCurrentTime)));
-    // Advance mBlockingDecisionsMadeUntilTime to nextCurrentTime by
-    // adding blocked time to all streams starting at mBlockingDecisionsMadeUntilTime
+    // Advance mStateComputedTime to nextCurrentTime by
+    // adding blocked time to all streams starting at mStateComputedTime
     for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
-      mStreams[i]->mBlocked.SetAtAndAfter(mBlockingDecisionsMadeUntilTime, true);
+      mStreams[i]->mBlocked.SetAtAndAfter(mStateComputedTime, true);
     }
-    mBlockingDecisionsMadeUntilTime = nextCurrentTime;
+    mStateComputedTime = nextCurrentTime;
   }
   mCurrentTimeStamp = now;
 
-  LOG(PR_LOG_DEBUG, ("Updating current time to %f (real %f, mBlockingDecisionsMadeUntilTime %f)",
+  LOG(PR_LOG_DEBUG, ("Updating current time to %f (real %f, mStateComputedTime %f)",
                      MediaTimeToSeconds(nextCurrentTime),
                      (now - mInitialTimeStamp).ToSeconds(),
-                     MediaTimeToSeconds(mBlockingDecisionsMadeUntilTime)));
+                     MediaTimeToSeconds(mStateComputedTime)));
 
   if (prevCurrentTime >= nextCurrentTime) {
     NS_ASSERTION(prevCurrentTime == nextCurrentTime, "Time can't go backwards!");
     // This could happen due to low clock resolution, maybe?
     LOG(PR_LOG_DEBUG, ("Time did not advance"));
     // There's not much left to do here, but the code below that notifies
     // listeners that streams have ended still needs to run.
   }
@@ -950,31 +883,30 @@ MediaStreamGraphImpl::WillUnderrun(Media
   *aEnd = NS_MIN(*aEnd, bufferEnd);
   return false;
 }
 
 void
 MediaStreamGraphImpl::RecomputeBlocking(GraphTime aEndBlockingDecisions)
 {
   bool blockingDecisionsWillChange = false;
-  // mBlockingDecisionsMadeUntilTime has been set in UpdateCurrentTime
-  while (mBlockingDecisionsMadeUntilTime < aEndBlockingDecisions) {
+  while (mStateComputedTime < aEndBlockingDecisions) {
     LOG(PR_LOG_DEBUG, ("Media graph %p computing blocking for time %f",
-                       this, MediaTimeToSeconds(mBlockingDecisionsMadeUntilTime)));
+                       this, MediaTimeToSeconds(mStateComputedTime)));
     GraphTime end = GRAPH_TIME_MAX;
-    RecomputeBlockingAt(mBlockingDecisionsMadeUntilTime, aEndBlockingDecisions, &end);
+    RecomputeBlockingAt(mStateComputedTime, aEndBlockingDecisions, &end);
     LOG(PR_LOG_DEBUG, ("Media graph %p computed blocking for interval %f to %f",
-                       this, MediaTimeToSeconds(mBlockingDecisionsMadeUntilTime),
-                       MediaTimeToSeconds(end)));
-    mBlockingDecisionsMadeUntilTime = end;
+                       this, MediaTimeToSeconds(mStateComputedTime),
+                       MediaTimeToSeconds(end)));                       
+    mStateComputedTime = end;
     if (end < GRAPH_TIME_MAX) {
       blockingDecisionsWillChange = true;
     }
   }
-  mBlockingDecisionsMadeUntilTime = aEndBlockingDecisions;
+  mStateComputedTime = aEndBlockingDecisions;
 
   for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
     MediaStream* stream = mStreams[i];
     GraphTime end;
     stream->mBlocked.GetAt(mCurrentTime, &end);
     if (end < GRAPH_TIME_MAX) {
       blockingDecisionsWillChange = true;
     }
@@ -1028,23 +960,16 @@ MediaStreamGraphImpl::RecomputeBlockingA
       continue;
     }
 
     bool underrun = WillUnderrun(stream, aTime, aEndBlockingDecisions, aEnd);
     if (underrun) {
       MarkStreamBlocked(stream, aTime, aEnd);
       continue;
     }
-
-    if (stream->mAudioOutputs.IsEmpty() && stream->mVideoOutputs.IsEmpty()) {
-      // See if the stream is being consumed anywhere. If not, it should block.
-      LOG(PR_LOG_DEBUG, ("MediaStream %p is blocked due to having no consumers", stream));
-      MarkStreamBlocked(stream, aTime, aEnd);
-      continue;
-    }
   }
 
   NS_ASSERTION(*aEnd > aTime, "Failed to advance!");
 }
 
 void
 MediaStreamGraphImpl::UpdateFirstActiveTracks(MediaStream* aStream)
 {
@@ -1082,17 +1007,17 @@ MediaStreamGraphImpl::CreateOrDestroyAud
 
   if (aStream->mAudioOutput)
     return;
 
   // No output stream created yet. Check if it's time to create one.
   GraphTime startTime =
     StreamTimeToGraphTime(aStream, track->GetStartTimeRoundDown(),
                           INCLUDE_TRAILING_BLOCKED_INTERVAL);
-  if (startTime >= mBlockingDecisionsMadeUntilTime) {
+  if (startTime >= mStateComputedTime) {
     // The stream wants to play audio, but nothing will play for the forseeable
     // future, so don't create the stream.
     return;
   }
 
   // Don't bother destroying the nsAudioStream for ended tracks yet.
 
   // XXX allocating a nsAudioStream could be slow so we're going to have to do
@@ -1281,27 +1206,18 @@ MediaStreamGraphImpl::RunThread()
 
     // Calculate independent action times for each batch of messages (each
     // batch corresponding to an event loop task). This isolates the performance
     // of different scripts to some extent.
     for (PRUint32 i = 0; i < messageQueue.Length(); ++i) {
       mProcessingGraphUpdateIndex = messageQueue[i].mGraphUpdateIndex;
       nsTArray<nsAutoPtr<ControlMessage> >& messages = messageQueue[i].mMessages;
 
-      for (PRUint32 j = 0; j < mStreams.Length(); ++j) {
-        mStreams[j]->mMessageAffectedTime = GRAPH_TIME_MAX;
-      }
       for (PRUint32 j = 0; j < messages.Length(); ++j) {
-        messages[j]->UpdateAffectedStream();
-      }
-
-      ChooseActionTime();
-
-      for (PRUint32 j = 0; j < messages.Length(); ++j) {
-        messages[j]->Process();
+        messages[j]->Run();
       }
     }
     messageQueue.Clear();
 
     PRInt32 writeAudioUpTo = AUDIO_TARGET_MS;
     GraphTime endBlockingDecisions =
       mCurrentTime + MillisecondsToMediaTime(writeAudioUpTo);
 
@@ -1310,28 +1226,27 @@ MediaStreamGraphImpl::RunThread()
     for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
       SourceMediaStream* is = mStreams[i]->AsSourceStream();
       if (is) {
         UpdateConsumptionState(is);
         ExtractPendingInput(is, endBlockingDecisions, &ensureNextIteration);
       }
     }
 
-    GraphTime prevBlockingDecisionsMadeUntilTime = mBlockingDecisionsMadeUntilTime;
+    GraphTime prevComputedTime = mStateComputedTime;
     RecomputeBlocking(endBlockingDecisions);
 
     PRUint32 audioStreamsActive = 0;
     bool allBlockedForever = true;
     // Figure out what each stream wants to do
     for (PRUint32 i = 0; i < mStreams.Length(); ++i) {
       MediaStream* stream = mStreams[i];
       UpdateFirstActiveTracks(stream);
-      CreateOrDestroyAudioStream(prevBlockingDecisionsMadeUntilTime, stream);
-      PlayAudio(stream, prevBlockingDecisionsMadeUntilTime,
-                mBlockingDecisionsMadeUntilTime);
+      CreateOrDestroyAudioStream(prevComputedTime, stream);
+      PlayAudio(stream, prevComputedTime, mStateComputedTime);
       if (stream->mAudioOutput) {
         ++audioStreamsActive;
       }
       PlayVideo(stream);
       SourceMediaStream* is = stream->AsSourceStream();
       if (is) {
         UpdateBufferSufficiencyState(is);
       }
@@ -1342,36 +1257,26 @@ MediaStreamGraphImpl::RunThread()
     }
     if (ensureNextIteration || !allBlockedForever || audioStreamsActive > 0) {
       EnsureNextIteration();
     }
 
     {
       // Not using MonitorAutoLock since we need to unlock in a way
       // that doesn't match lexical scopes.
-      mMonitor.Lock();
+      MonitorAutoLock lock(mMonitor);
       PrepareUpdatesToMainThreadState();
       if (mForceShutDown || (IsEmpty() && mMessageQueue.IsEmpty())) {
         // Enter shutdown mode. The stable-state handler will detect this
         // and complete shutdown. Destroy any streams immediately.
         LOG(PR_LOG_DEBUG, ("MediaStreamGraph %p waiting for main thread cleanup", this));
         // Commit to shutting down this graph object.
         mLifecycleState = LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP;
-        // Move mStreams to a temporary array, because after we unlock
-        // mMonitor, 'this' may be deleted by the main thread.
-        nsTArray<nsRefPtr<MediaStream> > streams;
-        mStreams.SwapElements(streams);
-
-        mMonitor.Unlock();
-        // Unlock mMonitor while destroying our streams, since
-        // SourceMediaStream::DestroyImpl needs to take its lock while
-        // we're not holding mMonitor.
-        for (PRUint32 i = 0; i < streams.Length(); ++i) {
-          streams[i]->DestroyImpl();
-        }
+        // No need to Destroy streams here. The main-thread owner of each
+        // stream is responsible for calling Destroy them.
         return;
       }
 
       PRIntervalTime timeout = PR_INTERVAL_NO_TIMEOUT;
       TimeStamp now = TimeStamp::Now();
       if (mNeedAnotherIteration) {
         PRInt64 timeoutMS = MEDIA_GRAPH_TARGET_PERIOD_MS -
           PRInt64((now - mCurrentTimeStamp).ToMilliseconds());
@@ -1389,18 +1294,16 @@ MediaStreamGraphImpl::RunThread()
         mMonitor.Wait(timeout);
         LOG(PR_LOG_DEBUG, ("Resuming after timeout; at %f, elapsed=%f",
                            (TimeStamp::Now() - mInitialTimeStamp).ToSeconds(),
                            (TimeStamp::Now() - now).ToSeconds()));
       }
       mWaitState = WAITSTATE_RUNNING;
       mNeedAnotherIteration = false;
       messageQueue.SwapElements(mMessageQueue);
-
-      mMonitor.Unlock();
     }
   }
 }
 
 void
 MediaStreamGraphImpl::ApplyStreamUpdate(StreamUpdate* aUpdate)
 {
   mMonitor.AssertCurrentThreadOwns();
@@ -1484,22 +1387,19 @@ public:
 };
 
 /*
  * Control messages forwarded from main thread to graph manager thread
  */
 class CreateMessage : public ControlMessage {
 public:
   CreateMessage(MediaStream* aStream) : ControlMessage(aStream) {}
-  virtual void UpdateAffectedStream()
+  virtual void Run()
   {
     mStream->GraphImpl()->AddStream(mStream);
-  }
-  virtual void Process()
-  {
     mStream->Init();
   }
 };
 
 class MediaStreamGraphShutdownObserver MOZ_FINAL : public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
@@ -1509,42 +1409,42 @@ public:
 }
 
 void
 MediaStreamGraphImpl::RunInStableState()
 {
   NS_ASSERTION(NS_IsMainThread(), "Must be called on main thread");
 
   nsTArray<nsCOMPtr<nsIRunnable> > runnables;
+  // When we're doing a forced shutdown, pending control messages may be
+  // run on the main thread via RunDuringShutdown. Those messages must
+  // run without the graph monitor being held. So, we collect them here.
+  nsTArray<nsAutoPtr<ControlMessage> > controlMessagesToRunDuringShutdown;
 
   {
     MonitorAutoLock lock(mMonitor);
     mPostedRunInStableStateEvent = false;
 
     runnables.SwapElements(mUpdateRunnables);
     for (PRUint32 i = 0; i < mStreamUpdates.Length(); ++i) {
       StreamUpdate* update = &mStreamUpdates[i];
       if (update->mStream) {
         ApplyStreamUpdate(update);
       }
     }
     mStreamUpdates.Clear();
 
     if (mLifecycleState == LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP && mForceShutDown) {
+      // Defer calls to RunDuringShutdown() to happen while mMonitor is not held.
       for (PRUint32 i = 0; i < mMessageQueue.Length(); ++i) {
         MessageBlock& mb = mMessageQueue[i];
-        for (PRUint32 j = 0; j < mb.mMessages.Length(); ++j) {
-          mb.mMessages[j]->ProcessDuringShutdown();
-        }
+        controlMessagesToRunDuringShutdown.MoveElementsFrom(mb.mMessages);
       }
       mMessageQueue.Clear();
-      for (PRUint32 i = 0; i < mCurrentTaskMessageQueue.Length(); ++i) {
-        mCurrentTaskMessageQueue[i]->ProcessDuringShutdown();
-      }
-      mCurrentTaskMessageQueue.Clear();
+      controlMessagesToRunDuringShutdown.MoveElementsFrom(mCurrentTaskMessageQueue);
       // Stop MediaStreamGraph threads. Do not clear gGraph since
       // we have outstanding DOM objects that may need it.
       mLifecycleState = LIFECYCLE_WAITING_FOR_THREAD_SHUTDOWN;
       nsCOMPtr<nsIRunnable> event = new MediaStreamGraphShutDownRunnable(this);
       NS_DispatchToMainThread(event);
     }
 
     if (mLifecycleState == LIFECYCLE_THREAD_NOT_STARTED) {
@@ -1593,16 +1493,19 @@ MediaStreamGraphImpl::RunInStableState()
   }
 
   // Make sure we get a new current time in the next event loop task
   mPostedRunInStableState = false;
 
   for (PRUint32 i = 0; i < runnables.Length(); ++i) {
     runnables[i]->Run();
   }
+  for (PRUint32 i = 0; i < controlMessagesToRunDuringShutdown.Length(); ++i) {
+    controlMessagesToRunDuringShutdown[i]->RunDuringShutdown();
+  }
 }
 
 static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
 
 void
 MediaStreamGraphImpl::EnsureRunInStableState()
 {
   NS_ASSERTION(NS_IsMainThread(), "main thread only");
@@ -1630,25 +1533,28 @@ MediaStreamGraphImpl::EnsureStableStateE
   nsCOMPtr<nsIRunnable> event = new MediaStreamGraphStableStateRunnable();
   NS_DispatchToMainThread(event);
 }
 
 void
 MediaStreamGraphImpl::AppendMessage(ControlMessage* aMessage)
 {
   NS_ASSERTION(NS_IsMainThread(), "main thread only");
+  NS_ASSERTION(!aMessage->GetStream() ||
+               !aMessage->GetStream()->IsDestroyed(),
+               "Stream already destroyed");
 
   if (mDetectedNotRunning &&
       mLifecycleState > LIFECYCLE_WAITING_FOR_MAIN_THREAD_CLEANUP) {
     // The graph control loop is not running and main thread cleanup has
     // happened. From now on we can't append messages to mCurrentTaskMessageQueue,
-    // because that will never be processed again, so just ProcessDuringShutdown
+    // because that will never be processed again, so just RunDuringShutdown
     // this message.
     // This should only happen during forced shutdown.
-    aMessage->ProcessDuringShutdown();
+    aMessage->RunDuringShutdown();
     delete aMessage;
     if (IsEmpty()) {
       NS_ASSERTION(gGraph == this, "Switched managers during forced shutdown?");
       gGraph = nullptr;
       delete this;
     }
     return;
   }
@@ -1658,17 +1564,17 @@ MediaStreamGraphImpl::AppendMessage(Cont
 }
 
 void
 MediaStream::Init()
 {
   MediaStreamGraphImpl* graph = GraphImpl();
   mBlocked.SetAtAndAfter(graph->mCurrentTime, true);
   mExplicitBlockerCount.SetAtAndAfter(graph->mCurrentTime, true);
-  mExplicitBlockerCount.SetAtAndAfter(graph->mLastActionTime, false);
+  mExplicitBlockerCount.SetAtAndAfter(graph->mStateComputedTime, false);
 }
 
 MediaStreamGraphImpl*
 MediaStream::GraphImpl()
 {
   return gGraph;
 }
 
@@ -1682,35 +1588,36 @@ MediaStream::DestroyImpl()
 }
 
 void
 MediaStream::Destroy()
 {
   class Message : public ControlMessage {
   public:
     Message(MediaStream* aStream) : ControlMessage(aStream) {}
-    virtual void UpdateAffectedStream()
+    virtual void Run()
     {
       mStream->DestroyImpl();
       mStream->GraphImpl()->RemoveStream(mStream);
     }
-    virtual void ProcessDuringShutdown()
-    { UpdateAffectedStream(); }
+    virtual void RunDuringShutdown()
+    { Run(); }
   };
   mWrapper = nullptr;
   GraphImpl()->AppendMessage(new Message(this));
+  mMainThreadDestroyed = true;
 }
 
 void
 MediaStream::AddAudioOutput(void* aKey)
 {
   class Message : public ControlMessage {
   public:
     Message(MediaStream* aStream, void* aKey) : ControlMessage(aStream), mKey(aKey) {}
-    virtual void UpdateAffectedStream()
+    virtual void Run()
     {
       mStream->AddAudioOutputImpl(mKey);
     }
     void* mKey;
   };
   GraphImpl()->AppendMessage(new Message(this, aKey));
 }
 
@@ -1728,17 +1635,17 @@ MediaStream::SetAudioOutputVolumeImpl(vo
 
 void
 MediaStream::SetAudioOutputVolume(void* aKey, float aVolume)
 {
   class Message : public ControlMessage {
   public:
     Message(MediaStream* aStream, void* aKey, float aVolume) :
       ControlMessage(aStream), mKey(aKey), mVolume(aVolume) {}
-    virtual void UpdateAffectedStream()
+    virtual void Run()
     {
       mStream->SetAudioOutputVolumeImpl(mKey, mVolume);
     }
     void* mKey;
     float mVolume;
   };
   GraphImpl()->AppendMessage(new Message(this, aKey, aVolume));
 }
@@ -1757,68 +1664,68 @@ MediaStream::RemoveAudioOutputImpl(void*
 
 void
 MediaStream::RemoveAudioOutput(void* aKey)
 {
   class Message : public ControlMessage {
   public:
     Message(MediaStream* aStream, void* aKey) :
       ControlMessage(aStream), mKey(aKey) {}
-    virtual void UpdateAffectedStream()
+    virtual void Run()
     {
       mStream->RemoveAudioOutputImpl(mKey);
     }
     void* mKey;
   };
   GraphImpl()->AppendMessage(new Message(this, aKey));
 }
 
 void
 MediaStream::AddVideoOutput(VideoFrameContainer* aContainer)
 {
   class Message : public ControlMessage {
   public:
     Message(MediaStream* aStream, VideoFrameContainer* aContainer) :
       ControlMessage(aStream), mContainer(aContainer) {}
-    virtual void UpdateAffectedStream()
+    virtual void Run()
     {
       mStream->AddVideoOutputImpl(mContainer.forget());
     }
     nsRefPtr<VideoFrameContainer> mContainer;
   };
   GraphImpl()->AppendMessage(new Message(this, aContainer));
 }
 
 void
 MediaStream::RemoveVideoOutput(VideoFrameContainer* aContainer)
 {
   class Message : public ControlMessage {
   public:
     Message(MediaStream* aStream, VideoFrameContainer* aContainer) :
       ControlMessage(aStream), mContainer(aContainer) {}
-    virtual void UpdateAffectedStream()
+    virtual void Run()
     {
       mStream->RemoveVideoOutputImpl(mContainer);
     }
     nsRefPtr<VideoFrameContainer> mContainer;
   };
   GraphImpl()->AppendMessage(new Message(this, aContainer));
 }
 
 void
 MediaStream::ChangeExplicitBlockerCount(PRInt32 aDelta)
 {
   class Message : public ControlMessage {
   public:
     Message(MediaStream* aStream, PRInt32 aDelta) :
       ControlMessage(aStream), mDelta(aDelta) {}
-    virtual void UpdateAffectedStream()
+    virtual void Run()
     {
       mStream->ChangeExplicitBlockerCountImpl(
-          mStream->GraphImpl()->mLastActionTime, mDelta);
+          mStream->GraphImpl()->mStateComputedTime, mDelta);
     }
     PRInt32 mDelta;
   };
   GraphImpl()->AppendMessage(new Message(this, aDelta));
 }
 
 void
 MediaStream::AddListenerImpl(already_AddRefed<MediaStreamListener> aListener)
@@ -1833,33 +1740,33 @@ MediaStream::AddListenerImpl(already_Add
 
 void
 MediaStream::AddListener(MediaStreamListener* aListener)
 {
   class Message : public ControlMessage {
   public:
     Message(MediaStream* aStream, MediaStreamListener* aListener) :
       ControlMessage(aStream), mListener(aListener) {}
-    virtual void UpdateAffectedStream()
+    virtual void Run()
     {
       mStream->AddListenerImpl(mListener.forget());
     }
     nsRefPtr<MediaStreamListener> mListener;
   };
   GraphImpl()->AppendMessage(new Message(this, aListener));
 }
 
 void
 MediaStream::RemoveListener(MediaStreamListener* aListener)
 {
   class Message : public ControlMessage {
   public:
     Message(MediaStream* aStream, MediaStreamListener* aListener) :
       ControlMessage(aStream), mListener(aListener) {}
-    virtual void UpdateAffectedStream()
+    virtual void Run()
     {
       mStream->RemoveListenerImpl(mListener);
     }
     nsRefPtr<MediaStreamListener> mListener;
   };
   GraphImpl()->AppendMessage(new Message(this, aListener));
 }
 
@@ -1986,19 +1893,18 @@ static const PRUint32 kIdleThreadTimeout
 
 /**
  * We make the initial mCurrentTime nonzero so that zero times can have
  * special meaning if necessary.
  */
 static const PRInt32 INITIAL_CURRENT_TIME = 1;
 
 MediaStreamGraphImpl::MediaStreamGraphImpl()
-  : mLastActionTime(INITIAL_CURRENT_TIME)
-  , mCurrentTime(INITIAL_CURRENT_TIME)
-  , mBlockingDecisionsMadeUntilTime(1)
+  : mCurrentTime(INITIAL_CURRENT_TIME)
+  , mStateComputedTime(INITIAL_CURRENT_TIME)
   , mProcessingGraphUpdateIndex(0)
   , mMonitor("MediaStreamGraphImpl")
   , mLifecycleState(LIFECYCLE_THREAD_NOT_STARTED)
   , mWaitState(WAITSTATE_RUNNING)
   , mNeedAnotherIteration(false)
   , mForceShutDown(false)
   , mPostedRunInStableStateEvent(false)
   , mDetectedNotRunning(false)
--- a/content/media/MediaStreamGraph.h
+++ b/content/media/MediaStreamGraph.h
@@ -197,34 +197,53 @@ class SourceMediaStream;
  * MediaStreamGraph::CreateInput/ProcessedMediaStream, and releases them by calling
  * Destroy() when no longer needed (actual destruction will be deferred).
  * The actual object is owned by the MediaStreamGraph. The basic idea is that
  * main thread objects will keep Streams alive as long as necessary (using the
  * cycle collector to clean up whenever needed).
  *
  * We make them refcounted only so that stream-related messages with MediaStream*
  * pointers can be sent to the main thread safely.
+ *
+ * The lifetimes of MediaStreams are controlled from the main thread.
+ * For MediaStreams exposed to the DOM, the lifetime is controlled by the DOM
+ * wrapper; the DOM wrappers own their associated MediaStreams. When a DOM
+ * wrapper is destroyed, it sends a Destroy message for the associated
+ * MediaStream and clears its reference (the last main-thread reference to
+ * the object). When the Destroy message is processed on the graph
+ * manager thread we immediately release the affected objects (disentangling them
+ * from other objects as necessary).
+ *
+ * This could cause problems for media processing if a MediaStream is
+ * destroyed while a downstream MediaStream is still using it. Therefore
+ * the DOM wrappers must keep upstream MediaStreams alive as long as they
+ * could be being used in the media graph.
+ *
+ * At any time, however, a set of MediaStream wrappers could be
+ * collected via cycle collection. Destroy messages will be sent
+ * for those objects in arbitrary order and the MediaStreamGraph has to be able
+ * to handle this.
  */
 class MediaStream {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaStream)
 
   MediaStream(nsDOMMediaStream* aWrapper)
     : mBufferStartTime(0)
     , mExplicitBlockerCount(0)
     , mBlocked(false)
     , mGraphUpdateIndices(0)
     , mFinished(false)
     , mNotifiedFinished(false)
     , mAudioPlaybackStartTime(0)
     , mBlockedAudioTime(0)
-    , mMessageAffectedTime(0)
     , mWrapper(aWrapper)
     , mMainThreadCurrentTime(0)
     , mMainThreadFinished(false)
+    , mMainThreadDestroyed(false)
   {
     for (PRUint32 i = 0; i < ArrayLength(mFirstActiveTracks); ++i) {
       mFirstActiveTracks[i] = TRACK_NONE;
     }
   }
   virtual ~MediaStream() {}
 
   /**
@@ -252,19 +271,32 @@ public:
   void ChangeExplicitBlockerCount(PRInt32 aDelta);
   // Events will be dispatched by calling methods of aListener.
   void AddListener(MediaStreamListener* aListener);
   void RemoveListener(MediaStreamListener* aListener);
   // Signal that the client is done with this MediaStream. It will be deleted later.
   void Destroy();
   // Returns the main-thread's view of how much data has been processed by
   // this stream.
-  StreamTime GetCurrentTime() { return mMainThreadCurrentTime; }
+  StreamTime GetCurrentTime()
+  {
+    NS_ASSERTION(NS_IsMainThread(), "Call only on main thread");
+    return mMainThreadCurrentTime;
+  }
   // Return the main thread's view of whether this stream has finished.
-  bool IsFinished() { return mMainThreadFinished; }
+  bool IsFinished()
+  {
+    NS_ASSERTION(NS_IsMainThread(), "Call only on main thread");
+    return mMainThreadFinished;
+  }
+  bool IsDestroyed()
+  {
+    NS_ASSERTION(NS_IsMainThread(), "Call only on main thread");
+    return mMainThreadDestroyed;
+  }
 
   friend class MediaStreamGraphImpl;
 
   virtual SourceMediaStream* AsSourceStream() { return nullptr; }
 
   // media graph thread only
   void Init();
   // These Impl methods perform the core functionality of the control methods
@@ -339,17 +371,17 @@ protected:
   VideoFrame mLastPlayedVideoFrame;
   // The number of times this stream has been explicitly blocked by the control
   // API, minus the number of times it has been explicitly unblocked.
   TimeVarying<GraphTime,PRUint32> mExplicitBlockerCount;
   nsTArray<nsRefPtr<MediaStreamListener> > mListeners;
 
   // Precomputed blocking status (over GraphTime).
   // This is only valid between the graph's mCurrentTime and
-  // mBlockingDecisionsMadeUntilTime. The stream is considered to have
+  // mStateComputedTime. The stream is considered to have
   // not been blocked before mCurrentTime (its mBufferStartTime is increased
   // as necessary to account for that time instead) --- this avoids us having to
   // record the entire history of the stream's blocking-ness in mBlocked.
   TimeVarying<GraphTime,bool> mBlocked;
   // Maps graph time to the graph update that affected this stream at that time
   TimeVarying<GraphTime,PRInt64> mGraphUpdateIndices;
 
   /**
@@ -372,25 +404,22 @@ protected:
   // blocking.
   MediaTime mBlockedAudioTime;
 
   // For each track type, this is the first active track found for that type.
   // The first active track is the track that started earliest; if multiple
   // tracks start at the same time, the one with the lowest ID.
   TrackID mFirstActiveTracks[MediaSegment::TYPE_COUNT];
 
-  // Temporary data used by MediaStreamGraph on the graph thread
-  // The earliest time for which we would like to change this stream's output.
-  GraphTime mMessageAffectedTime;
-
   // This state is only used on the main thread.
   nsDOMMediaStream* mWrapper;
   // Main-thread views of state
   StreamTime mMainThreadCurrentTime;
   bool mMainThreadFinished;
+  bool mMainThreadDestroyed;
 };
 
 /**
  * This is a stream into which a decoder can write audio and video.
  *
  * Audio and video can be written on any thread, but you probably want to
  * always write from the same thread to avoid unexpected interleavings.
  */
--- a/dom/Makefile.in
+++ b/dom/Makefile.in
@@ -58,16 +58,17 @@ DIRS += \
   messages \
   power \
   settings \
   sms \
   mms \
   src \
   locales \
   network \
+  permission \
   plugins/base \
   plugins/ipc \
   indexedDB \
   system \
   ipc \
   identity \
   workers \
   camera \
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -119,17 +119,17 @@ let DOMApplicationRegistry = {
         "name": activity,
         "title": manifest.name,
         "icon": manifest.iconURLForSize(128),
         "description": description
       }
       cpmm.sendAsyncMessage("Activities:Register", json);
 
       let launchPath =
-        Services.io.newURI(manifest.fullLaunchPath(description.href), null, null);
+        Services.io.newURI(manifest.resolveFromOrigin(description.href), null, null);
       let manifestURL = Services.io.newURI(aApp.manifestURL, null, null);
       msgmgr.registerPage("activity", launchPath, manifestURL);
     }
   },
 
   _unregisterActivities: function(aManifest, aApp) {
     if (!aManifest.activities) {
       return;
@@ -997,15 +997,19 @@ DOMApplicationManifest.prototype = {
   },
 
   fullLaunchPath: function(aStartPoint) {
     let startPoint = aStartPoint || "";
     let launchPath = this._localeProp("launch_path") || "";
     return this._origin.resolve(launchPath + startPoint);
   },
 
+  resolveFromOrigin: function(aURI) {
+    return this._origin.resolve(aURI);
+  },
+
   fullAppcachePath: function() {
     let appcachePath = this._localeProp("appcache_path");
     return this._origin.resolve(appcachePath ? appcachePath : "");
   }
 };
 
 DOMApplicationRegistry.init();
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -415,16 +415,17 @@
 #include "nsIImageDocument.h"
 
 // Storage includes
 #include "nsDOMStorage.h"
 
 // Device Storage
 #include "nsIDOMDeviceStorage.h"
 #include "nsIDOMDeviceStorageCursor.h"
+#include "nsIDOMDeviceStorageStat.h"
 
 // Drag and drop
 #include "nsIDOMDataTransfer.h"
 
 // Geolocation
 #include "nsIDOMGeoGeolocation.h"
 #include "nsIDOMGeoPosition.h"
 #include "nsIDOMGeoPositionCoords.h"
@@ -1436,16 +1437,19 @@ static nsDOMClassInfoData sClassInfoData
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(DeviceStorage, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(DeviceStorageCursor, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
+  NS_DEFINE_CLASSINFO_DATA(DeviceStorageStat, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
+
   NS_DEFINE_CLASSINFO_DATA(GeoGeolocation, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
   
   NS_DEFINE_CLASSINFO_DATA(GeoPosition, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS) 
   
   NS_DEFINE_CLASSINFO_DATA(GeoPositionCoords, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
@@ -4036,16 +4040,20 @@ nsDOMClassInfo::Init()
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(DeviceStorageCursor, nsIDOMDeviceStorageCursor)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMDeviceStorageCursor)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMDOMRequest)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMEventTarget)
   DOM_CLASSINFO_MAP_END
 
+  DOM_CLASSINFO_MAP_BEGIN(DeviceStorageStat, nsIDOMDeviceStorageStat)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMDeviceStorageStat)
+  DOM_CLASSINFO_MAP_END
+
   DOM_CLASSINFO_MAP_BEGIN(GeoGeolocation, nsIDOMGeoGeolocation)
      DOM_CLASSINFO_MAP_ENTRY(nsIDOMGeoGeolocation)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(GeoPosition, nsIDOMGeoPosition)
      DOM_CLASSINFO_MAP_ENTRY(nsIDOMGeoPosition)
   DOM_CLASSINFO_MAP_END
 
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -381,16 +381,17 @@ DOMCI_CLASS(ModalContentWindow)
 DOMCI_CLASS(DataContainerEvent)
 
 // event used for cross-domain message-passing and for server-sent events in
 // HTML5
 DOMCI_CLASS(MessageEvent)
 
 DOMCI_CLASS(DeviceStorage)
 DOMCI_CLASS(DeviceStorageCursor)
+DOMCI_CLASS(DeviceStorageStat)
 
 // Geolocation
 DOMCI_CLASS(GeoGeolocation)
 DOMCI_CLASS(GeoPosition)
 DOMCI_CLASS(GeoPositionCoords)
 DOMCI_CLASS(GeoPositionError)
 
 DOMCI_CLASS(BatteryManager)
--- a/dom/bluetooth/BluetoothService.h
+++ b/dom/bluetooth/BluetoothService.h
@@ -187,16 +187,24 @@ public:
                 const nsAString& aDeviceAddress,
                 nsAString& aDevicePath) = 0;
 
   virtual int
   GetDeviceServiceChannelInternal(const nsAString& aObjectPath,
                                   const nsAString& aPattern,
                                   int aAttributeId) = 0;
 
+  virtual nsTArray<PRUint32>
+  AddReservedServicesInternal(const nsAString& aAdapterPath,
+                              const nsTArray<PRUint32>& aServices) = 0;
+
+  virtual bool
+  RemoveReservedServicesInternal(const nsAString& aAdapterPath,
+                                 const nsTArray<PRUint32>& aServiceHandles) = 0;
+
   /**
    * Due to the fact that some operations require multiple calls, a
    * CommandThread is created that can run blocking, platform-specific calls
    * where either no asynchronous equivilent exists, or else where multiple
    * asynchronous calls would require excessive runnable bouncing between main
    * thread and IO thread.
    *
    * For instance, when we retrieve an Adapter object, we would like it to come
--- a/dom/bluetooth/linux/BluetoothDBusService.cpp
+++ b/dom/bluetooth/linux/BluetoothDBusService.cpp
@@ -974,16 +974,94 @@ BluetoothDBusService::GetDeviceServiceCh
 {
   // This is a blocking call, should not be run on main thread.
   MOZ_ASSERT(!NS_IsMainThread());
 
   const char* deviceObjectPath = NS_ConvertUTF16toUTF8(aObjectPath).get();
   const char* pattern = NS_ConvertUTF16toUTF8(aPattern).get();
 
   DBusMessage *reply =
-    dbus_func_args(mConnection, deviceObjectPath,
+    dbus_func_args(gThreadConnection->GetConnection(),
+                   deviceObjectPath,
                    DBUS_DEVICE_IFACE, "GetServiceAttributeValue",
                    DBUS_TYPE_STRING, &pattern,
                    DBUS_TYPE_UINT16, &aAttributeId,
                    DBUS_TYPE_INVALID);
 
   return reply ? dbus_returns_int32(reply) : -1;
 }
+
+static void
+ExtractHandles(DBusMessage *aReply, nsTArray<PRUint32>& aOutHandles)
+{
+  uint32_t* handles = NULL;
+  int len;
+
+  DBusError err;
+  dbus_error_init(&err);
+
+  if (dbus_message_get_args(aReply, &err,
+                            DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32, &handles, &len,
+                            DBUS_TYPE_INVALID)) {
+     if (!handles) {
+       LOG("Null array in extract_handles");
+     } else {
+        for (int i = 0; i < len; ++i) {
+        aOutHandles.AppendElement(handles[i]);
+      }
+    }
+  } else {
+    LOG_AND_FREE_DBUS_ERROR(&err);
+  }
+}
+
+nsTArray<PRUint32>
+BluetoothDBusService::AddReservedServicesInternal(const nsAString& aAdapterPath,
+                                                  const nsTArray<PRUint32>& aServices)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  nsTArray<PRUint32> ret;
+  const char* adapterPath = NS_ConvertUTF16toUTF8(aAdapterPath).get();
+
+  int length = aServices.Length();
+  if (length == 0) return ret;
+
+  const uint32_t* services = aServices.Elements();
+  DBusMessage* reply =
+    dbus_func_args(gThreadConnection->GetConnection(),
+                   adapterPath,
+                   DBUS_ADAPTER_IFACE, "AddReservedServiceRecords",
+                   DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32,
+                   &services, length, DBUS_TYPE_INVALID);
+
+  if (!reply) {
+    LOG("Null DBus message. Couldn't extract handles.");
+    return ret;
+  }
+
+  ExtractHandles(reply, ret);
+  return ret;
+}
+
+bool
+BluetoothDBusService::RemoveReservedServicesInternal(const nsAString& aAdapterPath,
+                                                     const nsTArray<PRUint32>& aServiceHandles)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  int length = aServiceHandles.Length();
+  if (length == 0) return false;
+
+  const uint32_t* services = aServiceHandles.Elements();
+
+  DBusMessage* reply =
+    dbus_func_args(gThreadConnection->GetConnection(),
+                   NS_ConvertUTF16toUTF8(aAdapterPath).get(),
+                   DBUS_ADAPTER_IFACE, "RemoveReservedServiceRecords",
+                   DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32,
+                   &services, length, DBUS_TYPE_INVALID);
+
+  if (!reply) return false;
+
+  dbus_message_unref(reply);
+  return true;
+}
--- a/dom/bluetooth/linux/BluetoothDBusService.h
+++ b/dom/bluetooth/linux/BluetoothDBusService.h
@@ -44,16 +44,24 @@ public:
   GetDevicePath(const nsAString& aAdapterPath,
                 const nsAString& aDeviceAddress,
                 nsAString& aDevicePath);
   virtual int
   GetDeviceServiceChannelInternal(const nsAString& aObjectPath,
                                   const nsAString& aPattern,
                                   int aAttributeId);
 
+  virtual nsTArray<PRUint32>
+  AddReservedServicesInternal(const nsAString& aAdapterPath,
+                              const nsTArray<PRUint32>& aServices);
+
+  virtual bool
+  RemoveReservedServicesInternal(const nsAString& aAdapterPath,
+                                 const nsTArray<PRUint32>& aServiceHandles);
+
 private:
   nsresult SendGetPropertyMessage(const nsAString& aPath,
                                   const char* aInterface,
                                   void (*aCB)(DBusMessage *, void *),
                                   BluetoothReplyRunnable* aRunnable);
   nsresult SendDiscoveryMessage(const nsAString& aAdapterPath,
                                 const char* aMessageName,
                                 BluetoothReplyRunnable* aRunnable);
--- a/dom/contacts/ContactManager.js
+++ b/dom/contacts/ContactManager.js
@@ -13,16 +13,17 @@ else
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
+Cu.import("resource://gre/modules/PermissionPromptHelper.jsm");
 
 XPCOMUtils.defineLazyGetter(Services, "DOMRequest", function() {
   return Cc["@mozilla.org/dom/dom-request-service;1"].getService(Ci.nsIDOMRequestService);
 });
 
 XPCOMUtils.defineLazyGetter(this, "cpmm", function() {
   return Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
 });
@@ -272,90 +273,30 @@ function ContactManager()
   debug("Constructor");
 }
 
 ContactManager.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
   _oncontactchange: null,
 
   set oncontactchange(aCallback) {
-    if (this.hasPrivileges)
+    debug("set oncontactchange");
+    let allowCallback = function() {
       this._oncontactchange = aCallback;
-    else
+    }.bind(this);
+    let cancelCallback = function() {
       throw Components.results.NS_ERROR_FAILURE;
+    }
+    this.askPermission("listen", null, allowCallback, cancelCallback);
   },
 
   get oncontactchange() {
     return this._oncontactchange;
   },
 
-  save: function save(aContact) {
-    let request;
-    if (this.hasPrivileges) {
-      debug("save: " + JSON.stringify(aContact) + " :" + aContact.id);
-      let newContact = {};
-      newContact.properties = {
-        name:            [],
-        honorificPrefix: [],
-        givenName:       [],
-        additionalName:  [],
-        familyName:      [],
-        honorificSuffix: [],
-        nickname:        [],
-        email:           [],
-        photo:           [],
-        url:             [],
-        category:        [],
-        adr:             [],
-        tel:             [],
-        org:             [],
-        jobTitle:        [],
-        bday:            null,
-        note:            [],
-        impp:            [],
-        anniversary:     null,
-        sex:             null,
-        genderIdentity:  null
-      };
-      for (let field in newContact.properties)
-        newContact.properties[field] = aContact[field];
-
-      let reason;
-      if (aContact.id == "undefined") {
-        // for example {25c00f01-90e5-c545-b4d4-21E2ddbab9e0} becomes
-        // 25c00f0190e5c545b4d421E2ddbab9e0
-        aContact.id = this._getRandomId().replace('-', '', 'g').replace('{', '').replace('}', '');
-        reason = "create";
-      } else {
-        reason = "update";
-      }
-
-      this._setMetaData(newContact, aContact);
-      debug("send: " + JSON.stringify(newContact));
-      request = this.createRequest();
-      cpmm.sendAsyncMessage("Contact:Save", {contact: newContact,
-                                             requestID: this.getRequestId({request: request, reason: reason })});
-      return request;
-    } else {
-      throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
-    }
-  },
-
-  remove: function removeContact(aRecord) {
-    let request;
-    if (this.hasPrivileges) {
-      request = this.createRequest();
-      cpmm.sendAsyncMessage("Contact:Remove", {id: aRecord.id,
-                                               requestID: this.getRequestId({request: request, reason: "remove"})});
-      return request;
-    } else {
-      throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
-    }
-  },
-
   _setMetaData: function(aNewContact, aRecord) {
     aNewContact.id = aRecord.id;
     aNewContact.published = aRecord.published;
     aNewContact.updated = aRecord.updated;
   },
 
   _convertContactsArray: function(aContacts) {
     let contacts = new Array();
@@ -403,81 +344,186 @@ ContactManager.prototype = {
       case "Contacts:Find:Return:KO":
       case "Contact:Save:Return:KO":
       case "Contact:Remove:Return:KO":
       case "Contacts:Clear:Return:KO":
         req = this.getRequest(msg.requestID);
         if (req)
           Services.DOMRequest.fireError(req.request, msg.errorMsg);
         break;
+      case "PermissionPromptHelper:AskPermission:OK":
+        debug("id: " + msg.requestID);
+        req = this.getRequest(msg.requestID);
+        if (!req) {
+          break;
+        }
+
+        if (msg.result == Ci.nsIPermissionManager.ALLOW_ACTION) {
+          req.allow();
+        } else {
+          req.cancel();
+        }
+        break;
       default: 
         debug("Wrong message: " + aMessage.name);
     }
     this.removeRequest(msg.requestID);
   },
 
-  find: function(aOptions) {
+  askPermission: function (aAccess, aReqeust, aAllowCallback, aCancelCallback) {
+    debug("askPermission for contacts");
+    let requestID = this.getRequestId({
+      request: aReqeust,
+      allow: function() {
+        aAllowCallback();
+      }.bind(this),
+      cancel : function() {
+        if (aCancelCallback) {
+          aCancelCallback()
+        } else if (request) {
+          Services.DOMRequest.fireError(request, "Not Allowed");
+        }
+      }.bind(this)
+    });
+
+    let principal = this._window.document.nodePrincipal;
+    cpmm.sendAsyncMessage("PermissionPromptHelper:AskPermission", {
+      type: "contacts",
+      access: aAccess,
+      requestID: requestID,
+      origin: principal.origin,
+      appID: principal.appId,
+      browserFlag: principal.isInBrowserElement
+    });
+  },
+
+  save: function save(aContact) {
     let request;
-    if (this.hasPrivileges) {
-      request = this.createRequest();
-      cpmm.sendAsyncMessage("Contacts:Find", {findOptions: aOptions, 
-                                              requestID: this.getRequestId({request: request, reason: "find"})});
-      return request;
+    debug("save: " + JSON.stringify(aContact) + " :" + aContact.id);
+    let newContact = {};
+    newContact.properties = {
+      name:            [],
+      honorificPrefix: [],
+      givenName:       [],
+      additionalName:  [],
+      familyName:      [],
+      honorificSuffix: [],
+      nickname:        [],
+      email:           [],
+      photo:           [],
+      url:             [],
+      category:        [],
+      adr:             [],
+      tel:             [],
+      org:             [],
+      jobTitle:        [],
+      bday:            null,
+      note:            [],
+      impp:            [],
+      anniversary:     null,
+      sex:             null,
+      genderIdentity:  null
+    };
+    for (let field in newContact.properties) {
+      newContact.properties[field] = aContact[field];
+    }
+
+    let reason;
+    if (aContact.id == "undefined") {
+      // for example {25c00f01-90e5-c545-b4d4-21E2ddbab9e0} becomes
+      // 25c00f0190e5c545b4d421E2ddbab9e0
+      aContact.id = this._getRandomId().replace('-', '', 'g').replace('{', '').replace('}', '');
+      reason = "create";
     } else {
-      debug("find not allowed");
-      throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+      reason = "update";
     }
+
+    this._setMetaData(newContact, aContact);
+    debug("send: " + JSON.stringify(newContact));
+    request = this.createRequest();
+    let options = { contact: newContact };
+    let allowCallback = function() {
+      cpmm.sendAsyncMessage("Contact:Save", {requestID: this.getRequestId({request: request, reason: reason}), options: options});
+    }.bind(this)
+    this.askPermission(reason, request, allowCallback);
+    return request;
+  },
+
+  find: function(aOptions) {
+    debug("find! " + JSON.stringify(aOptions));
+    let request;
+    request = this.createRequest();
+    let options = { findOptions: aOptions };
+    let allowCallback = function() {
+      cpmm.sendAsyncMessage("Contacts:Find", {requestID: this.getRequestId({request: request, reason: "find"}), options: options});
+    }.bind(this)
+    this.askPermission("find", request, allowCallback);
+    return request;
+  },
+
+  remove: function removeContact(aRecord) {
+    let request;
+    request = this.createRequest();
+    let options = { id: aRecord.id };
+    let allowCallback = function() {
+      cpmm.sendAsyncMessage("Contact:Remove", {requestID: this.getRequestId({request: request, reason: "remove"}), options: options});
+    }.bind(this)
+    this.askPermission("remove", request, allowCallback);
+    return request;
   },
 
   clear: function() {
+    debug("clear");
     let request;
-    if (this.hasPrivileges) {
-      request = this.createRequest();
-      cpmm.sendAsyncMessage("Contacts:Clear", {requestID: this.getRequestId({request: request, reason: "remove"})});
-      return request;
-    } else {
-      debug("clear not allowed");
-      throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
-    }
+    request = this.createRequest();
+    let options = {};
+    let allowCallback = function() {
+      cpmm.sendAsyncMessage("Contacts:Clear", {requestID: this.getRequestId({request: request, reason: "remove"}), options: options});
+    }.bind(this)
+    this.askPermission("remove", request, allowCallback);
+    return request;
   },
 
   getSimContacts: function(aType) {
     let request;
-    if (this.hasPrivileges) {
+    request = this.createRequest();
+
+    let allowCallback = function() {
       let callback = function(aType, aContacts) {
         debug("got SIM contacts: " + aType + " " + JSON.stringify(aContacts));
-        let result = aContacts.map(function(c) { return { name: [c.alphaId], tel: [c.number] } });
+        let result = aContacts.map(function(c) {
+          var contact = new Contact();
+          contact.init( { name: [c.alphaId], tel: [ { number: c.number } ] } );
+          return contact;
+        });
         debug("result: " + JSON.stringify(result));
         Services.DOMRequest.fireSuccess(request, result);
       };
       debug("getSimContacts " + aType);
-      request = this.createRequest();
+
       mRIL.getICCContacts(aType, callback);
-      return request;
-    } else {
-      debug("getSimContacts not allowed");
+    }.bind(this);
+
+    let cancelCallback = function() {
       throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
     }
+    this.askPermission("getSimContacts", request, allowCallback, cancelCallback);
+    return request;
   },
 
   init: function(aWindow) {
     // Set navigator.mozContacts to null.
     if (!Services.prefs.getBoolPref("dom.mozContacts.enabled"))
       return null;
 
     this.initHelper(aWindow, ["Contacts:Find:Return:OK", "Contacts:Find:Return:KO",
                               "Contacts:Clear:Return:OK", "Contacts:Clear:Return:KO",
                               "Contact:Save:Return:OK", "Contact:Save:Return:KO",
-                              "Contact:Remove:Return:OK", "Contact:Remove:Return:KO"]);
-
-    let perm = Services.perms.testExactPermissionFromPrincipal(aWindow.document.nodePrincipal, "contacts");
- 
-    //only pages with perm set can use the contacts
-    this.hasPrivileges = perm == Ci.nsIPermissionManager.ALLOW_ACTION;
-    debug("Contacts permission: " + this.hasPrivileges);
+                              "Contact:Remove:Return:OK", "Contact:Remove:Return:KO",
+                              "PermissionPromptHelper:AskPermission:OK"]);
   },
 
   // Called from DOMRequestIpcHelper
   uninit: function uninit() {
     debug("uninit call");
     if (this._oncontactchange)
       this._oncontactchange = null;
   },
--- a/dom/contacts/fallback/ContactDB.jsm
+++ b/dom/contacts/fallback/ContactDB.jsm
@@ -297,17 +297,16 @@ ContactDB.prototype = {
    *        Object specifying search options. Possible attributes:
    *        - filterBy
    *        - filterOp
    *        - filterValue
    *        - count
    */
   find: function find(aSuccessCb, aFailureCb, aOptions) {
     debug("ContactDB:find val:" + aOptions.filterValue + " by: " + aOptions.filterBy + " op: " + aOptions.filterOp + "\n");
-
     let self = this;
     this.newTxn("readonly", function (txn, store) {
       if (aOptions && (aOptions.filterOp == "equals" || aOptions.filterOp == "contains")) {
         self._findWithIndex(txn, store, aOptions);
       } else {
         self._findAll(txn, store, aOptions);
       }
     }, aSuccessCb, aFailureCb);
--- a/dom/contacts/fallback/ContactService.jsm
+++ b/dom/contacts/fallback/ContactService.jsm
@@ -53,29 +53,31 @@ let DOMContactManager = {
     if (this._db)
       this._db.close();
     this._db = null;
   },
 
   receiveMessage: function(aMessage) {
     debug("Fallback DOMContactManager::receiveMessage " + aMessage.name);
     let mm = aMessage.target.QueryInterface(Ci.nsIFrameMessageManager);
-    let msg = aMessage.json;
+    let msg = aMessage.data;
 
     /*
      * Sorting the contacts by sortBy field. sortBy can either be familyName or givenName.
      * If 2 entries have the same sortyBy field or no sortBy field is present, we continue 
      * sorting with the other sortyBy field.
      */
     function sortfunction(a, b){
       let x, y;
       let sortByNameSet = true;
       let result = 0;
-      let sortBy = msg.findOptions.sortBy;
-      let sortOrder = msg.findOptions.sortOrder;
+      let findOptions = msg.options.findOptions;
+      let sortBy = findOptions.sortBy;
+      let sortOrder = findOptions.sortOrder;
+      
       if (!a.properties[sortBy] || !(x = a.properties[sortBy][0].toLowerCase())) {
         sortByNameSet = false;
       }
 
       if (!b.properties[sortBy] || !(y = b.properties[sortBy][0].toLowerCase())) {
         if (sortByNameSet) {
           return sortOrder == 'ascending' ? 1 : -1;
         }
@@ -102,45 +104,50 @@ let DOMContactManager = {
 
     switch (aMessage.name) {
       case "Contacts:Find":
         let result = new Array();
         this._db.find(
           function(contacts) {
             for (let i in contacts)
               result.push(contacts[i]);
-            if (msg.findOptions.sortOrder !== 'undefined' && msg.findOptions.sortBy !== 'undefined') {
-              debug('sortBy: ' + msg.findOptions.sortBy + ', sortOrder: ' + msg.findOptions.sortOrder );
-              result.sort(sortfunction);
-              if (msg.findOptions.filterLimit)
-                result = result.slice(0, msg.findOptions.filterLimit);
+            if (msg.options && msg.options.findOptions) {
+              let findOptions = msg.options.findOptions;
+              if (findOptions.sortOrder !== 'undefined' && findOptions.sortBy !== 'undefined') {
+                debug('sortBy: ' + findOptions.sortBy + ', sortOrder: ' + findOptions.sortOrder );
+                result.sort(sortfunction);
+                if (findOptions.filterLimit)
+                  result = result.slice(0, findOptions.filterLimit);
+              }
             }
 
             debug("result:" + JSON.stringify(result));
             mm.sendAsyncMessage("Contacts:Find:Return:OK", {requestID: msg.requestID, contacts: result});
           }.bind(this),
           function(aErrorMsg) { mm.sendAsyncMessage("Contacts:Find:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }) }.bind(this),
-          msg.findOptions);
+          msg.options.findOptions);
         break;
       case "Contact:Save":
         this._db.saveContact(
-          msg.contact, 
-          function() { mm.sendAsyncMessage("Contact:Save:Return:OK", { requestID: msg.requestID, contactID: msg.contact.id }); }.bind(this),
+          msg.options.contact,
+          function() { mm.sendAsyncMessage("Contact:Save:Return:OK", { requestID: msg.requestID, contactID: msg.options.contact.id }); }.bind(this),
           function(aErrorMsg) { mm.sendAsyncMessage("Contact:Save:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)
         );
         break;
       case "Contact:Remove":
         this._db.removeContact(
-          msg.id, 
-          function() { mm.sendAsyncMessage("Contact:Remove:Return:OK", { requestID: msg.requestID, contactID: msg.id }); }.bind(this),
+          msg.options.id,
+          function() { mm.sendAsyncMessage("Contact:Remove:Return:OK", { requestID: msg.requestID, contactID: msg.options.id }); }.bind(this),
           function(aErrorMsg) { mm.sendAsyncMessage("Contact:Remove:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)
         );
         break;
       case "Contacts:Clear":
         this._db.clear(
           function() { mm.sendAsyncMessage("Contacts:Clear:Return:OK", { requestID: msg.requestID }); }.bind(this),
           function(aErrorMsg) { mm.sendAsyncMessage("Contacts:Clear:Return:KO", { requestID: msg.requestID, errorMsg: aErrorMsg }); }.bind(this)
         );
+      default:
+        debug("WRONG MESSAGE NAME: " + aMessage.name);
     }
   }
 }
 
 DOMContactManager.init();
--- a/dom/contacts/tests/test_contacts_basics.html
+++ b/dom/contacts/tests/test_contacts_basics.html
@@ -401,31 +401,31 @@ var steps = [
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Adding a new contact with properties1");
     createResult1 = new mozContact();
     createResult1.init(properties1);
+    mozContacts.oncontactchange = null;
     req = navigator.mozContacts.save(createResult1);
     req.onsuccess = function () {
       ok(createResult1.id, "The contact now has an ID.");
       sample_id1 = createResult1.id;
       checkContacts(properties1, createResult1);
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving by substring tel1");
     var options = {filterBy: ["tel"],
                    filterOp: "contains",
                    filterValue: properties1.tel[1].number.substring(1,5)};
-    mozContacts.oncontactchange = null;
     req = mozContacts.find(options);
     req.onsuccess = function () {
       ok(req.result.length == 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       checkContacts(createResult1, properties1);
       next();
     };
--- a/dom/devicestorage/DeviceStorage.h
+++ b/dom/devicestorage/DeviceStorage.h
@@ -58,16 +58,21 @@ private:
 
   nsCOMPtr<nsIPrincipal> mPrincipal;
 
   friend class WatchFileEvent;
   friend class DeviceStorageRequest;
 
   bool  mIsWatchingFile;
 
+#ifdef MOZ_WIDGET_GONK
+  PRUint32 mLastVolumeState; // Values match nsIVolume.idl
+  void DispatchMountChangeEvent(bool aMounted);
+#endif
+
   // nsIDOMDeviceStorage.type
   enum {
       DEVICE_STORAGE_TYPE_DEFAULT = 0,
       DEVICE_STORAGE_TYPE_SHARED,
       DEVICE_STORAGE_TYPE_EXTERNAL
   };
 };
 
--- a/dom/devicestorage/DeviceStorageRequestChild.cpp
+++ b/dom/devicestorage/DeviceStorageRequestChild.cpp
@@ -2,16 +2,17 @@
 /* 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 "DeviceStorageRequestChild.h"
 #include "nsDeviceStorage.h"
 #include "nsDOMFile.h"
+#include "mozilla/dom/ipc/Blob.h"
 
 namespace mozilla {
 namespace dom {
 namespace devicestorage {
 
 DeviceStorageRequestChild::DeviceStorageRequestChild()
 {
   MOZ_COUNT_CTOR(DeviceStorageRequestChild);
@@ -23,16 +24,17 @@ DeviceStorageRequestChild::DeviceStorage
   , mFile(aFile)
 {
   MOZ_COUNT_CTOR(DeviceStorageRequestChild);
 }
 
 DeviceStorageRequestChild::~DeviceStorageRequestChild() {
   MOZ_COUNT_DTOR(DeviceStorageRequestChild);
 }
+
 bool
 DeviceStorageRequestChild::Recv__delete__(const DeviceStorageResponseValue& aValue)
 {
   switch (aValue.type()) {
 
     case DeviceStorageResponseValue::TErrorResponse:
     {
       ErrorResponse r = aValue;
@@ -45,47 +47,49 @@ DeviceStorageRequestChild::Recv__delete_
       jsval result = StringToJsval(mRequest->GetOwner(), mFile->mPath);
       mRequest->FireSuccess(result);
       break;
     }
 
     case DeviceStorageResponseValue::TBlobResponse:
     {
       BlobResponse r = aValue;
-
-      // I am going to hell for this.  bent says he'll save me.
-      const InfallibleTArray<PRUint8> bits = r.bits();
-      void* buffer = PR_Malloc(bits.Length());
-      memcpy(buffer, (void*) bits.Elements(), bits.Length());
+      BlobChild* actor = static_cast<BlobChild*>(r.blobChild());
+      nsCOMPtr<nsIDOMBlob> blob = actor->GetBlob();
 
-      nsString mimeType;
-      mimeType.AssignWithConversion(r.contentType());
+      jsval result = InterfaceToJsval(mRequest->GetOwner(), blob, &NS_GET_IID(nsIDOMBlob));
+      mRequest->FireSuccess(result);
+      break;
+    }
 
-      nsCOMPtr<nsIDOMBlob> blob = new nsDOMMemoryFile(buffer,
-                                                      bits.Length(),
-                                                      mFile->mPath,
-                                                      mimeType);
+    case DeviceStorageResponseValue::TStatStorageResponse:
+    {
+      StatStorageResponse r = aValue;
 
-      jsval result = BlobToJsval(mRequest->GetOwner(), blob);
+      nsRefPtr<nsIDOMDeviceStorageStat> domstat = new nsDOMDeviceStorageStat(r.freeBytes(), r.totalBytes());
+      jsval result = InterfaceToJsval(mRequest->GetOwner(), domstat, &NS_GET_IID(nsIDOMDeviceStorageStat));
       mRequest->FireSuccess(result);
       break;
     }
 
     case DeviceStorageResponseValue::TEnumerationResponse:
     {
       EnumerationResponse r = aValue;
       nsDOMDeviceStorageCursor* cursor = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
 
       PRUint32 count = r.paths().Length();
       for (PRUint32 i = 0; i < count; i++) {
         nsCOMPtr<nsIFile> f;
-        NS_NewLocalFile(r.paths()[i].fullpath(), false, getter_AddRefs(f));
+        nsresult rv = NS_NewLocalFile(r.paths()[i].fullpath(), false, getter_AddRefs(f));
+        if (NS_FAILED(rv)) {
+          continue;
+        }
 
         nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
-        dsf->SetPath(r.paths()[i].path());
+        dsf->SetPath(r.paths()[i].name());
         cursor->mFiles.AppendElement(dsf);
       }
 
       nsCOMPtr<ContinueCursorEvent> event = new ContinueCursorEvent(cursor);
       NS_DispatchToMainThread(event);
       break;
     }
 
--- a/dom/devicestorage/DeviceStorageRequestParent.cpp
+++ b/dom/devicestorage/DeviceStorageRequestParent.cpp
@@ -3,16 +3,19 @@
  * 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 "DeviceStorageRequestParent.h"
 #include "nsDOMFile.h"
 #include "nsIMIMEService.h"
 #include "nsCExternalHandlerService.h"
 #include "mozilla/unused.h"
+#include "mozilla/dom/ipc/Blob.h"
+#include "ContentParent.h"
+#include "nsProxyRelease.h"
 
 namespace mozilla {
 namespace dom {
 namespace devicestorage {
 
 DeviceStorageRequestParent::DeviceStorageRequestParent(const DeviceStorageParams& aParams)
 {
   MOZ_COUNT_CTOR(DeviceStorageRequestParent);
@@ -21,65 +24,89 @@ DeviceStorageRequestParent::DeviceStorag
     case DeviceStorageParams::TDeviceStorageAddParams:
     {
       DeviceStorageAddParams p = aParams;
 
       nsCOMPtr<nsIFile> f;
       NS_NewLocalFile(p.fullpath(), false, getter_AddRefs(f));
 
       nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
-      nsRefPtr<WriteFileEvent> r = new WriteFileEvent(this, dsf, p.bits());
+
+      BlobParent* bp = static_cast<BlobParent*>(p.blobParent());
+      nsCOMPtr<nsIDOMBlob> blob = bp->GetBlob();
+
+      nsCOMPtr<nsIInputStream> stream;
+      blob->GetInternalStream(getter_AddRefs(stream));
+
+      nsRefPtr<CancelableRunnable> r = new WriteFileEvent(this, dsf, stream);
 
       nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
       NS_ASSERTION(target, "Must have stream transport service");
       target->Dispatch(r, NS_DISPATCH_NORMAL);
       break;
     }
 
     case DeviceStorageParams::TDeviceStorageGetParams:
     {
       DeviceStorageGetParams p = aParams;
 
       nsCOMPtr<nsIFile> f;
       NS_NewLocalFile(p.fullpath(), false, getter_AddRefs(f));
 
       nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
-      nsRefPtr<ReadFileEvent> r = new ReadFileEvent(this, dsf);
+      dsf->SetPath(p.name());
+      nsRefPtr<CancelableRunnable> r = new ReadFileEvent(this, dsf);
 
       nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
       NS_ASSERTION(target, "Must have stream transport service");
       target->Dispatch(r, NS_DISPATCH_NORMAL);
       break;
     }
 
     case DeviceStorageParams::TDeviceStorageDeleteParams:
     {
       DeviceStorageDeleteParams p = aParams;
 
       nsCOMPtr<nsIFile> f;
       NS_NewLocalFile(p.fullpath(), false, getter_AddRefs(f));
 
       nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
-      nsRefPtr<DeleteFileEvent> r = new DeleteFileEvent(this, dsf);
+      nsRefPtr<CancelableRunnable> r = new DeleteFileEvent(this, dsf);
+
+      nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+      NS_ASSERTION(target, "Must have stream transport service");
+      target->Dispatch(r, NS_DISPATCH_NORMAL);
+      break;
+    }
+
+    case DeviceStorageParams::TDeviceStorageStatParams:
+    {
+      DeviceStorageStatParams p = aParams;
+
+      nsCOMPtr<nsIFile> f;
+      NS_NewLocalFile(p.fullpath(), false, getter_AddRefs(f));
+
+      nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
+      nsRefPtr<StatFileEvent> r = new StatFileEvent(this, dsf);
 
       nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
       NS_ASSERTION(target, "Must have stream transport service");
       target->Dispatch(r, NS_DISPATCH_NORMAL);
       break;
     }
 
     case DeviceStorageParams::TDeviceStorageEnumerationParams:
     {
       DeviceStorageEnumerationParams p = aParams;
 
       nsCOMPtr<nsIFile> f;
       NS_NewLocalFile(p.fullpath(), false, getter_AddRefs(f));
 
       nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
-      nsRefPtr<EnumerateFileEvent> r = new EnumerateFileEvent(this, dsf, p.since());
+      nsRefPtr<CancelableRunnable> r = new EnumerateFileEvent(this, dsf, p.since());
 
       nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
       NS_ASSERTION(target, "Must have stream transport service");
       target->Dispatch(r, NS_DISPATCH_NORMAL);
       break;
     }
     default:
     {
@@ -89,227 +116,281 @@ DeviceStorageRequestParent::DeviceStorag
   }
 }
 
 DeviceStorageRequestParent::~DeviceStorageRequestParent()
 {
   MOZ_COUNT_DTOR(DeviceStorageRequestParent);
 }
 
+NS_IMPL_THREADSAFE_ADDREF(DeviceStorageRequestParent);
+NS_IMPL_THREADSAFE_RELEASE(DeviceStorageRequestParent);
+
+void
+DeviceStorageRequestParent::ActorDestroy(ActorDestroyReason)
+{
+  PRInt32 count = mRunnables.Length();
+  for (PRInt32 index = 0; index < count; index++) {
+    mRunnables[index]->Cancel();
+  }
+}
+
 DeviceStorageRequestParent::PostErrorEvent::PostErrorEvent(DeviceStorageRequestParent* aParent,
                                                            const char* aError)
-  : mParent(aParent)
+  : CancelableRunnable(aParent)
 {
-  mError.AssignWithConversion(aError);
+  CopyASCIItoUTF16(aError, mError);
 }
 
 DeviceStorageRequestParent::PostErrorEvent::~PostErrorEvent() {}
 
-NS_IMETHODIMP
-DeviceStorageRequestParent::PostErrorEvent::Run() {
+nsresult
+DeviceStorageRequestParent::PostErrorEvent::CancelableRun() {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
   ErrorResponse response(mError);
   unused << mParent->Send__delete__(mParent, response);
   return NS_OK;
 }
 
 
 DeviceStorageRequestParent::PostSuccessEvent::PostSuccessEvent(DeviceStorageRequestParent* aParent)
-  : mParent(aParent)
+  : CancelableRunnable(aParent)
 {
 }
 
 DeviceStorageRequestParent::PostSuccessEvent::~PostSuccessEvent() {}
 
-NS_IMETHODIMP
-DeviceStorageRequestParent::PostSuccessEvent::Run() {
+nsresult
+DeviceStorageRequestParent::PostSuccessEvent::CancelableRun() {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   SuccessResponse response;
   unused <<  mParent->Send__delete__(mParent, response);
   return NS_OK;
 }
 
 DeviceStorageRequestParent::PostBlobSuccessEvent::PostBlobSuccessEvent(DeviceStorageRequestParent* aParent,
-                                                                       void* aBuffer,
+                                                                       DeviceStorageFile* aFile,
                                                                        PRUint32 aLength,
                                                                        nsACString& aMimeType)
-  : mParent(aParent)
+  : CancelableRunnable(aParent)
+  , mLength(aLength)
+  , mFile(aFile)
   , mMimeType(aMimeType)
 {
-  mBits.SetCapacity(aLength);
-  void* bits = mBits.Elements();
-  memcpy(bits, aBuffer, aLength);
 }
 
 DeviceStorageRequestParent::PostBlobSuccessEvent::~PostBlobSuccessEvent() {}
 
-NS_IMETHODIMP
-DeviceStorageRequestParent::PostBlobSuccessEvent::Run() {
+nsresult
+DeviceStorageRequestParent::PostBlobSuccessEvent::CancelableRun() {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  BlobResponse response(mBits, mMimeType);
+  nsString mime;
+  mime.AssignWithConversion(mMimeType);
+  CopyASCIItoUTF16(mMimeType, mime);
+
+  nsCOMPtr<nsIDOMBlob> blob = new nsDOMFileFile(mFile->mPath, mime, mLength, mFile->mFile);
+
+  ContentParent* cp = static_cast<ContentParent*>(mParent->Manager());
+  BlobParent* actor = cp->GetOrCreateActorForBlob(blob);
+
+  BlobResponse response;
+  response.blobParent() = actor;
+
   unused <<  mParent->Send__delete__(mParent, response);
   return NS_OK;
 }
 
 DeviceStorageRequestParent::PostEnumerationSuccessEvent::PostEnumerationSuccessEvent(DeviceStorageRequestParent* aParent,
                                                                                      InfallibleTArray<DeviceStorageFileValue>& aPaths)
-  : mParent(aParent)
+  : CancelableRunnable(aParent)
   , mPaths(aPaths)
 {
 }
 
 DeviceStorageRequestParent::PostEnumerationSuccessEvent::~PostEnumerationSuccessEvent() {}
 
-NS_IMETHODIMP
-DeviceStorageRequestParent::PostEnumerationSuccessEvent::Run() {
+nsresult
+DeviceStorageRequestParent::PostEnumerationSuccessEvent::CancelableRun() {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   EnumerationResponse response(mPaths);
   unused <<  mParent->Send__delete__(mParent, response);
   return NS_OK;
 }
 
 DeviceStorageRequestParent::WriteFileEvent::WriteFileEvent(DeviceStorageRequestParent* aParent,
                                                            DeviceStorageFile* aFile,
-                                                           InfallibleTArray<PRUint8>& aBits)
-  : mParent(aParent)
+                                                           nsIInputStream* aInputStream)
+  : CancelableRunnable(aParent)
   , mFile(aFile)
-  , mBits(aBits)
+  , mInputStream(aInputStream)
 {
 }
 
 DeviceStorageRequestParent::WriteFileEvent::~WriteFileEvent()
 {
 }
 
-NS_IMETHODIMP
-DeviceStorageRequestParent::WriteFileEvent::Run()
+nsresult
+DeviceStorageRequestParent::WriteFileEvent::CancelableRun()
 {
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
   nsRefPtr<nsRunnable> r;
-  nsresult rv = mFile->Write(mBits);
+
+  if (!mInputStream) {
+    r = new PostErrorEvent(mParent, POST_ERROR_EVENT_UNKNOWN);
+    NS_DispatchToMainThread(r);
+    return NS_OK;
+  }
+
+  nsresult rv = mFile->Write(mInputStream);
 
   if (NS_FAILED(rv)) {
     r = new PostErrorEvent(mParent, POST_ERROR_EVENT_UNKNOWN);
   }
   else {
     r = new PostPathResultEvent(mParent, mFile->mPath);
   }
 
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
 
 
 DeviceStorageRequestParent::DeleteFileEvent::DeleteFileEvent(DeviceStorageRequestParent* aParent,
                                                              DeviceStorageFile* aFile)
-  : mParent(aParent)
+  : CancelableRunnable(aParent)
   , mFile(aFile)
 {
 }
 
 DeviceStorageRequestParent::DeleteFileEvent::~DeleteFileEvent()
 {
 }
 
-NS_IMETHODIMP
-DeviceStorageRequestParent::DeleteFileEvent::Run()
+nsresult
+DeviceStorageRequestParent::DeleteFileEvent::CancelableRun()
 {
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
   mFile->mFile->Remove(true);
 
   nsRefPtr<nsRunnable> r;
 
   bool check = false;
   mFile->mFile->Exists(&check);
   if (check) {
     r = new PostErrorEvent(mParent, POST_ERROR_EVENT_UNKNOWN);
   }
   else {
     r = new PostPathResultEvent(mParent, mFile->mPath);
   }
+
+  NS_DispatchToMainThread(r);
+  return NS_OK;
+}
+
+DeviceStorageRequestParent::StatFileEvent::StatFileEvent(DeviceStorageRequestParent* aParent,
+                                                         DeviceStorageFile* aFile)
+  : CancelableRunnable(aParent)
+  , mFile(aFile)
+{
+}
+
+DeviceStorageRequestParent::StatFileEvent::~StatFileEvent()
+{
+}
+
+nsresult
+DeviceStorageRequestParent::StatFileEvent::CancelableRun()
+{
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
+  nsCOMPtr<nsIRunnable> r;
+  PRUint64 diskUsage = DeviceStorageFile::DirectoryDiskUsage(mFile->mFile);
+  PRInt64 freeSpace = 0;
+  nsresult rv = mFile->mFile->GetDiskSpaceAvailable(&freeSpace);
+  if (NS_FAILED(rv)) {
+    r = new PostErrorEvent(mParent, POST_ERROR_EVENT_UNKNOWN);
+    NS_DispatchToMainThread(r);
+    return NS_OK;
+  }
+  r = new PostStatResultEvent(mParent, diskUsage, freeSpace);
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
 
 DeviceStorageRequestParent::ReadFileEvent::ReadFileEvent(DeviceStorageRequestParent* aParent,
                                                          DeviceStorageFile* aFile)
-  : mParent(aParent)
+  : CancelableRunnable(aParent)
   , mFile(aFile)
 {
   nsCOMPtr<nsIMIMEService> mimeService = do_GetService(NS_MIMESERVICE_CONTRACTID);
   if (mimeService) {
     nsresult rv = mimeService->GetTypeFromFile(mFile->mFile, mMimeType);
     if (NS_FAILED(rv)) {
       mMimeType.Truncate();
     }
   }
 }
 
 DeviceStorageRequestParent::ReadFileEvent::~ReadFileEvent()
 {
 }
 
-NS_IMETHODIMP
-DeviceStorageRequestParent::ReadFileEvent::Run()
+nsresult
+DeviceStorageRequestParent::ReadFileEvent::CancelableRun()
 {
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
   nsCOMPtr<nsIRunnable> r;
   bool check = false;
   mFile->mFile->Exists(&check);
+
   if (!check) {
     r = new PostErrorEvent(mParent, POST_ERROR_EVENT_FILE_DOES_NOT_EXIST);
     NS_DispatchToMainThread(r);
     return NS_OK;
   }
 
   PRInt64 fileSize;
   nsresult rv = mFile->mFile->GetFileSize(&fileSize);
   if (NS_FAILED(rv)) {
     r = new PostErrorEvent(mParent, POST_ERROR_EVENT_UNKNOWN);
     NS_DispatchToMainThread(r);
     return NS_OK;
   }
-  
-  PRFileDesc *fileHandle;
-  rv = mFile->mFile->OpenNSPRFileDesc(PR_RDONLY, 0, &fileHandle);
 
-  // i am going to hell.  this is temp until bent provides seralizaiton of blobs.
-  void* buf = (void*) malloc(fileSize);
-  PRInt32 read = PR_Read(fileHandle, buf, fileSize); 
-  if (read != fileSize) {
-    r = new PostErrorEvent(mParent, POST_ERROR_EVENT_UNKNOWN);
-    NS_DispatchToMainThread(r);
-    return NS_OK;
-  }
-
-  r = new PostBlobSuccessEvent(mParent, buf, fileSize, mMimeType);
-
-  PR_Free(buf);
-  PR_Close(fileHandle);
-
+  r = new PostBlobSuccessEvent(mParent, mFile, fileSize, mMimeType);
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
 
 DeviceStorageRequestParent::EnumerateFileEvent::EnumerateFileEvent(DeviceStorageRequestParent* aParent,
                                                                    DeviceStorageFile* aFile,
                                                                    PRUint32 aSince)
-  : mParent(aParent)
+  : CancelableRunnable(aParent)
   , mFile(aFile)
   , mSince(aSince)
 {
 }
 
 DeviceStorageRequestParent::EnumerateFileEvent::~EnumerateFileEvent()
 {
 }
 
-NS_IMETHODIMP
-DeviceStorageRequestParent::EnumerateFileEvent::Run()
+nsresult
+DeviceStorageRequestParent::EnumerateFileEvent::CancelableRun()
 {
+  NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+
   nsCOMPtr<nsIRunnable> r;
   bool check = false;
   mFile->mFile->Exists(&check);
   if (!check) {
     r = new PostErrorEvent(mParent, POST_ERROR_EVENT_FILE_DOES_NOT_EXIST);
     NS_DispatchToMainThread(r);
     return NS_OK;
   }
@@ -318,43 +399,66 @@ DeviceStorageRequestParent::EnumerateFil
   mFile->CollectFiles(files, mSince);
 
   InfallibleTArray<DeviceStorageFileValue> values;
 
   PRUint32 count = files.Length();
   for (PRUint32 i = 0; i < count; i++) {
     nsString fullpath;
     files[i]->mFile->GetPath(fullpath);
-    DeviceStorageFileValue dsvf(fullpath, files[i]->mPath);
+    DeviceStorageFileValue dsvf(files[i]->mPath, fullpath);
     values.AppendElement(dsvf);
   }
 
   r = new PostEnumerationSuccessEvent(mParent, values);
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
 
 
 DeviceStorageRequestParent::PostPathResultEvent::PostPathResultEvent(DeviceStorageRequestParent* aParent,
-                                                             const nsAString& aPath)
-  : mParent(aParent)
+                                                                     const nsAString& aPath)
+  : CancelableRunnable(aParent)
   , mPath(aPath)
 {
 }
 
 DeviceStorageRequestParent::PostPathResultEvent::~PostPathResultEvent()
 {
 }
 
-NS_IMETHODIMP
-DeviceStorageRequestParent::PostPathResultEvent::Run()
+nsresult
+DeviceStorageRequestParent::PostPathResultEvent::CancelableRun()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   SuccessResponse response;
   unused <<  mParent->Send__delete__(mParent, response);
   return NS_OK;
 }
 
+DeviceStorageRequestParent::PostStatResultEvent::PostStatResultEvent(DeviceStorageRequestParent* aParent,
+                                                                     PRInt64 aFreeBytes,
+                                                                     PRInt64 aTotalBytes)
+  : CancelableRunnable(aParent)
+  , mFreeBytes(aFreeBytes)
+  , mTotalBytes(aTotalBytes)
+{
+}
+
+DeviceStorageRequestParent::PostStatResultEvent::~PostStatResultEvent()
+{
+}
+
+nsresult
+DeviceStorageRequestParent::PostStatResultEvent::CancelableRun()
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+  StatStorageResponse response(mFreeBytes, mTotalBytes);
+  unused <<  mParent->Send__delete__(mParent, response);
+  return NS_OK;
+}
+
 
 } // namespace devicestorage
 } // namespace dom
 } // namespace mozilla
--- a/dom/devicestorage/DeviceStorageRequestParent.h
+++ b/dom/devicestorage/DeviceStorageRequestParent.h
@@ -6,131 +6,194 @@
 #ifndef mozilla_dom_devicestorage_DeviceStorageRequestParent_h
 #define mozilla_dom_devicestorage_DeviceStorageRequestParent_h
 
 #include "mozilla/dom/devicestorage/PDeviceStorageRequestParent.h"
 #include "mozilla/dom/ContentChild.h"
 
 #include "nsThreadUtils.h"
 #include "nsDeviceStorage.h"
+#include "nsTArray.h"
 
 namespace mozilla {
 namespace dom {
 namespace devicestorage {
 
 class DeviceStorageRequestParent : public PDeviceStorageRequestParent
 {
 public:
   DeviceStorageRequestParent(const DeviceStorageParams& aParams);
+
+  NS_IMETHOD_(nsrefcnt) AddRef();
+  NS_IMETHOD_(nsrefcnt) Release();
+  virtual void ActorDestroy(ActorDestroyReason);
+
+protected:
   ~DeviceStorageRequestParent();
 
 private:
-  class PostErrorEvent : public nsRunnable
+  nsAutoRefCnt mRefCnt;
+
+  class CancelableRunnable : public nsRunnable
+  {
+  public:
+    CancelableRunnable(DeviceStorageRequestParent* aParent)
+      : mParent(aParent)
+      , mCanceled(false)
+    {
+      mParent->AddRunnable(this);
+    }
+
+    virtual ~CancelableRunnable() {
+    }
+
+    NS_IMETHOD Run() {
+      nsresult rv = NS_OK;
+      if (!mCanceled) {
+        rv = CancelableRun();
+        mParent->RemoveRunnable(this);
+      }
+      return rv;
+    }
+
+    void Cancel() {
+      mCanceled = true;
+    }
+
+    virtual nsresult CancelableRun() = 0;
+
+  protected:
+    nsRefPtr<DeviceStorageRequestParent> mParent;
+  private:
+    bool mCanceled;
+  };
+
+  class PostErrorEvent : public CancelableRunnable
   {
     public:
       PostErrorEvent(DeviceStorageRequestParent* aParent, const char* aError);
-      ~PostErrorEvent();
-      NS_IMETHOD Run();
+      virtual ~PostErrorEvent();
+      virtual nsresult CancelableRun();
     private:
-      DeviceStorageRequestParent* mParent;
       nsString mError;
   };
 
-  class PostSuccessEvent : public nsRunnable
+  class PostSuccessEvent : public CancelableRunnable
   {
     public:
       PostSuccessEvent(DeviceStorageRequestParent* aParent);
-      ~PostSuccessEvent();
-      NS_IMETHOD Run();
-    private:
-      DeviceStorageRequestParent* mParent;
-  };
-
-  class PostBlobSuccessEvent : public nsRunnable
-  {
-    public:
-      PostBlobSuccessEvent(DeviceStorageRequestParent* aParent, void* aBuffer, PRUint32 aLength, nsACString& aMimeType);
-      ~PostBlobSuccessEvent();
-      NS_IMETHOD Run();
-    private:
-      DeviceStorageRequestParent* mParent;
-      InfallibleTArray<PRUint8> mBits;
-      nsCString mMimeType;
-  };
-
-  class PostEnumerationSuccessEvent : public nsRunnable
-  {
-    public:
-      PostEnumerationSuccessEvent(DeviceStorageRequestParent* aParent, InfallibleTArray<DeviceStorageFileValue>& aPaths);
-      ~PostEnumerationSuccessEvent();
-      NS_IMETHOD Run();
-    private:
-      DeviceStorageRequestParent* mParent;
-      InfallibleTArray<DeviceStorageFileValue> mPaths;
+      virtual ~PostSuccessEvent();
+      virtual nsresult CancelableRun();
   };
 
-  class WriteFileEvent : public nsRunnable
-  {
-    public:
-      WriteFileEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile, InfallibleTArray<PRUint8>& aBits);
-      ~WriteFileEvent();
-      NS_IMETHOD Run();
-    private:
-      DeviceStorageRequestParent* mParent;
-      nsRefPtr<DeviceStorageFile> mFile;
-    InfallibleTArray<PRUint8> mBits; // another copy?
-  };
-
-  class DeleteFileEvent : public nsRunnable
+  class PostBlobSuccessEvent : public CancelableRunnable
   {
     public:
-      DeleteFileEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile);
-      ~DeleteFileEvent();
-      NS_IMETHOD Run();
+      PostBlobSuccessEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile, PRUint32 aLength, nsACString& aMimeType);
+      virtual ~PostBlobSuccessEvent();
+      virtual nsresult CancelableRun();
     private:
-      DeviceStorageRequestParent* mParent;
-      nsRefPtr<DeviceStorageFile> mFile;
-  };
-
-  class ReadFileEvent : public nsRunnable
-  {
-    public:
-      ReadFileEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile);
-      ~ReadFileEvent();
-      NS_IMETHOD Run();
-    private:
-      DeviceStorageRequestParent* mParent;
+      PRUint32 mLength;
       nsRefPtr<DeviceStorageFile> mFile;
       nsCString mMimeType;
   };
 
-  class EnumerateFileEvent : public nsRunnable
+  class PostEnumerationSuccessEvent : public CancelableRunnable
+  {
+    public:
+      PostEnumerationSuccessEvent(DeviceStorageRequestParent* aParent, InfallibleTArray<DeviceStorageFileValue>& aPaths);
+      virtual ~PostEnumerationSuccessEvent();
+      virtual nsresult CancelableRun();
+    private:
+      InfallibleTArray<DeviceStorageFileValue> mPaths;
+  };
+
+  class WriteFileEvent : public CancelableRunnable
+  {
+    public:
+      WriteFileEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile, nsIInputStream* aInputStream);
+      virtual ~WriteFileEvent();
+      virtual nsresult CancelableRun();
+    private:
+      nsRefPtr<DeviceStorageFile> mFile;
+      nsCOMPtr<nsIInputStream> mInputStream;
+  };
+
+  class DeleteFileEvent : public CancelableRunnable
+  {
+    public:
+      DeleteFileEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile);
+      virtual ~DeleteFileEvent();
+      virtual nsresult CancelableRun();
+    private:
+      nsRefPtr<DeviceStorageFile> mFile;
+  };
+
+  class StatFileEvent : public CancelableRunnable
+  {
+    public:
+      StatFileEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile);
+      virtual ~StatFileEvent();
+      virtual nsresult CancelableRun();
+     private:
+       nsRefPtr<DeviceStorageFile> mFile;
+   };
+
+  class ReadFileEvent : public CancelableRunnable
+  {
+    public:
+      ReadFileEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile);
+      virtual ~ReadFileEvent();
+      virtual nsresult CancelableRun();
+    private:
+      nsRefPtr<DeviceStorageFile> mFile;
+      nsCString mMimeType;
+  };
+
+  class EnumerateFileEvent : public CancelableRunnable
   {
     public:
       EnumerateFileEvent(DeviceStorageRequestParent* aParent, DeviceStorageFile* aFile, PRUint32 aSince);
-      ~EnumerateFileEvent();
-      NS_IMETHOD Run();
+      virtual ~EnumerateFileEvent();
+      virtual nsresult CancelableRun();
     private:
-      DeviceStorageRequestParent* mParent;
       nsRefPtr<DeviceStorageFile> mFile;
       PRUint32 mSince;
   };
 
-  class PostPathResultEvent : public nsRunnable
+  class PostPathResultEvent : public CancelableRunnable
   {
     public:
       PostPathResultEvent(DeviceStorageRequestParent* aParent, const nsAString& aPath);
-      ~PostPathResultEvent();
-      NS_IMETHOD Run();
+      virtual ~PostPathResultEvent();
+      virtual nsresult CancelableRun();
     private:
-      DeviceStorageRequestParent* mParent;
       nsRefPtr<DeviceStorageFile> mFile;
-      InfallibleTArray<PRUint8> mBits;
       nsString mPath;
   };
 
+  class PostStatResultEvent : public CancelableRunnable
+ {
+    public:
+      PostStatResultEvent(DeviceStorageRequestParent* aParent,
+                          PRInt64 aFreeBytes,
+                          PRInt64 aTotalBytes);
+      virtual ~PostStatResultEvent();
+      virtual nsresult CancelableRun();
+    private:
+      PRInt64 mFreeBytes, mTotalBytes;
+   };
+
+protected:
+  void AddRunnable(CancelableRunnable* aRunnable) {
+    mRunnables.AppendElement(aRunnable);
+  }
+  void RemoveRunnable(CancelableRunnable* aRunnable) {
+    mRunnables.RemoveElement(aRunnable);
+  }
+  nsTArray<nsRefPtr<CancelableRunnable> > mRunnables;
 };
 
 } // namespace devicestorage
 } // namespace dom
 } // namespace mozilla
 
 #endif
--- a/dom/devicestorage/PDeviceStorageRequest.ipdl
+++ b/dom/devicestorage/PDeviceStorageRequest.ipdl
@@ -1,13 +1,14 @@
 /* -*- Mode: C++; tab-width: 8; 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 protocol PBlob;
 include protocol PContent;
 
 namespace mozilla {
 namespace dom {
 namespace devicestorage {
 
 
 struct ErrorResponse
@@ -16,40 +17,43 @@ struct ErrorResponse
 };
 
 struct SuccessResponse
 {
 };
 
 struct BlobResponse
 {
-  // todo going away
-  PRUint8[] bits;
-  nsCString contentType;
+  PBlob blob;
 };
 
 struct DeviceStorageFileValue
 {
-  // todo going away
+  nsString name;
   nsString fullpath;
-  nsString path;
 };
 
 struct EnumerationResponse
 {
   DeviceStorageFileValue[] paths;
-  // todo bent PBlob
+};
+
+struct StatStorageResponse
+{
+  PRInt64 totalBytes;
+  PRInt64 freeBytes;
 };
 
 union DeviceStorageResponseValue
 {
   ErrorResponse;
   SuccessResponse;
   BlobResponse;
   EnumerationResponse;
+  StatStorageResponse;
 };
 
 sync protocol PDeviceStorageRequest {
     manager PContent;
 child:
     __delete__(DeviceStorageResponseValue response);
 };
 
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/PBrowserChild.h"
+#include "mozilla/dom/ipc/Blob.h"
 #include "mozilla/dom/devicestorage/PDeviceStorageRequestChild.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/PContentPermissionRequestChild.h"
 
 #include "nsDeviceStorage.h"
 
 #include "nsDOMEvent.h"
 #include "nsServiceManagerUtils.h"
@@ -141,27 +142,29 @@ DeviceStorageFile::AppendRelativePath() 
   }
   mFile->AppendRelativePath(temp);
 #else
   mFile->AppendRelativePath(mPath);
 #endif
 }
 
 nsresult
-DeviceStorageFile::Write(nsIDOMBlob* aBlob)
+DeviceStorageFile::Write(nsIInputStream* aInputStream)
 {
+  if (!aInputStream) {
+    return NS_ERROR_FAILURE;
+  }
+
   nsresult rv = mFile->Create(nsIFile::NORMAL_FILE_TYPE, 00600);
   if (NS_FAILED(rv)) {
     return rv;
   }
-  nsCOMPtr<nsIInputStream> stream;
-  aBlob->GetInternalStream(getter_AddRefs(stream));
 
   PRUint32 bufSize;
-  stream->Available(&bufSize);
+  aInputStream->Available(&bufSize);
 
   nsCOMPtr<nsIOutputStream> outputStream;
   NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mFile);
 
   if (!outputStream) {
     return NS_ERROR_FAILURE;
   }
 
@@ -170,17 +173,17 @@ DeviceStorageFile::Write(nsIDOMBlob* aBl
                              outputStream,
                              4096*4);
 
   if (!bufferedOutputStream) {
     return NS_ERROR_FAILURE;
   }
 
   PRUint32 wrote;
-  bufferedOutputStream->WriteFrom(stream, bufSize, &wrote);
+  bufferedOutputStream->WriteFrom(aInputStream, bufSize, &wrote);
   bufferedOutputStream->Close();
   outputStream->Close();
   if (bufSize != wrote) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
@@ -273,148 +276,197 @@ DeviceStorageFile::collectFilesInternal(
     } else if (isFile) {
       nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(f);
       dsf->SetPath(newPath);
       aFiles.AppendElement(dsf);
     }
   }
 }
 
+PRUint64
+DeviceStorageFile::DirectoryDiskUsage(nsIFile* aFile, PRUint64 aSoFar)
+{
+  if (!aFile) {
+    return aSoFar;
+  }
+
+  nsresult rv;
+  nsCOMPtr<nsISimpleEnumerator> e;
+  rv = aFile->GetDirectoryEntries(getter_AddRefs(e));
+
+  if (NS_FAILED(rv) || !e) {
+    return aSoFar;
+  }
+
+  nsCOMPtr<nsIDirectoryEnumerator> files = do_QueryInterface(e);
+  NS_ASSERTION(files, "GetDirectoryEntries must return a nsIDirectoryEnumerator");
+
+  nsCOMPtr<nsIFile> f;
+  while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(f))) && f) {
+    bool isDir;
+    rv = f->IsDirectory(&isDir);
+    if (NS_FAILED(rv)) {
+      continue;
+    }
+
+    bool isFile;
+    rv = f->IsFile(&isFile);
+    if (NS_FAILED(rv)) {
+      continue;
+    }
+
+    bool isLink;
+    rv = f->IsSymlink(&isLink);
+    if (NS_FAILED(rv)) {
+      continue;
+    }
+      
+    if (isLink) {
+      // for now, lets just totally ignore symlinks.
+      NS_WARNING("DirectoryDiskUsage ignores symlinks");
+    } else if (isDir) {
+      aSoFar += DirectoryDiskUsage(f, aSoFar);
+    } else if (isFile) {
+      PRInt64 size;
+      rv = f->GetFileSize(&size);
+      if (NS_SUCCEEDED(rv)) {
+	aSoFar += size;
+      }
+    }
+  }
+  return aSoFar;
+}
+
 NS_IMPL_THREADSAFE_ISUPPORTS0(DeviceStorageFile)
 
+#ifdef MOZ_WIDGET_GONK
+static void
+RegisterForSDCardChanges(nsIObserver* aObserver)
+{
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  obs->AddObserver(aObserver, NS_VOLUME_STATE_CHANGED, false);
+}
+
+static void
+UnregisterForSDCardChanges(nsIObserver* aObserver)
+{
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  obs->RemoveObserver(aObserver, NS_VOLUME_STATE_CHANGED);
+}
+#endif
 
 void
 nsDOMDeviceStorage::SetRootFileForType(const nsAString& aType)
 {
   nsCOMPtr<nsIFile> f;
   nsCOMPtr<nsIProperties> dirService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
   NS_ASSERTION(dirService, "Must have directory service");
 
-#ifdef MOZ_WIDGET_GONK
-  mFile = nullptr;
-
-  nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
-  if (!vs) {
-    return;
-  }
-
-  nsCOMPtr<nsIVolume> v;
-  vs->GetVolumeByPath(NS_LITERAL_STRING("/sdcard"), getter_AddRefs(v));
-  
-  if (!v) {
-    return;
-  }
-
-  PRInt32 state;
-  v->GetState(&state);
-
-  if (state != nsIVolume::STATE_MOUNTED) {
-    return;
-  }
-#endif
-
   // Picture directory
   if (aType.Equals(NS_LITERAL_STRING("pictures"))) {
 #ifdef MOZ_WIDGET_GONK
     NS_NewLocalFile(NS_LITERAL_STRING("/sdcard/DCIM"), false, getter_AddRefs(f));
 #elif defined (MOZ_WIDGET_COCOA)
     dirService->Get(NS_OSX_PICTURE_DOCUMENTS_DIR, NS_GET_IID(nsIFile), getter_AddRefs(f));
 #elif defined (XP_UNIX)
     dirService->Get(NS_UNIX_XDG_PICTURES_DIR, NS_GET_IID(nsIFile), getter_AddRefs(f));
 #endif
   }
 
   // Video directory
-  if (aType.Equals(NS_LITERAL_STRING("videos"))) {
+  else if (aType.Equals(NS_LITERAL_STRING("videos"))) {
 #ifdef MOZ_WIDGET_GONK
     NS_NewLocalFile(NS_LITERAL_STRING("/sdcard/Movies"), false, getter_AddRefs(f));
 #elif defined (MOZ_WIDGET_COCOA)
     dirService->Get(NS_OSX_MOVIE_DOCUMENTS_DIR, NS_GET_IID(nsIFile), getter_AddRefs(f));
 #elif defined (XP_UNIX)
     dirService->Get(NS_UNIX_XDG_VIDEOS_DIR, NS_GET_IID(nsIFile), getter_AddRefs(f));
 #endif
   }
 
   // Music directory
-  if (aType.Equals(NS_LITERAL_STRING("music"))) {
+  else if (aType.Equals(NS_LITERAL_STRING("music"))) {
 #ifdef MOZ_WIDGET_GONK
     NS_NewLocalFile(NS_LITERAL_STRING("/sdcard/Music"), false, getter_AddRefs(f));
 #elif defined (MOZ_WIDGET_COCOA)
     dirService->Get(NS_OSX_MUSIC_DOCUMENTS_DIR, NS_GET_IID(nsIFile), getter_AddRefs(f));
 #elif defined (XP_UNIX)
     dirService->Get(NS_UNIX_XDG_MUSIC_DIR, NS_GET_IID(nsIFile), getter_AddRefs(f));
 #endif
   }
 
   // in testing, we have access to a few more directory locations
   if (mozilla::Preferences::GetBool("device.storage.testing", false)) {
 
     // testing directory
     if (aType.Equals(NS_LITERAL_STRING("testing"))) {
       dirService->Get(NS_OS_TEMP_DIR, NS_GET_IID(nsIFile), getter_AddRefs(f));
       if (f) {
-	f->AppendRelativeNativePath(NS_LITERAL_CSTRING("device-storage-testing"));
-	f->Create(nsIFile::DIRECTORY_TYPE, 0777);
-       f->Normalize();
+        f->AppendRelativeNativePath(NS_LITERAL_CSTRING("device-storage-testing"));
+        f->Create(nsIFile::DIRECTORY_TYPE, 0777);
+        f->Normalize();
       }
     }
   } 
 
+#ifdef MOZ_WIDGET_GONK
+  RegisterForSDCardChanges(this);
+#endif
   mFile = f;
 }
 
-static jsval nsIFileToJsval(nsPIDOMWindow* aWindow, DeviceStorageFile* aFile)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(aWindow, "Null Window");
-
-  if (aFile->mEditable) {
-    // TODO - needs janv's file handle support.
-    return JSVAL_NULL;
-  }
-
-  if (aFile == nullptr) {
-    return JSVAL_NULL;
-  }
-
-  nsCOMPtr<nsIDOMBlob> blob = new nsDOMFileFile(aFile->mFile, aFile->mPath);
-  return BlobToJsval(aWindow, blob);
-}
-
-
-jsval BlobToJsval(nsPIDOMWindow* aWindow, nsIDOMBlob* aBlob)
+jsval InterfaceToJsval(nsPIDOMWindow* aWindow, nsISupports* aObject, const nsIID* aIID)
 {
   nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aWindow);
   if (!sgo) {
     return JSVAL_NULL;
   }
 
   nsIScriptContext *scriptContext = sgo->GetScriptContext();
   if (!scriptContext) {
     return JSVAL_NULL;
   }
 
   JSContext *cx = scriptContext->GetNativeContext();
   if (!cx) {
     return JSVAL_NULL;
   }
 
-  jsval wrappedFile;
+  jsval someJsVal;
   nsresult rv = nsContentUtils::WrapNative(cx,
                                            JS_GetGlobalObject(cx),
-                                           aBlob,
-                                           &NS_GET_IID(nsIDOMFile),
-                                           &wrappedFile);
+                                           aObject,
+                                           aIID,
+                                           &someJsVal);
   if (NS_FAILED(rv)) {
     return JSVAL_NULL;
   }
 
-  return wrappedFile;
+  return someJsVal;
 }
 
+jsval nsIFileToJsval(nsPIDOMWindow* aWindow, DeviceStorageFile* aFile)
+{
+  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  NS_ASSERTION(aWindow, "Null Window");
+
+  if (aFile->mEditable) {
+    // TODO - needs janv's file handle support.
+    return JSVAL_NULL;
+  }
+
+  if (aFile == nullptr) {
+    return JSVAL_NULL;
+  }
+
+  nsCOMPtr<nsIDOMBlob> blob = new nsDOMFileFile(aFile->mFile, aFile->mPath);
+  return InterfaceToJsval(aWindow, blob, &NS_GET_IID(nsIDOMBlob));
+ }
+
+
 jsval StringToJsval(nsPIDOMWindow* aWindow, nsAString& aString)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   NS_ASSERTION(aWindow, "Null Window");
 
   nsCOMPtr<nsIScriptGlobalObject> sgo = do_QueryInterface(aWindow);
   if (!sgo) {
     return JSVAL_NULL;
@@ -532,17 +584,16 @@ public:
     else {
       mError.Append(NS_LITERAL_STRING("null path"));
     }
   }
 
   NS_IMETHOD Run()
   {
     NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-
     mRequest->FireError(mError);
     mRequest = nullptr;
     return NS_OK;
   }
 
 private:
   nsRefPtr<DOMRequest> mRequest;
   nsString mError;
@@ -560,17 +611,16 @@ ContinueCursorEvent::ContinueCursorEvent
 
 ContinueCursorEvent::~ContinueCursorEvent() {}
 
 NS_IMETHODIMP
 ContinueCursorEvent::Run() {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   jsval val;
-
   nsDOMDeviceStorageCursor* cursor = static_cast<nsDOMDeviceStorageCursor*>(mRequest.get());
   if (cursor->mFiles.Length() == 0) {
     val = JSVAL_NULL;
   }
   else {
     nsRefPtr<DeviceStorageFile> file = cursor->mFiles[0];
     cursor->mFiles.RemoveElementAt(0);
 
@@ -761,16 +811,49 @@ nsDOMDeviceStorageCursor::Recv__delete__
 }
 
 void
 nsDOMDeviceStorageCursor::IPDLRelease()
 {
   Release();
 }
 
+class PostStatResultEvent : public nsRunnable
+{
+public:
+  PostStatResultEvent(nsRefPtr<DOMRequest>& aRequest, PRInt64 aFreeBytes, PRInt64 aTotalBytes)
+    : mFreeBytes(aFreeBytes)
+    , mTotalBytes(aTotalBytes)
+    {
+      mRequest.swap(aRequest);
+    }
+
+  ~PostStatResultEvent() {}
+
+  NS_IMETHOD Run()
+  {
+    NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+
+    nsRefPtr<nsIDOMDeviceStorageStat> domstat = new nsDOMDeviceStorageStat(mFreeBytes, mTotalBytes);
+
+    jsval result = InterfaceToJsval(mRequest->GetOwner(),
+				    domstat,
+				    &NS_GET_IID(nsIDOMDeviceStorageStat));
+
+    mRequest->FireSuccess(result);
+    mRequest = nsnull;
+    return NS_OK;
+  }
+
+private:
+  PRInt64 mFreeBytes, mTotalBytes;
+  nsRefPtr<DOMRequest> mRequest;
+};
+
+
 class PostResultEvent : public nsRunnable
 {
 public:
   PostResultEvent(nsRefPtr<DOMRequest>& aRequest, DeviceStorageFile* aFile)
     : mFile(aFile)
     {
       mRequest.swap(aRequest);
     }
@@ -818,17 +901,20 @@ public:
     }
 
   ~WriteFileEvent() {}
 
   NS_IMETHOD Run()
   {
     NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
-    nsresult rv = mFile->Write(mBlob);
+    nsCOMPtr<nsIInputStream> stream;
+    mBlob->GetInternalStream(getter_AddRefs(stream));
+
+    nsresult rv = mFile->Write(stream);
 
     if (NS_FAILED(rv)) {
       mFile->mFile->Remove(false);
 
       nsCOMPtr<PostErrorEvent> event = new PostErrorEvent(mRequest,
                                                           POST_ERROR_EVENT_UNKNOWN,
                                                           mFile);
       NS_DispatchToMainThread(event);
@@ -859,17 +945,16 @@ public:
 
   ~ReadFileEvent() {}
 
   NS_IMETHOD Run()
   {
     NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
 
     nsRefPtr<nsRunnable> r;
-
     if (!mFile->mEditable) {
       bool check = false;
       mFile->mFile->Exists(&check);
       if (!check) {
         r = new PostErrorEvent(mRequest, POST_ERROR_EVENT_FILE_DOES_NOT_EXIST, mFile);
       }
     }
 
@@ -903,42 +988,77 @@ public:
 
     mFile->mFile->Remove(true);
 
     nsRefPtr<nsRunnable> r;
 
     bool check = false;
     mFile->mFile->Exists(&check);
     if (check) {
-      r = new PostErrorEvent(mRequest, POST_ERROR_EVENT_UNKNOWN, mFile);
+      r = new PostErrorEvent(mRequest, POST_ERROR_EVENT_FILE_DOES_NOT_EXIST, mFile);
     }
     else {
       r = new PostResultEvent(mRequest, mFile->mPath);
     }
     NS_DispatchToMainThread(r);
     return NS_OK;
   }
 
 private:
   nsRefPtr<DeviceStorageFile> mFile;
   nsRefPtr<DOMRequest> mRequest;
 };
 
+class StatFileEvent : public nsRunnable
+{
+public:
+  StatFileEvent(DeviceStorageFile* aFile, nsRefPtr<DOMRequest>& aRequest)
+  : mFile(aFile)
+    {
+      mRequest.swap(aRequest);
+    }
+
+  ~StatFileEvent() {}
+
+  NS_IMETHOD Run()
+  {
+    NS_ASSERTION(!NS_IsMainThread(), "Wrong thread!");
+    nsCOMPtr<nsIRunnable> r;
+    PRUint64 diskUsage = DeviceStorageFile::DirectoryDiskUsage(mFile->mFile);
+    PRInt64 freeSpace = 0;
+    nsresult rv = mFile->mFile->GetDiskSpaceAvailable(&freeSpace);
+    if (NS_FAILED(rv)) {
+      r = new PostErrorEvent(mRequest, POST_ERROR_EVENT_UNKNOWN, mFile);
+      NS_DispatchToMainThread(r);
+      return NS_OK;
+    }
+
+    r = new PostStatResultEvent(mRequest, diskUsage, freeSpace);
+    NS_DispatchToMainThread(r);
+    return NS_OK;
+  }
+
+private:
+  nsRefPtr<DeviceStorageFile> mFile;
+  nsRefPtr<DOMRequest> mRequest;
+};
+
 class DeviceStorageRequest MOZ_FINAL
   : public nsIContentPermissionRequest
   , public nsIRunnable
   , public PCOMContentPermissionRequestChild
 {
 public:
 
     enum DeviceStorageRequestType {
         DEVICE_STORAGE_REQUEST_READ,
         DEVICE_STORAGE_REQUEST_WRITE,
         DEVICE_STORAGE_REQUEST_DELETE,
-        DEVICE_STORAGE_REQUEST_WATCH
+        DEVICE_STORAGE_REQUEST_WATCH,
+        DEVICE_STORAGE_REQUEST_STAT
     };
 
     DeviceStorageRequest(const DeviceStorageRequestType aRequestType,
                          nsPIDOMWindow *aWindow,
                          nsIPrincipal *aPrincipal,
                          DeviceStorageFile *aFile,
                          DOMRequest* aRequest,
                          nsDOMDeviceStorage *aDeviceStorage,
@@ -1053,44 +1173,40 @@ public:
     switch(mRequestType) {
       case DEVICE_STORAGE_REQUEST_WRITE:
       {
         if (!mBlob) {
           return NS_ERROR_FAILURE;
         }
 
         if (XRE_GetProcessType() != GeckoProcessType_Default) {
-          PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile);
 
-          nsCOMPtr<nsIInputStream> stream;
-          mBlob->GetInternalStream(getter_AddRefs(stream));
-
-          InfallibleTArray<PRUint8> bits;
-          PRUint32 bufSize, numRead;
+	  BlobChild* actor = ContentChild::GetSingleton()->GetOrCreateActorForBlob(mBlob);
+	  if (!actor) {
+	    return NS_ERROR_FAILURE;
+	  }
 
-          stream->Available(&bufSize);
-          bits.SetCapacity(bufSize);
+          DeviceStorageAddParams params;
+	  params.blobChild() = actor;
+	  params.name() = mFile->mPath;
+	  params.fullpath() = fullpath;
 
-          void* buffer = (void*) bits.Elements();
-
-          stream->Read((char*)buffer, bufSize, &numRead);
-
-          DeviceStorageAddParams params(fullpath, bits);
+          PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile);
           ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
           return NS_OK;
         }
         r = new WriteFileEvent(mBlob, mFile, mRequest);
         break;
       }
 
       case DEVICE_STORAGE_REQUEST_READ:
       {
         if (XRE_GetProcessType() != GeckoProcessType_Default) {
           PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile);
-          DeviceStorageGetParams params(fullpath);
+          DeviceStorageGetParams params(mFile->mPath, fullpath);
           ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
           return NS_OK;
         }
 
         r = new ReadFileEvent(mFile, mRequest);
         break;
       }
 
@@ -1101,16 +1217,28 @@ public:
           DeviceStorageDeleteParams params(fullpath);
           ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
           return NS_OK;
         }
         r = new DeleteFileEvent(mFile, mRequest);
         break;
       }
 
+      case DEVICE_STORAGE_REQUEST_STAT:
+      {
+        if (XRE_GetProcessType() != GeckoProcessType_Default) {
+          PDeviceStorageRequestChild* child = new DeviceStorageRequestChild(mRequest, mFile);
+          DeviceStorageStatParams params(fullpath);
+          ContentChild::GetSingleton()->SendPDeviceStorageRequestConstructor(child, params);
+	  return NS_OK;
+        }
+        r = new StatFileEvent(mFile, mRequest);
+        break;
+      }
+
       case DEVICE_STORAGE_REQUEST_WATCH:
       {
          if (XRE_GetProcessType() != GeckoProcessType_Default) {
            nsString fullpath;
            mFile->mFile->GetPath(fullpath);
            nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
            obs->AddObserver(mDeviceStorage, "file-watcher-update", false);
            ContentChild::GetSingleton()->SendAddFileWatch(fullpath);
@@ -1208,16 +1336,20 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(DeviceStorage)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(nsDOMDeviceStorage, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(nsDOMDeviceStorage, nsDOMEventTargetHelper)
 
 nsDOMDeviceStorage::nsDOMDeviceStorage()
   : mIsWatchingFile(false)
+#ifdef MOZ_WIDGET_GONK
+  , mLastVolumeState(nsIVolume::STATE_INIT)
+#endif
+
 { }
 
 nsresult
 nsDOMDeviceStorage::Init(nsPIDOMWindow* aWindow, const nsAString &aType)
 {
   NS_ASSERTION(aWindow, "Must have a content dom");
 
   SetRootFileForType(aType);
@@ -1242,16 +1374,19 @@ nsDOMDeviceStorage::~nsDOMDeviceStorage(
 {
 }
 
 void
 nsDOMDeviceStorage::Shutdown()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
+#ifdef MOZ_WIDGET_GONK
+  UnregisterForSDCardChanges(this);
+#endif
   if (mIsWatchingFile) {
     if (XRE_GetProcessType() != GeckoProcessType_Default) {
       nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
       obs->RemoveObserver(this, "file-watcher-update");
 
       nsString fullpath;
       mFile->GetPath(fullpath);
       ContentChild::GetSingleton()->SendRemoveFileWatch(fullpath);
@@ -1405,16 +1540,37 @@ nsDOMDeviceStorage::Delete(const JS::Val
     r = new DeviceStorageRequest(DeviceStorageRequest::DEVICE_STORAGE_REQUEST_DELETE,
                                  win, mPrincipal, dsf, request);
   }
   NS_DispatchToMainThread(r);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMDeviceStorage::Stat(nsIDOMDOMRequest** aRetval)
+{
+  nsCOMPtr<nsPIDOMWindow> win = GetOwner();
+  if (!win) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  nsRefPtr<DOMRequest> request = new DOMRequest(win);
+  NS_ADDREF(*aRetval = request);
+
+  nsRefPtr<DeviceStorageFile> dsf = new DeviceStorageFile(mFile);
+  nsCOMPtr<nsIRunnable> r = new DeviceStorageRequest(DeviceStorageRequest::DEVICE_STORAGE_REQUEST_STAT,
+						     win,
+						     mPrincipal,
+						     dsf,
+						     request);
+  NS_DispatchToMainThread(r);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMDeviceStorage::Enumerate(const JS::Value & aName,
                              const JS::Value & aOptions,
                              JSContext* aCx,
                              PRUint8 aArgc,
                              nsIDOMDeviceStorageCursor** aRetval)
 {
   return EnumerateInternal(aName, aOptions, aCx, aArgc, false, aRetval);
 }
@@ -1519,49 +1675,140 @@ nsDOMDeviceStorage::EnumerateInternal(co
   nsCOMPtr<nsIContentPermissionPrompt> prompt = do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
   if (prompt) {
     prompt->Prompt(r);
   }
 
   return NS_OK;
 }
 
+#ifdef MOZ_WIDGET_GONK
+void
+nsDOMDeviceStorage::DispatchMountChangeEvent(bool aMounted)
+{
+  nsCOMPtr<nsIDOMEvent> event;
+  NS_NewDOMDeviceStorageChangeEvent(getter_AddRefs(event), nullptr, nullptr);
+
+  nsCOMPtr<nsIDOMDeviceStorageChangeEvent> ce = do_QueryInterface(event);
+  nsresult rv = ce->InitDeviceStorageChangeEvent(NS_LITERAL_STRING("change"),
+                                                 true, false,
+                                                 NS_LITERAL_STRING(""), 
+                                                 aMounted ? NS_LITERAL_STRING("available")
+                                                          : NS_LITERAL_STRING("unavailable"));
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  bool ignore;
+  DispatchEvent(ce, &ignore);
+}
+#endif
+
+DOMCI_DATA(DeviceStorageStat, nsDOMDeviceStorageStat)
+
+NS_INTERFACE_MAP_BEGIN(nsDOMDeviceStorageStat)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMDeviceStorageStat)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(DeviceStorageStat)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(nsDOMDeviceStorageStat)
+NS_IMPL_RELEASE(nsDOMDeviceStorageStat)
+
+nsDOMDeviceStorageStat::nsDOMDeviceStorageStat(PRUint64 aFreeBytes, PRUint64 aTotalBytes)
+  : mFreeBytes(aFreeBytes)
+  , mTotalBytes(aTotalBytes)
+{
+}
+
+nsDOMDeviceStorageStat::~nsDOMDeviceStorageStat()
+{
+}
+
+NS_IMETHODIMP
+nsDOMDeviceStorageStat::GetTotalBytes(PRUint64 *aTotalBytes)
+{
+  *aTotalBytes = mTotalBytes;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMDeviceStorageStat::GetFreeBytes(PRUint64 *aFreeBytes)
+{
+  *aFreeBytes = mFreeBytes;
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 nsDOMDeviceStorage::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *aData)
 {
-  // data strings will have the format of
-  //  reason:path
-  nsDependentString data(aData);
+  if (!strcmp(aTopic, "file-watcher-update")) {
+
+    // data strings will have the format of
+    //  reason:path
+    nsDependentString data(aData);
 
-  nsAString::const_iterator start, end;
-  nsAString::const_iterator colon;
+    nsAString::const_iterator start, end;
+    nsAString::const_iterator colon;
+
+    data.BeginReading(start);
+    data.EndReading(end);
+    colon = end;
 
-  data.BeginReading(start);
-  data.EndReading(end);
-  colon = end;
+    nsString reason;
+    nsString filepath;
+    if (!FindInReadable(NS_LITERAL_STRING(":"), start, colon)) {
+      return NS_OK;
+    }
+   
+    filepath = Substring(colon, end);
+    data.BeginReading(start);
+    reason = Substring(start, --colon);
 
-  nsString reason;
-  nsString filepath;
-  if (!FindInReadable(NS_LITERAL_STRING(":"), start, colon)) {
+    nsCOMPtr<nsIFile> f;
+    NS_NewLocalFile(filepath, false, getter_AddRefs(f));
+ 
+    nsCString creason;
+    CopyUTF16toUTF8(reason, creason);
+
+    Update(creason.get(), f);
     return NS_OK;
   }
-   
-  filepath = Substring(colon, end);
-  data.BeginReading(start);
-  reason = Substring(start, --colon);
+
+#ifdef MOZ_WIDGET_GONK
+  if (!strcmp(aTopic, NS_VOLUME_STATE_CHANGED)) {
+    nsCOMPtr<nsIVolume> vol = do_QueryInterface(aSubject);
+    if (!vol) {
+      return NS_OK;
+    }
+    nsString volName;
+    vol->GetName(volName);
+    if (!volName.Equals(NS_LITERAL_STRING("sdcard"))) {
+      return NS_OK;
+    }
 
-  nsCOMPtr<nsIFile> f;
-  NS_NewLocalFile(filepath, false, getter_AddRefs(f));
- 
-  nsCString creason;
-  creason.AssignWithConversion(reason);
-  CopyUTF16toUTF8(reason, creason);
+    PRInt32 state;
+    nsresult rv = vol->GetState(&state);
+    if (NS_FAILED(rv)) {
+      return NS_OK;
+    }
 
-  Update(creason.get(), f);
- 
+    if (mLastVolumeState != state) {
+      mLastVolumeState = state;
+      if (state == nsIVolume::STATE_MOUNTED ||
+	  state == nsIVolume::STATE_NOMEDIA ||
+	  state == nsIVolume::STATE_SHARED  ) {
+	bool mounted = (state == nsIVolume::STATE_MOUNTED);
+	DispatchMountChangeEvent(mounted);
+      }
+      return NS_OK;
+    }
+    return NS_OK;
+  }
+#endif
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMDeviceStorage::Update(const char* aReason, nsIFile* aFile)
 {
   nsString rootpath;
   nsresult rv = mFile->GetPath(rootpath);
--- a/dom/devicestorage/nsDeviceStorage.h
+++ b/dom/devicestorage/nsDeviceStorage.h
@@ -10,16 +10,17 @@ class nsPIDOMWindow;
 
 #include "DOMRequest.h"
 #include "nsAutoPtr.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDOMClassInfoID.h"
 #include "nsIClassInfo.h"
 #include "nsIContentPermissionPrompt.h"
 #include "nsIDOMDeviceStorageCursor.h"
+#include "nsIDOMDeviceStorageStat.h"
 #include "nsIDOMWindow.h"
 #include "nsIURI.h"
 #include "nsInterfaceHashtable.h"
 #include "nsIPrincipal.h"
 #include "nsString.h"
 #include "nsWeakPtr.h"
 #include "nsIDOMEventListener.h"
 #include "nsIDOMEventTarget.h"
@@ -29,41 +30,45 @@ class nsPIDOMWindow;
 
 
 #define POST_ERROR_EVENT_FILE_DOES_NOT_EXIST         "File location doesn't exists"
 #define POST_ERROR_EVENT_FILE_NOT_ENUMERABLE         "File location is not enumerable"
 #define POST_ERROR_EVENT_PERMISSION_DENIED           "Permission Denied"
 #define POST_ERROR_EVENT_ILLEGAL_FILE_NAME           "Illegal file name"
 #define POST_ERROR_EVENT_UNKNOWN                     "Unknown"
 #define POST_ERROR_EVENT_NON_STRING_TYPE_UNSUPPORTED "Non-string type unsupported"
+#define POST_ERROR_EVENT_NOT_IMPLEMENTED             "Not implemented"
 
 using namespace mozilla::dom;
 
-class DeviceStorageFile MOZ_FINAL : public nsISupports {
+class DeviceStorageFile MOZ_FINAL
+  : public nsISupports {
 public:
   nsCOMPtr<nsIFile> mFile;
   nsString mPath;
   bool mEditable;
 
   DeviceStorageFile(nsIFile* aFile, const nsAString& aPath);
   DeviceStorageFile(nsIFile* aFile);
   void SetPath(const nsAString& aPath);
   void SetEditable(bool aEditable);
 
   NS_DECL_ISUPPORTS
 
   // we want to make sure that the names of file can't reach
   // outside of the type of storage the user asked for.
   bool IsSafePath();
-  
-  nsresult Write(nsIDOMBlob* blob);
+
+  nsresult Write(nsIInputStream* aInputStream);
   nsresult Write(InfallibleTArray<PRUint8>& bits);
   void CollectFiles(nsTArray<nsRefPtr<DeviceStorageFile> > &aFiles, PRUint64 aSince = 0);
   void collectFilesInternal(nsTArray<nsRefPtr<DeviceStorageFile> > &aFiles, PRUint64 aSince, nsAString& aRootPath);
 
+  static PRUint64 DirectoryDiskUsage(nsIFile* aFile, PRUint64 aSoFar = 0);
+
 private:
   void NormalizeFilePath();
   void AppendRelativePath();
 };
 
 class ContinueCursorEvent MOZ_FINAL: public nsRunnable
 {
 public:
@@ -101,15 +106,29 @@ public:
 
 private:
   ~nsDOMDeviceStorageCursor();
 
   nsRefPtr<DeviceStorageFile> mFile;
   nsCOMPtr<nsIPrincipal> mPrincipal;
 };
 
+class nsDOMDeviceStorageStat MOZ_FINAL
+  : public nsIDOMDeviceStorageStat
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMDEVICESTORAGESTAT
+
+  nsDOMDeviceStorageStat(PRUint64 aFreeBytes, PRUint64 aTotalBytes);
+
+private:
+  ~nsDOMDeviceStorageStat();
+  PRUint64 mFreeBytes, mTotalBytes;
+};
+
 //helpers
 jsval StringToJsval(nsPIDOMWindow* aWindow, nsAString& aString);
-jsval nsIFileToJsval(nsPIDOMWindow* aWindow, DeviceStorageFile* aFile, bool aEditable);
-jsval BlobToJsval(nsPIDOMWindow* aWindow, nsIDOMBlob* aBlob);
+jsval nsIFileToJsval(nsPIDOMWindow* aWindow, DeviceStorageFile* aFile);
+jsval InterfaceToJsval(nsPIDOMWindow* aWindow, nsISupports* aObject, const nsIID* aIID);
 
 
 #endif
--- a/dom/devicestorage/test/Makefile.in
+++ b/dom/devicestorage/test/Makefile.in
@@ -14,12 +14,13 @@ MOCHITEST_FILES	= \
 		test_sanity.html \
 		test_basic.html \
 		test_enumerate.html \
 		test_enumerateMultipleContinue.html \
 		test_overwrite.html \
 		test_dotdot.html \
 		test_enumerateOptions.html \
 		test_lastModificationFilter.html \
+		test_stat.html \
 		devicestorage_common.js \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/devicestorage/test/test_stat.html
@@ -0,0 +1,72 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html> <!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=717103
+-->
+<head>
+  <title>Test for the device storage API </title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="devicestorage_common.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=717103">Mozilla Bug 717103</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+devicestorage_setup();
+
+function statSuccess(e) {
+  ok(e.target.result.freeBytes  > 0, "free bytes should exist and be greater than zero");
+  ok(e.target.result.totalBytes > 0, "total bytes should exist and be greater than zero");
+  devicestorage_cleanup();
+}
+
+function statError(e) {
+  ok(false, "statError was called");
+  devicestorage_cleanup();
+}
+
+var isMac = /Mac/.test(navigator.platform);
+var isWin = /Win/.test(navigator.platform);
+
+if (isMac || isWin) {
+  todo(false, "stat is not available on mac or windows yet. see bug xxxx");
+  devicestorage_cleanup();
+} else {
+  var storage = navigator.getDeviceStorage("testing");
+  ok(navigator.getDeviceStorage, "Should have getDeviceStorage");
+
+
+  function addError(e) {
+    ok(false, "addError was called : " + e.target.error.name);
+    devicestorage_cleanup();
+  }
+
+  function addSuccess(e) {
+    request = storage.stat();
+    ok(request, "Should have a non-null request");
+
+    request.onsuccess = statSuccess;
+    request.onerror = statError;
+  }
+
+  request = storage.addNamed(createRandomBlob(), "a/b");
+  request.onsuccess = addSuccess;
+  request.onerror = addError;
+
+}
+
+</script>
+</pre>
+</body>
+</html>
+
--- a/dom/identity/tests/Makefile.in
+++ b/dom/identity/tests/Makefile.in
@@ -6,23 +6,16 @@ DEPTH            = @DEPTH@
 topsrcdir        = @top_srcdir@
 srcdir           = @srcdir@
 VPATH            = @srcdir@
 
 relativesrcdir   = @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
-DIRS = \
-  $(NULL)
-
-include $(topsrcdir)/config/rules.mk
-
-_TEST_FILES = \
+MOCHITEST_FILES = \
   head_identity.js \
   test_identity_idp_auth_basics.html \
   test_identity_idp_prov_basics.html \
   test_identity_rp_basics.html \
   $(NULL)
 
-libs:: $(_TEST_FILES)
-	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
-
+include $(topsrcdir)/config/rules.mk
--- a/dom/indexedDB/IDBDatabase.cpp
+++ b/dom/indexedDB/IDBDatabase.cpp
@@ -273,16 +273,18 @@ IDBDatabase::IsInvalidated()
 }
 
 void
 IDBDatabase::CloseInternal(bool aIsDead)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   if (!mClosed) {
+    mClosed = true;
+
     // If we're getting called from Unlink, avoid cloning the DatabaseInfo.
     {
       nsRefPtr<DatabaseInfo> previousInfo;
       mDatabaseInfo.swap(previousInfo);
 
       if (!aIsDead) {
         nsRefPtr<DatabaseInfo> clonedInfo = previousInfo->Clone();
 
@@ -295,18 +297,16 @@ IDBDatabase::CloseInternal(bool aIsDead)
       mgr->OnDatabaseClosed(this);
     }
 
     // And let the parent process know as well.
     if (mActorChild) {
       NS_ASSERTION(!IndexedDatabaseManager::IsMainProcess(), "Wrong process!");
       mActorChild->SendClose(aIsDead);
     }
-
-    mClosed = true;
   }
 }
 
 bool
 IDBDatabase::IsClosed()
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
   return mClosed;
--- a/dom/interfaces/devicestorage/Makefile.in
+++ b/dom/interfaces/devicestorage/Makefile.in
@@ -14,14 +14,15 @@ LIBRARY_NAME     = domdevicestorage_s
 XPIDL_MODULE     = dom_devicestorage
 LIBXUL_LIBRARY   = 1
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/dom/dom-config.mk
 
 XPIDLSRCS = \
 	nsIDOMDeviceStorage.idl \
+	nsIDOMDeviceStorageStat.idl \
 	nsIDOMDeviceStorageCursor.idl \
 	nsIDOMNavigatorDeviceStorage.idl \
 	nsIDOMDeviceStorageChangeEvent.idl
 
 include $(topsrcdir)/config/rules.mk
 
--- a/dom/interfaces/devicestorage/nsIDOMDeviceStorage.idl
+++ b/dom/interfaces/devicestorage/nsIDOMDeviceStorage.idl
@@ -10,17 +10,17 @@ interface nsIDOMDeviceStorageCursor;
 interface nsIDOMDeviceStorageChangeEvent;
 interface nsIDOMEventListener;
 
 dictionary DeviceStorageEnumerationParameters
 {
   jsval since;
 };
 
-[scriptable, uuid(3dbe0137-ca73-44c5-bcde-25c297bf7c65), builtinclass]
+[scriptable, uuid(36f3b16b-a398-4b19-944e-ce299b714725), builtinclass]
 interface nsIDOMDeviceStorage : nsIDOMEventTarget
 {
     attribute nsIDOMEventListener onchange;
     nsIDOMDOMRequest add(in nsIDOMBlob aBlob);
     nsIDOMDOMRequest addNamed(in nsIDOMBlob aBlob, in DOMString aName);
 
     [implicit_jscontext]
     nsIDOMDOMRequest get(in jsval aName);
@@ -31,9 +31,11 @@ interface nsIDOMDeviceStorage : nsIDOMEv
     [implicit_jscontext]
     nsIDOMDOMRequest delete(in jsval aName);
 
     [optional_argc, implicit_jscontext]
     nsIDOMDeviceStorageCursor enumerate([optional] in jsval aName, /* DeviceStorageEnumerationParameters */ [optional] in jsval options);
 
     [optional_argc, implicit_jscontext]
     nsIDOMDeviceStorageCursor enumerateEditable([optional] in jsval aName, /* DeviceStorageEnumerationParameters */ [optional] in jsval options);
+
+    nsIDOMDOMRequest stat();
 };
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/devicestorage/nsIDOMDeviceStorageStat.idl
@@ -0,0 +1,12 @@
+/* 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 "domstubs.idl"
+
+[scriptable, uuid(b951ec07-d5db-42fc-bf4c-4eded202f7f5)]
+interface nsIDOMDeviceStorageStat : nsISupports
+{
+  readonly attribute PRUint64 totalBytes;
+  readonly attribute PRUint64 freeBytes;
+};
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -68,16 +68,20 @@
 #include "nsPermission.h"
 #include "nsPermissionManager.h"
 #endif
 
 #if defined(MOZ_WIDGET_ANDROID)
 #include "APKOpen.h"
 #endif
 
+#if defined(MOZ_WIDGET_GONK)
+#include "nsVolume.h"
+#endif
+
 #ifdef XP_WIN
 #include <process.h>
 #define getpid _getpid
 #endif
 
 #ifdef ACCESSIBILITY
 #include "nsIAccessibilityService.h"
 #endif
@@ -94,16 +98,19 @@ using namespace mozilla::docshell;
 using namespace mozilla::dom::devicestorage;
 using namespace mozilla::dom::sms;
 using namespace mozilla::dom::indexedDB;
 using namespace mozilla::hal_sandbox;
 using namespace mozilla::ipc;
 using namespace mozilla::layers;
 using namespace mozilla::net;
 using namespace mozilla::places;
+#if defined(MOZ_WIDGET_GONK)
+using namespace mozilla::system;
+#endif
 
 namespace mozilla {
 namespace dom {
 
 class MemoryReportRequestChild : public PMemoryReportRequestChild
 {
 public:
     MemoryReportRequestChild();
@@ -207,19 +214,20 @@ ConsoleListener::Observe(nsIConsoleMessa
     NS_ENSURE_SUCCESS(rv, rv);
     mChild->SendConsoleMessage(msg);
     return NS_OK;
 }
 
 ContentChild* ContentChild::sSingleton;
 
 ContentChild::ContentChild()
- : mID(PRUint64(-1))
+ :
+   mID(PRUint64(-1))
 #ifdef ANDROID
- , mScreenSize(0, 0)
+   ,mScreenSize(0, 0)
 #endif
 {
     // This process is a content process, so it's clearly running in
     // multiprocess mode!
     nsDebugImpl::SetMultiprocessMode("Child");
 }
 
 ContentChild::~ContentChild()
@@ -936,10 +944,23 @@ ContentChild::RecvFilePathUpdate(const n
     data.Append(NS_LITERAL_STRING(":"));
     data.Append(path);
 
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     obs->NotifyObservers(nullptr, "file-watcher-update", data.get());
     return true;
 }
 
+bool
+ContentChild::RecvFileSystemUpdate(const nsString& aFsName, const nsString& aName, const PRInt32 &aState)
+{
+#ifdef MOZ_WIDGET_GONK
+    nsRefPtr<nsVolume> volume = new nsVolume(aFsName, aName, aState);
+
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    nsString stateStr(NS_ConvertUTF8toUTF16(volume->StateStr()));
+    obs->NotifyObservers(volume, NS_VOLUME_STATE_CHANGED, stateStr.get());
+#endif
+    return true;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -158,16 +158,17 @@ public:
     virtual bool RecvCycleCollect();
 
     virtual bool RecvAppInfo(const nsCString& version, const nsCString& buildID);
     virtual bool RecvSetID(const PRUint64 &id);
 
     virtual bool RecvLastPrivateDocShellDestroyed();
 
     virtual bool RecvFilePathUpdate(const nsString& path, const nsCString& reason);
+    virtual bool RecvFileSystemUpdate(const nsString& aFsName, const nsString& aName, const PRInt32& aState);
 
 #ifdef ANDROID
     gfxIntSize GetScreenSize() { return mScreenSize; }
 #endif
 
     // Get the directory for IndexedDB files. We query the parent for this and
     // cache the value
     nsString &GetIndexedDBPath();
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -91,16 +91,21 @@
 #ifdef MOZ_SYDNEYAUDIO
 # include "AudioParent.h"
 #endif
 
 #ifdef MOZ_WIDGET_ANDROID
 # include "AndroidBridge.h"
 #endif
 
+#ifdef MOZ_WIDGET_GONK
+#include "nsIVolume.h"
+#include "nsIVolumeService.h"
+#endif
+
 static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
 static const char* sClipboardTextFlavors[] = { kUnicodeMime };
 
 using base::KillProcess;
 using namespace mozilla::dom::devicestorage;
 using namespace mozilla::dom::sms;
 using namespace mozilla::dom::indexedDB;
 using namespace mozilla::hal_sandbox;
@@ -265,16 +270,19 @@ ContentParent::Init()
     if (obs) {
         obs->AddObserver(this, "xpcom-shutdown", false);
         obs->AddObserver(this, NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC, false);
         obs->AddObserver(this, "child-memory-reporter-request", false);
         obs->AddObserver(this, "memory-pressure", false);
         obs->AddObserver(this, "child-gc-request", false);
         obs->AddObserver(this, "child-cc-request", false);
         obs->AddObserver(this, "last-pb-context-exited", false);
+#ifdef MOZ_WIDGET_GONK
+        obs->AddObserver(this, NS_VOLUME_STATE_CHANGED, false);
+#endif
 #ifdef ACCESSIBILITY
         obs->AddObserver(this, "a11y-init-or-shutdown", false);
 #endif
     }
     Preferences::AddStrongObserver(this, "");
     nsCOMPtr<nsIThreadInternal>
             threadInt(do_QueryInterface(NS_GetCurrentThread()));
     if (threadInt) {
@@ -426,16 +434,19 @@ ContentParent::ActorDestroy(ActorDestroy
     if (obs) {
         obs->RemoveObserver(static_cast<nsIObserver*>(this), "xpcom-shutdown");
         obs->RemoveObserver(static_cast<nsIObserver*>(this), "memory-pressure");
         obs->RemoveObserver(static_cast<nsIObserver*>(this), "child-memory-reporter-request");
         obs->RemoveObserver(static_cast<nsIObserver*>(this), NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC);
         obs->RemoveObserver(static_cast<nsIObserver*>(this), "child-gc-request");
         obs->RemoveObserver(static_cast<nsIObserver*>(this), "child-cc-request");
         obs->RemoveObserver(static_cast<nsIObserver*>(this), "last-pb-context-exited");
+#ifdef MOZ_WIDGET_GONK
+        obs->RemoveObserver(static_cast<nsIObserver*>(this), NS_VOLUME_STATE_CHANGED);
+#endif
 #ifdef ACCESSIBILITY
         obs->RemoveObserver(static_cast<nsIObserver*>(this), "a11y-init-or-shutdown");
 #endif
     }
 
     mMessageManager->Disconnect();
 
     // clear the child memory reporters
@@ -868,16 +879,34 @@ ContentParent::Observe(nsISupports* aSub
         unused << SendGarbageCollect();
     }
     else if (!strcmp(aTopic, "child-cc-request")){
         unused << SendCycleCollect();
     }
     else if (!strcmp(aTopic, "last-pb-context-exited")) {
         unused << SendLastPrivateDocShellDestroyed();
     }
+#ifdef MOZ_WIDGET_GONK
+    else if(!strcmp(aTopic, NS_VOLUME_STATE_CHANGED)) {
+        nsCOMPtr<nsIVolume> vol = do_QueryInterface(aSubject);
+        if (!vol) {
+            return NS_ERROR_NOT_AVAILABLE;
+        }
+
+        nsString volName;
+        nsString mountPoint;
+        PRInt32  state;
+
+        vol->GetName(volName);
+        vol->GetMountPoint(mountPoint);
+        vol->GetState(&state);
+
+        unused << SendFileSystemUpdate(volName, mountPoint, state);
+    }
+#endif
 #ifdef ACCESSIBILITY
     // Make sure accessibility is running in content process when accessibility
     // gets initiated in chrome process.
     else if (aData && (*aData == '1') &&
              !strcmp(aTopic, "a11y-init-or-shutdown")) {
         unused << SendActivateA11y();
     }
 #endif
@@ -928,23 +957,26 @@ ContentParent::DeallocPBrowser(PBrowserP
     TabParent* parent = static_cast<TabParent*>(frame);
     NS_RELEASE(parent);
     return true;
 }
 
 PDeviceStorageRequestParent*
 ContentParent::AllocPDeviceStorageRequest(const DeviceStorageParams& aParams)
 {
-  return new DeviceStorageRequestParent(aParams);
+  DeviceStorageRequestParent* result = new DeviceStorageRequestParent(aParams);
+  NS_ADDREF(result);
+  return result;
 }
 
 bool
 ContentParent::DeallocPDeviceStorageRequest(PDeviceStorageRequestParent* doomed)
 {
-  delete doomed;
+  DeviceStorageRequestParent *parent = static_cast<DeviceStorageRequestParent*>(doomed);
+  NS_RELEASE(parent);
   return true;
 }
 
 PBlobParent*
 ContentParent::AllocPBlob(const BlobConstructorParams& aParams)
 {
   return BlobParent::Create(aParams);
 }
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -66,24 +66,31 @@ struct FontListEntry {
     nsString  faceName;
     nsCString filepath;
     PRUint16  weight;
     PRInt16   stretch;
     PRUint8   italic;
     PRUint8   index;
 };
 
+struct DeviceStorageStatParams
+{
+  nsString fullpath;
+};
+
 struct DeviceStorageAddParams
 {
+  PBlob blob;
+  nsString name;
   nsString fullpath;
-  PRUint8[] bits;
 };
 
 struct DeviceStorageGetParams
 {
+  nsString name;
   nsString fullpath;
 };
 
 struct DeviceStorageDeleteParams
 {
   nsString fullpath;
 };
 
@@ -94,16 +101,17 @@ struct DeviceStorageEnumerationParams
 };
 
 union DeviceStorageParams
 {
   DeviceStorageAddParams;
   DeviceStorageGetParams;
   DeviceStorageDeleteParams;
   DeviceStorageEnumerationParams;
+  DeviceStorageStatParams;
 };
 
 struct SlicedBlobConstructorParams
 {
   PBlob source;
   uint64_t begin;
   uint64_t end;
   nsString contentType;
@@ -201,16 +209,18 @@ child:
 
     SetID(PRUint64 id);
 
     // Notify child that last-pb-context-exited notification was observed
     LastPrivateDocShellDestroyed();
 
     FilePathUpdate(nsString filepath, nsCString reasons);
 
+    FileSystemUpdate(nsString fsName, nsString mountPoint, PRInt32 fsState);
+
 parent:
     PAudio(PRInt32 aNumChannels, PRInt32 aRate, PRInt32 aFormat);
 
     PDeviceStorageRequest(DeviceStorageParams params);
 
     sync PCrashReporter(NativeThreadId tid, PRUint32 processType);
 
     PHal();
--- a/dom/locales/en-US/chrome/layout/css.properties
+++ b/dom/locales/en-US/chrome/layout/css.properties
@@ -109,16 +109,17 @@ PEMQExpectedExpressionStart=Expected '('
 PEMQExpressionEOF=contents of media query expression
 PEMQExpectedFeatureName=Expected media feature name but found '%1$S'.
 PEMQExpectedFeatureNameEnd=Expected ':' or ')' after media feature name but found '%1$S'.
 PEMQNoMinMaxWithoutValue=Media features with min- or max- must have a value.
 PEMQExpectedFeatureValue=Found invalid value for media feature.
 PEBadFontBlockStart=Expected '{' to begin @font-face rule but found '%1$S'.
 PEBadFontBlockEnd=Expected '}' to end @font-face rule but found '%1$S'.
 PEAnonBoxNotAlone=Did not expect anonymous box.
+PEBadDirValue=Expected 'ltr' or 'rtl' in direction selector but found '%1$S'.
 PESupportsConditionStartEOF='not' or '('
 PESupportsConditionInParensStartEOF='not', '(' or identifier
 PESupportsConditionNotEOF='not'
 PESupportsConditionExpectedOpenParen=Expected '(' while parsing supports condition but found '%1$S'.
 PESupportsConditionExpectedCloseParen=Expected ')' while parsing supports condition but found '%1$S'.
 PESupportsConditionExpectedStart=Expected 'not' or '(' while parsing supports condition but found '%1$S'.
 PESupportsConditionExpectedNot=Expected 'not' while parsing supports condition but found '%1$S'.
 PESupportsGroupRuleStart=Expected '{' to begin @supports rule but found '%1$S'.
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -178,17 +178,23 @@ public:
     kAudioTrack = 2
   };
 
   NS_IMETHOD
   Run()
   {
     mManager = MediaManager::Get();
 
-    if (mPicture) {
+    // It is an error is audio or video are requested along with picture.
+    if (mPicture && (mAudio || mVideo)) {
+      NS_DispatchToMainThread(new ErrorCallbackRunnable(
+        mError, NS_LITERAL_STRING("NOT_SUPPORTED_ERR"), mWindowID
+      ));
+      return NS_OK;
+    } else {
       SendPicture();
       return NS_OK;
     }
 
     // XXX: Implement merging two streams (See bug 758391).
     if (mAudio && mVideo) {
       NS_DispatchToMainThread(new ErrorCallbackRunnable(
         mError, NS_LITERAL_STRING("NOT_IMPLEMENTED"), mWindowID
new file mode 100644
--- /dev/null
+++ b/dom/permission/Makefile.in
@@ -0,0 +1,26 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH = @DEPTH@
+topsrcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+MODULE         = dom
+LIBRARY_NAME   = jsdompermission_s
+
+XPIDL_MODULE   = dom_permission
+GRE_MODULE     = 1
+
+EXTRA_JS_MODULES =   \
+  PermissionPromptHelper.jsm \
+  $(NULL)
+
+include $(topsrcdir)/config/config.mk
+include $(topsrcdir)/ipc/chromium/chromium-config.mk
+include $(topsrcdir)/config/rules.mk
+
+DEFINES += -D_IMPL_NS_LAYOUT
new file mode 100644
--- /dev/null
+++ b/dom/permission/PermissionPromptHelper.jsm
@@ -0,0 +1,97 @@
+/* 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/. */
+
+/* PermissionPromptHelper checks the permissionDB for a given permission
+ * name and performs prompting if needed.
+ * Usage: send PermissionPromptHelper:AskPermission via the FrameMessageManager with:
+ * |origin|, |appID|, |browserFlag| -> used for getting the principal and
+ * |type| and |access| to call testExactPermissionFromPrincipal.
+ * Note that |access| isn't currently used.
+ * Other arugments are:
+ * requestID: ID that gets returned with the result message.
+ *
+ * Once the permission is checked, it returns with the message
+ * "PermissionPromptHelper:AskPermission:OK"
+ * The result contains the |result| e.g.Ci.nsIPermissionManager.ALLOW_ACTION
+ * and a requestID that
+ */
+
+"use strict";
+
+let DEBUG = 0;
+if (DEBUG)
+  debug = function (s) { dump("-*- Permission Prompt Helper component: " + s + "\n"); }
+else
+  debug = function (s) {}
+
+const Cu = Components.utils; 
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+let EXPORTED_SYMBOLS = ["PermissionPromptHelper"];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "ppmm", function() {
+  return Cc["@mozilla.org/parentprocessmessagemanager;1"].getService(Ci.nsIFrameMessageManager);
+});
+
+var permissionManager = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager);
+var secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
+var appsService = Cc["@mozilla.org/AppsService;1"].getService(Ci.nsIAppsService);
+
+let PermissionPromptHelper = {
+  init: function() {
+    debug("Init");
+    ppmm.addMessageListener("PermissionPromptHelper:AskPermission", this);
+    Services.obs.addObserver(this, "profile-before-change", false);
+  },
+
+  askPermission: function(aMessage, aCallbacks) {
+    let msg = aMessage.json;
+    debug("askPerm: " + JSON.stringify(aMessage.json));
+    let uri = Services.io.newURI(msg.origin, null, null);
+    let principal = secMan.getAppCodebasePrincipal(uri, msg.appID, msg.browserFlag);
+    let perm = permissionManager.testExactPermissionFromPrincipal(principal, msg.type);
+
+    switch(perm) {
+      case Ci.nsIPermissionManager.ALLOW_ACTION:
+        aCallbacks.allow();
+        return;
+      case Ci.nsIPermissionManager.DENY_ACTION:
+        aCallbacks.cancel();
+        return;
+    }
+
+  // FIXXMEE PROMPT MAGIC! Bug 773114.
+  // We have to diplay the prompt here.
+  },
+
+  observe: function(aSubject, aTopic, aData) {
+    ppmm.removeMessageListener("PermissionPromptHelper:AskPermission", this);
+    Services.obs.removeObserver(this, "profile-before-change");
+    ppmm = null;
+  },
+
+  receiveMessage: function(aMessage) {
+    debug("PermissionPromptHelper::receiveMessage " + aMessage.name);
+    let mm = aMessage.target.QueryInterface(Ci.nsIFrameMessageManager);
+    let msg = aMessage.data;
+
+    let result;
+    if (aMessage.name == "PermissionPromptHelper:AskPermission") {
+      this.askPermission(aMessage, {
+        cancel: function() {
+          mm.sendAsyncMessage("PermissionPromptHelper:AskPermission:OK", {result: Ci.nsIPermissionManager.DENY_ACTION, requestID: msg.requestID});
+        },
+        allow: function() {
+          mm.sendAsyncMessage("PermissionPromptHelper:AskPermission:OK", {result: Ci.nsIPermissionManager.ALLOW_ACTION, requestID: msg.requestID});
+        }
+      });
+    }
+  }
+}
+
+PermissionPromptHelper.init();
--- a/dom/plugins/ipc/PluginInstanceParent.cpp
+++ b/dom/plugins/ipc/PluginInstanceParent.cpp
@@ -445,17 +445,17 @@ PluginInstanceParent::AnswerNPN_SetValue
         ImageContainer *container = GetImageContainer();
         if (!container) {
             *result = NPERR_GENERIC_ERROR;
             return true;
         }
 
         mDrawingModel = drawingModel;
         *result = mNPNIface->setvalue(mNPP, NPPVpluginDrawingModel,
-                                      (void*)drawingModel);
+                                      (void*)(intptr_t)drawingModel);
 
         if (mRemoteImageDataShmem.IsWritable()) {
             container->SetRemoteImageData(nullptr, nullptr);
             container->SetCompositionNotifySink(nullptr);
             DeallocShmem(mRemoteImageDataShmem);
             mRemoteImageDataMutex = NULL;
         }
     } else {
--- a/dom/system/gonk/Makefile.in
+++ b/dom/system/gonk/Makefile.in
@@ -64,16 +64,17 @@ CPPSRCS += \
   Volume.cpp \
   VolumeCommand.cpp \
   VolumeManager.cpp \
   VolumeServiceIOThread.cpp \
   VolumeServiceTest.cpp \
   $(NULL)
 # for our local copy of AudioSystem.h
 LOCAL_INCLUDES += -I$(topsrcdir)/media/libsydneyaudio/src
+EXPORTS = nsVolume.h
 endif
 
 EXTRA_COMPONENTS = \
   NetworkManager.manifest \
   NetworkManager.js \
   RadioInterfaceLayer.manifest \
   RadioInterfaceLayer.js \
   RILContentHelper.js \
--- a/dom/system/gonk/VolumeManager.cpp
+++ b/dom/system/gonk/VolumeManager.cpp
@@ -20,21 +20,23 @@
 #include <fcntl.h>
 #include <sys/socket.h>
 
 namespace mozilla {
 namespace system {
 
 static RefPtr<VolumeManager> sVolumeManager;
 
+VolumeManager::STATE VolumeManager::mState = VolumeManager::UNINITIALIZED;
+VolumeManager::StateObserverList VolumeManager::mStateObserverList;
+
 /***************************************************************************/
 
 VolumeManager::VolumeManager()
-  : mState(VolumeManager::STARTING),
-    mSocket(-1),
+  : mSocket(-1),
     mCommandPending(false),
     mRcvIdx(0)
 {
   DBG("VolumeManager constructor called");
 }
 
 VolumeManager::~VolumeManager()
 {
@@ -57,59 +59,55 @@ VolumeManager::GetVolume(size_t aIndex)
   MOZ_ASSERT(aIndex < NumVolumes());
   return sVolumeManager->mVolumeArray[aIndex];
 }
 
 //static
 VolumeManager::STATE
 VolumeManager::State()
 {
-  if (!sVolumeManager) {
-    return VolumeManager::UNINITIALIZED;
-  }
-  return sVolumeManager->mState;
+  return mState;
 }
 
 //static
 const char *
-VolumeManager::StateStr()
+VolumeManager::StateStr(VolumeManager::STATE aState)
 {
-  switch (State()) {
+  switch (aState) {
     case UNINITIALIZED: return "Uninitialized";
     case STARTING:      return "Starting";
     case VOLUMES_READY: return "Volumes Ready";
   }
   return "???";
 }
 
 
 //static
 void
 VolumeManager::SetState(STATE aNewState)
 {
-  if (!sVolumeManager) {
-    return;
-  }
-  if (sVolumeManager->mState != aNewState) {
-    sVolumeManager->mState = aNewState;
-    sVolumeManager->mStateObserverList.Broadcast(StateChangedEvent());
+  if (mState != aNewState) {
+    LOG("changing state from '%s' to '%s'",
+        StateStr(mState), StateStr(aNewState));
+    mState = aNewState;
+    mStateObserverList.Broadcast(StateChangedEvent());
   }
 }
 
 //static
 void
 VolumeManager::RegisterStateObserver(StateObserver *aObserver)
 {
-  sVolumeManager->mStateObserverList.AddObserver(aObserver);
+  mStateObserverList.AddObserver(aObserver);
 }
 
 //static
 void VolumeManager::UnregisterStateObserver(StateObserver *aObserver)
 {
-  sVolumeManager->mStateObserverList.RemoveObserver(aObserver);
+  mStateObserverList.RemoveObserver(aObserver);
 }
 
 //static
 TemporaryRef<Volume>
 VolumeManager::FindVolumeByName(const nsCSubstring &aName)
 {
   if (!sVolumeManager) {
     return NULL;
@@ -391,16 +389,17 @@ VolumeManager::Restart()
 void
 VolumeManager::Start()
 {
   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
 
   if (!sVolumeManager) {
     return;
   }
+  SetState(STARTING);
   if (!sVolumeManager->OpenSocket()) {
     // Socket open failed, try again in a second.
     MessageLoopForIO::current()->
       PostDelayedTask(FROM_HERE,
                       NewRunnableFunction(VolumeManager::Start),
                       1000);
   }
 }
--- a/dom/system/gonk/VolumeManager.h
+++ b/dom/system/gonk/VolumeManager.h
@@ -98,17 +98,18 @@ public:
   enum STATE
   {
     UNINITIALIZED,
     STARTING,
     VOLUMES_READY
   };
 
   static STATE State();
-  static const char *StateStr();
+  static const char *StateStr(STATE aState);
+  static const char *StateStr() { return StateStr(State()); }
 
   class StateChangedEvent
   {
   public:
     StateChangedEvent() {}
   };
 
   typedef mozilla::Observer<StateChangedEvent>      StateObserver;
@@ -141,25 +142,26 @@ private:
   static void SetState(STATE aNewState);
 
   void Restart();
   void WriteCommandData();
   void HandleBroadcast(int aResponseCode, nsCString &aResponseLine);
 
   typedef std::queue<RefPtr<VolumeCommand> > CommandQueue;
 
+  static STATE              mState;
+  static StateObserverList  mStateObserverList;
+
   static const int    kRcvBufSize = 1024;
-  STATE               mState;
   ScopedClose         mSocket;
   VolumeArray         mVolumeArray;
   CommandQueue        mCommands;
   bool                mCommandPending;
   char                mRcvBuf[kRcvBufSize];
   size_t              mRcvIdx;
-  StateObserverList   mStateObserverList;
   MessageLoopForIO                       *mIOLoop;
   MessageLoopForIO::FileDescriptorWatcher mReadWatcher;
   MessageLoopForIO::FileDescriptorWatcher mWriteWatcher;
   RefPtr<VolumeResponseCallback>          mBroadcastCallback;
 };
 
 /***************************************************************************
 *
--- a/dom/system/gonk/VolumeServiceIOThread.cpp
+++ b/dom/system/gonk/VolumeServiceIOThread.cpp
@@ -27,16 +27,19 @@ VolumeServiceIOThread::~VolumeServiceIOT
   Volume::UnregisterObserver(this);
   VolumeManager::UnregisterStateObserver(this);
 }
 
 void
 VolumeServiceIOThread::Notify(Volume * const &aVolume)
 {
   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+  if (VolumeManager::State() != VolumeManager::VOLUMES_READY) {
+    return;
+  }
   nsVolumeService::UpdateVolumeIOThread(aVolume);
 }
 
 void
 VolumeServiceIOThread::Notify(const VolumeManager::StateChangedEvent &aEvent)
 {
   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
   UpdateAllVolumes();
--- a/dom/system/gonk/nsVolume.cpp
+++ b/dom/system/gonk/nsVolume.cpp
@@ -1,16 +1,17 @@
 /* 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 "nsVolume.h"
 #include "nsISupportsUtils.h"
 #include "nsIVolume.h"
 #include "nsVolumeStat.h"
+#include "Volume.h"
 
 namespace mozilla {
 namespace system {
 
 const char *
 NS_VolumeStateStr(PRInt32 aState)
 {
   switch (aState) {
@@ -25,16 +26,23 @@ NS_VolumeStateStr(PRInt32 aState)
     case nsIVolume::STATE_SHARED:     return "Shared";
     case nsIVolume::STATE_SHAREDMNT:  return "Shared-Mounted";
   }
   return "???";
 }
 
 NS_IMPL_THREADSAFE_ISUPPORTS1(nsVolume, nsIVolume)
 
+nsVolume::nsVolume(const Volume *aVolume)
+  : mName(NS_ConvertUTF8toUTF16(aVolume->Name())),
+    mMountPoint(NS_ConvertUTF8toUTF16(aVolume->MountPoint())),
+    mState(aVolume->State())
+{
+}
+
 NS_IMETHODIMP nsVolume::GetName(nsAString &aName)
 {
   aName = mName;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsVolume::GetMountPoint(nsAString &aMountPoint)
 {
--- a/dom/system/gonk/nsVolume.h
+++ b/dom/system/gonk/nsVolume.h
@@ -1,31 +1,33 @@
 /* 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 mozilla_system_nsvolume_h__
 #define mozilla_system_nsvolume_h__
 
-#include "Volume.h"
 #include "nsIVolume.h"
+#include "nsString.h"
 
 namespace mozilla {
 namespace system {
 
+class Volume;
+
 class nsVolume : public nsIVolume
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIVOLUME
 
-  nsVolume(const Volume *aVolume)
-    : mName(NS_ConvertUTF8toUTF16(aVolume->Name())),
-      mMountPoint(NS_ConvertUTF8toUTF16(aVolume->MountPoint())),
-      mState(aVolume->State())
+  nsVolume(const Volume *aVolume);
+
+  nsVolume(const nsAString &aName, const nsAString &aMountPoint, const PRInt32 &aState)
+    : mName(aName), mMountPoint(aMountPoint), mState(aState)
   {
   }
 
   nsVolume(const nsAString &aName)
     : mName(aName),
       mState(STATE_INIT)
   {
   }
@@ -45,26 +47,26 @@ public:
   }
 
   const nsString &Name() const        { return mName; }
   const char *NameStr() const         { return NS_LossyConvertUTF16toASCII(mName).get(); }
 
   const nsString &MountPoint() const  { return mMountPoint; }
   const char *MountPointStr() const   { return NS_LossyConvertUTF16toASCII(mMountPoint).get(); }
 
-  long State() const                  { return mState; }
-  const char *StateStr() const        { return Volume::StateStr((Volume::STATE)mState); }
+  PRInt32 State() const               { return mState; }
+  const char *StateStr() const        { return NS_VolumeStateStr(mState); }
 
   typedef nsTArray<nsRefPtr<nsVolume> > Array;
 
 private:
   ~nsVolume() {}
 
 protected:
   nsString mName;
   nsString mMountPoint;
-  long mState;
+  PRInt32  mState;
 };
 
 } // system
 } // mozilla
 
 #endif  // mozilla_system_nsvolume_h__
--- a/dom/system/gonk/nsVolumeService.cpp
+++ b/dom/system/gonk/nsVolumeService.cpp
@@ -168,17 +168,17 @@ public:
     : mVolume(new nsVolume(aVolume))
   {
     MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
   }
 
   NS_IMETHOD Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
-    DBG("UpdateVolumeRunnable::Run '%s' state %s",
+    LOG("UpdateVolumeRunnable::Run '%s' state %s",
         mVolume->NameStr(), mVolume->StateStr());
 
     nsCOMPtr<nsIVolumeService> ivs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
     if (!ivs) {
       return NS_OK;
     }
     nsCOMPtr<nsVolumeService> vs(do_QueryInterface(ivs));
     if (!vs) {
@@ -191,14 +191,16 @@ public:
 
 private:
   nsRefPtr<nsVolume>  mVolume;
 };
 
 //static
 void nsVolumeService::UpdateVolumeIOThread(const Volume *aVolume)
 {
+  LOG("UpdateVolumeIOThread: Volume '%s' state %s mount '%s'",
+      aVolume->NameStr(), aVolume->StateStr(), aVolume->MountPoint().get());
   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
   NS_DispatchToMainThread(new UpdateVolumeRunnable(aVolume));
 }
 
 } // system
 } // mozilla
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -4068,20 +4068,67 @@ let GsmPDUHelper = {
     }
 
     if (dataBits != 0) {
       this.writeHexOctet(data & 0xFF);
     }
   },
 
   /**
+   * Read GSM 8-bit unpacked octets,
+   * which are SMS default 7-bit alphabets with bit 8 set to 0.
+   *
+   * @param numOctets
+   *        Number of octets to be read.
+   */
+  read8BitUnpackedToString: function read8BitUnpackedToString(numOctets) {
+    let ret = "";
+    let escapeFound = false;
+    let i;
+    const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+    const langShiftTable = PDU_NL_SINGLE_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+
+    for(i = 0; i < numOctets; i++) {
+      let octet = this.readHexOctet();
+      if (octet == 0xff) {
+        i++;
+        break;
+      }
+
+      if (escapeFound) {
+        escapeFound = false;
+        if (octet == PDU_NL_EXTENDED_ESCAPE) {
+          // According to 3GPP TS 23.038, section 6.2.1.1, NOTE 1, "On
+          // receipt of this code, a receiving entity shall display a space
+          // until another extensiion table is defined."
+          ret += " ";
+        } else if (octet == PDU_NL_RESERVED_CONTROL) {
+          // According to 3GPP TS 23.038 B.2, "This code represents a control
+          // character and therefore must not be used for language specific
+          // characters."
+          ret += " ";
+        } else {
+          ret += langShiftTable[octet];
+        }
+      } else if (octet == PDU_NL_EXTENDED_ESCAPE) {
+        escapeFound = true;
+      } else {
+        ret += langTable[octet];
+      }
+    }
+
+    Buf.seekIncoming((numOctets - i) * PDU_HEX_OCTET_SIZE);
+    return ret;
+  },
+
+  /**
    * Read user data and decode as a UCS2 string.
    *
    * @param numOctets
-   *        num of octets to read as UCS2 string.
+   *        Number of octets to be read as UCS2 string.
    *
    * @return a string.
    */
   readUCS2String: function readUCS2String(numOctets) {
     let str = "";
     let length = numOctets / 2;
     for (let i = 0; i < length; ++i) {
       let code = (this.readHexOctet() << 8) | this.readHexOctet();
@@ -4103,16 +4150,113 @@ let GsmPDUHelper = {
     for (let i = 0; i < message.length; ++i) {
       let code = message.charCodeAt(i);
       this.writeHexOctet((code >> 8) & 0xFF);
       this.writeHexOctet(code & 0xFF);
     }
   },
 
   /**
+   * Read UCS2 String on UICC.
+   *
+   * @see TS 101.221, Annex A.
+   * @param scheme
+   *        Coding scheme for UCS2 on UICC. One of 0x80, 0x81 or 0x82.
+   * @param numOctets
+   *        Number of octets to be read as UCS2 string.
+   */
+  readICCUCS2String: function readICCUCS2String(scheme, numOctets) {
+    let str = "";
+    switch (scheme) {
+      /**
+       * +------+---------+---------+---------+---------+------+------+
+       * | 0x80 | Ch1_msb | Ch1_lsb | Ch2_msb | Ch2_lsb | 0xff | 0xff |
+       * +------+---------+---------+---------+---------+------+------+
+       */
+      case 0x80:
+        let isOdd = numOctets % 2;
+        let i;
+        for (i = 0; i < numOctets - isOdd; i += 2) {
+          let code = (this.readHexOctet() << 8) | this.readHexOctet();
+          if (code == 0xffff) {
+            i += 2;
+            break;
+          }
+          str += String.fromCharCode(code);
+        }
+
+        // Skip trailing 0xff
+        Buf.seekIncoming((numOctets - i) * PDU_HEX_OCTET_SIZE);
+        break;
+      case 0x81: // Fall through
+      case 0x82:
+        /**
+         * +------+-----+--------+-----+-----+-----+--------+------+
+         * | 0x81 | len | offset | Ch1 | Ch2 | ... | Ch_len | 0xff |
+         * +------+-----+--------+-----+-----+-----+--------+------+
+         *
+         * len : The length of characters.
+         * offset : 0hhh hhhh h000 0000
+         * Ch_n: bit 8 = 0
+         *       GSM default alphabets
+         *       bit 8 = 1
+         *       UCS2 character whose char code is (Ch_n & 0x7f) + offset
+         *
+         * +------+-----+------------+------------+-----+-----+-----+--------+
+         * | 0x82 | len | offset_msb | offset_lsb | Ch1 | Ch2 | ... | Ch_len |
+         * +------+-----+------------+------------+-----+-----+-----+--------+
+         *
+         * len : The length of characters.
+         * offset_msb, offset_lsn: offset
+         * Ch_n: bit 8 = 0
+         *       GSM default alphabets
+         *       bit 8 = 1
+         *       UCS2 character whose char code is (Ch_n & 0x7f) + offset
+         */
+        let len = this.readHexOctet();
+        let offset, headerLen;
+        if (scheme == 0x81) {
+          offset = this.readHexOctet() << 7;
+          headerLen = 2;
+        } else {
+          offset = (this.readHexOctet() << 8) | this.readHexOctet();
+          headerLen = 3;
+        }
+
+        for (let i = 0; i < len; i++) {
+          let ch = this.readHexOctet();
+          if (ch & 0x80) {
+            // UCS2
+            str += String.fromCharCode((ch & 0x7f) + offset);
+          } else {
+            // GSM 8bit
+            let count = 0, gotUCS2 = 0;
+            while ((i + count + 1 < len)) {
+              count++;
+              if (this.readHexOctet() & 0x80) {
+                gotUCS2 = 1;
+                break;
+              };
+            }
+            // Unread.
+            // +1 for the GSM alphabet indexed at i,
+            Buf.seekIncoming(-1 * (count + 1) * PDU_HEX_OCTET_SIZE);
+            str += this.read8BitUnpackedToString(count + 1 - gotUCS2);
+            i += count - gotUCS2;
+          }
+        }
+
+        // Skipping trailing 0xff
+        Buf.seekIncoming((numOctets - len - headerLen) * PDU_HEX_OCTET_SIZE);
+        break;
+    }
+    return str;
+  },
+
+  /**
    * Read 1 + UDHL octets and construct user data header.
    *
    * @param msg
    *        message object for output.
    *
    * @see 3GPP TS 23.040 9.2.3.24
    */
   readUserDataHeader: function readUserDataHeader(msg) {
@@ -4350,50 +4494,38 @@ let GsmPDUHelper = {
     return addr;
   },
   
   /**
    * Read Alpha Identifier.
    *
    * @see TS 131.102
    *
-   * @param len
-   *        The length of Alpha Identifier in bytes.
+   * @param numOctets
+   *        Number of octets to be read.
    *
-   * it uses either
+   * It uses either
    *  1. SMS default 7-bit alphabet with bit 8 set to 0.
    *  2. UCS2 string.
    *
    * Unused bytes should be set to 0xff.
    */
-  readAlphaIdentifier: function readAlphaIdentifier(len) {
-    let temp, isUCS2 = false;
-    let alphaId = "";
-
-    // Read the 1st byte to determine the encoding.
-    if ((temp = GsmPDUHelper.readHexOctet()) == 0x80) {
-      isUCS2 = true;
-    } else if (temp != 0xff) {
-      alphaId += String.fromCharCode(temp);
+  readAlphaIdentifier: function readAlphaIdentifier(numOctets) {
+    let temp;
+
+    // Read the 1st octet to determine the encoding.
+    if ((temp = GsmPDUHelper.readHexOctet()) == 0x80 ||
+         temp == 0x81 ||
+         temp == 0x82) {
+      numOctets--;
+      return this.readICCUCS2String(temp, numOctets);
+    } else {
+      Buf.seekIncoming(-1 * PDU_HEX_OCTET_SIZE);
+      return this.read8BitUnpackedToString(numOctets);
     }
-    len--;
-
-    while (len) {
-      if ((temp = GsmPDUHelper.readHexOctet()) != 0xff) {
-        if (isUCS2) {
-          let temp2 = GsmPDUHelper.readHexOctet();
-          len--;
-          alphaId += String.fromCharCode((temp << 8) | temp2);
-        } else {
-          alphaId += String.fromCharCode(temp);
-        }
-      }
-      len--;
-    }
-    return alphaId;
   },
 
   /**
    * Read Dialling number.
    *
    * @see TS 131.102
    *
    * @param len
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_icc.js
@@ -0,0 +1,97 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+subscriptLoader.loadSubScript("resource://gre/modules/ril_consts.js", this);
+
+function run_test() {
+  run_next_test();
+}
+
+/**
+ * Helper function.
+ */
+function newUint8Worker() {
+  let worker = newWorker();
+  let index = 0; // index for read
+  let buf = [];
+
+  worker.Buf.writeUint8 = function (value) {
+    buf.push(value);
+  };
+
+  worker.Buf.readUint8 = function () {
+    return buf[index++];
+  };
+
+  worker.Buf.seekIncoming = function (offset) {
+    index += offset;
+  };
+
+  worker.debug = do_print;
+
+  return worker;
+}
+/**
+ * Verify GsmPDUHelper#readICCUCS2String()
+ */
+add_test(function test_read_icc_ucs2_string() {
+  let worker = newUint8Worker();
+  let helper = worker.GsmPDUHelper;
+
+  // 0x80
+  let text = "TEST";
+  helper.writeUCS2String(text);
+  // Also write two unused octets.
+  let ffLen = 2;
+  for (let i = 0; i < ffLen; i++) {
+    helper.writeHexOctet(0xff);
+  }
+  do_check_eq(helper.readICCUCS2String(0x80, (2 * text.length) + ffLen), text);
+
+  // 0x81
+  let array = [0x08, 0xd2, 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61, 0xca,
+               0xff, 0xff];
+  let len = array.length;
+  for (let i = 0; i < len; i++) {
+    helper.writeHexOctet(array[i]);
+  }
+  do_check_eq(helper.readICCUCS2String(0x81, len), "Mozilla\u694a");
+
+  // 0x82
+  let array2 = [0x08, 0x69, 0x00, 0x4d, 0x6f, 0x7a, 0x69, 0x6c, 0x6c, 0x61,
+                0xca, 0xff, 0xff];
+  let len2 = array2.length;
+  for (let i = 0; i < len2; i++) {
+    helper.writeHexOctet(array2[i]);
+  }
+  do_check_eq(helper.readICCUCS2String(0x82, len2), "Mozilla\u694a");
+
+  run_next_test();
+});
+
+/**
+ * Verify GsmPDUHelper#read8BitUnpackedToString
+ */
+add_test(function test_read_8bit_unpacked_to_string() {
+  let worker = newUint8Worker();
+  let helper = worker.GsmPDUHelper;
+  let buf = worker.Buf;
+  const langTable = PDU_NL_LOCKING_SHIFT_TABLES[PDU_NL_IDENTIFIER_DEFAULT];
+
+  // Only write characters before PDU_NL_EXTENDED_ESCAPE to simplify test.
+  for (let i = 0; i < PDU_NL_EXTENDED_ESCAPE; i++) {
+    helper.writeHexOctet(i);
+  }
+
+  // Also write two unused fields.
+  let ffLen = 2;
+  for (let i = 0; i < ffLen; i++) {
+    helper.writeHexOctet(0xff);
+  }
+
+  do_check_eq(helper.read8BitUnpackedToString(PDU_NL_EXTENDED_ESCAPE + ffLen),
+              langTable.substring(0, PDU_NL_EXTENDED_ESCAPE));
+  run_next_test();
+});
+
+
--- a/dom/system/gonk/tests/xpcshell.ini
+++ b/dom/system/gonk/tests/xpcshell.ini
@@ -1,6 +1,7 @@
 [DEFAULT]
 head = header_helpers.js
 tail =
 
 [test_ril_worker_buf.js]
+[test_ril_worker_icc.js]
 [test_ril_worker_sms.js]
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -65,16 +65,17 @@ var interfaceNamesInGlobalScope =
     "Pkcs11",
     "NotifyAudioAvailableEvent",
     "Array",
     "SVGZoomAndPan",
     "XULPopupElement",
     "MediaError",
     "DeviceStorageCursor",
     "DeviceStorageChangeEvent",
+    "DeviceStorageStat",
     "PageTransitionEvent",
     "DataContainerEvent",
     "MozCSSKeyframesRule",
     "SVGAnimatedInteger",
     "TouchEvent",
     "OpenWindowEventDetail",
     "IDBIndex",
     "EventListener",
--- a/editor/libeditor/base/nsEditRules.h
+++ b/editor/libeditor/base/nsEditRules.h
@@ -19,39 +19,39 @@ class nsISelection;
 /***************************************************************************
  * base for an object to encapsulate any additional info needed to be passed
  * to rules system by the editor
  */
 class nsRulesInfo
 {
   public:
   
-  nsRulesInfo(nsEditor::OperationID aAction) : action(aAction) {}
+  nsRulesInfo(OperationID aAction) : action(aAction) {}
   virtual ~nsRulesInfo() {}
   
-  nsEditor::OperationID action;
+  OperationID action;
 };
 
 /***************************************************************************
  * Interface of editing rules.
  *  
  */
 class nsIEditRules : public nsISupports
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IEDITRULES_IID)
   
 //Interfaces for addref and release and queryinterface
 //NOTE: Use   NS_DECL_ISUPPORTS_INHERITED in any class inherited from nsIEditRules
 
   NS_IMETHOD Init(nsPlaintextEditor *aEditor)=0;
   NS_IMETHOD DetachEditor()=0;
-  NS_IMETHOD BeforeEdit(nsEditor::OperationID action,
+  NS_IMETHOD BeforeEdit(OperationID action,
                         nsIEditor::EDirection aDirection) = 0;
-  NS_IMETHOD AfterEdit(nsEditor::OperationID action,
+  NS_IMETHOD AfterEdit(OperationID action,
                        nsIEditor::EDirection aDirection) = 0;
   NS_IMETHOD WillDoAction(mozilla::Selection* aSelection, nsRulesInfo* aInfo,
                           bool* aCancel, bool* aHandled) = 0;
   NS_IMETHOD DidDoAction(nsISelection *aSelection, nsRulesInfo *aInfo, nsresult aResult)=0;
   NS_IMETHOD DocumentIsEmpty(bool *aDocumentIsEmpty)=0;
   NS_IMETHOD DocumentModified()=0;
 };
 
--- a/editor/libeditor/base/nsEditor.cpp
+++ b/editor/libeditor/base/nsEditor.cpp
@@ -138,17 +138,17 @@ extern nsIParserService *sParserService;
 nsEditor::nsEditor()
 :  mPlaceHolderName(nullptr)
 ,  mSelState(nullptr)
 ,  mPhonetic(nullptr)
 ,  mModCount(0)
 ,  mFlags(0)
 ,  mUpdateCount(0)
 ,  mPlaceHolderBatch(0)
-,  mAction(kOpNone)
+,  mAction(OperationID::none)
 ,  mHandlingActionCount(0)
 ,  mIMETextOffset(0)
 ,  mIMEBufferLength(0)
 ,  mDirection(eNone)
 ,  mDocDirtyState(-1)
 ,  mSpellcheckCheckboxState(eTriUnset)
 ,  mInIMEMode(false)
 ,  mIsIMEComposing(false)
@@ -780,17 +780,17 @@ nsEditor::Undo(PRUint32 aCount)
 #endif
 
   ForceCompositionEnd();
 
   bool hasTxnMgr, hasTransaction = false;
   CanUndo(&hasTxnMgr, &hasTransaction);
   NS_ENSURE_TRUE(hasTransaction, NS_OK);
 
-  nsAutoRules beginRulesSniffing(this, kOpUndo, nsIEditor::eNone);
+  nsAutoRules beginRulesSniffing(this, OperationID::undo, nsIEditor::eNone);
 
   if (!mTxnMgr) {
     return NS_OK;
   }
 
   for (PRUint32 i = 0; i < aCount; ++i) {
     nsresult rv = mTxnMgr->UndoTransaction();
     NS_ENSURE_SUCCESS(rv, rv);
@@ -823,17 +823,17 @@ nsEditor::Redo(PRUint32 aCount)
 #ifdef NS_DEBUG_EDITOR
   if (gNoisy) { printf("Editor::Redo ----------\n"); }
 #endif
 
   bool hasTxnMgr, hasTransaction = false;
   CanRedo(&hasTxnMgr, &hasTransaction);
   NS_ENSURE_TRUE(hasTransaction, NS_OK);
 
-  nsAutoRules beginRulesSniffing(this, kOpRedo, nsIEditor::eNone);
+  nsAutoRules beginRulesSniffing(this, OperationID::redo, nsIEditor::eNone);
 
   if (!mTxnMgr) {
     return NS_OK;
   }
 
   for (PRUint32 i = 0; i < aCount; ++i) {
     nsresult rv = mTxnMgr->RedoTransaction();
     NS_ENSURE_SUCCESS(rv, rv);
@@ -1342,17 +1342,17 @@ NS_IMETHODIMP nsEditor::SetSpellcheckUse
 
 NS_IMETHODIMP nsEditor::CreateNode(const nsAString& aTag,
                                    nsIDOMNode *    aParent,
                                    PRInt32         aPosition,
                                    nsIDOMNode **   aNewNode)
 {
   PRInt32 i;
 
-  nsAutoRules beginRulesSniffing(this, kOpCreateNode, nsIEditor::eNext);
+  nsAutoRules beginRulesSniffing(this, OperationID::createNode, nsIEditor::eNext);
   
   for (i = 0; i < mActionListeners.Count(); i++)
     mActionListeners[i]->WillCreateNode(aTag, aParent, aPosition);
 
   nsRefPtr<CreateElementTxn> txn;
   nsresult result = CreateTxnForCreateElement(aTag, aParent, aPosition,
                                               getter_AddRefs(txn));
   if (NS_SUCCEEDED(result)) 
@@ -1374,17 +1374,17 @@ NS_IMETHODIMP nsEditor::CreateNode(const
 }
 
 
 NS_IMETHODIMP nsEditor::InsertNode(nsIDOMNode * aNode,
                                    nsIDOMNode * aParent,
                                    PRInt32      aPosition)
 {
   PRInt32 i;
-  nsAutoRules beginRulesSniffing(this, kOpInsertNode, nsIEditor::eNext);
+  nsAutoRules beginRulesSniffing(this, OperationID::insertNode, nsIEditor::eNext);
 
   for (i = 0; i < mActionListeners.Count(); i++)
     mActionListeners[i]->WillInsertNode(aNode, aParent, aPosition);
 
   nsRefPtr<InsertElementTxn> txn;
   nsresult result = CreateTxnForInsertElement(aNode, aParent, aPosition,
                                               getter_AddRefs(txn));
   if (NS_SUCCEEDED(result))  {
@@ -1401,17 +1401,17 @@ NS_IMETHODIMP nsEditor::InsertNode(nsIDO
 
 
 NS_IMETHODIMP 
 nsEditor::SplitNode(nsIDOMNode * aNode,
                     PRInt32      aOffset,
                     nsIDOMNode **aNewLeftNode)
 {
   PRInt32 i;
-  nsAutoRules beginRulesSniffing(this, kOpSplitNode, nsIEditor::eNext);
+  nsAutoRules beginRulesSniffing(this, OperationID::splitNode, nsIEditor::eNext);
 
   for (i = 0; i < mActionListeners.Count(); i++)
     mActionListeners[i]->WillSplitNode(aNode, aOffset);
 
   nsRefPtr<SplitElementTxn> txn;
   nsresult result = CreateTxnForSplitNode(aNode, aOffset, getter_AddRefs(txn));
   if (NS_SUCCEEDED(result))  
   {
@@ -1450,17 +1450,17 @@ nsEditor::JoinNodes(nsINode* aNodeToKeep
 }
 
 NS_IMETHODIMP
 nsEditor::JoinNodes(nsIDOMNode * aLeftNode,
                     nsIDOMNode * aRightNode,
                     nsIDOMNode * aParent)
 {
   PRInt32 i;
-  nsAutoRules beginRulesSniffing(this, kOpJoinNode, nsIEditor::ePrevious);
+  nsAutoRules beginRulesSniffing(this, OperationID::joinNode, nsIEditor::ePrevious);
 
   // remember some values; later used for saved selection updating.
   // find the offset between the nodes to be joined.
   PRInt32 offset = GetChildOffset(aRightNode, aParent);
   // find the number of children of the lefthand node
   PRUint32 oldLeftNodeLen;
   nsresult result = GetLengthOfDOMNode(aLeftNode, oldLeftNodeLen);
   NS_ENSURE_SUCCESS(result, result);
@@ -1489,17 +1489,17 @@ nsEditor::DeleteNode(nsIDOMNode* aNode)
   nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
   NS_ENSURE_STATE(node);
   return DeleteNode(node);
 }
 
 nsresult
 nsEditor::DeleteNode(nsINode* aNode)
 {
-  nsAutoRules beginRulesSniffing(this, kOpCreateNode, nsIEditor::ePrevious);
+  nsAutoRules beginRulesSniffing(this, OperationID::createNode, nsIEditor::ePrevious);
 
   // save node location for selection updating code.
   for (PRInt32 i = 0; i < mActionListeners.Count(); i++) {
     mActionListeners[i]->WillDeleteNode(aNode->AsDOMNode());
   }
 
   nsRefPtr<DeleteNodeTxn> txn;
   nsresult res = CreateTxnForDeleteNode(aNode, getter_AddRefs(txn));
@@ -2190,17 +2190,17 @@ nsEditor::StartOperation(OperationID opI
 }
 
 
 /** All editor operations which alter the doc should be followed
  *  with a call to EndOperation */
 NS_IMETHODIMP
 nsEditor::EndOperation()
 {
-  mAction = kOpNone;
+  mAction = OperationID::none;
   mDirection = eNone;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsEditor::CloneAttribute(const nsAString & aAttribute,
                          nsIDOMNode *aDestNode, nsIDOMNode *aSourceNode)
 {
@@ -2700,17 +2700,17 @@ NS_IMETHODIMP nsEditor::CreateTxnForInse
 
 NS_IMETHODIMP nsEditor::DeleteText(nsIDOMCharacterData *aElement,
                               PRUint32             aOffset,
                               PRUint32             aLength)
 {
   nsRefPtr<DeleteTextTxn> txn;
   nsresult result = CreateTxnForDeleteText(aElement, aOffset, aLength,
                                            getter_AddRefs(txn));
-  nsAutoRules beginRulesSniffing(this, kOpDeleteText, nsIEditor::ePrevious);
+  nsAutoRules beginRulesSniffing(this, OperationID::deleteText, nsIEditor::ePrevious);
   if (NS_SUCCEEDED(result))  
   {
     // let listeners know what's up
     PRInt32 i;
     for (i = 0; i < mActionListeners.Count(); i++)
       mActionListeners[i]->WillDeleteText(aElement, aOffset, aLength);
     
     result = DoTransaction(txn); 
@@ -4306,17 +4306,17 @@ nsEditor::DeleteSelectionImpl(EDirection
   PRInt32 deleteCharOffset = 0, deleteCharLength = 0;
   res = CreateTxnForDeleteSelection(aAction, getter_AddRefs(txn),
                                     getter_AddRefs(deleteNode),
                                     &deleteCharOffset, &deleteCharLength);
   nsCOMPtr<nsIDOMCharacterData> deleteCharData(do_QueryInterface(deleteNode));
 
   if (NS_SUCCEEDED(res))  
   {
-    nsAutoRules beginRulesSniffing(this, kOpDeleteSelection, aAction);
+    nsAutoRules beginRulesSniffing(this, OperationID::deleteSelection, aAction);
     PRInt32 i;
     // Notify nsIEditActionListener::WillDelete[Selection|Text|Node]
     if (!deleteNode)
       for (i = 0; i < mActionListeners.Count(); i++)
         mActionListeners[i]->WillDeleteSelection(selection);
     else if (deleteCharData)
       for (i = 0; i < mActionListeners.Count(); i++)
         mActionListeners[i]->WillDeleteText(deleteCharData, deleteCharOffset, 1);
@@ -5038,24 +5038,23 @@ nsEditor::HandleInlineSpellCheck(Operati
                                    nsISelection *aSelection,
                                    nsIDOMNode *previousSelectedNode,
                                    PRInt32 previousSelectedOffset,
                                    nsIDOMNode *aStartNode,
                                    PRInt32 aStartOffset,
                                    nsIDOMNode *aEndNode,
                                    PRInt32 aEndOffset)
 {
-  return mInlineSpellChecker ? mInlineSpellChecker->SpellCheckAfterEditorChange(action,
-                                                       aSelection,
-                                                       previousSelectedNode,
-                                                       previousSelectedOffset,
-                                                       aStartNode,
-                                                       aStartOffset,
-                                                       aEndNode,
-                                                       aEndOffset) : NS_OK;
+  // Have to cast action here because this method is from an IDL
+  return mInlineSpellChecker ? mInlineSpellChecker->SpellCheckAfterEditorChange(
+                                 (PRInt32)action, aSelection,
+                                 previousSelectedNode, previousSelectedOffset,
+                                 aStartNode, aStartOffset, aEndNode,
+                                 aEndOffset)
+                             : NS_OK;
 }
 
 already_AddRefed<nsIContent>
 nsEditor::FindSelectionRoot(nsINode *aNode)
 {
   nsCOMPtr<nsIContent> rootContent = GetRoot();
   return rootContent.forget();
 }
--- a/editor/libeditor/base/nsEditor.h
+++ b/editor/libeditor/base/nsEditor.h
@@ -80,16 +80,63 @@ namespace mozilla {
 namespace widget {
 struct IMEState;
 } // namespace widget
 } // namespace mozilla
 
 #define kMOZEditorBogusNodeAttrAtom nsEditProperty::mozEditorBogusNode
 #define kMOZEditorBogusNodeValue NS_LITERAL_STRING("TRUE")
 
+// This is PRInt32 instead of PRInt16 because nsIInlineSpellChecker.idl's
+// spellCheckAfterEditorChange is defined to take it as a long.
+MOZ_BEGIN_ENUM_CLASS(OperationID, PRInt32)
+  ignore = -1,
+  none = 0,
+  undo,
+  redo,
+  insertNode,
+  createNode,
+  deleteNode,
+  splitNode,
+  joinNode,
+  deleteText = 1003,
+
+  // text commands
+  insertText         = 2000,
+  insertIMEText      = 2001,
+  deleteSelection    = 2002,
+  setTextProperty    = 2003,
+  removeTextProperty = 2004,
+  outputText         = 2005,
+
+  // html only action
+  insertBreak         = 3000,
+  makeList            = 3001,
+  indent              = 3002,
+  outdent             = 3003,
+  align               = 3004,
+  makeBasicBlock      = 3005,
+  removeList          = 3006,
+  makeDefListItem     = 3007,
+  insertElement       = 3008,
+  insertQuotation     = 3009,
+  htmlPaste           = 3012,
+  loadHTML            = 3013,
+  resetTextProperties = 3014,
+  setAbsolutePosition = 3015,
+  removeAbsolutePosition = 3016,
+  decreaseZIndex      = 3017,
+  increaseZIndex      = 3018
+MOZ_END_ENUM_CLASS(OperationID)
+
+inline bool operator!(const OperationID& aOp)
+{
+  return aOp == OperationID::none;
+}
+
 /** implementation of an editor object.  it will be the controller/focal point 
  *  for the main editor services. i.e. the GUIManager, publishing, transaction 
  *  manager, event interfaces. the idea for the event interfaces is to have them 
  *  delegate the actual commands to the editor independent of the XPFE implementation.
  */
 class nsEditor : public nsIEditor,
                  public nsIEditorIMESupport,
                  public nsSupportsWeakReference,
@@ -99,57 +146,16 @@ class nsEditor : public nsIEditor,
 public:
 
   enum IterDirection
   {
     kIterForward,
     kIterBackward
   };
 
-  enum OperationID
-  {
-    kOpIgnore = -1,
-    kOpNone = 0,
-    kOpUndo,
-    kOpRedo,
-    kOpInsertNode,
-    kOpCreateNode,
-    kOpDeleteNode,
-    kOpSplitNode,
-    kOpJoinNode,
-    kOpDeleteText = 1003,
-
-    // text commands
-    kOpInsertText         = 2000,
-    kOpInsertIMEText      = 2001,
-    kOpDeleteSelection    = 2002,
-    kOpSetTextProperty    = 2003,
-    kOpRemoveTextProperty = 2004,
-    kOpOutputText         = 2005,
-
-    // html only action
-    kOpInsertBreak         = 3000,
-    kOpMakeList            = 3001,
-    kOpIndent              = 3002,
-    kOpOutdent             = 3003,
-    kOpAlign               = 3004,
-    kOpMakeBasicBlock      = 3005,
-    kOpRemoveList          = 3006,
-    kOpMakeDefListItem     = 3007,
-    kOpInsertElement       = 3008,
-    kOpInsertQuotation     = 3009,
-    kOpHTMLPaste           = 3012,
-    kOpLoadHTML            = 3013,
-    kOpResetTextProperties = 3014,
-    kOpSetAbsolutePosition = 3015,
-    kOpRemoveAbsolutePosition = 3016,
-    kOpDecreaseZIndex      = 3017,
-    kOpIncreaseZIndex      = 3018
-  };
-
   /** The default constructor. This should suffice. the setting of the interfaces is done
    *  after the construction of the editor class.
    */
   nsEditor();
   /** The default destructor. This should suffice. Should this be pure virtual 
    *  for someone to derive from the nsEditor later? I don't believe so.
    */
   virtual ~nsEditor();
--- a/editor/libeditor/base/nsEditorUtils.h
+++ b/editor/libeditor/base/nsEditorUtils.h
@@ -73,17 +73,17 @@ class NS_STACK_CLASS nsAutoSelectionRese
 
 /***************************************************************************
  * stack based helper class for StartOperation()/EndOperation() sandwich
  */
 class NS_STACK_CLASS nsAutoRules
 {
   public:
   
-  nsAutoRules(nsEditor *ed, nsEditor::OperationID action,
+  nsAutoRules(nsEditor *ed, OperationID action,
               nsIEditor::EDirection aDirection) :
          mEd(ed), mDoNothing(false)
   { 
     if (mEd && !mEd->mAction) // mAction will already be set if this is nested call
     {
       mEd->StartOperation(action, aDirection);
     }
     else mDoNothing = true; // nested calls will end up here
--- a/editor/libeditor/html/nsHTMLAbsPosition.cpp
+++ b/editor/libeditor/html/nsHTMLAbsPosition.cpp
@@ -56,27 +56,27 @@ using namespace mozilla;
 
 #define  BLACK_BG_RGB_TRIGGER 0xd0
 
 NS_IMETHODIMP
 nsHTMLEditor::AbsolutePositionSelection(bool aEnabled)
 {
   nsAutoEditBatch beginBatching(this);
   nsAutoRules beginRulesSniffing(this,
-                                 aEnabled ? kOpSetAbsolutePosition :
-                                            kOpRemoveAbsolutePosition,
+                                 aEnabled ? OperationID::setAbsolutePosition :
+                                            OperationID::removeAbsolutePosition,
                                  nsIEditor::eNext);
   
   // the line below does not match the code; should it be removed?
   // Find out if the selection is collapsed:
   nsRefPtr<Selection> selection = GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
 
-  nsTextRulesInfo ruleInfo(aEnabled ? kOpSetAbsolutePosition :
-                                      kOpRemoveAbsolutePosition);
+  nsTextRulesInfo ruleInfo(aEnabled ? OperationID::setAbsolutePosition :
+                                      OperationID::removeAbsolutePosition);
   bool cancel, handled;
   nsresult res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
   if (NS_FAILED(res) || cancel)
     return res;
   
   return mRules->DidDoAction(selection, &ruleInfo, res);
 }
 
@@ -169,26 +169,26 @@ nsHTMLEditor::SetElementZIndex(nsIDOMEle
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsHTMLEditor::RelativeChangeZIndex(PRInt32 aChange)
 {
   nsAutoEditBatch beginBatching(this);
   nsAutoRules beginRulesSniffing(this,
-                                 (aChange < 0) ? kOpDecreaseZIndex :
-                                                 kOpIncreaseZIndex,
+                                 (aChange < 0) ? OperationID::decreaseZIndex :
+                                                 OperationID::increaseZIndex,
                                  nsIEditor::eNext);
   
   // brade: can we get rid of this comment?
   // Find out if the selection is collapsed:
   nsRefPtr<Selection> selection = GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
-  nsTextRulesInfo ruleInfo(aChange < 0 ? kOpDecreaseZIndex :
-                                         kOpIncreaseZIndex);
+  nsTextRulesInfo ruleInfo(aChange < 0 ? OperationID::decreaseZIndex :
+                                         OperationID::increaseZIndex);
   bool cancel, handled;
   nsresult res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
   if (cancel || NS_FAILED(res))
     return res;
   
   return mRules->DidDoAction(selection, &ruleInfo, res);
 }
 
--- a/editor/libeditor/html/nsHTMLDataTransfer.cpp
+++ b/editor/libeditor/html/nsHTMLDataTransfer.cpp
@@ -150,23 +150,23 @@ static nsCOMPtr<nsIDOMNode> GetTablePare
 
 NS_IMETHODIMP nsHTMLEditor::LoadHTML(const nsAString & aInputString)
 {
   NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED);
 
   // force IME commit; set up rules sniffing and batching
   ForceCompositionEnd();
   nsAutoEditBatch beginBatching(this);
-  nsAutoRules beginRulesSniffing(this, kOpLoadHTML, nsIEditor::eNext);
+  nsAutoRules beginRulesSniffing(this, OperationID::loadHTML, nsIEditor::eNext);
 
   // Get selection
   nsRefPtr<Selection> selection = GetSelection();
   NS_ENSURE_STATE(selection);
 
-  nsTextRulesInfo ruleInfo(kOpLoadHTML);
+  nsTextRulesInfo ruleInfo(OperationID::loadHTML);
   bool cancel, handled;
   nsresult rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
   NS_ENSURE_SUCCESS(rv, rv);
   if (cancel) {
     return NS_OK; // rules canceled the operation
   }
 
   if (!handled)
@@ -247,17 +247,17 @@ nsHTMLEditor::DoInsertHTMLWithContext(co
                                       bool aDeleteSelection,
                                       bool aTrustedInput)
 {
   NS_ENSURE_TRUE(mRules, NS_ERROR_NOT_INITIALIZED);
 
   // force IME commit; set up rules sniffing and batching
   ForceCompositionEnd();
   nsAutoEditBatch beginBatching(this);
-  nsAutoRules beginRulesSniffing(this, kOpHTMLPaste, nsIEditor::eNext);
+  nsAutoRules beginRulesSniffing(this, OperationID::htmlPaste, nsIEditor::eNext);
 
   // Get selection
   nsRefPtr<Selection> selection = GetSelection();
   NS_ENSURE_STATE(selection);
 
   // create a dom document fragment that represents the structure to paste
   nsCOMPtr<nsIDOMNode> fragmentAsNode, streamStartParent, streamEndParent;
   PRInt32 streamStartOffset = 0, streamEndOffset = 0;
@@ -386,17 +386,17 @@ nsHTMLEditor::DoInsertHTMLWithContext(co
       rv = DeleteTableCell(1);
       NS_ENSURE_SUCCESS(rv, rv);
     }
     // collapse selection to beginning of deleted table content
     selection->CollapseToStart();
   }
 
   // give rules a chance to handle or cancel
-  nsTextRulesInfo ruleInfo(kOpInsertElement);
+  nsTextRulesInfo ruleInfo(OperationID::insertElement);
   bool cancel, handled;
   rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
   NS_ENSURE_SUCCESS(rv, rv);
   if (cancel) {
     return NS_OK; // rules canceled the operation
   }
 
   if (!handled)
@@ -1741,24 +1741,24 @@ NS_IMETHODIMP nsHTMLEditor::PasteAsQuota
   nsAutoString citation;
   return PasteAsCitedQuotation(citation, aSelectionType);
 }
 
 NS_IMETHODIMP nsHTMLEditor::PasteAsCitedQuotation(const nsAString & aCitation,
                                                   PRInt32 aSelectionType)
 {
   nsAutoEditBatch beginBatching(this);
-  nsAutoRules beginRulesSniffing(this, kOpInsertQuotation, nsIEditor::eNext);
+  nsAutoRules beginRulesSniffing(this, OperationID::insertQuotation, nsIEditor::eNext);
 
   // get selection
   nsRefPtr<Selection> selection = GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
 
   // give rules a chance to handle or cancel
-  nsTextRulesInfo ruleInfo(kOpInsertElement);
+  nsTextRulesInfo ruleInfo(OperationID::insertElement);
   bool cancel, handled;
   nsresult rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
   NS_ENSURE_SUCCESS(rv, rv);
   if (cancel || handled) {
     return NS_OK; // rules canceled the operation
   }
 
   nsCOMPtr<nsIDOMNode> newNode;
@@ -1949,20 +1949,20 @@ nsHTMLEditor::InsertAsPlaintextQuotation
     return nsPlaintextEditor::InsertAsQuotation(aQuotedText, aNodeInserted);
 
   nsCOMPtr<nsIDOMNode> newNode;
   // get selection
   nsRefPtr<Selection> selection = GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
 
   nsAutoEditBatch beginBatching(this);
-  nsAutoRules beginRulesSniffing(this, kOpInsertQuotation, nsIEditor::eNext);
+  nsAutoRules beginRulesSniffing(this, OperationID::insertQuotation, nsIEditor::eNext);
 
   // give rules a chance to handle or cancel
-  nsTextRulesInfo ruleInfo(kOpInsertElement);
+  nsTextRulesInfo ruleInfo(OperationID::insertElement);
   bool cancel, handled;
   nsresult rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
   NS_ENSURE_SUCCESS(rv, rv);
   if (cancel || handled) {
     return NS_OK; // rules canceled the operation
   }
 
   // Wrap the inserted quote in a <span> so it won't be wrapped:
@@ -2042,20 +2042,20 @@ nsHTMLEditor::InsertAsCitedQuotation(con
 
   nsCOMPtr<nsIDOMNode> newNode;
 
   // get selection
   nsRefPtr<Selection> selection = GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
 
   nsAutoEditBatch beginBatching(this);
-  nsAutoRules beginRulesSniffing(this, kOpInsertQuotation, nsIEditor::eNext);
+  nsAutoRules beginRulesSniffing(this, OperationID::insertQuotation, nsIEditor::eNext);
 
   // give rules a chance to handle or cancel
-  nsTextRulesInfo ruleInfo(kOpInsertElement);
+  nsTextRulesInfo ruleInfo(OperationID::insertElement);
   bool cancel, handled;
   nsresult rv = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
   NS_ENSURE_SUCCESS(rv, rv);
   if (cancel || handled) {
     return NS_OK; // rules canceled the operation
   }
 
   rv = DeleteSelectionAndCreateNode(NS_LITERAL_STRING("blockquote"), getter_AddRefs(newNode));
--- a/editor/libeditor/html/nsHTMLEditRules.cpp
+++ b/editor/libeditor/html/nsHTMLEditRules.cpp
@@ -84,29 +84,29 @@ static bool IsBlockNode(nsIDOMNode* node
 }
 
 static bool IsInlineNode(nsIDOMNode* node)
 {
   return !IsBlockNode(node);
 }
 
 static bool
-IsStyleCachePreservingAction(nsEditor::OperationID action)
-{
-  return action == nsEditor::kOpDeleteSelection ||
-         action == nsEditor::kOpInsertBreak ||
-         action == nsEditor::kOpMakeList ||
-         action == nsEditor::kOpIndent ||
-         action == nsEditor::kOpOutdent ||
-         action == nsEditor::kOpAlign ||
-         action == nsEditor::kOpMakeBasicBlock ||
-         action == nsEditor::kOpRemoveList ||
-         action == nsEditor::kOpMakeDefListItem ||
-         action == nsEditor::kOpInsertElement ||
-         action == nsEditor::kOpInsertQuotation;
+IsStyleCachePreservingAction(OperationID action)
+{
+  return action == OperationID::deleteSelection ||
+         action == OperationID::insertBreak ||
+         action == OperationID::makeList ||
+         action == OperationID::indent ||
+         action == OperationID::outdent ||
+         action == OperationID::align ||
+         action == OperationID::makeBasicBlock ||
+         action == OperationID::removeList ||
+         action == OperationID::makeDefListItem ||
+         action == OperationID::insertElement ||
+         action == OperationID::insertQuotation;
 }
  
 class nsTableCellAndListItemFunctor : public nsBoolDomIterFunctor
 {
   public:
     virtual bool operator()(nsIDOMNode* aNode)  // used to build list of all li's, td's & th's iterator covers
     {
       if (nsHTMLEditUtils::IsTableCell(aNode)) return true;
@@ -272,17 +272,17 @@ nsHTMLEditRules::Init(nsPlaintextEditor 
 NS_IMETHODIMP
 nsHTMLEditRules::DetachEditor()
 {
   mHTMLEditor = nullptr;
   return nsTextEditRules::DetachEditor();
 }
 
 NS_IMETHODIMP
-nsHTMLEditRules::BeforeEdit(nsEditor::OperationID action,
+nsHTMLEditRules::BeforeEdit(OperationID action,
                             nsIEditor::EDirection aDirection)
 {
   if (mLockRulesSniffing) return NS_OK;
   
   nsAutoLockRulesSniffing lockIt((nsTextEditRules*)this);
   mDidExplicitlySetInterline = false;
 
   if (!mActionNesting++)
@@ -325,19 +325,19 @@ nsHTMLEditRules::BeforeEdit(nsEditor::Op
     }
     if(mUtilRange)
     {
       // ditto for mUtilRange.
       mUtilRange->Reset(); 
     }
 
     // remember current inline styles for deletion and normal insertion operations
-    if (action == nsEditor::kOpInsertText ||
-        action == nsEditor::kOpInsertIMEText ||
-        action == nsEditor::kOpDeleteSelection ||
+    if (action == OperationID::insertText ||
+        action == OperationID::insertIMEText ||
+        action == OperationID::deleteSelection ||
         IsStyleCachePreservingAction(action)) {
       nsCOMPtr<nsIDOMNode> selNode = selStartNode;
       if (aDirection == nsIEditor::eNext)
         selNode = selEndNode;
       res = CacheInlineStyles(selNode);
       NS_ENSURE_SUCCESS(res, res);
     }
 
@@ -356,17 +356,17 @@ nsHTMLEditRules::BeforeEdit(nsEditor::Op
     // let rules remember the top level action
     mTheAction = action;
   }
   return NS_OK;
 }
 
 
 NS_IMETHODIMP
-nsHTMLEditRules::AfterEdit(nsEditor::OperationID action,
+nsHTMLEditRules::AfterEdit(OperationID action,
                            nsIEditor::EDirection aDirection)
 {
   if (mLockRulesSniffing) return NS_OK;
 
   nsAutoLockRulesSniffing lockIt(this);
 
   NS_PRECONDITION(mActionNesting>0, "bad action nesting!");
   nsresult res = NS_OK;
@@ -391,21 +391,21 @@ nsHTMLEditRules::AfterEdit(nsEditor::Ope
     }
   }
 
   return res;
 }
 
 
 nsresult
-nsHTMLEditRules::AfterEditInner(nsEditor::OperationID action,
+nsHTMLEditRules::AfterEditInner(OperationID action,
                                 nsIEditor::EDirection aDirection)
 {
   ConfirmSelectionInBody();
-  if (action == nsEditor::kOpIgnore) return NS_OK;
+  if (action == OperationID::ignore) return NS_OK;
   
   nsCOMPtr<nsISelection>selection;
   nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
   NS_ENSURE_SUCCESS(res, res);
   
   nsCOMPtr<nsIDOMNode> rangeStartParent, rangeEndParent;
   PRInt32 rangeStartOffset = 0, rangeEndOffset = 0;
   // do we have a real range to act on?
@@ -415,59 +415,59 @@ nsHTMLEditRules::AfterEditInner(nsEditor
     mDocChangeRange->GetStartContainer(getter_AddRefs(rangeStartParent));
     mDocChangeRange->GetEndContainer(getter_AddRefs(rangeEndParent));
     mDocChangeRange->GetStartOffset(&rangeStartOffset);
     mDocChangeRange->GetEndOffset(&rangeEndOffset);
     if (rangeStartParent && rangeEndParent) 
       bDamagedRange = true; 
   }
   
-  if (bDamagedRange && !((action == nsEditor::kOpUndo) || (action == nsEditor::kOpRedo)))
+  if (bDamagedRange && !((action == OperationID::undo) || (action == OperationID::redo)))
   {
     // don't let any txns in here move the selection around behind our back.
     // Note that this won't prevent explicit selection setting from working.
     nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
    
     // expand the "changed doc range" as needed
     res = PromoteRange(mDocChangeRange, action);
     NS_ENSURE_SUCCESS(res, res);
 
     // if we did a ranged deletion, make sure we have a place to put caret.
     // Note we only want to do this if the overall operation was deletion,
-    // not if deletion was done along the way for kOpLoadHTML, kOpInsertText, etc.
+    // not if deletion was done along the way for OperationID::loadHTML, OperationID::insertText, etc.
     // That's why this is here rather than DidDeleteSelection().
-    if ((action == nsEditor::kOpDeleteSelection) && mDidRangedDelete)
+    if ((action == OperationID::deleteSelection) && mDidRangedDelete)
     {
       res = InsertBRIfNeeded(selection);
       NS_ENSURE_SUCCESS(res, res);
     }  
     
     // add in any needed <br>s, and remove any unneeded ones.
     res = AdjustSpecialBreaks();
     NS_ENSURE_SUCCESS(res, res);
     
     // merge any adjacent text nodes
-    if ( (action != nsEditor::kOpInsertText &&
-         action != nsEditor::kOpInsertIMEText) )
+    if ( (action != OperationID::insertText &&
+         action != OperationID::insertIMEText) )
     {
       res = mHTMLEditor->CollapseAdjacentTextNodes(mDocChangeRange);
       NS_ENSURE_SUCCESS(res, res);
     }
 
     // clean up any empty nodes in the selection
     res = RemoveEmptyNodes();
     NS_ENSURE_SUCCESS(res, res);
 
     // attempt to transform any unneeded nbsp's into spaces after doing various operations
-    if ((action == nsEditor::kOpInsertText) || 
-        (action == nsEditor::kOpInsertIMEText) ||
-        (action == nsEditor::kOpDeleteSelection) ||
-        (action == nsEditor::kOpInsertBreak) || 
-        (action == nsHTMLEditor::kOpHTMLPaste ||
-        (action == nsHTMLEditor::kOpLoadHTML)))
+    if ((action == OperationID::insertText) || 
+        (action == OperationID::insertIMEText) ||
+        (action == OperationID::deleteSelection) ||
+        (action == OperationID::insertBreak) || 
+        (action == OperationID::htmlPaste ||
+        (action == OperationID::loadHTML)))
     {
       res = AdjustWhitespace(selection);
       NS_ENSURE_SUCCESS(res, res);
       
       // also do this for original selection endpoints. 
       nsWSRunObject(mHTMLEditor, mRangeItem->startNode,
                     mRangeItem->startOffset).AdjustWhitespace();
       // we only need to handle old selection endpoint if it was different from start
@@ -481,31 +481,31 @@ nsHTMLEditRules::AfterEditInner(nsEditor
     // if we created a new block, make sure selection lands in it
     if (mNewBlock)
     {
       res = PinSelectionToNewBlock(selection);
       mNewBlock = 0;
     }
 
     // adjust selection for insert text, html paste, and delete actions
-    if ((action == nsEditor::kOpInsertText) || 
-        (action == nsEditor::kOpInsertIMEText) ||
-        (action == nsEditor::kOpDeleteSelection) ||
-        (action == nsEditor::kOpInsertBreak) || 
-        (action == nsHTMLEditor::kOpHTMLPaste ||
-        (action == nsHTMLEditor::kOpLoadHTML)))
+    if ((action == OperationID::insertText) || 
+        (action == OperationID::insertIMEText) ||
+        (action == OperationID::deleteSelection) ||
+        (action == OperationID::insertBreak) || 
+        (action == OperationID::htmlPaste ||
+        (action == OperationID::loadHTML)))
     {
       res = AdjustSelection(selection, aDirection);
       NS_ENSURE_SUCCESS(res, res);
     }
 
     // check for any styles which were removed inappropriately
-    if (action == nsEditor::kOpInsertText ||
-        action == nsEditor::kOpInsertIMEText ||
-        action == nsEditor::kOpDeleteSelection ||
+    if (action == OperationID::insertText ||
+        action == OperationID::insertIMEText ||
+        action == OperationID::deleteSelection ||
         IsStyleCachePreservingAction(action)) {
       mHTMLEditor->mTypeInState->UpdateSelState(selection);
       res = ReapplyCachedStyles();
       NS_ENSURE_SUCCESS(res, res);
       res = ClearCachedStyles();
       NS_ENSURE_SUCCESS(res, res);
     }    
   }
@@ -543,19 +543,19 @@ nsHTMLEditRules::WillDoAction(Selection*
   *aCancel = false;
   *aHandled = false;
 
   // my kingdom for dynamic cast
   nsTextRulesInfo *info = static_cast<nsTextRulesInfo*>(aInfo);
 
   // Deal with actions for which we don't need to check whether the selection is
   // editable.
-  if (info->action == nsEditor::kOpOutputText ||
-      info->action == nsEditor::kOpUndo ||
-      info->action == nsEditor::kOpRedo) {
+  if (info->action == OperationID::outputText ||
+      info->action == OperationID::undo ||
+      info->action == OperationID::redo) {
     return nsTextEditRules::WillDoAction(aSelection, aInfo, aCancel, aHandled);
   }
 
   // Nothing to do if there's no selection to act on
   if (!aSelection) {
     return NS_OK;
   }
   NS_ENSURE_TRUE(aSelection->GetRangeCount(), NS_OK);
@@ -578,77 +578,77 @@ nsHTMLEditRules::WillDoAction(Selection*
 
     if (!mHTMLEditor->IsModifiableNode(range->GetCommonAncestor())) {
       *aCancel = true;
       return NS_OK;
     }
   }
 
   switch (info->action) {
-    case nsEditor::kOpInsertText:
-    case nsEditor::kOpInsertIMEText:
+    case OperationID::insertText:
+    case OperationID::insertIMEText:
       return WillInsertText(info->action, aSelection, aCancel, aHandled,
                             info->inString, info->outString, info->maxLength);
-    case nsEditor::kOpLoadHTML:
+    case OperationID::loadHTML:
       return WillLoadHTML(aSelection, aCancel);
-    case nsEditor::kOpInsertBreak:
+    case OperationID::insertBreak:
       return WillInsertBreak(aSelection, aCancel, aHandled);
-    case nsEditor::kOpDeleteSelection:
+    case OperationID::deleteSelection:
       return WillDeleteSelection(aSelection, info->collapsedAction,
                                  info->stripWrappers, aCancel, aHandled);
-    case nsEditor::kOpMakeList:
+    case OperationID::makeList:
       return WillMakeList(aSelection, info->blockType, info->entireList,
                           info->bulletType, aCancel, aHandled);
-    case nsEditor::kOpIndent:
+    case OperationID::indent:
       return WillIndent(aSelection, aCancel, aHandled);
-    case nsEditor::kOpOutdent:
+    case OperationID::outdent:
       return WillOutdent(aSelection, aCancel, aHandled);
-    case nsEditor::kOpSetAbsolutePosition:
+    case OperationID::setAbsolutePosition:
       return WillAbsolutePosition(aSelection, aCancel, aHandled);
-    case nsEditor::kOpRemoveAbsolutePosition:
+    case OperationID::removeAbsolutePosition:
       return WillRemoveAbsolutePosition(aSelection, aCancel, aHandled);
-    case nsEditor::kOpAlign:
+    case OperationID::align:
       return WillAlign(aSelection, info->alignType, aCancel, aHandled);
-    case nsEditor::kOpMakeBasicBlock:
+    case OperationID::makeBasicBlock:
       return WillMakeBasicBlock(aSelection, info->blockType, aCancel, aHandled);
-    case nsEditor::kOpRemoveList:
+    case OperationID::removeList:
       return WillRemoveList(aSelection, info->bOrdered, aCancel, aHandled);
-    case nsEditor::kOpMakeDefListItem:
+    case OperationID::makeDefListItem:
       return WillMakeDefListItem(aSelection, info->blockType, info->entireList,
                                  aCancel, aHandled);
-    case nsEditor::kOpInsertElement:
+    case OperationID::insertElement:
       return WillInsert(aSelection, aCancel);
-    case nsEditor::kOpDecreaseZIndex:
+    case OperationID::decreaseZIndex:
       return WillRelativeChangeZIndex(aSelection, -1, aCancel, aHandled);
-    case nsEditor::kOpIncreaseZIndex:
+    case OperationID::increaseZIndex:
       return WillRelativeChangeZIndex(aSelection, 1, aCancel, aHandled);
     default:
       return nsTextEditRules::WillDoAction(aSelection, aInfo,
                                            aCancel, aHandled);
   }
 }
 
 
 NS_IMETHODIMP 
 nsHTMLEditRules::DidDoAction(nsISelection *aSelection,
                              nsRulesInfo *aInfo, nsresult aResult)
 {
   nsTextRulesInfo *info = static_cast<nsTextRulesInfo*>(aInfo);
   switch (info->action)
   {
-    case nsEditor::kOpInsertBreak:
+    case OperationID::insertBreak:
       return DidInsertBreak(aSelection, aResult);
-    case nsEditor::kOpDeleteSelection:
+    case OperationID::deleteSelection:
       return DidDeleteSelection(aSelection, info->collapsedAction, aResult);
-    case nsEditor::kOpMakeBasicBlock:
-    case nsEditor::kOpIndent:
-    case nsEditor::kOpOutdent:
-    case nsEditor::kOpAlign:
+    case OperationID::makeBasicBlock:
+    case OperationID::indent:
+    case OperationID::outdent:
+    case OperationID::align:
       return DidMakeBasicBlock(aSelection, aInfo, aResult);
-    case nsEditor::kOpSetAbsolutePosition: {
+    case OperationID::setAbsolutePosition: {
       nsresult rv = DidMakeBasicBlock(aSelection, aInfo, aResult);
       NS_ENSURE_SUCCESS(rv, rv);
       return DidAbsolutePosition();
     }
     default:
       // pass thru to nsTextEditRules
       return nsTextEditRules::DidDoAction(aSelection, aInfo, aResult);
   }
@@ -797,23 +797,23 @@ nsHTMLEditRules::GetAlignment(bool *aMix
            offset == rootOffset)
   {
     // if we have selected the body, let's look at the first editable node
     mHTMLEditor->GetNextNode(parent, offset, true, address_of(nodeToExamine));
   }
   else
   {
     nsCOMArray<nsIDOMRange> arrayOfRanges;
-    res = GetPromotedRanges(selection, arrayOfRanges, nsEditor::kOpAlign);
+    res = GetPromotedRanges(selection, arrayOfRanges, OperationID::align);
     NS_ENSURE_SUCCESS(res, res);
 
     // use these ranges to construct a list of nodes to act on.
     nsCOMArray<nsIDOMNode> arrayOfNodes;
     res = GetNodesForOperation(arrayOfRanges, arrayOfNodes,
-                               nsEditor::kOpAlign, true);
+                               OperationID::align, true);
     NS_ENSURE_SUCCESS(res, res);                                 
     nodeToExamine = arrayOfNodes.SafeObjectAt(0);
   }
 
   NS_ENSURE_TRUE(nodeToExamine, NS_ERROR_NULL_POINTER);
 
   NS_NAMED_LITERAL_STRING(typeAttrName, "align");
   nsIAtom  *dummyProperty = nullptr;
@@ -922,17 +922,17 @@ nsHTMLEditRules::GetIndentState(bool *aC
   nsCOMPtr<nsISelection>selection;
   nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
   NS_ENSURE_SUCCESS(res, res);
   nsCOMPtr<nsISelectionPrivate> selPriv(do_QueryInterface(selection));
   NS_ENSURE_TRUE(selPriv, NS_ERROR_FAILURE);
 
   // contruct a list of nodes to act on.
   nsCOMArray<nsIDOMNode> arrayOfNodes;
-  res = GetNodesFromSelection(selection, nsEditor::kOpIndent,
+  res = GetNodesFromSelection(selection, OperationID::indent,
                               arrayOfNodes, true);
   NS_ENSURE_SUCCESS(res, res);
 
   // examine nodes in selection for blockquotes or list elements;
   // these we can outdent.  Note that we return true for canOutdent
   // if *any* of the selection is outdentable, rather than all of it.
   PRInt32 listCount = arrayOfNodes.Count();
   PRInt32 i;
@@ -1221,44 +1221,44 @@ nsHTMLEditRules::WillInsert(nsISelection
       // the selection start to be before the mozBR.
       selNode = nsEditor::GetNodeLocation(priorNode, &selOffset);
       res = aSelection->Collapse(selNode,selOffset);
       NS_ENSURE_SUCCESS(res, res);
     }
   }
 
   if (mDidDeleteSelection &&
-      (mTheAction == nsEditor::kOpInsertText ||
-       mTheAction == nsEditor::kOpInsertIMEText ||
-       mTheAction == nsEditor::kOpDeleteSelection)) {
+      (mTheAction == OperationID::insertText ||
+       mTheAction == OperationID::insertIMEText ||
+       mTheAction == OperationID::deleteSelection)) {
     res = ReapplyCachedStyles();
     NS_ENSURE_SUCCESS(res, res);
   }
   // For most actions we want to clear the cached styles, but there are
   // exceptions
   if (!IsStyleCachePreservingAction(mTheAction)) {
     res = ClearCachedStyles();
     NS_ENSURE_SUCCESS(res, res);
   }
 
   return NS_OK;
 }    
 
 nsresult
-nsHTMLEditRules::WillInsertText(nsEditor::OperationID aAction,
+nsHTMLEditRules::WillInsertText(OperationID aAction,
                                 Selection*       aSelection,
                                 bool            *aCancel,
                                 bool            *aHandled,
                                 const nsAString *inString,
                                 nsAString       *outString,
                                 PRInt32          aMaxLength)
 {  
   if (!aSelection || !aCancel || !aHandled) { return NS_ERROR_NULL_POINTER; }
 
-  if (inString->IsEmpty() && aAction != nsEditor::kOpInsertIMEText) {
+  if (inString->IsEmpty() && aAction != OperationID::insertIMEText) {
     // HACK: this is a fix for bug 19395
     // I can't outlaw all empty insertions
     // because IME transaction depend on them
     // There is more work to do to make the 
     // world safe for IME.
     *aCancel = true;
     *aHandled = false;
     return NS_OK;
@@ -1296,17 +1296,17 @@ nsHTMLEditRules::WillInsertText(nsEditor
   NS_ENSURE_SUCCESS(res, res);
 
   // dont put text in places that can't have it
   if (!mHTMLEditor->IsTextNode(selNode) &&
       !mHTMLEditor->CanContainTag(selNode, nsGkAtoms::textTagName)) {
     return NS_ERROR_FAILURE;
   }
     
-  if (aAction == nsEditor::kOpInsertIMEText) {
+  if (aAction == OperationID::insertIMEText) {
     // Right now the nsWSRunObject code bails on empty strings, but IME needs 
     // the InsertTextImpl() call to still happen since empty strings are meaningful there.
     if (inString->IsEmpty())
     {
       res = mHTMLEditor->InsertTextImpl(*inString, address_of(selNode), &selOffset, doc);
     }
     else
     {
@@ -2716,17 +2716,17 @@ nsHTMLEditRules::JoinBlocks(nsCOMPtr<nsI
 */
 nsresult
 nsHTMLEditRules::MoveBlock(nsIDOMNode *aLeftBlock, nsIDOMNode *aRightBlock, PRInt32 aLeftOffset, PRInt32 aRightOffset)
 {
   nsCOMArray<nsIDOMNode> arrayOfNodes;
   nsCOMPtr<nsISupports> isupports;
   // GetNodesFromPoint is the workhorse that figures out what we wnat to move.
   nsresult res = GetNodesFromPoint(DOMPoint(aRightBlock,aRightOffset),
-                                   nsEditor::kOpMakeList, arrayOfNodes, true);
+                                   OperationID::makeList, arrayOfNodes, true);
   NS_ENSURE_SUCCESS(res, res);
   PRInt32 listCount = arrayOfNodes.Count();
   PRInt32 i;
   for (i=0; i<listCount; i++)
   {
     // get the node to act on
     nsIDOMNode* curNode = arrayOfNodes[i];
     if (IsBlockNode(curNode))
@@ -3160,17 +3160,17 @@ nsHTMLEditRules::WillRemoveList(Selectio
   *aCancel = false;
   *aHandled = true;
   
   nsresult res = NormalizeSelection(aSelection);
   NS_ENSURE_SUCCESS(res, res);
   nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
   
   nsCOMArray<nsIDOMRange> arrayOfRanges;
-  res = GetPromotedRanges(aSelection, arrayOfRanges, nsEditor::kOpMakeList);
+  res = GetPromotedRanges(aSelection, arrayOfRanges, OperationID::makeList);
   NS_ENSURE_SUCCESS(res, res);
   
   // use these ranges to contruct a list of nodes to act on.
   nsCOMArray<nsIDOMNode> arrayOfNodes;
   res = GetListActionNodes(arrayOfNodes, false);
   NS_ENSURE_SUCCESS(res, res);                                 
                                      
   // Remove all non-editable nodes.  Leave them be.
@@ -3248,17 +3248,17 @@ nsHTMLEditRules::WillMakeBasicBlock(Sele
   NS_ENSURE_SUCCESS(res, res);
   nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
   nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
   *aHandled = true;
   nsString tString(*aBlockType);
 
   // contruct a list of nodes to act on.
   nsCOMArray<nsIDOMNode> arrayOfNodes;
-  res = GetNodesFromSelection(aSelection, nsEditor::kOpMakeBasicBlock,
+  res = GetNodesFromSelection(aSelection, OperationID::makeBasicBlock,
                               arrayOfNodes);
   NS_ENSURE_SUCCESS(res, res);
 
   // Remove all non-editable nodes.  Leave them be.
   PRInt32 listCount = arrayOfNodes.Count();
   PRInt32 i;
   for (i=listCount-1; i>=0; i--)
   {
@@ -3440,17 +3440,17 @@ nsHTMLEditRules::WillCSSIndent(Selection
     arrayOfNodes.AppendObject(liNode);
   }
   else
   {
     // convert the selection ranges into "promoted" selection ranges:
     // this basically just expands the range to include the immediate
     // block parent, and then further expands to include any ancestors
     // whose children are all in the range
-    res = GetNodesFromSelection(aSelection, nsEditor::kOpIndent, arrayOfNodes);
+    res = GetNodesFromSelection(aSelection, OperationID::indent, arrayOfNodes);
     NS_ENSURE_SUCCESS(res, res);
   }
   
   NS_NAMED_LITERAL_STRING(quoteType, "blockquote");
   // if nothing visible in list, make an empty block
   if (ListIsEmptyLine(arrayOfNodes))
   {
     nsCOMPtr<nsIDOMNode> parent, theBlock;
@@ -3622,22 +3622,22 @@ nsHTMLEditRules::WillHTMLIndent(Selectio
   nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
   
   // convert the selection ranges into "promoted" selection ranges:
   // this basically just expands the range to include the immediate
   // block parent, and then further expands to include any ancestors
   // whose children are all in the range
   
   nsCOMArray<nsIDOMRange> arrayOfRanges;
-  res = GetPromotedRanges(aSelection, arrayOfRanges, nsEditor::kOpIndent);
+  res = GetPromotedRanges(aSelection, arrayOfRanges, OperationID::indent);
   NS_ENSURE_SUCCESS(res, res);
   
   // use these ranges to contruct a list of nodes to act on.
   nsCOMArray<nsIDOMNode> arrayOfNodes;
-  res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, nsEditor::kOpIndent);
+  res = GetNodesForOperation(arrayOfRanges, arrayOfNodes, OperationID::indent);
   NS_ENSURE_SUCCESS(res, res);                                 
                                      
   NS_NAMED_LITERAL_STRING(quoteType, "blockquote");
 
   // if nothing visible in list, make an empty block
   if (ListIsEmptyLine(arrayOfNodes))
   {
     nsCOMPtr<nsIDOMNode> parent, theBlock;
@@ -3845,17 +3845,17 @@ nsHTMLEditRules::WillOutdent(Selection* 
   {
     nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
     
     // convert the selection ranges into "promoted" selection ranges:
     // this basically just expands the range to include the immediate
     // block parent, and then further expands to include any ancestors
     // whose children are all in the range
     nsCOMArray<nsIDOMNode> arrayOfNodes;
-    res = GetNodesFromSelection(aSelection, nsEditor::kOpOutdent,
+    res = GetNodesFromSelection(aSelection, OperationID::outdent,
                                 arrayOfNodes);
     NS_ENSURE_SUCCESS(res, res);
 
     // Ok, now go through all the nodes and remove a level of blockquoting, 
     // or whatever is appropriate.  Wohoo!
 
     nsCOMPtr<nsIDOMNode> curBlockQuote, firstBQChild, lastBQChild;
     bool curBlockQuoteIsIndentedWithCSS = false;
@@ -4419,17 +4419,17 @@ nsHTMLEditRules::WillAlign(Selection* aS
   nsAutoSelectionReset selectionResetter(aSelection, mHTMLEditor);
 
   // convert the selection ranges into "promoted" selection ranges:
   // this basically just expands the range to include the immediate
   // block parent, and then further expands to include any ancestors
   // whose children are all in the range
   *aHandled = true;
   nsCOMArray<nsIDOMNode> arrayOfNodes;
-  res = GetNodesFromSelection(aSelection, nsEditor::kOpAlign, arrayOfNodes);
+  res = GetNodesFromSelection(aSelection, OperationID::align, arrayOfNodes);
   NS_ENSURE_SUCCESS(res, res);
 
   // if we don't have any nodes, or we have only a single br, then we are
   // creating an empty alignment div.  We have to do some different things for these.
   bool emptyDiv = false;
   PRInt32 listCount = arrayOfNodes.Count();
   if (!listCount) emptyDiv = true;
   if (listCount == 1)
@@ -5204,33 +5204,33 @@ nsHTMLEditRules::NormalizeSelection(nsIS
 
 
 ///////////////////////////////////////////////////////////////////////////
 // GetPromotedPoint: figure out where a start or end point for a block
 //                   operation really is
 void
 nsHTMLEditRules::GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode* aNode,
                                   PRInt32 aOffset,
-                                  nsEditor::OperationID actionID,
+                                  OperationID actionID,
                                   nsCOMPtr<nsIDOMNode>* outNode,
                                   PRInt32* outOffset)
 {
   nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
   MOZ_ASSERT(node && outNode && outOffset);
 
   // default values
   *outNode = node->AsDOMNode();
   *outOffset = aOffset;
 
   // we do one thing for text actions, something else entirely for other
   // actions
-  if (actionID == nsEditor::kOpInsertText ||
-      actionID == nsEditor::kOpInsertIMEText ||
-      actionID == nsEditor::kOpInsertBreak ||
-      actionID == nsEditor::kOpDeleteText) {
+  if (actionID == OperationID::insertText ||
+      actionID == OperationID::insertIMEText ||
+      actionID == OperationID::insertBreak ||
+      actionID == OperationID::deleteText) {
     bool isSpace, isNBSP;
     nsCOMPtr<nsIContent> content = do_QueryInterface(node), temp;
     // for text actions, we want to look backwards (or forwards, as
     // appropriate) for additional whitespace or nbsp's.  We may have to act on
     // these later even though they are outside of the initial selection.  Even
     // if they are in another node!
     while (content) {
       PRInt32 offset;
@@ -5289,32 +5289,32 @@ nsHTMLEditRules::GetPromotedPoint(RulesE
     nsCOMPtr<nsIContent> nearNode =
       mHTMLEditor->GetPriorHTMLNode(node, offset, true);
     while (!nearNode && node->Tag() != nsGkAtoms::body &&
            node->GetNodeParent()) {
       // some cutoffs are here: we don't need to also include them in the
       // aWhere == kEnd case.  as long as they are in one or the other it will
       // work.  special case for outdent: don't keep looking up if we have
       // found a blockquote element to act on
-      if (actionID == nsHTMLEditor::kOpOutdent &&
+      if (actionID == OperationID::outdent &&
           node->Tag() == nsGkAtoms::blockquote) {
         break;
       }
 
       PRInt32 parentOffset = node->GetNodeParent()->IndexOf(node);
       nsCOMPtr<nsINode> parent = node->GetNodeParent();
 
       // Don't walk past the editable section. Note that we need to check
       // before walking up to a parent because we need to return the parent
       // object, so the parent itself might not be in the editable area, but
       // it's OK if we're not performing a block-level action.
-      bool blockLevelAction = actionID == nsHTMLEditor::kOpIndent ||
-                              actionID == nsHTMLEditor::kOpOutdent ||
-                              actionID == nsHTMLEditor::kOpAlign ||
-                              actionID == nsHTMLEditor::kOpMakeBasicBlock;
+      bool blockLevelAction = actionID == OperationID::indent ||
+                              actionID == OperationID::outdent ||
+                              actionID == OperationID::align ||
+                              actionID == OperationID::makeBasicBlock;
       if (!mHTMLEditor->IsDescendantOfEditorRoot(parent) &&
           (blockLevelAction || !mHTMLEditor->IsDescendantOfEditorRoot(node))) {
         break;
       }
 
       node = parent;
       offset = parentOffset;
       nearNode = mHTMLEditor->GetPriorHTMLNode(node, offset, true);
@@ -5381,17 +5381,17 @@ nsHTMLEditRules::GetPromotedPoint(RulesE
 
 ///////////////////////////////////////////////////////////////////////////
 // GetPromotedRanges: run all the selection range endpoint through 
 //                    GetPromotedPoint()
 //                       
 nsresult 
 nsHTMLEditRules::GetPromotedRanges(nsISelection *inSelection, 
                                    nsCOMArray<nsIDOMRange> &outArrayOfRanges, 
-                                   nsEditor::OperationID inOperationType)
+                                   OperationID inOperationType)
 {
   NS_ENSURE_TRUE(inSelection, NS_ERROR_NULL_POINTER);
 
   PRInt32 rangeCount;
   nsresult res = inSelection->GetRangeCount(&rangeCount);
   NS_ENSURE_SUCCESS(res, res);
   
   PRInt32 i;
@@ -5422,17 +5422,17 @@ nsHTMLEditRules::GetPromotedRanges(nsISe
 
 
 ///////////////////////////////////////////////////////////////////////////
 // PromoteRange: expand a range to include any parents for which all
 //               editable children are already in range. 
 //                       
 nsresult 
 nsHTMLEditRules::PromoteRange(nsIDOMRange *inRange, 
-                              nsEditor::OperationID inOperationType)
+                              OperationID inOperationType)
 {
   NS_ENSURE_TRUE(inRange, NS_ERROR_NULL_POINTER);
   nsresult res;
   nsCOMPtr<nsIDOMNode> startNode, endNode;
   PRInt32 startOffset, endOffset;
   
   res = inRange->GetStartContainer(getter_AddRefs(startNode));
   NS_ENSURE_SUCCESS(res, res);
@@ -5522,17 +5522,17 @@ private:
 
 ///////////////////////////////////////////////////////////////////////////
 // GetNodesForOperation: run through the ranges in the array and construct 
 //                       a new array of nodes to be acted on.
 //                       
 nsresult 
 nsHTMLEditRules::GetNodesForOperation(nsCOMArray<nsIDOMRange>& inArrayOfRanges, 
                                       nsCOMArray<nsIDOMNode>& outArrayOfNodes, 
-                                      nsEditor::OperationID inOperationType,
+                                      OperationID inOperationType,
                                       bool aDontTouchContent)
 {
   PRInt32 rangeCount = inArrayOfRanges.Count();
   
   PRInt32 i;
   nsCOMPtr<nsIDOMRange> opRange;
 
   nsresult res = NS_OK;
@@ -5605,50 +5605,50 @@ nsHTMLEditRules::GetNodesForOperation(ns
       NS_ENSURE_SUCCESS(res, res);
       if (!outArrayOfNodes.AppendObjects(nodes))
         return NS_ERROR_OUT_OF_MEMORY;
     }
   }    
 
   // certain operations should not act on li's and td's, but rather inside 
   // them.  alter the list as needed
-  if (inOperationType == nsEditor::kOpMakeBasicBlock) {
+  if (inOperationType == OperationID::makeBasicBlock) {
     PRInt32 listCount = outArrayOfNodes.Count();
     for (i=listCount-1; i>=0; i--)
     {
       nsCOMPtr<nsIDOMNode> node = outArrayOfNodes[i];
       if (nsHTMLEditUtils::IsListItem(node))
       {
         PRInt32 j=i;
         outArrayOfNodes.RemoveObjectAt(i);
         res = GetInnerContent(node, outArrayOfNodes, &j);
         NS_ENSURE_SUCCESS(res, res);
       }
     }
   }
   // indent/outdent already do something special for list items, but
   // we still need to make sure we don't act on table elements
-  else if (inOperationType == nsEditor::kOpOutdent ||
-           inOperationType == nsEditor::kOpIndent ||
-           inOperationType == nsEditor::kOpSetAbsolutePosition) {
+  else if (inOperationType == OperationID::outdent ||
+           inOperationType == OperationID::indent ||
+           inOperationType == OperationID::setAbsolutePosition) {
     PRInt32 listCount = outArrayOfNodes.Count();
     for (i=listCount-1; i>=0; i--)
     {
       nsCOMPtr<nsIDOMNode> node = outArrayOfNodes[i];
       if (nsHTMLEditUtils::IsTableElementButNotTable(node))
       {
         PRInt32 j=i;
         outArrayOfNodes.RemoveObjectAt(i);
         res = GetInnerContent(node, outArrayOfNodes, &j);
         NS_ENSURE_SUCCESS(res, res);
       }
     }
   }
   // outdent should look inside of divs.
-  if (inOperationType == nsEditor::kOpOutdent &&
+  if (inOperationType == OperationID::outdent &&
       !mHTMLEditor->IsCSSEnabled()) {
     PRInt32 listCount = outArrayOfNodes.Count();
     for (i=listCount-1; i>=0; i--)
     {
       nsCOMPtr<nsIDOMNode> node = outArrayOfNodes[i];
       if (nsHTMLEditUtils::IsDiv(node))
       {
         PRInt32 j=i;
@@ -5657,22 +5657,22 @@ nsHTMLEditRules::GetNodesForOperation(ns
         NS_ENSURE_SUCCESS(res, res);
       }
     }
   }
 
 
   // post process the list to break up inline containers that contain br's.
   // but only for operations that might care, like making lists or para's...
-  if (inOperationType == nsEditor::kOpMakeBasicBlock ||
-      inOperationType == nsEditor::kOpMakeList ||
-      inOperationType == nsEditor::kOpAlign ||
-      inOperationType == nsEditor::kOpSetAbsolutePosition ||
-      inOperationType == nsEditor::kOpIndent ||
-      inOperationType == nsEditor::kOpOutdent) {
+  if (inOperationType == OperationID::makeBasicBlock ||
+      inOperationType == OperationID::makeList ||
+      inOperationType == OperationID::align ||
+      inOperationType == OperationID::setAbsolutePosition ||
+      inOperationType == OperationID::indent ||
+      inOperationType == OperationID::outdent) {
     PRInt32 listCount = outArrayOfNodes.Count();
     for (i=listCount-1; i>=0; i--)
     {
       nsCOMPtr<nsIDOMNode> node = outArrayOfNodes[i];
       if (!aDontTouchContent && IsInlineNode(node) 
            && mHTMLEditor->IsContainer(node) && !mHTMLEditor->IsTextNode(node))
       {
         nsCOMArray<nsIDOMNode> arrayOfInlines;
@@ -5766,17 +5766,17 @@ nsHTMLEditRules::GetListActionNodes(nsCO
     if (outArrayOfNodes.Count()) return NS_OK;
   }
 
   {
     // We don't like other people messing with our selection!
     nsAutoTxnsConserveSelection dontSpazMySelection(mHTMLEditor);
 
     // contruct a list of nodes to act on.
-    res = GetNodesFromSelection(selection, nsEditor::kOpMakeList,
+    res = GetNodesFromSelection(selection, OperationID::makeList,
                                 outArrayOfNodes, aDontTouchContent);
     NS_ENSURE_SUCCESS(res, res);
   }
                
   // pre process our list of nodes...                      
   PRInt32 listCount = outArrayOfNodes.Count();
   PRInt32 i;
   for (i=listCount-1; i>=0; i--)
@@ -5896,17 +5896,17 @@ nsresult
 nsHTMLEditRules::GetParagraphFormatNodes(nsCOMArray<nsIDOMNode>& outArrayOfNodes,
                                          bool aDontTouchContent)
 {  
   nsCOMPtr<nsISelection>selection;
   nsresult res = mHTMLEditor->GetSelection(getter_AddRefs(selection));
   NS_ENSURE_SUCCESS(res, res);
 
   // contruct a list of nodes to act on.
-  res = GetNodesFromSelection(selection, nsEditor::kOpMakeBasicBlock,
+  res = GetNodesFromSelection(selection, OperationID::makeBasicBlock,
                               outArrayOfNodes, aDontTouchContent);
   NS_ENSURE_SUCCESS(res, res);
 
   // pre process our list of nodes...                      
   PRInt32 listCount = outArrayOfNodes.Count();
   PRInt32 i;
   for (i=listCount-1; i>=0; i--)
   {
@@ -6069,17 +6069,17 @@ nsHTMLEditRules::GetHighestInlineParent(
 
 
 ///////////////////////////////////////////////////////////////////////////
 // GetNodesFromPoint: given a particular operation, construct a list  
 //                     of nodes from a point that will be operated on. 
 //                       
 nsresult 
 nsHTMLEditRules::GetNodesFromPoint(DOMPoint point,
-                                   nsEditor::OperationID operation,
+                                   OperationID operation,
                                    nsCOMArray<nsIDOMNode> &arrayOfNodes,
                                    bool dontTouchContent)
 {
   nsresult res;
 
   // get our point
   nsCOMPtr<nsIDOMNode> node;
   PRInt32 offset;
@@ -6110,17 +6110,17 @@ nsHTMLEditRules::GetNodesFromPoint(DOMPo
 
 
 ///////////////////////////////////////////////////////////////////////////
 // GetNodesFromSelection: given a particular operation, construct a list  
 //                     of nodes from the selection that will be operated on. 
 //                       
 nsresult 
 nsHTMLEditRules::GetNodesFromSelection(nsISelection *selection,
-                                       nsEditor::OperationID operation,
+                                       OperationID operation,
                                        nsCOMArray<nsIDOMNode>& arrayOfNodes,
                                        bool dontTouchContent)
 {
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
   nsresult res;
   
   // promote selection ranges
   nsCOMArray<nsIDOMRange> arrayOfRanges;
@@ -8578,23 +8578,23 @@ nsHTMLEditRules::WillAbsolutePosition(Se
   
   // convert the selection ranges into "promoted" selection ranges:
   // this basically just expands the range to include the immediate
   // block parent, and then further expands to include any ancestors
   // whose children are all in the range
   
   nsCOMArray<nsIDOMRange> arrayOfRanges;
   res = GetPromotedRanges(aSelection, arrayOfRanges,
-                          nsEditor::kOpSetAbsolutePosition);
+                          OperationID::setAbsolutePosition);
   NS_ENSURE_SUCCESS(res, res);
   
   // use these ranges to contruct a list of nodes to act on.
   nsCOMArray<nsIDOMNode> arrayOfNodes;
   res = GetNodesForOperation(arrayOfRanges, arrayOfNodes,
-                             nsEditor::kOpSetAbsolutePosition);
+                             OperationID::setAbsolutePosition);
   NS_ENSURE_SUCCESS(res, res);                                 
                                      
   NS_NAMED_LITERAL_STRING(divType, "div");
 
 
   // if nothing visible in list, make an empty block
   if (ListIsEmptyLine(arrayOfNodes))
   {
--- a/editor/libeditor/html/nsHTMLEditRules.h
+++ b/editor/libeditor/html/nsHTMLEditRules.h
@@ -72,19 +72,19 @@ public:
   
             nsHTMLEditRules();
   virtual   ~nsHTMLEditRules();
 
 
   // nsIEditRules methods
   NS_IMETHOD Init(nsPlaintextEditor *aEditor);
   NS_IMETHOD DetachEditor();
-  NS_IMETHOD BeforeEdit(nsEditor::OperationID action,
+  NS_IMETHOD BeforeEdit(OperationID action,
                         nsIEditor::EDirection aDirection);
-  NS_IMETHOD AfterEdit(nsEditor::OperationID action,
+  NS_IMETHOD AfterEdit(OperationID action,
                        nsIEditor::EDirection aDirection);
   NS_IMETHOD WillDoAction(mozilla::Selection* aSelection, nsRulesInfo* aInfo,
                           bool* aCancel, bool* aHandled);
   NS_IMETHOD DidDoAction(nsISelection *aSelection, nsRulesInfo *aInfo, nsresult aResult);
   NS_IMETHOD DocumentModified();
 
   nsresult GetListState(bool *aMixed, bool *aOL, bool *aUL, bool *aDL);
   nsresult GetListItemState(bool *aMixed, bool *aLI, bool *aDT, bool *aDD);
@@ -123,17 +123,17 @@ protected:
   enum BRLocation
   {
     kBeforeBlock,
     kBlockEnd
   };
 
   // nsHTMLEditRules implementation methods
   nsresult WillInsert(nsISelection *aSelection, bool *aCancel);
-  nsresult WillInsertText(  nsEditor::OperationID aAction,
+  nsresult WillInsertText(  OperationID aAction,
                             mozilla::Selection* aSelection,
                             bool            *aCancel,
                             bool            *aHandled,
                             const nsAString *inString,
                             nsAString       *outString,
                             PRInt32          aMaxLength);
   nsresult WillLoadHTML(nsISelection *aSelection, bool *aCancel);
   nsresult WillInsertBreak(mozilla::Selection* aSelection,
@@ -203,17 +203,17 @@ protected:
   nsresult ReturnInHeader(nsISelection *aSelection, nsIDOMNode *aHeader, nsIDOMNode *aTextNode, PRInt32 aOffset);
   nsresult ReturnInParagraph(nsISelection *aSelection, nsIDOMNode *aHeader, nsIDOMNode *aTextNode, PRInt32 aOffset, bool *aCancel, bool *aHandled);
   nsresult SplitParagraph(nsIDOMNode *aPara,
                           nsIDOMNode *aBRNode, 
                           nsISelection *aSelection,
                           nsCOMPtr<nsIDOMNode> *aSelNode, 
                           PRInt32 *aOffset);
   nsresult ReturnInListItem(nsISelection *aSelection, nsIDOMNode *aHeader, nsIDOMNode *aTextNode, PRInt32 aOffset);
-  nsresult AfterEditInner(nsEditor::OperationID action,
+  nsresult AfterEditInner(OperationID action,
                           nsIEditor::EDirection aDirection);
   nsresult RemovePartOfBlock(nsIDOMNode *aBlock, 
                              nsIDOMNode *aStartChild, 
                              nsIDOMNode *aEndChild,
                              nsCOMPtr<nsIDOMNode> *aLeftNode = 0,
                              nsCOMPtr<nsIDOMNode> *aRightNode = 0);
   nsresult SplitBlock(nsIDOMNode *aBlock, 
                       nsIDOMNode *aStartChild, 
@@ -248,35 +248,35 @@ protected:
                               bool *aHandled);
   nsresult CheckForInvisibleBR(nsIDOMNode *aBlock, nsHTMLEditRules::BRLocation aWhere, 
                                nsCOMPtr<nsIDOMNode> *outBRNode, PRInt32 aOffset=0);
   nsresult ExpandSelectionForDeletion(nsISelection *aSelection);
   bool IsFirstNode(nsIDOMNode *aNode);
   bool IsLastNode(nsIDOMNode *aNode);
   nsresult NormalizeSelection(nsISelection *inSelection);
   void GetPromotedPoint(RulesEndpoint aWhere, nsIDOMNode* aNode,
-                        PRInt32 aOffset, nsEditor::OperationID actionID,
+                        PRInt32 aOffset, OperationID actionID,
                         nsCOMPtr<nsIDOMNode>* outNode, PRInt32* outOffset);
   nsresult GetPromotedRanges(nsISelection *inSelection, 
                              nsCOMArray<nsIDOMRange> &outArrayOfRanges, 
-                             nsEditor::OperationID inOperationType);
+                             OperationID inOperationType);
   nsresult PromoteRange(nsIDOMRange *inRange,
-                        nsEditor::OperationID inOperationType);
+                        OperationID inOperationType);
   nsresult GetNodesForOperation(nsCOMArray<nsIDOMRange>& inArrayOfRanges, 
                                 nsCOMArray<nsIDOMNode>& outArrayOfNodes, 
-                                nsEditor::OperationID inOperationType,
+                                OperationID inOperationType,
                                 bool aDontTouchContent=false);
   nsresult GetChildNodesForOperation(nsIDOMNode *inNode, 
                                      nsCOMArray<nsIDOMNode>& outArrayOfNodes);
   nsresult GetNodesFromPoint(DOMPoint point,
-                             nsEditor::OperationID operation,
+                             OperationID operation,
                              nsCOMArray<nsIDOMNode>& arrayOfNodes,
                              bool dontTouchContent);
   nsresult GetNodesFromSelection(nsISelection *selection,
-                                 nsEditor::OperationID operation,
+                                 OperationID operation,
                                  nsCOMArray<nsIDOMNode>& arrayOfNodes,
                                  bool aDontTouchContent=false);
   nsresult GetListActionNodes(nsCOMArray<nsIDOMNode> &outArrayOfNodes, bool aEntireList, bool aDontTouchContent=false);
   void GetDefinitionListItemTypes(mozilla::dom::Element* aElement, bool* aDT, bool* aDD);
   nsresult GetParagraphFormatNodes(nsCOMArray<nsIDOMNode>& outArrayOfNodes, bool aDontTouchContent=false);
   nsresult LookInsideDivBQandList(nsCOMArray<nsIDOMNode>& aNodeArray);
   nsresult BustUpInlinesAtRangeEndpoints(nsRangeStore &inRange);
   nsresult BustUpInlinesAtBRs(nsIDOMNode *inNode, 
--- a/editor/libeditor/html/nsHTMLEditor.cpp
+++ b/editor/libeditor/html/nsHTMLEditor.cpp
@@ -1162,17 +1162,17 @@ nsHTMLEditor::CollapseSelectionToDeepest
 
 
 // This is mostly like InsertHTMLWithCharsetAndContext, 
 //  but we can't use that because it is selection-based and 
 //  the rules code won't let us edit under the <head> node
 NS_IMETHODIMP
 nsHTMLEditor::ReplaceHeadContentsWithHTML(const nsAString& aSourceToInsert)
 {
-  nsAutoRules beginRulesSniffing(this, kOpIgnore, nsIEditor::eNone); // don't do any post processing, rules get confused
+  nsAutoRules beginRulesSniffing(this, OperationID::ignore, nsIEditor::eNone); // don't do any post processing, rules get confused
   nsCOMPtr<nsISelection> selection;
   nsresult res = GetSelection(getter_AddRefs(selection));
   NS_ENSURE_SUCCESS(res, res);
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
 
   ForceCompositionEnd();
 
   // Do not use nsAutoRules -- rules code won't let us insert in <head>
@@ -1484,26 +1484,26 @@ nsHTMLEditor::InsertElementAtSelection(n
   nsresult res = NS_ERROR_NOT_INITIALIZED;
   
   NS_ENSURE_TRUE(aElement, NS_ERROR_NULL_POINTER);
   
   nsCOMPtr<nsIDOMNode> node = do_QueryInterface(aElement);
   
   ForceCompositionEnd();
   nsAutoEditBatch beginBatching(this);
-  nsAutoRules beginRulesSniffing(this, kOpInsertElement, nsIEditor::eNext);
+  nsAutoRules beginRulesSniffing(this, OperationID::insertElement, nsIEditor::eNext);
 
   nsRefPtr<Selection> selection = GetSelection();
   if (!selection) {
     return NS_ERROR_FAILURE;
   }
 
   // hand off to the rules system, see if it has anything to say about this
   bool cancel, handled;
-  nsTextRulesInfo ruleInfo(kOpInsertElement);
+  nsTextRulesInfo ruleInfo(OperationID::insertElement);
   ruleInfo.insertElement = aElement;
   res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
   if (cancel || (NS_FAILED(res))) return res;
 
   if (!handled)
   {
     if (aDeleteSelection)
     {
@@ -1945,23 +1945,23 @@ nsHTMLEditor::MakeOrChangeList(const nsA
   if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
 
   // Protect the edit rules object from dying
   nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
 
   bool cancel, handled;
 
   nsAutoEditBatch beginBatching(this);
-  nsAutoRules beginRulesSniffing(this, kOpMakeList, nsIEditor::eNext);
+  nsAutoRules beginRulesSniffing(this, OperationID::makeList, nsIEditor::eNext);
   
   // pre-process
   nsRefPtr<Selection> selection = GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
 
-  nsTextRulesInfo ruleInfo(kOpMakeList);
+  nsTextRulesInfo ruleInfo(OperationID::makeList);
   ruleInfo.blockType = &aListType;
   ruleInfo.entireList = entireList;
   ruleInfo.bulletType = &aBulletType;
   res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
   if (cancel || (NS_FAILED(res))) return res;
 
   if (!handled)
   {
@@ -2021,23 +2021,23 @@ nsHTMLEditor::RemoveList(const nsAString
   if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
 
   // Protect the edit rules object from dying
   nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
 
   bool cancel, handled;
 
   nsAutoEditBatch beginBatching(this);
-  nsAutoRules beginRulesSniffing(this, kOpRemoveList, nsIEditor::eNext);
+  nsAutoRules beginRulesSniffing(this, OperationID::removeList, nsIEditor::eNext);
   
   // pre-process
   nsRefPtr<Selection> selection = GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
 
-  nsTextRulesInfo ruleInfo(kOpRemoveList);
+  nsTextRulesInfo ruleInfo(OperationID::removeList);
   if (aListType.LowerCaseEqualsLiteral("ol"))
     ruleInfo.bOrdered = true;
   else  ruleInfo.bOrdered = false;
   res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
   if (cancel || (NS_FAILED(res))) return res;
 
   // no default behavior for this yet.  what would it mean?
 
@@ -2052,22 +2052,22 @@ nsHTMLEditor::MakeDefinitionItem(const n
   if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
 
   // Protect the edit rules object from dying
   nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
 
   bool cancel, handled;
 
   nsAutoEditBatch beginBatching(this);
-  nsAutoRules beginRulesSniffing(this, kOpMakeDefListItem, nsIEditor::eNext);
+  nsAutoRules beginRulesSniffing(this, OperationID::makeDefListItem, nsIEditor::eNext);
   
   // pre-process
   nsRefPtr<Selection> selection = GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
-  nsTextRulesInfo ruleInfo(kOpMakeDefListItem);
+  nsTextRulesInfo ruleInfo(OperationID::makeDefListItem);
   ruleInfo.blockType = &aItemType;
   res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
   if (cancel || (NS_FAILED(res))) return res;
 
   if (!handled)
   {
     // todo: no default for now.  we count on rules to handle it.
   }
@@ -2083,22 +2083,22 @@ nsHTMLEditor::InsertBasicBlock(const nsA
   if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
 
   // Protect the edit rules object from dying
   nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
 
   bool cancel, handled;
 
   nsAutoEditBatch beginBatching(this);
-  nsAutoRules beginRulesSniffing(this, kOpMakeBasicBlock, nsIEditor::eNext);
+  nsAutoRules beginRulesSniffing(this, OperationID::makeBasicBlock, nsIEditor::eNext);
   
   // pre-process
   nsRefPtr<Selection> selection = GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
-  nsTextRulesInfo ruleInfo(kOpMakeBasicBlock);
+  nsTextRulesInfo ruleInfo(OperationID::makeBasicBlock);
   ruleInfo.blockType = &aBlockType;
   res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
   if (cancel || (NS_FAILED(res))) return res;
 
   if (!handled)
   {
     // Find out if the selection is collapsed:
     bool isCollapsed = selection->Collapsed();
@@ -2151,20 +2151,20 @@ nsHTMLEditor::Indent(const nsAString& aI
 {
   nsresult res;
   if (!mRules) { return NS_ERROR_NOT_INITIALIZED; }
 
   // Protect the edit rules object from dying
   nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
 
   bool cancel, handled;
-  OperationID opID = kOpIndent;
+  OperationID opID = OperationID::indent;
   if (aIndent.LowerCaseEqualsLiteral("outdent"))
   {
-    opID = kOpOutdent;
+    opID = OperationID::outdent;
   }
   nsAutoEditBatch beginBatching(this);
   nsAutoRules beginRulesSniffing(this, opID, nsIEditor::eNext);
   
   // pre-process
   nsRefPtr<Selection> selection = GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
 
@@ -2231,25 +2231,25 @@ nsHTMLEditor::Indent(const nsAString& aI
 
 NS_IMETHODIMP
 nsHTMLEditor::Align(const nsAString& aAlignType)
 {
   // Protect the edit rules object from dying
   nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
 
   nsAutoEditBatch beginBatching(this);
-  nsAutoRules beginRulesSniffing(this, kOpAlign, nsIEditor::eNext);
+  nsAutoRules beginRulesSniffing(this, OperationID::align, nsIEditor::eNext);
 
   nsCOMPtr<nsIDOMNode> node;
   bool cancel, handled;
   
   // Find out if the selection is collapsed:
   nsRefPtr<Selection> selection = GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
-  nsTextRulesInfo ruleInfo(kOpAlign);
+  nsTextRulesInfo ruleInfo(OperationID::align);
   ruleInfo.alignType = &aAlignType;
   nsresult res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
   if (cancel || NS_FAILED(res))
     return res;
   
   res = mRules->DidDoAction(selection, &ruleInfo, res);
   return res;
 }
@@ -4659,22 +4659,22 @@ nsHTMLEditor::SetCSSBackgroundColor(cons
   // Protect the edit rules object from dying
   nsCOMPtr<nsIEditRules> kungFuDeathGrip(mRules);
 
   nsRefPtr<Selection> selection = GetSelection();
 
   bool isCollapsed = selection->Collapsed();
 
   nsAutoEditBatch batchIt(this);
-  nsAutoRules beginRulesSniffing(this, kOpInsertElement, nsIEditor::eNext);
+  nsAutoRules beginRulesSniffing(this, OperationID::insertElement, nsIEditor::eNext);
   nsAutoSelectionReset selectionResetter(selection, this);
   nsAutoTxnsConserveSelection dontSpazMySelection(this);
   
   bool cancel, handled;
-  nsTextRulesInfo ruleInfo(kOpSetTextProperty);
+  nsTextRulesInfo ruleInfo(OperationID::setTextProperty);
   nsresult res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
   NS_ENSURE_SUCCESS(res, res);
   if (!cancel && !handled)
   {
     // get selection range enumerator
     nsCOMPtr<nsIEnumerator> enumerator;
     res = selection->GetEnumerator(getter_AddRefs(enumerator));
     NS_ENSURE_SUCCESS(res, res);
--- a/editor/libeditor/html/nsHTMLEditorStyle.cpp
+++ b/editor/libeditor/html/nsHTMLEditorStyle.cpp
@@ -122,22 +122,22 @@ nsHTMLEditor::SetInlineProperty(nsIAtom 
   if (selection->Collapsed()) {
     // manipulating text attributes on a collapsed selection only sets state
     // for the next text insertion
     mTypeInState->SetProp(aProperty, aAttribute, aValue);
     return NS_OK;
   }
 
   nsAutoEditBatch batchIt(this);
-  nsAutoRules beginRulesSniffing(this, kOpInsertElement, nsIEditor::eNext);
+  nsAutoRules beginRulesSniffing(this, OperationID::insertElement, nsIEditor::eNext);
   nsAutoSelectionReset selectionResetter(selection, this);
   nsAutoTxnsConserveSelection dontSpazMySelection(this);
 
   bool cancel, handled;
-  nsTextRulesInfo ruleInfo(kOpSetTextProperty);
+  nsTextRulesInfo ruleInfo(OperationID::setTextProperty);
   nsresult res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
   NS_ENSURE_SUCCESS(res, res);
   if (!cancel && !handled) {
     // get selection range enumerator
     nsCOMPtr<nsIEnumerator> enumerator;
     res = selection->GetEnumerator(getter_AddRefs(enumerator));
     NS_ENSURE_SUCCESS(res, res);
     NS_ENSURE_TRUE(enumerator, NS_ERROR_FAILURE);
@@ -1327,17 +1327,17 @@ NS_IMETHODIMP nsHTMLEditor::GetInlinePro
     val = &aValue;
   return GetInlinePropertyBase( aProperty, att, val, aFirst, aAny, aAll, &outValue);
 }
 
 
 NS_IMETHODIMP nsHTMLEditor::RemoveAllInlineProperties()
 {
   nsAutoEditBatch batchIt(this);
-  nsAutoRules beginRulesSniffing(this, kOpResetTextProperties, nsIEditor::eNext);
+  nsAutoRules beginRulesSniffing(this, OperationID::resetTextProperties, nsIEditor::eNext);
 
   nsresult res = RemoveInlinePropertyImpl(nullptr, nullptr);
   NS_ENSURE_SUCCESS(res, res);
   return ApplyDefaultProperties();
 }
 
 NS_IMETHODIMP nsHTMLEditor::RemoveInlineProperty(nsIAtom *aProperty, const nsAString &aAttribute)
 {
@@ -1367,22 +1367,22 @@ nsresult nsHTMLEditor::RemoveInlinePrope
       mTypeInState->ClearProp(aProperty, *aAttribute);
     } else {
       mTypeInState->ClearAllProps();
     }
     return NS_OK;
   }
 
   nsAutoEditBatch batchIt(this);
-  nsAutoRules beginRulesSniffing(this, kOpRemoveTextProperty, nsIEditor::eNext);
+  nsAutoRules beginRulesSniffing(this, OperationID::removeTextProperty, nsIEditor::eNext);
   nsAutoSelectionReset selectionResetter(selection, this);
   nsAutoTxnsConserveSelection dontSpazMySelection(this);
   
   bool cancel, handled;
-  nsTextRulesInfo ruleInfo(kOpRemoveTextProperty);
+  nsTextRulesInfo ruleInfo(OperationID::removeTextProperty);
   res = mRules->WillDoAction(selection, &ruleInfo, &cancel, &handled);
   NS_ENSURE_SUCCESS(res, res);
   if (!cancel && !handled)
   {
     // get selection range enumerator
     nsCOMPtr<nsIEnumerator> enumerator;
     res = selection->GetEnumerator(getter_AddRefs(enumerator));
     NS_ENSURE_SUCCESS(res, res);
@@ -1561,17 +1561,17 @@ nsHTMLEditor::RelativeFontChange( PRInt3
 
     // manipulating text attributes on a collapsed selection only sets state for the next text insertion
     mTypeInState->SetProp(atom, EmptyString(), EmptyString());
     return NS_OK;
   }
   
   // wrap with txn batching, rules sniffing, and selection preservation code
   nsAutoEditBatch batchIt(this);
-  nsAutoRules beginRulesSniffing(this, kOpSetTextProperty, nsIEditor::eNext);
+  nsAutoRules beginRulesSniffing(this, OperationID::setTextProperty, nsIEditor::eNext);
   nsAutoSelectionReset selectionResetter(selection, this);
   nsAutoTxnsConserveSelection dontSpazMySelection(this);
 
   // get selection range enumerator
   nsCOMPtr<nsIEnumerator> enumerator;
   nsresult res = selection->GetEnumerator(getter_AddRefs(enumerator));
   NS_ENSURE_SUCCESS(res, res);