Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Thu, 18 Apr 2013 14:01:31 -0400
changeset 129230 1bc754a570f0f859c7798684c17eb9bcba815011
parent 129229 d214b2f5a61f06e0efbea845e1698f84e857ddb4 (current diff)
parent 129191 b390e72c5a543b600f8613650b0ceb0bfe747d96 (diff)
child 129231 fce173d8800d6afe5c2760e991fe8954191e57b6
push id24562
push userryanvm@gmail.com
push dateFri, 19 Apr 2013 01:24:04 +0000
treeherdermozilla-central@f8d27fe5d7c0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone23.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 m-c to inbound.
--- a/dom/apps/src/AppsUtils.jsm
+++ b/dom/apps/src/AppsUtils.jsm
@@ -201,23 +201,72 @@ this.AppsUtils = {
     isCoreApp = app.basePath == this.getCoreAppsBasePath();
 #endif
     debug(app.name + " isCoreApp: " + isCoreApp);
     return { "basePath":  app.basePath + "/",
              "isCoreApp": isCoreApp };
   },
 
   /**
+    * Remove potential HTML tags from displayable fields in the manifest.
+    * We check name, description, developer name, and permission description
+    */
+  sanitizeManifest: function(aManifest) {
+    let sanitizer = Cc["@mozilla.org/parserutils;1"]
+                      .getService(Ci.nsIParserUtils);
+    if (!sanitizer) {
+      return;
+    }
+
+    function sanitize(aStr) {
+      return sanitizer.convertToPlainText(aStr,
+               Ci.nsIDocumentEncoder.OutputRaw, 0);
+    }
+
+    function sanitizeEntryPoint(aRoot) {
+      aRoot.name = sanitize(aRoot.name);
+
+      if (aRoot.description) {
+        aRoot.description = sanitize(aRoot.description);
+      }
+
+      if (aRoot.developer && aRoot.developer.name) {
+        aRoot.developer.name = sanitize(aRoot.developer.name);
+      }
+
+      if (aRoot.permissions) {
+        for (let permission in aRoot.permissions) {
+          if (aRoot.permissions[permission].description) {
+            aRoot.permissions[permission].description =
+             sanitize(aRoot.permissions[permission].description);
+          }
+        }
+      }
+    }
+
+    // First process the main section, then the entry points.
+    sanitizeEntryPoint(aManifest);
+
+    if (aManifest.entry_points) {
+      for (let entry in aManifest.entry_points) {
+        sanitizeEntryPoint(aManifest.entry_points[entry]);
+      }
+    }
+  },
+
+  /**
    * From https://developer.mozilla.org/en/OpenWebApps/The_Manifest
    * Only the name property is mandatory.
    */
   checkManifest: function(aManifest, app) {
     if (aManifest.name == undefined)
       return false;
 
+    this.sanitizeManifest(aManifest);
+
     // launch_path, entry_points launch paths, message hrefs, and activity hrefs can't be absolute
     if (aManifest.launch_path && isAbsoluteURI(aManifest.launch_path))
       return false;
 
     function checkAbsoluteEntryPoints(entryPoints) {
       for (let name in entryPoints) {
         if (entryPoints[name].launch_path && isAbsoluteURI(entryPoints[name].launch_path)) {
           return true;
--- a/dom/apps/tests/Makefile.in
+++ b/dom/apps/tests/Makefile.in
@@ -19,9 +19,11 @@ MOCHITEST_FILES = \
   file_cached_app.template.webapp \
   file_cached_app.template.appcache \
   $(NULL)
 
 MOCHITEST_CHROME_FILES = \
   test_apps_service.xul \
   $(NULL)
 
+XPCSHELL_TESTS = unit
+
 include $(topsrcdir)/config/rules.mk
--- a/dom/apps/tests/file_hosted_app.template.webapp
+++ b/dom/apps/tests/file_hosted_app.template.webapp
@@ -1,5 +1,5 @@
 {
   "name": "Really Rapid Release (hosted)",
-  "description": "Updated even faster than Firefox, just to annoy slashdotters.",
+  "description": "Updated even faster than <a href='http://mozilla.org'>Firefox</a>, just to annoy slashdotters.",
   "launch_path": "/tests/dom/apps/tests/file_app.sjs?apptype=hosted"
 }
--- a/dom/apps/tests/test_app_update.html
+++ b/dom/apps/tests/test_app_update.html
@@ -64,16 +64,18 @@ https://bugzilla.mozilla.org/show_bug.cg
     yield;
 
     var request = navigator.mozApps.install(gHostedManifestURL);
     request.onerror = cbError;
     request.onsuccess = continueTest;
     yield;
     var app = request.result;
     ok(app, "App is non-null");
+    ok(app.manifest.description == "Updated even faster than Firefox, just to annoy slashdotters.",
+       "Manifest is HTML-sanitized");
 
     // Check the app a few times.
     checkAppState(app, true, 2, continueTest);
     yield;
     checkAppState(app, true, 2, continueTest);
     yield;
 
     // Bump the version and check the app again. The app is not cached, so the
new file mode 100644
--- /dev/null
+++ b/dom/apps/tests/unit/test_manifestSanitizer.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function testEntryPoint(aRoot) {
+  do_check_true(aRoot.name == "hello world");
+  do_check_true(aRoot.description == "A bold name");
+  do_check_true(aRoot.developer.name == "Blink Inc.");
+
+  let permissions = aRoot.permissions;
+  do_check_true(permissions.contacts.description == "Required for autocompletion in the share screen");
+  do_check_true(permissions.alarms.description == "Required to schedule notifications");
+}
+
+function run_test() {
+  Components.utils.import("resource:///modules/AppsUtils.jsm");
+
+  do_check_true(!!AppsUtils);
+
+  // Test manifest, with one entry point.
+  let manifest = {
+    name: "hello <b>world</b>",
+    description: "A bold name",
+    developer: {
+      name: "<blink>Blink</blink> Inc.",
+      url: "http://blink.org"
+    },
+    permissions : {
+      "contacts": {
+        "description": "Required for autocompletion in the <a href='http://shareme.com'>share</a> screen",
+        "access": "readcreate"
+        },
+      "alarms": {
+        "description": "Required to schedule notifications"
+      }
+    },
+
+    entry_points: {
+      "subapp": {
+        name: "hello <b>world</b>",
+        description: "A bold name",
+        developer: {
+          name: "<blink>Blink</blink> Inc.",
+          url: "http://blink.org"
+        },
+        permissions : {
+          "contacts": {
+            "description": "Required for autocompletion in the <a href='http://shareme.com'>share</a> screen",
+            "access": "readcreate"
+            },
+          "alarms": {
+            "description": "Required to schedule notifications"
+          }
+        }
+      }
+    }
+  }
+
+  AppsUtils.sanitizeManifest(manifest);
+
+  // Check the main section and the subapp entry point.
+  testEntryPoint(manifest);
+  testEntryPoint(manifest.entry_points.subapp);
+}
new file mode 100644
--- /dev/null
+++ b/dom/apps/tests/unit/xpcshell.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+head =
+tail =
+
+[test_manifestSanitizer.js]
--- a/dom/bluetooth/BluetoothScoManager.cpp
+++ b/dom/bluetooth/BluetoothScoManager.cpp
@@ -78,23 +78,21 @@ BluetoothScoManager::NotifyAudioManager(
     do_GetService("@mozilla.org/telephony/audiomanager;1");
   NS_ENSURE_TRUE_VOID(am);
 
   if (aAddress.IsEmpty()) {
     if (NS_FAILED(obs->NotifyObservers(nullptr, BLUETOOTH_SCO_STATUS_CHANGED, nullptr))) {
       NS_WARNING("Failed to notify bluetooth-sco-status-changed observsers!");
       return;
     }
-    am->SetForceForUse(am->USE_COMMUNICATION, am->FORCE_NONE);
   } else {
     if (NS_FAILED(obs->NotifyObservers(nullptr, BLUETOOTH_SCO_STATUS_CHANGED, aAddress.BeginReading()))) {
       NS_WARNING("Failed to notify bluetooth-sco-status-changed observsers!");
       return;
     }
-    am->SetForceForUse(am->USE_COMMUNICATION, am->FORCE_BT_SCO);
   }
 }
 
 NS_IMPL_ISUPPORTS1(BluetoothScoManagerObserver, nsIObserver)
 
 namespace {
 StaticAutoPtr<BluetoothScoManager> gBluetoothScoManager;
 StaticRefPtr<BluetoothScoManagerObserver> sScoObserver;
--- a/dom/camera/CameraControlImpl.cpp
+++ b/dom/camera/CameraControlImpl.cpp
@@ -270,29 +270,50 @@ CameraControlImpl::OnShutter()
 {
   nsCOMPtr<nsIRunnable> onShutter = NS_NewRunnableMethod(this, &CameraControlImpl::OnShutterInternal);
   nsresult rv = NS_DispatchToMainThread(onShutter);
   if (NS_FAILED(rv)) {
     DOM_CAMERA_LOGW("Failed to dispatch onShutter event to main thread (%d)\n", rv);
   }
 }
 
-void
-CameraControlImpl::OnClosedInternal()
+class OnClosedTask : public nsRunnable
 {
-  DOM_CAMERA_LOGI("Camera hardware was closed\n");
-  if (mOnClosedCb.get()) {
-    mOnClosedCb->HandleEvent();
+public:
+  OnClosedTask(nsMainThreadPtrHandle<nsICameraClosedCallback> onClosed, uint64_t aWindowId)
+    : mOnClosedCb(onClosed)
+    , mWindowId(aWindowId)
+  {
+    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
+  }
+
+  virtual ~OnClosedTask()
+  {
+    DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
   }
-}
+
+  NS_IMETHOD Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    if (mOnClosedCb.get() && nsDOMCameraManager::IsWindowStillActive(mWindowId)) {
+      mOnClosedCb->HandleEvent();
+    }
+    return NS_OK;
+  }
+
+protected:
+  nsMainThreadPtrHandle<nsICameraClosedCallback> mOnClosedCb;
+  uint64_t mWindowId;
+};
 
 void
 CameraControlImpl::OnClosed()
 {
-  nsCOMPtr<nsIRunnable> onClosed = NS_NewRunnableMethod(this, &CameraControlImpl::OnClosedInternal);
+  nsCOMPtr<nsIRunnable> onClosed = new OnClosedTask(mOnClosedCb, mWindowId);
   nsresult rv = NS_DispatchToMainThread(onClosed);
   if (NS_FAILED(rv)) {
     DOM_CAMERA_LOGW("Failed to dispatch onClosed event to main thread (%d)\n", rv);
   }
 }
 
 void
 CameraControlImpl::OnRecorderStateChange(const nsString& aStateMsg, int32_t aStatus, int32_t aTrackNumber)
--- a/dom/camera/GonkCameraHwMgr.cpp
+++ b/dom/camera/GonkCameraHwMgr.cpp
@@ -213,16 +213,20 @@ GonkCameraHardware::Close()
 }
 
 GonkCameraHardware::~GonkCameraHardware()
 {
   DOM_CAMERA_LOGT( "%s:%d : this=%p\n", __func__, __LINE__, (void*)this );
   mCamera.clear();
   mNativeWindow.clear();
 
+  if (mClosing) {
+    return;
+  }
+
   /**
    * Trigger the OnClosed event; the upper layers can't do anything
    * with the hardware layer once they receive this event.
    */
   if (mTarget) {
     OnClosed(mTarget);
   }
 }
--- a/dom/contacts/fallback/ContactDB.jsm
+++ b/dom/contacts/fallback/ContactDB.jsm
@@ -301,16 +301,67 @@ ContactDB.prototype = {
             objectStore.deleteIndex(names[i]);
           }
         }
       } else if (currVersion == 7) {
         if (DEBUG) debug("Adding object store for cached searches");
         db.createObjectStore(SAVED_GETALL_STORE_NAME);
       }
     }
+
+    // Add default contacts
+    if (aOldVersion == 0) {
+      let jsm = {};
+      Cu.import("resource://gre/modules/FileUtils.jsm", jsm);
+      Cu.import("resource://gre/modules/NetUtil.jsm", jsm);
+      // Loading resource://app/defaults/contacts.json doesn't work because
+      // contacts.json is not in the omnijar.
+      // So we look for the app dir instead and go from here...
+      let contactsFile = jsm.FileUtils.getFile("DefRt", ["contacts.json"], false);
+      if (!contactsFile || (contactsFile && !contactsFile.exists())) {
+        // For b2g desktop
+        contactsFile = jsm.FileUtils.getFile("ProfD", ["contacts.json"], false);
+        if (!contactsFile || (contactsFile && !contactsFile.exists())) {
+          return;
+        }
+      }
+
+      let chan = jsm.NetUtil.newChannel(contactsFile);
+      let stream = chan.open();
+      // Obtain a converter to read from a UTF-8 encoded input stream.
+      let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+                      .createInstance(Ci.nsIScriptableUnicodeConverter);
+      converter.charset = "UTF-8";
+      let rawstr = converter.ConvertToUnicode(jsm.NetUtil.readInputStreamToString(
+                                              stream,
+                                              stream.available()) || "");
+      stream.close();
+      let contacts;
+      try {
+        contacts = JSON.parse(rawstr);
+      } catch(e) {
+        if (DEBUG) debug("Error parsing " + contactsFile.path + " : " + e);
+        return;
+      }
+
+      let idService = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
+      objectStore = aTransaction.objectStore(STORE_NAME);
+
+      for (let i = 0; i < contacts.length; i++) {
+        let contact = {};
+        contact.properties = contacts[i];
+        contact.id = idService.generateUUID().toString().replace('-', '', 'g')
+                                                        .replace('{', '')
+                                                        .replace('}', '');
+        contact = this.makeImport(contact);
+        this.updateRecordMetadata(contact);
+        if (DEBUG) debug("import: " + JSON.stringify(contact));
+        objectStore.put(contact);
+      }
+    }
   },
 
   makeImport: function makeImport(aContact) {
     let contact = {};
     contact.properties = {
       name:            [],
       honorificPrefix: [],
       givenName:       [],
--- a/dom/mms/src/ril/MmsService.js
+++ b/dom/mms/src/ril/MmsService.js
@@ -52,19 +52,39 @@ const DELIVERY_NOT_DOWNLOADED = "not-dow
 const DELIVERY_SENDING        = "sending";
 const DELIVERY_SENT           = "sent";
 const DELIVERY_ERROR          = "error";
 
 const DELIVERY_STATUS_SUCCESS = "success";
 const DELIVERY_STATUS_PENDING = "pending";
 const DELIVERY_STATUS_ERROR   = "error";
 
+const PREF_SEND_RETRY_COUNT =
+  Services.prefs.getIntPref("dom.mms.sendRetryCount");
 
-const MAX_RETRY_COUNT = Services.prefs.getIntPref("dom.mms.retrievalRetryCount");
-const DELAY_TIME_TO_RETRY = Services.prefs.getIntPref("dom.mms.retrievalRetryInterval");
+const PREF_SEND_RETRY_INTERVAL =
+  Services.prefs.getIntPref("dom.mms.sendRetryInterval");
+
+const PREF_RETRIEVAL_RETRY_COUNT =
+  Services.prefs.getIntPref("dom.mms.retrievalRetryCount");
+
+const PREF_RETRIEVAL_RETRY_INTERVALS = (function () {
+  let intervals =
+    Services.prefs.getCharPref("dom.mms.retrievalRetryIntervals").split(",");
+  for (let i = 0; i < PREF_RETRIEVAL_RETRY_COUNT; ++i) {
+    intervals[i] = parseInt(intervals[i], 10);
+    // If one of the intervals isn't valid (e.g., 0 or NaN),
+    // assign a 10-minute interval to it as a default.
+    if (!intervals[i]) {
+      intervals[i] = 600000;
+    }
+  }
+  intervals.length = PREF_RETRIEVAL_RETRY_COUNT;
+  return intervals;
+})();
 
 XPCOMUtils.defineLazyServiceGetter(this, "gpps",
                                    "@mozilla.org/network/protocol-proxy-service;1",
                                    "nsIProtocolProxyService");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
@@ -603,24 +623,24 @@ RetrieveTransaction.prototype = {
    *        A callback function that takes two arguments: one for X-Mms-Status,
    *        the other for the parsed M-Retrieve.conf message.
    */
   run: function run(callback) {
     this.retryCount = 0;
     let that = this;
     this.retrieve((function retryCallback(mmsStatus, msg) {
       if (MMS.MMS_PDU_STATUS_DEFERRED == mmsStatus &&
-          that.retryCount < MAX_RETRY_COUNT) {
-        that.retryCount++;
+          that.retryCount < PREF_RETRIEVAL_RETRY_COUNT) {
         let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
         timer.initWithCallback((function (){
                                  this.retrieve(retryCallback);
                                }).bind(that),
-                               DELAY_TIME_TO_RETRY,
+                               PREF_RETRIEVAL_RETRY_INTERVALS[that.retryCount],
                                Ci.nsITimer.TYPE_ONE_SHOT);
+        that.retryCount++;
         return;
       }
       if (callback) {
         callback(mmsStatus, msg);
       }
     }).bind(this));
   },
 
@@ -809,22 +829,22 @@ SendTransaction.prototype = {
       callbackIfValid(MMS.MMS_PDU_ERROR_PERMANENT_FAILURE, null);
       return;
     }
 
     this.retryCount = 0;
     let retryCallback = (function (mmsStatus, msg) {
       if ((MMS.MMS_PDU_ERROR_TRANSIENT_FAILURE == mmsStatus ||
             MMS.MMS_PDU_ERROR_PERMANENT_FAILURE == mmsStatus) &&
-          this.retryCount < MAX_RETRY_COUNT) {
+          this.retryCount < PREF_SEND_RETRY_COUNT) {
         this.retryCount++;
 
         let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
         timer.initWithCallback(this.send.bind(this, retryCallback),
-                               DELAY_TIME_TO_RETRY,
+                               PREF_SEND_RETRY_INTERVAL,
                                Ci.nsITimer.TYPE_ONE_SHOT);
         return;
       }
 
       callbackIfValid(mmsStatus, msg);
     }).bind(this);
     this.send(retryCallback);
   },
--- a/dom/network/interfaces/nsIDOMMobileConnection.idl
+++ b/dom/network/interfaces/nsIDOMMobileConnection.idl
@@ -8,17 +8,17 @@ interface nsIDOMEventListener;
 interface nsIDOMDOMRequest;
 interface nsIDOMMozMobileICCInfo;
 interface nsIDOMMozMobileConnectionInfo;
 interface nsIDOMMozMobileNetworkInfo;
 interface nsIDOMMozMobileCellInfo;
 interface nsIDOMMozIccManager;
 interface nsIDOMMozMobileCFInfo;
 
-[scriptable, builtinclass, uuid(2065b3c3-e876-4be1-b373-428ee254a63e)]
+[scriptable, builtinclass, uuid(bde7e16c-ff1f-4c7f-b1cd-480984cbb206)]
 interface nsIDOMMozMobileConnection : nsIDOMEventTarget
 {
   const long ICC_SERVICE_CLASS_VOICE = (1 << 0);
   const long ICC_SERVICE_CLASS_DATA = (1 << 1);
   const long ICC_SERVICE_CLASS_FAX = (1 << 2);
   const long ICC_SERVICE_CLASS_SMS = (1 << 3);
   const long ICC_SERVICE_CLASS_DATA_SYNC = (1 << 4);
   const long ICC_SERVICE_CLASS_DATA_ASYNC = (1 << 5);
@@ -249,17 +249,17 @@ interface nsIDOMMozMobileConnection : ns
    */
   nsIDOMDOMRequest sendMMI(in DOMString mmi);
 
   /**
    * Cancel the current MMI request if one exists.
    */
   nsIDOMDOMRequest cancelMMI();
 
-   /**
+  /**
    * Configures call forward options.
    *
    * @param CFInfo
    *        An object containing the call forward rule to set.
    *
    * If successful, the request's onsuccess will be called.
    *
    * Otherwise, the request's onerror will be called, and the request's error
@@ -281,16 +281,43 @@ interface nsIDOMMozMobileConnection : ns
    *
    * Otherwise, the request's onerror will be called, and the request's error
    * will be either 'RadioNotAvailable', 'RequestNotSupported',
    * or 'GenericFailure'.
    */
   nsIDOMDOMRequest getCallForwardingOption(in unsigned short reason);
 
   /**
+   * Configures call waiting options.
+   *
+   * @param enabled
+   *        Value containing the desired call waiting status.
+   *
+   * If successful, the request's onsuccess will be called.
+   *
+   * Otherwise, the request's onerror will be called, and the request's error
+   * will be either 'RadioNotAvailable', 'RequestNotSupported',
+   * 'IllegalSIMorME', or 'GenericFailure'
+   */
+  nsIDOMDOMRequest setCallWaitingOption(in bool enabled);
+
+  /**
+   * Queries current call waiting options.
+   *
+   * If successful, the request's onsuccess will be called, and the request's
+   * result will be a boolean indicating the call waiting status.
+   *
+   *
+   * Otherwise, the request's onerror will be called, and the request's error
+   * will be either 'RadioNotAvailable', 'RequestNotSupported',
+   * or 'GenericFailure'.
+   */
+  nsIDOMDOMRequest getCallWaitingOption();
+
+  /**
    * The 'cardstatechange' event is notified when the 'cardState' attribute
    * changes value.
    */
   [implicit_jscontext] attribute jsval oncardstatechange;
 
    /**
    * The 'iccinfochange' event is notified whenever the icc info object
    * changes.
--- a/dom/network/interfaces/nsIMobileConnectionProvider.idl
+++ b/dom/network/interfaces/nsIMobileConnectionProvider.idl
@@ -30,17 +30,17 @@ interface nsIMobileConnectionListener : 
                            in unsigned short timeSeconds,
                            in unsigned short serviceClass);
 };
 
 /**
  * XPCOM component (in the content process) that provides the mobile
  * network information.
  */
-[scriptable, uuid(152da558-c3c0-45ad-9ac5-1adaf7a83c0d)]
+[scriptable, uuid(3b02afb4-2fc5-471a-b529-816125bf90ae)]
 interface nsIMobileConnectionProvider : nsISupports
 {
   /**
    * Called when a content process registers receiving unsolicited messages from
    * RadioInterfaceLayer in the chrome process. Only a content process that has
    * the 'mobileconnection' permission is allowed to register.
    */
   void registerMobileConnectionMsg(in nsIMobileConnectionListener listener);
@@ -62,9 +62,13 @@ interface nsIMobileConnectionProvider : 
 
   nsIDOMDOMRequest sendMMI(in nsIDOMWindow window, in DOMString mmi);
   nsIDOMDOMRequest cancelMMI(in nsIDOMWindow window);
 
   nsIDOMDOMRequest getCallForwardingOption(in nsIDOMWindow   window,
                                            in unsigned short reason);
   nsIDOMDOMRequest setCallForwardingOption(in nsIDOMWindow          window,
                                            in nsIDOMMozMobileCFInfo CFInfo);
+
+  nsIDOMDOMRequest setCallWaitingOption(in nsIDOMWindow   window,
+                                        in bool enabled);
+  nsIDOMDOMRequest getCallWaitingOption(in nsIDOMWindow   window);
 };
--- a/dom/network/src/MobileConnection.cpp
+++ b/dom/network/src/MobileConnection.cpp
@@ -295,16 +295,41 @@ MobileConnection::SetCallForwardingOptio
 
   if (!mProvider) {
     return NS_ERROR_FAILURE;
   }
 
   return mProvider->SetCallForwardingOption(GetOwner(), aCFInfo, aRequest);
 }
 
+NS_IMETHODIMP
+MobileConnection::GetCallWaitingOption(nsIDOMDOMRequest** aRequest)
+{
+  *aRequest = nullptr;
+
+  if (!mProvider) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return mProvider->GetCallWaitingOption(GetOwner(), aRequest);
+}
+
+NS_IMETHODIMP
+MobileConnection::SetCallWaitingOption(bool aEnabled,
+                                       nsIDOMDOMRequest** aRequest)
+{
+  *aRequest = nullptr;
+
+  if (!mProvider) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return mProvider->SetCallWaitingOption(GetOwner(), aEnabled, aRequest);
+}
+
 // nsIMobileConnectionListener
 
 NS_IMETHODIMP
 MobileConnection::NotifyVoiceChanged()
 {
   return DispatchTrustedEvent(NS_LITERAL_STRING("voicechange"));
 }
 
--- a/dom/phonenumberutils/PhoneNumber.jsm
+++ b/dom/phonenumberutils/PhoneNumber.jsm
@@ -23,19 +23,19 @@ this.PhoneNumber = (function (dataBase) 
   const SPLIT_FIRST_GROUP = /^(\d+)(.*)$/;
   const VALID_ALPHA_PATTERN = /[a-zA-Z]/g;
   const LEADING_PLUS_CHARS_PATTERN = /^[+\uFF0B]+/g;
 
   // Format of the string encoded meta data. If the name contains "^" or "$"
   // we will generate a regular expression from the value, with those special
   // characters as prefix/suffix.
   const META_DATA_ENCODING = ["region",
-                              "^internationalPrefix",
+                              "^(?:internationalPrefix)",
                               "nationalPrefix",
-                              "^nationalPrefixForParsing",
+                              "^(?:nationalPrefixForParsing)",
                               "nationalPrefixTransformRule",
                               "nationalPrefixFormattingRule",
                               "^possiblePattern$",
                               "^nationalPattern$",
                               "formats"];
 
   const FORMAT_ENCODING = ["^pattern$",
                            "nationalFormat",
--- a/dom/phonenumberutils/tests/test_phonenumber.xul
+++ b/dom/phonenumberutils/tests/test_phonenumber.xul
@@ -284,15 +284,18 @@ Format("0997654321", "CL", "997654321", 
 Format("997654321", "CL", "997654321", "CL", "(99) 765 4321", "+56 99 765 4321");
 
 // Colombian international number without the leading "+"
 Format("5712234567", "CO", "12234567", "CO", "(1) 2234567", "+57 1 2234567");
 
 // Dialing 911 in the US. This is not a national number.
 CantParse("911", "US");
 
+// China mobile number with a 0 in it
+Format("15955042864", "CN", "15955042864", "CN", "0159 5504 2864", "+86 159 5504 2864");
+
 // Test normalizing numbers. Only 0-9,#* are valid in a phone number.
 Normalize("+ABC # * , 9 _ 1 _0", "+222#*,910");
 Normalize("ABCDEFGHIJKLMNOPQRSTUVWXYZ", "22233344455566677778889999");
 Normalize("abcdefghijklmnopqrstuvwxyz", "22233344455566677778889999");
 
 </script>
 </window>
--- a/dom/system/gonk/AudioManager.cpp
+++ b/dom/system/gonk/AudioManager.cpp
@@ -149,21 +149,27 @@ AudioManager::Observe(nsISupports* aSubj
       String8 cmd;
       cmd.appendFormat("bt_samplerate=%d", kBtSampleRate);
       AudioSystem::setParameters(0, cmd);
       const char* address = NS_ConvertUTF16toUTF8(nsDependentString(aData)).get();
       AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET,
                                             AUDIO_POLICY_DEVICE_STATE_AVAILABLE, address);
       AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,
                                             AUDIO_POLICY_DEVICE_STATE_AVAILABLE, address);
+      SetForceForUse(nsIAudioManager::USE_COMMUNICATION, nsIAudioManager::FORCE_BT_SCO);
     } else {
       AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_OUT_BLUETOOTH_SCO_HEADSET,
                                             AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE, "");
       AudioSystem::setDeviceConnectionState(AUDIO_DEVICE_IN_BLUETOOTH_SCO_HEADSET,
                                             AUDIO_POLICY_DEVICE_STATE_UNAVAILABLE, "");
+      // only force to none if the current force setting is bt_sco
+      int32_t force;
+      GetForceForUse(nsIAudioManager::USE_COMMUNICATION, &force);
+      if (force == nsIAudioManager::FORCE_BT_SCO)
+        SetForceForUse(nsIAudioManager::USE_COMMUNICATION, nsIAudioManager::FORCE_NONE);
     }
 
     return NS_OK;
   }
   return NS_ERROR_UNEXPECTED;
 }
 
 static void
--- a/dom/system/gonk/RILContentHelper.js
+++ b/dom/system/gonk/RILContentHelper.js
@@ -72,16 +72,18 @@ const RIL_IPC_MSG_NAMES = [
   "RIL:SendMMI:Return:KO",
   "RIL:CancelMMI:Return:OK",
   "RIL:CancelMMI:Return:KO",
   "RIL:StkCommand",
   "RIL:StkSessionEnd",
   "RIL:DataError",
   "RIL:SetCallForwardingOption",
   "RIL:GetCallForwardingOption",
+  "RIL:SetCallWaitingOption",
+  "RIL:GetCallWaitingOption",
   "RIL:CellBroadcastReceived",
   "RIL:CfStateChanged",
   "RIL:IccOpenChannel",
   "RIL:IccCloseChannel",
   "RIL:IccExchangeAPDU",
   "RIL:UpdateIccContact"
 ];
 
@@ -731,16 +733,47 @@ RILContentHelper.prototype = {
       reason: cfInfo.reason,
       number: cfInfo.number,
       timeSeconds: cfInfo.timeSeconds
     });
 
     return request;
   },
 
+  getCallWaitingOption: function getCallWaitingOption(window) {
+    if (window == null) {
+      throw Components.Exception("Can't get window object",
+                                  Cr.NS_ERROR_UNEXPECTED);
+    }
+    let request = Services.DOMRequest.createRequest(window);
+    let requestId = this.getRequestId(request);
+
+    cpmm.sendAsyncMessage("RIL:GetCallWaitingOption", {
+      requestId: requestId
+    });
+
+    return request;
+  },
+
+  setCallWaitingOption: function setCallWaitingOption(window, enabled) {
+    if (window == null) {
+      throw Components.Exception("Can't get window object",
+                                  Cr.NS_ERROR_UNEXPECTED);
+    }
+    let request = Services.DOMRequest.createRequest(window);
+    let requestId = this.getRequestId(request);
+
+    cpmm.sendAsyncMessage("RIL:SetCallWaitingOption", {
+      requestId: requestId,
+      enabled: enabled
+    });
+
+    return request;
+  },
+
   _mobileConnectionListeners: null,
   _telephonyListeners: null,
   _cellBroadcastListeners: null,
   _voicemailListeners: null,
   _iccListeners: null,
   _enumerateTelephonyCallbacks: null,
 
   voicemailStatus: null,
@@ -1099,16 +1132,22 @@ RILContentHelper.prototype = {
                            [msg.json.errorMsg]);
         break;
       case "RIL:GetCallForwardingOption":
         this.handleGetCallForwardingOption(msg.json);
         break;
       case "RIL:SetCallForwardingOption":
         this.handleSetCallForwardingOption(msg.json);
         break;
+      case "RIL:GetCallWaitingOption":
+        this.handleGetCallWaitingOption(msg.json);
+        break;
+      case "RIL:SetCallWaitingOption":
+        this.handleSetCallWaitingOption(msg.json);
+        break;
       case "RIL:CfStateChanged":
         this._deliverEvent("_mobileConnectionListeners",
                            "notifyCFStateChange",
                            [msg.json.success, msg.json.action,
                             msg.json.reason, msg.json.number,
                             msg.json.timeSeconds, msg.json.serviceClass]);
         break;
       case "RIL:CellBroadcastReceived":
@@ -1279,16 +1318,44 @@ RILContentHelper.prototype = {
 
     if (!message.success) {
       Services.DOMRequest.fireError(request, message.errorMsg);
       return;
     }
     Services.DOMRequest.fireSuccess(request, null);
   },
 
+  handleGetCallWaitingOption: function handleGetCallWaitingOption(message) {
+    let requestId = message.requestId;
+    let request = this.takeRequest(requestId);
+    if (!request) {
+      return;
+    }
+
+    if (!message.success) {
+      Services.DOMRequest.fireError(request, message.errorMsg);
+      return;
+    }
+    Services.DOMRequest.fireSuccess(request, message.enabled);
+  },
+
+  handleSetCallWaitingOption: function handleSetCallWaitingOption(message) {
+    let requestId = message.requestId;
+    let request = this.takeRequest(requestId);
+    if (!request) {
+      return;
+    }
+
+    if (!message.success) {
+      Services.DOMRequest.fireError(request, message.errorMsg);
+      return;
+    }
+    Services.DOMRequest.fireSuccess(request, null);
+  },
+
   handleSendCancelMMIOK: function handleSendCancelMMIOK(message) {
     let request = this.takeRequest(message.requestId);
     if (!request) {
       return;
     }
 
     // MMI query call forwarding options request returns a set of rules that
     // will be exposed in the form of an array of nsIDOMMozMobileCFInfo
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -98,17 +98,19 @@ const RIL_IPC_MOBILECONNECTION_MSG_NAMES
   "RIL:SendStkEventDownload",
   "RIL:IccOpenChannel",
   "RIL:IccExchangeAPDU",
   "RIL:IccCloseChannel",
   "RIL:UpdateIccContact",
   "RIL:RegisterMobileConnectionMsg",
   "RIL:RegisterIccMsg",
   "RIL:SetCallForwardingOption",
-  "RIL:GetCallForwardingOption"
+  "RIL:GetCallForwardingOption",
+  "RIL:SetCallWaitingOption",
+  "RIL:GetCallWaitingOption"
 ];
 
 const RIL_IPC_VOICEMAIL_MSG_NAMES = [
   "RIL:RegisterVoicemailMsg",
   "RIL:GetVoicemailInfo"
 ];
 
 const RIL_IPC_CELLBROADCAST_MSG_NAMES = [
@@ -258,18 +260,16 @@ function RadioInterfaceLayer() {
     this.rilContext.voice.lastKnownMcc = Services.prefs.getCharPref("ril.lastKnownMcc");
   } catch (e) {}
 
   this.voicemailInfo = {
     number: null,
     displayName: null
   };
 
-  this.callWaitingStatus = null;
-
   // Read the 'ril.radio.disabled' setting in order to start with a known
   // value at boot time.
   let lock = gSettingsService.createLock();
   lock.get("ril.radio.disabled", this);
 
   // Read preferred network type from the setting DB.
   lock.get("ril.radio.preferredNetworkType", this);
 
@@ -528,16 +528,24 @@ RadioInterfaceLayer.prototype = {
       case "RIL:SetCallForwardingOption":
         this.saveRequestTarget(msg);
         this.setCallForwardingOption(msg.json);
         break;
       case "RIL:GetCallForwardingOption":
         this.saveRequestTarget(msg);
         this.getCallForwardingOption(msg.json);
         break;
+      case "RIL:SetCallWaitingOption":
+        this.saveRequestTarget(msg);
+        this.setCallWaitingOption(msg.json);
+        break;
+      case "RIL:GetCallWaitingOption":
+        this.saveRequestTarget(msg);
+        this.getCallWaitingOption(msg.json);
+        break;
       case "RIL:RegisterCellBroadcastMsg":
         this.registerMessageTarget("cellbroadcast", msg.target);
         break;
       case "RIL:GetVoicemailInfo":
         // This message is sync.
         return this.voicemailInfo;
     }
   },
@@ -618,19 +626,16 @@ RadioInterfaceLayer.prototype = {
         break;
       case "radiostatechange":
         this.handleRadioStateChange(message);
         break;
       case "cardstatechange":
         this.rilContext.cardState = message.cardState;
         this._sendMobileConnectionMessage("RIL:CardStateChanged", message);
         break;
-      case "setCallWaiting":
-        this.handleCallWaitingStatusChange(message);
-        break;
       case "sms-received":
         let ackOk = this.handleSmsReceived(message);
         if (ackOk) {
           this.worker.postMessage({
             rilMessageType: "ackSMS",
             result: RIL.PDU_FCS_OK
           });
         }
@@ -708,16 +713,22 @@ RadioInterfaceLayer.prototype = {
         this.handleSetPreferredNetworkType(message);
         break;
       case "queryCallForwardStatus":
         this.handleQueryCallForwardStatus(message);
         break;
       case "setCallForward":
         this.handleSetCallForward(message);
         break;
+      case "queryCallWaiting":
+        this.handleQueryCallWaiting(message);
+        break;
+      case "setCallWaiting":
+        this.handleSetCallWaiting(message);
+        break;
       case "setCellBroadcastSearchList":
         this.handleSetCellBroadcastSearchList(message);
         break;
       case "setRadioEnabled":
         let lock = gSettingsService.createLock();
         lock.set("ril.radio.disabled", !message.on, null, null);
         break;
       default:
@@ -1158,50 +1169,16 @@ RadioInterfaceLayer.prototype = {
       this.setRadioEnabled(true);
     }
     if (this.rilContext.radioState == RIL.GECKO_RADIOSTATE_READY &&
         !this._radioEnabled) {
       this.setRadioEnabled(false);
     }
   },
 
-  handleCallWaitingStatusChange: function handleCallWaitingStatusChange(message) {
-    let newStatus = message.enabled;
-
-    // RIL fails in setting call waiting status. Set "ril.callwaiting.enabled"
-    // to null and set the error message.
-    if (message.errorMsg) {
-      let lock = gSettingsService.createLock();
-      lock.set("ril.callwaiting.enabled", null, null, message.errorMsg);
-      return;
-    }
-
-    this.callWaitingStatus = newStatus;
-  },
-
-  setCallWaitingEnabled: function setCallWaitingEnabled(value) {
-    debug("Current call waiting status is " + this.callWaitingStatus +
-          ", desired call waiting status is " + value);
-    if (!this.rilContext.voice.connected) {
-      // The voice network is not connected. Wait for that.
-      return;
-    }
-
-    if (value == null) {
-      // We haven't read the valid value from the settings DB yet.
-      // Wait for that.
-      return;
-    }
-
-    if (this.callWaitingStatus != value) {
-      debug("Setting call waiting status to " + value);
-      this.worker.postMessage({rilMessageType: "setCallWaiting", enabled: value});
-    }
-  },
-
   updateRILNetworkInterface: function updateRILNetworkInterface() {
     if (this._dataCallSettingsToRead.length) {
       debug("We haven't read completely the APN data from the " +
             "settings DB yet. Wait for that.");
       return;
     }
 
     // This check avoids data call connection if the radio is not ready
@@ -1873,16 +1850,25 @@ RadioInterfaceLayer.prototype = {
       messageType = message.success ? "RIL:SendMMI:Return:OK" :
                                       "RIL:SendMMI:Return:KO";
     } else {
       messageType = "RIL:SetCallForwardingOption";
     }
     this._sendRequestResults(messageType, message);
   },
 
+  handleQueryCallWaiting: function handleQueryCallWaiting(message) {
+    debug("handleQueryCallWaiting: " + JSON.stringify(message));
+    this._sendRequestResults("RIL:GetCallWaitingOption", message);
+  },
+
+  handleSetCallWaiting: function handleSetCallWaiting(message) {
+    debug("handleSetCallWaiting: " + JSON.stringify(message));
+    this._sendRequestResults("RIL:SetCallWaitingOption", message);
+  },
   // nsIObserver
 
   observe: function observe(subject, topic, data) {
     switch (topic) {
       case kSysMsgListenerReadyObserverTopic:
         Services.obs.removeObserver(this, kSysMsgListenerReadyObserverTopic);
         this._sysMsgListenerReady = true;
         this._resendQueuedTargetMessage();
@@ -1932,21 +1918,16 @@ RadioInterfaceLayer.prototype = {
   // Flag to determine the radio state to start with when we boot up. It
   // corresponds to the 'ril.radio.disabled' setting from the UI.
   _radioEnabled: null,
 
   // Flag to ignore any radio power change requests during We're changing
   // the radio power.
   _changingRadioPower: false,
 
-  // Flag to determine whether we reject a waiting call directly or we
-  // notify the UI of a waiting call. It corresponds to the
-  // 'ril.callwaiting.enbled' setting from the UI.
-  _callWaitingEnabled: null,
-
   // APN data for making data calls.
   dataCallSettings: {},
   dataCallSettingsMMS: {},
   dataCallSettingsSUPL: {},
   _dataCallSettingsToRead: [],
   _oldRilDataEnabledState: null,
 
   // Flag to determine whether to use NITZ. It corresponds to the
@@ -2018,20 +1999,16 @@ RadioInterfaceLayer.prototype = {
       case "ril.supl.apn":
       case "ril.supl.user":
       case "ril.supl.passwd":
       case "ril.supl.httpProxyHost":
       case "ril.supl.httpProxyPort":
         key = aName.slice(9);
         this.dataCallSettingsSUPL[key] = aResult;
         break;
-      case "ril.callwaiting.enabled":
-        this._callWaitingEnabled = aResult;
-        this.setCallWaitingEnabled(this._callWaitingEnabled);
-        break;
       case kTimeNitzAutomaticUpdateEnabled:
         this._nitzAutomaticUpdateEnabled = aResult;
 
         // Set the latest cached NITZ time if the setting is enabled.
         if (this._nitzAutomaticUpdateEnabled && this._lastNitzMessage) {
           this.setNitzTime(this._lastNitzMessage);
         }
         break;
@@ -2228,16 +2205,28 @@ RadioInterfaceLayer.prototype = {
   getCallForwardingOption: function getCallForwardingOption(message) {
     debug("getCallForwardingOption: " + JSON.stringify(message));
     message.rilMessageType = "queryCallForwardStatus";
     message.serviceClass = RIL.ICC_SERVICE_CLASS_NONE;
     message.number = null;
     this.worker.postMessage(message);
   },
 
+  setCallWaitingOption: function setCallWaitingOption(message) {
+    debug("setCallWaitingOption: " + JSON.stringify(message));
+    message.rilMessageType = "setCallWaiting";
+    this.worker.postMessage(message);
+  },
+
+  getCallWaitingOption: function getCallWaitingOption(message) {
+    debug("getCallWaitingOption: " + JSON.stringify(message));
+    message.rilMessageType = "queryCallWaiting";
+    this.worker.postMessage(message);
+  },
+
   get microphoneMuted() {
     return gAudioManager.microphoneMuted;
   },
   set microphoneMuted(value) {
     if (value == this.microphoneMuted) {
       return;
     }
     gAudioManager.microphoneMuted = value;
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -1377,16 +1377,29 @@ let RIL = {
   setRadioPower: function setRadioPower(options) {
     Buf.newParcel(REQUEST_RADIO_POWER, options);
     Buf.writeUint32(1);
     Buf.writeUint32(options.on ? 1 : 0);
     Buf.sendParcel();
   },
 
   /**
+   * Query call waiting status.
+   *
+   */
+  queryCallWaiting: function queryCallWaiting(options) {
+    Buf.newParcel(REQUEST_QUERY_CALL_WAITING, options);
+    Buf.writeUint32(1);
+    // As per 3GPP TS 24.083, section 1.6 UE doesn't need to send service
+    // class parameter in call waiting interrogation  to network
+    Buf.writeUint32(ICC_SERVICE_CLASS_NONE);
+    Buf.sendParcel();
+  },
+
+  /**
    * Set call waiting status.
    *
    * @param on
    *        Boolean indicating the desired waiting status.
    */
   setCallWaiting: function setCallWaiting(options) {
     Buf.newParcel(REQUEST_SET_CALL_WAITING, options);
     Buf.writeUint32(2);
@@ -4728,19 +4741,35 @@ RIL[REQUEST_QUERY_CALL_FORWARD_STATUS] =
 RIL[REQUEST_SET_CALL_FORWARD] =
   function REQUEST_SET_CALL_FORWARD(length, options) {
     options.success = options.rilRequestError == 0;
     if (!options.success) {
       options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
     }
     this.sendDOMMessage(options);
 };
-RIL[REQUEST_QUERY_CALL_WAITING] = null;
+RIL[REQUEST_QUERY_CALL_WAITING] =
+  function REQUEST_QUERY_CALL_WAITING(length, options) {
+  options.success = options.rilRequestError == 0;
+  if (!options.success) {
+    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
+    this.sendDOMMessage(options);
+    return;
+  }
+  options.length = Buf.readUint32();
+  options.enabled = ((Buf.readUint32() == 1) &&
+                     ((Buf.readUint32() & ICC_SERVICE_CLASS_VOICE) == 0x01));
+  this.sendDOMMessage(options);
+};
+
 RIL[REQUEST_SET_CALL_WAITING] = function REQUEST_SET_CALL_WAITING(length, options) {
-  options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
+  options.success = options.rilRequestError == 0;
+  if (!options.success) {
+    options.errorMsg = RIL_ERROR_TO_GECKO_ERROR[options.rilRequestError];
+  }
   this.sendDOMMessage(options);
 };
 RIL[REQUEST_SMS_ACKNOWLEDGE] = null;
 RIL[REQUEST_GET_IMEI] = function REQUEST_GET_IMEI(length, options) {
   this.IMEI = Buf.readString();
   // So far we only send the IMEI back to the DOM if it was requested via MMI.
   if (!options.mmi) {
     return;
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/tests/test_ril_worker_cw.js
@@ -0,0 +1,131 @@
+/* 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();
+}
+
+function _getWorker() {
+  let _postedMessage;
+  let _worker = newWorker({
+    postRILMessage: function fakePostRILMessage(data) {
+    },
+    postMessage: function fakePostMessage(message) {
+      _postedMessage = message;
+    }
+  });
+  return {
+    get postedMessage() {
+      return _postedMessage;
+    },
+    get worker() {
+      return _worker;
+    }
+  };
+}
+
+add_test(function test_setCallWaiting_success() {
+  let workerHelper = _getWorker();
+  let worker = workerHelper.worker;
+
+  worker.RIL.setCallWaiting = function fakeSetCallWaiting(options) {
+    worker.RIL[REQUEST_SET_CALL_WAITING](0, {
+      rilRequestError: ERROR_SUCCESS
+    });
+  };
+
+  worker.RIL.setCallWaiting({
+    enabled: true
+  });
+
+  let postedMessage = workerHelper.postedMessage;
+
+  do_check_eq(postedMessage.errorMsg, undefined);
+  do_check_true(postedMessage.success);
+
+  run_next_test();
+});
+
+add_test(function test_setCallWaiting_generic_failure() {
+  let workerHelper = _getWorker();
+  let worker = workerHelper.worker;
+
+  worker.RIL.setCallWaiting = function fakeSetCallWaiting(options) {
+    worker.RIL[REQUEST_SET_CALL_WAITING](0, {
+      rilRequestError: ERROR_GENERIC_FAILURE
+    });
+  };
+
+  worker.RIL.setCallWaiting({
+    enabled: true
+  });
+
+  let postedMessage = workerHelper.postedMessage;
+
+  do_check_eq(postedMessage.errorMsg, "GenericFailure");
+  do_check_false(postedMessage.success);
+
+  run_next_test();
+});
+
+add_test(function test_queryCallWaiting_success_enabled_true() {
+  let workerHelper = _getWorker();
+  let worker = workerHelper.worker;
+
+  worker.Buf.readUint32 = function fakeReadUint32() {
+    return worker.Buf.int32Array.pop();
+  };
+
+  worker.RIL.queryCallWaiting = function fakeQueryCallWaiting(options) {
+    worker.Buf.int32Array = [
+      1,  // serviceClass
+      1,  // enabled
+      1   // length
+    ];
+    worker.RIL[REQUEST_QUERY_CALL_WAITING](1, {
+      rilRequestError: ERROR_SUCCESS
+    });
+  };
+
+  worker.RIL.queryCallWaiting({});
+
+  let postedMessage = workerHelper.postedMessage;
+
+  do_check_eq(postedMessage.errorMsg, undefined);
+  do_check_true(postedMessage.success);
+  do_check_eq(postedMessage.length, 1);
+  do_check_true(postedMessage.enabled);
+  run_next_test();
+});
+
+add_test(function test_queryCallWaiting_success_enabled_false() {
+  let workerHelper = _getWorker();
+  let worker = workerHelper.worker;
+
+  worker.Buf.readUint32 = function fakeReadUint32() {
+    return worker.Buf.int32Array.pop();
+  };
+
+  worker.RIL.queryCallWaiting = function fakeQueryCallWaiting(options) {
+    worker.Buf.int32Array = [
+      1,  // serviceClass
+      0,  // enabled
+      1   // length
+    ];
+    worker.RIL[REQUEST_QUERY_CALL_WAITING](1, {
+      rilRequestError: ERROR_SUCCESS
+    });
+  };
+
+  worker.RIL.queryCallWaiting({});
+
+  let postedMessage = workerHelper.postedMessage;
+
+  do_check_eq(postedMessage.errorMsg, undefined);
+  do_check_true(postedMessage.success);
+  do_check_eq(postedMessage.length, 1);
+  do_check_false(postedMessage.enabled);
+  run_next_test();
+});
--- a/dom/system/gonk/tests/xpcshell.ini
+++ b/dom/system/gonk/tests/xpcshell.ini
@@ -4,8 +4,9 @@ tail =
 
 [test_ril_worker_buf.js]
 [test_ril_worker_icc.js]
 [test_ril_worker_sms.js]
 [test_ril_worker_mmi.js]
 [test_ril_worker_cf.js]
 [test_ril_worker_cellbroadcast.js]
 [test_ril_worker_ruim.js]
+[test_ril_worker_cw.js]
--- a/dom/tests/mochitest/webapps/apps/utf8.webapp
+++ b/dom/tests/mochitest/webapps/apps/utf8.webapp
@@ -1,5 +1,5 @@
 {
- "name": "TheBOM  ゲゴケ゚セニツ゚ヅヂチ",
+ "name": "TheBOM ゲゴケ゚セニツ゚ヅヂチ",
  "description": "This App is THE BOM, yo. ヅヂチ",
  "installs_allowed_from": ["*"]
 }
--- a/dom/tests/mochitest/webapps/test_install_utf8.xul
+++ b/dom/tests/mochitest/webapps/test_install_utf8.xul
@@ -19,17 +19,17 @@
 <script>
 
 SimpleTest.waitForExplicitFinish();
 
 var url = "http://test/chrome/dom/tests/mochitest/webapps/apps/utf8.webapp";
 
 confirmNextInstall();
 navigator.mozApps.install(url, null).onsuccess = function onInstall() {
-  is(this.result.manifest.name, "TheBOM  ゲゴケ゚セニツ゚ヅヂチ", "manifest.name");
+  is(this.result.manifest.name, "TheBOM ゲゴケ゚セニツ゚ヅヂチ", "manifest.name");
   is(this.result.manifest.description, "This App is THE BOM, yo. ヅヂチ",
      "manifest.description");
 
   navigator.mozApps.mgmt.uninstall(this.result).onsuccess = function onUninstall() {
     SimpleTest.finish();
   }
 };
 
--- a/ipc/chromium/src/base/message_loop.cc
+++ b/ipc/chromium/src/base/message_loop.cc
@@ -81,26 +81,29 @@ static LPTOP_LEVEL_EXCEPTION_FILTER GetT
 // static
 MessageLoop* MessageLoop::current() {
   // TODO(darin): sadly, we cannot enable this yet since people call us even
   // when they have no intention of using us.
   //DCHECK(loop) << "Ouch, did you forget to initialize me?";
   return lazy_tls_ptr.Pointer()->Get();
 }
 
+PRInt32 message_loop_id_seq = 0;
+
 MessageLoop::MessageLoop(Type type)
     : type_(type),
       nestable_tasks_allowed_(true),
       exception_restoration_(false),
       state_(NULL),
       run_depth_base_(1),
 #ifdef OS_WIN
       os_modal_loop_(false),
 #endif  // OS_WIN
-      next_sequence_num_(0) {
+      next_sequence_num_(0),
+      id_(PR_ATOMIC_INCREMENT(&message_loop_id_seq)) {
   DCHECK(!current()) << "should only have one message loop per thread";
   lazy_tls_ptr.Pointer()->Set(this);
   if (type_ == TYPE_MOZILLA_UI) {
     pump_ = new mozilla::ipc::MessagePump();
     return;
   }
   if (type_ == TYPE_MOZILLA_CHILD) {
     pump_ = new mozilla::ipc::MessagePumpForChildProcess();
--- a/ipc/chromium/src/base/message_loop.h
+++ b/ipc/chromium/src/base/message_loop.h
@@ -214,16 +214,19 @@ public:
   // Normally, it is not necessary to instantiate a MessageLoop.  Instead, it
   // is typical to make use of the current thread's MessageLoop instance.
   explicit MessageLoop(Type type = TYPE_DEFAULT);
   ~MessageLoop();
 
   // Returns the type passed to the constructor.
   Type type() const { return type_; }
 
+  // Unique, non-repeating ID for this message loop.
+  PRInt32 id() const { return id_; }
+
   // Optional call to connect the thread name with this loop.
   void set_thread_name(const std::string& thread_name) {
     DCHECK(thread_name_.empty()) << "Should not rename this thread!";
     thread_name_ = thread_name;
   }
   const std::string& thread_name() const { return thread_name_; }
 
   // Returns the MessageLoop object for the current thread, or null if none.
@@ -369,16 +372,17 @@ public:
                        int delay_ms, bool nestable);
 
   // base::MessagePump::Delegate methods:
   virtual bool DoWork();
   virtual bool DoDelayedWork(base::TimeTicks* next_delayed_work_time);
   virtual bool DoIdleWork();
 
   Type type_;
+  PRInt32 id_;
 
   // A list of tasks that need to be processed by this instance.  Note that
   // this queue is only accessed (push/pop) by our current thread.
   TaskQueue work_queue_;
 
   // Contains delayed tasks, sorted by their 'delayed_run_time' property.
   DelayedTaskQueue delayed_work_queue_;
 
--- a/ipc/glue/AsyncChannel.cpp
+++ b/ipc/glue/AsyncChannel.cpp
@@ -266,22 +266,23 @@ AsyncChannel::ThreadLink::SendClose()
     // The I/O thread would then invoke OnChannelErrorFromLink().
     // As usual, we skip that process and just invoke the
     // OnChannelErrorFromLink() method directly.
     if (mTargetChan)
         mTargetChan->OnChannelErrorFromLink();
 }
 
 AsyncChannel::AsyncChannel(AsyncListener* aListener)
-  : mListener(aListener),
+  : mListener(aListener->asWeakPtr()),
     mChannelState(ChannelClosed),
     mWorkerLoop(),
     mChild(false),
     mChannelErrorTask(NULL),
-    mLink(NULL)
+    mLink(NULL),
+    mWorkerLoopID(-1)
 {
     MOZ_COUNT_CTOR(AsyncChannel);
 }
 
 AsyncChannel::~AsyncChannel()
 {
     MOZ_COUNT_DTOR(AsyncChannel);
     Clear();
@@ -291,16 +292,17 @@ bool
 AsyncChannel::Open(Transport* aTransport,
                    MessageLoop* aIOLoop,
                    AsyncChannel::Side aSide)
 {
     ProcessLink *link;
     NS_PRECONDITION(!mLink, "Open() called > once");
     mMonitor = new RefCountedMonitor();
     mWorkerLoop = MessageLoop::current();
+    mWorkerLoopID = mWorkerLoop->id();
     mLink = link = new ProcessLink(this);
     link->Open(aTransport, aIOLoop, aSide); // n.b.: sets mChild
     return true;
 }
 
 /* Opens a connection to another thread in the same process.
 
    This handshake proceeds as follows:
@@ -348,16 +350,17 @@ AsyncChannel::Open(AsyncChannel *aTarget
     NS_ASSERTION(ChannelConnected == mChannelState, "not connected when awoken");
     return (ChannelConnected == mChannelState);
 }
 
 void 
 AsyncChannel::CommonThreadOpenInit(AsyncChannel *aTargetChan, Side aSide)
 {
     mWorkerLoop = MessageLoop::current();
+    mWorkerLoopID = mWorkerLoop->id();
     mLink = new ThreadLink(this, aTargetChan);
     mChild = (aSide == Child); 
 }
 
 // Invoked when the other side has begun the open.
 void
 AsyncChannel::OnOpenAsSlave(AsyncChannel *aTargetChan, Side aSide)
 {
@@ -378,32 +381,27 @@ AsyncChannel::OnOpenAsSlave(AsyncChannel
 }
 
 void
 AsyncChannel::Close()
 {
     AssertWorkerThread();
 
     {
-        // n.b.: We increase the ref count of monitor temporarily
-        //       for the duration of this block.  Otherwise, the
-        //       function NotifyMaybeChannelError() will call
-        //       ::Clear() which can free the monitor.
-        nsRefPtr<RefCountedMonitor> monitor(mMonitor);
-        MonitorAutoLock lock(*monitor);
+        MonitorAutoLock lock(*mMonitor);
 
         if (ChannelError == mChannelState ||
             ChannelTimeout == mChannelState) {
             // See bug 538586: if the listener gets deleted while the
             // IO thread's NotifyChannelError event is still enqueued
             // and subsequently deletes us, then the error event will
             // also be deleted and the listener will never be notified
             // of the channel error.
             if (mListener) {
-                MonitorAutoUnlock unlock(*monitor);
+                MonitorAutoUnlock unlock(*mMonitor);
                 NotifyMaybeChannelError();
             }
             return;
         }
 
         if (ChannelConnected != mChannelState)
             // XXX be strict about this until there's a compelling reason
             // to relax
@@ -570,22 +568,30 @@ AsyncChannel::NotifyMaybeChannelError()
     mListener->OnChannelError();
 
     Clear();
 }
 
 void
 AsyncChannel::Clear()
 {
-    mListener = 0;
+    // Don't clear mWorkerLoopID; we use it in AssertLinkThread() and
+    // AssertWorkerThread().
+    //
+    // Also don't clear mListener.  If we clear it, then sending a message
+    // through this channel after it's Clear()'ed can cause this process to
+    // crash.
+    //
+    // In practice, mListener owns the channel, so the channel gets deleted
+    // before mListener.  But just to be safe, mListener is a weak pointer.
+
     mWorkerLoop = 0;
 
     delete mLink;
     mLink = 0;
-    mMonitor = 0;
 
     if (mChannelErrorTask) {
         mChannelErrorTask->Cancel();
         mChannelErrorTask = NULL;
     }
 }
 
 static void
--- a/ipc/glue/AsyncChannel.h
+++ b/ipc/glue/AsyncChannel.h
@@ -6,16 +6,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef ipc_glue_AsyncChannel_h
 #define ipc_glue_AsyncChannel_h 1
 
 #include "base/basictypes.h"
 #include "base/message_loop.h"
 
+#include "mozilla/WeakPtr.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/ipc/Transport.h"
 
 //-----------------------------------------------------------------------------
 
 namespace mozilla {
 namespace ipc {
 
@@ -57,17 +58,19 @@ protected:
         ChannelClosing,
         ChannelError
     };
 
 public:
     typedef IPC::Message Message;
     typedef mozilla::ipc::Transport Transport;
 
-    class /*NS_INTERFACE_CLASS*/ AsyncListener: protected HasResultCodes
+    class /*NS_INTERFACE_CLASS*/ AsyncListener
+        : protected HasResultCodes
+        , public mozilla::SupportsWeakPtr<AsyncListener>
     {
     public:
         virtual ~AsyncListener() { }
 
         virtual void OnChannelClose() = 0;
         virtual void OnChannelError() = 0;
         virtual Result OnMessageReceived(const Message& aMessage) = 0;
         virtual void OnProcessingError(Result aError) = 0;
@@ -194,24 +197,24 @@ public:
     };
 
 protected:
     // The "link" thread is either the I/O thread (ProcessLink) or the
     // other actor's work thread (ThreadLink).  In either case, it is
     // NOT our worker thread.
     void AssertLinkThread() const
     {
-        NS_ABORT_IF_FALSE(mWorkerLoop != MessageLoop::current(),
+        NS_ABORT_IF_FALSE(mWorkerLoopID != MessageLoop::current()->id(),
                           "on worker thread but should not be!");
     }
 
     // Can be run on either thread
     void AssertWorkerThread() const
     {
-        NS_ABORT_IF_FALSE(mWorkerLoop == MessageLoop::current(),
+        NS_ABORT_IF_FALSE(mWorkerLoopID == MessageLoop::current()->id(),
                           "not on worker thread!");
     }
 
     bool Connected() const {
         mMonitor->AssertCurrentThreadOwns();
         // The transport layer allows us to send messages before
         // receiving the "connected" ack from the remote side.
         return (ChannelOpening == mChannelState ||
@@ -253,20 +256,24 @@ protected:
     }
     void NotifyChannelClosed();
     void NotifyMaybeChannelError();
     void OnOpenAsSlave(AsyncChannel *aTargetChan, Side aSide);
     void CommonThreadOpenInit(AsyncChannel *aTargetChan, Side aSide);
 
     virtual void Clear();
 
-    AsyncListener* mListener;
+    mozilla::WeakPtr<AsyncListener> mListener;
     ChannelState mChannelState;
     nsRefPtr<RefCountedMonitor> mMonitor;
     MessageLoop* mWorkerLoop;   // thread where work is done
     bool mChild;                // am I the child or parent?
     CancelableTask* mChannelErrorTask; // NotifyMaybeChannelError runnable
     Link *mLink;                // link to other thread/process
+
+    // id() of mWorkerLoop.  This persists even after mWorkerLoop is cleared
+    // during channel shutdown.
+    int mWorkerLoopID;
 };
 
 } // namespace ipc
 } // namespace mozilla
 #endif  // ifndef ipc_glue_AsyncChannel_h
--- a/ipc/glue/RPCChannel.h
+++ b/ipc/glue/RPCChannel.h
@@ -147,17 +147,17 @@ protected:
 protected:
     virtual void OnMessageReceivedFromLink(const Message& msg) MOZ_OVERRIDE;
     virtual void OnChannelErrorFromLink() MOZ_OVERRIDE;
 
 private:
     // Called on worker thread only
 
     RPCListener* Listener() const {
-        return static_cast<RPCListener*>(mListener);
+        return static_cast<RPCListener*>(mListener.get());
     }
 
     virtual bool ShouldDeferNotifyMaybeError() const MOZ_OVERRIDE {
         return IsOnCxxStack();
     }
 
     bool EventOccurred() const;
 
--- a/ipc/glue/SyncChannel.cpp
+++ b/ipc/glue/SyncChannel.cpp
@@ -137,17 +137,17 @@ SyncChannel::OnDispatchMessage(const Mes
     AssertWorkerThread();
     NS_ABORT_IF_FALSE(msg.is_sync(), "only sync messages here");
     NS_ABORT_IF_FALSE(!msg.is_reply(), "wasn't awaiting reply");
 
     Message* reply = 0;
 
     mProcessingSyncMessage = true;
     Result rv =
-        static_cast<SyncListener*>(mListener)->OnMessageReceived(msg, reply);
+        static_cast<SyncListener*>(mListener.get())->OnMessageReceived(msg, reply);
     mProcessingSyncMessage = false;
 
     if (!MaybeHandleError(rv, "SyncChannel")) {
         // FIXME/cjones: error handling; OnError()?
         delete reply;
         reply = new Message();
         reply->set_sync();
         reply->set_reply();
@@ -226,17 +226,17 @@ bool
 SyncChannel::ShouldContinueFromTimeout()
 {
     AssertWorkerThread();
     mMonitor->AssertCurrentThreadOwns();
 
     bool cont;
     {
         MonitorAutoUnlock unlock(*mMonitor);
-        cont = static_cast<SyncListener*>(mListener)->OnReplyTimeout();
+        cont = static_cast<SyncListener*>(mListener.get())->OnReplyTimeout();
     }
 
     if (!cont) {
         // NB: there's a sublety here.  If parents were allowed to
         // send sync messages to children, then it would be possible
         // for this synchronous close-on-timeout to race with async
         // |OnMessageReceived| tasks arriving from the child, posted
         // to the worker thread's event loop.  This would complicate
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -4168,18 +4168,22 @@ pref("wap.UAProf.url", "");
 pref("wap.UAProf.tagname", "x-wap-profile");
 
 // Retrieval mode for MMS
 // manual: Manual retrieval mode.
 // automatic: Automatic retrieval mode even in roaming.
 // automatic-home: Automatic retrieval mode in home network.
 // never: Never retrieval mode.
 pref("dom.mms.retrieval_mode", "manual");
-pref("dom.mms.retrievalRetryCount", 3);
-pref("dom.mms.retrievalRetryInterval", 300000);
+
+pref("dom.mms.sendRetryCount", 3);
+pref("dom.mms.sendRetryInterval", 300000);
+
+pref("dom.mms.retrievalRetryCount", 4);
+pref("dom.mms.retrievalRetryIntervals", "60000,300000,600000,1800000");
 
 // If the user puts a finger down on an element and we think the user
 // might be executing a pan gesture, how long do we wait before
 // tentatively deciding the gesture is actually a tap and activating
 // the target element?
 pref("ui.touch_activation.delay_ms", 100);
 
 // nsMemoryInfoDumper can watch a fifo in the temp directory and take various
--- a/testing/xpcshell/xpcshell.ini
+++ b/testing/xpcshell/xpcshell.ini
@@ -5,16 +5,17 @@
 [include:chrome/test/unit/xpcshell.ini]
 [include:intl/locale/tests/unit/xpcshell.ini]
 [include:netwerk/cookie/test/unit/xpcshell.ini]
 [include:modules/libjar/zipwriter/test/unit/xpcshell.ini]
 [include:uriloader/exthandler/tests/unit/xpcshell.ini]
 [include:parser/xml/test/unit/xpcshell.ini]
 [include:image/test/unit/xpcshell.ini]
 [include:dom/activities/tests/unit/xpcshell.ini]
+[include:dom/apps/tests/unit/xpcshell.ini]
 [include:dom/encoding/test/unit/xpcshell.ini]
 [include:dom/plugins/test/unit/xpcshell.ini]
 [include:dom/mobilemessage/tests/xpcshell.ini]
 [include:dom/mms/tests/xpcshell.ini]
 [include:dom/network/tests/unit/xpcshell.ini]
 [include:dom/network/tests/unit_ipc/xpcshell.ini]
 [include:dom/network/tests/unit_stats/xpcshell.ini]
 [include:dom/payment/tests/unit/xpcshell.ini]