Merge m-c to f-t
authorPhil Ringnalda <philringnalda@gmail.com>
Sun, 08 Dec 2013 08:31:41 -0800
changeset 174161 d4988a27c498999a205223a8a0882e37d225c4b4
parent 174160 550462c1dae0f6e8fd81357b2ece2b74fcd3831f (current diff)
parent 174130 2827800c529aee3ea948dfd9674fd50b91481c59 (diff)
child 174162 846cd38148100d51ffc8beed0ccc23458cff1683
push id3224
push userlsblakk@mozilla.com
push dateTue, 04 Feb 2014 01:06:49 +0000
treeherdermozilla-beta@60c04d0987f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone28.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 f-t
accessible/tests/mochitest/testTextboxes.js
accessible/tests/mochitest/test_textboxes.html
accessible/tests/mochitest/test_textboxes.xul
--- a/CLOBBER
+++ b/CLOBBER
@@ -13,9 +13,9 @@
 #          |               |
 #          O <-- Clobber   O  <-- Clobber
 #
 # Note: The description below will be part of the error message shown to users.
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
-Bug 937317 required a clobber on Windows landing, backout probably will too
+Bug 908695 required a clobber on Windows because bug 928195
--- a/accessible/src/base/nsAccessibilityService.cpp
+++ b/accessible/src/base/nsAccessibilityService.cpp
@@ -1305,17 +1305,17 @@ nsAccessibilityService::CreateAccessible
 
   } else if (role.EqualsLiteral("xul:tabs")) {
     accessible = new XULTabsAccessible(aContent, aDoc);
 
   } else if (role.EqualsLiteral("xul:text")) {
     accessible = new XULLabelAccessible(aContent, aDoc);
 
   } else if (role.EqualsLiteral("xul:textbox")) {
-    accessible = new XULTextFieldAccessible(aContent, aDoc);
+    accessible = new EnumRoleAccessible(aContent, aDoc, roles::SECTION);
 
   } else if (role.EqualsLiteral("xul:thumb")) {
     accessible = new XULThumbAccessible(aContent, aDoc);
 
   } else if (role.EqualsLiteral("xul:tree")) {
     accessible = CreateAccessibleForXULTree(aContent, aDoc);
 
   } else if (role.EqualsLiteral("xul:treecolumns")) {
--- a/accessible/src/generic/Accessible.cpp
+++ b/accessible/src/generic/Accessible.cpp
@@ -1047,108 +1047,86 @@ Accessible::TakeFocus()
   nsCOMPtr<nsIDOMElement> element(do_QueryInterface(focusContent));
   nsFocusManager* fm = nsFocusManager::GetFocusManager();
   if (fm)
     fm->SetFocus(element, 0);
 
   return NS_OK;
 }
 
-ENameValueFlag
-Accessible::GetHTMLName(nsString& aLabel)
+void
+Accessible::XULElmName(DocAccessible* aDocument,
+                       nsIContent* aElm, nsString& aName)
 {
-  Accessible* labelAcc = nullptr;
-  HTMLLabelIterator iter(Document(), this);
-  while ((labelAcc = iter.Next())) {
-    nsTextEquivUtils::AppendTextEquivFromContent(this, labelAcc->GetContent(),
-                                                 &aLabel);
-    aLabel.CompressWhitespace();
-  }
-
-  if (!aLabel.IsEmpty())
-    return eNameOK;
-
-  nsTextEquivUtils::GetNameFromSubtree(this, aLabel);
-  return aLabel.IsEmpty() ? eNameOK : eNameFromSubtree;
-}
-
-/**
-  * 3 main cases for XUL Controls to be labeled
-  *   1 - control contains label="foo"
-  *   2 - control has, as a child, a label element
-  *        - label has either value="foo" or children
-  *   3 - non-child label contains control="controlID"
-  *        - label has either value="foo" or children
-  * Once a label is found, the search is discontinued, so a control
-  *  that has a label child as well as having a label external to
-  *  the control that uses the control="controlID" syntax will use
-  *  the child label for its Name.
-  */
-ENameValueFlag
-Accessible::GetXULName(nsString& aName)
-{
+  /**
+   * 3 main cases for XUL Controls to be labeled
+   *   1 - control contains label="foo"
+   *   2 - control has, as a child, a label element
+   *        - label has either value="foo" or children
+   *   3 - non-child label contains control="controlID"
+   *        - label has either value="foo" or children
+   * Once a label is found, the search is discontinued, so a control
+   *  that has a label child as well as having a label external to
+   *  the control that uses the control="controlID" syntax will use
+   *  the child label for its Name.
+   */
+
   // CASE #1 (via label attribute) -- great majority of the cases
-  nsCOMPtr<nsIDOMXULLabeledControlElement> labeledEl =
-    do_QueryInterface(mContent);
+  nsCOMPtr<nsIDOMXULLabeledControlElement> labeledEl = do_QueryInterface(aElm);
   if (labeledEl) {
     labeledEl->GetLabel(aName);
   } else {
-    nsCOMPtr<nsIDOMXULSelectControlItemElement> itemEl =
-      do_QueryInterface(mContent);
+    nsCOMPtr<nsIDOMXULSelectControlItemElement> itemEl = do_QueryInterface(aElm);
     if (itemEl) {
       itemEl->GetLabel(aName);
     } else {
-      nsCOMPtr<nsIDOMXULSelectControlElement> select =
-        do_QueryInterface(mContent);
+      nsCOMPtr<nsIDOMXULSelectControlElement> select = do_QueryInterface(aElm);
       // Use label if this is not a select control element which 
       // uses label attribute to indicate which option is selected
       if (!select) {
-        nsCOMPtr<nsIDOMXULElement> xulEl(do_QueryInterface(mContent));
+        nsCOMPtr<nsIDOMXULElement> xulEl(do_QueryInterface(aElm));
         if (xulEl)
           xulEl->GetAttribute(NS_LITERAL_STRING("label"), aName);
       }
     }
   }
 
   // CASES #2 and #3 ------ label as a child or <label control="id" ... > </label>
   if (aName.IsEmpty()) {
     Accessible* labelAcc = nullptr;
-    XULLabelIterator iter(Document(), mContent);
+    XULLabelIterator iter(aDocument, aElm);
     while ((labelAcc = iter.Next())) {
       nsCOMPtr<nsIDOMXULLabelElement> xulLabel =
         do_QueryInterface(labelAcc->GetContent());
       // Check if label's value attribute is used
       if (xulLabel && NS_SUCCEEDED(xulLabel->GetValue(aName)) && aName.IsEmpty()) {
         // If no value attribute, a non-empty label must contain
         // children that define its text -- possibly using HTML
         nsTextEquivUtils::
-          AppendTextEquivFromContent(this, labelAcc->GetContent(), &aName);
+          AppendTextEquivFromContent(labelAcc, labelAcc->GetContent(), &aName);
       }
     }
   }
 
   aName.CompressWhitespace();
   if (!aName.IsEmpty())
-    return eNameOK;
+    return;
 
   // Can get text from title of <toolbaritem> if we're a child of a <toolbaritem>
-  nsIContent *bindingParent = mContent->GetBindingParent();
-  nsIContent *parent = bindingParent? bindingParent->GetParent() :
-                                      mContent->GetParent();
+  nsIContent *bindingParent = aElm->GetBindingParent();
+  nsIContent* parent =
+    bindingParent? bindingParent->GetParent() : aElm->GetParent();
   while (parent) {
     if (parent->Tag() == nsGkAtoms::toolbaritem &&
         parent->GetAttr(kNameSpaceID_None, nsGkAtoms::title, aName)) {
       aName.CompressWhitespace();
-      return eNameOK;
+      return;
     }
     parent = parent->GetParent();
   }
-
-  nsTextEquivUtils::GetNameFromSubtree(this, aName);
-  return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
 }
 
 nsresult
 Accessible::HandleAccEvent(AccEvent* aEvent)
 {
   NS_ENSURE_ARG_POINTER(aEvent);
 
   nsCOMPtr<nsIObserverService> obsService = services::GetObserverService();
@@ -2473,21 +2451,40 @@ Accessible::ARIAName(nsString& aName)
     aName.CompressWhitespace();
   }
 }
 
 // Accessible protected
 ENameValueFlag
 Accessible::NativeName(nsString& aName)
 {
-  if (mContent->IsHTML())
-    return GetHTMLName(aName);
-
-  if (mContent->IsXUL())
-    return GetXULName(aName);
+  if (mContent->IsHTML()) {
+    Accessible* label = nullptr;
+    HTMLLabelIterator iter(Document(), this);
+    while ((label = iter.Next())) {
+      nsTextEquivUtils::AppendTextEquivFromContent(this, label->GetContent(),
+                                                   &aName);
+      aName.CompressWhitespace();
+    }
+
+    if (!aName.IsEmpty())
+      return eNameOK;
+
+    nsTextEquivUtils::GetNameFromSubtree(this, aName);
+    return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
+  }
+
+  if (mContent->IsXUL()) {
+    XULElmName(mDoc, mContent, aName);
+    if (!aName.IsEmpty())
+      return eNameOK;
+
+    nsTextEquivUtils::GetNameFromSubtree(this, aName);
+    return aName.IsEmpty() ? eNameOK : eNameFromSubtree;
+  }
 
   if (mContent->IsSVG()) {
     // If user agents need to choose among multiple ‘desc’ or ‘title’ elements
     // for processing, the user agent shall choose the first one.
     for (nsIContent* childElm = mContent->GetFirstChild(); childElm;
          childElm = childElm->GetNextSibling()) {
       if (childElm->IsSVG(nsGkAtoms::title)) {
         nsTextEquivUtils::AppendTextEquivFromContent(this, childElm, &aName);
--- a/accessible/src/generic/Accessible.h
+++ b/accessible/src/generic/Accessible.h
@@ -874,20 +874,20 @@ protected:
   // Name helpers
 
   /**
    * Returns the accessible name specified by ARIA.
    */
   void ARIAName(nsString& aName);
 
   /**
-   * Compute the name of HTML/XUL node.
+   * Return the name for XUL element.
    */
-  mozilla::a11y::ENameValueFlag GetHTMLName(nsString& aName);
-  mozilla::a11y::ENameValueFlag GetXULName(nsString& aName);
+  static void XULElmName(DocAccessible* aDocument,
+                         nsIContent* aElm, nsString& aName);
 
   // helper method to verify frames
   static nsresult GetFullKeyName(const nsAString& aModifierName, const nsAString& aKeyName, nsAString& aStringOut);
 
   /**
    * Return an accessible for the given DOM node, or if that node isn't
    * accessible, return the accessible for the next DOM node which has one
    * (based on forward depth first search).
--- a/accessible/src/html/HTMLFormControlAccessible.cpp
+++ b/accessible/src/html/HTMLFormControlAccessible.cpp
@@ -323,26 +323,20 @@ HTMLTextFieldAccessible::NativeAttribute
 
 ENameValueFlag
 HTMLTextFieldAccessible::NativeName(nsString& aName)
 {
   ENameValueFlag nameFlag = Accessible::NativeName(aName);
   if (!aName.IsEmpty())
     return nameFlag;
 
-  if (mContent->GetBindingParent()) {
-    // XXX: bug 459640
-    // There's a binding parent.
-    // This means we're part of another control, so use parent accessible for name.
-    // This ensures that a textbox inside of a XUL widget gets
-    // an accessible name.
-    Accessible* parent = Parent();
-    if (parent)
-      parent->GetName(aName);
-  }
+  // If part of compound of XUL widget then grab a name from XUL widget element.
+  nsIContent* widgetElm = XULWidgetElm();
+  if (widgetElm)
+    XULElmName(mDoc, widgetElm, aName);
 
   if (!aName.IsEmpty())
     return eNameOK;
 
   // text inputs and textareas might have useful placeholder text
   mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::placeholder, aName);
   return eNameOK;
 }
@@ -364,18 +358,23 @@ HTMLTextFieldAccessible::Value(nsString&
   if (input)
     input->GetValue(aValue);
 }
 
 void
 HTMLTextFieldAccessible::ApplyARIAState(uint64_t* aState) const
 {
   HyperTextAccessibleWrap::ApplyARIAState(aState);
+  aria::MapToState(aria::eARIAAutoComplete, mContent->AsElement(), aState);
 
-  aria::MapToState(aria::eARIAAutoComplete, mContent->AsElement(), aState);
+  // If part of compound of XUL widget then pick up ARIA stuff from XUL widget
+  // element.
+  nsIContent* widgetElm = XULWidgetElm();
+  if (widgetElm)
+    aria::MapToState(aria::eARIAAutoComplete, widgetElm->AsElement(), aState);
 }
 
 uint64_t
 HTMLTextFieldAccessible::NativeState()
 {
   uint64_t state = HyperTextAccessibleWrap::NativeState();
 
   // can be focusable, focused, protected. readonly, unavailable, selected
@@ -403,19 +402,18 @@ HTMLTextFieldAccessible::NativeState()
     state |= states::HASPOPUP | states::SUPPORTS_AUTOCOMPLETION;
     return state;
   }
 
   // Expose autocomplete state if it has associated autocomplete list.
   if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::list))
     return state | states::SUPPORTS_AUTOCOMPLETION | states::HASPOPUP;
 
-  // No parent can mean a fake widget created for XUL textbox. If accessible
-  // is unattached from tree then we don't care.
-  if (mParent && Preferences::GetBool("browser.formfill.enable")) {
+  // Ordinal XUL textboxes don't support autocomplete.
+  if (!XULWidgetElm() && Preferences::GetBool("browser.formfill.enable")) {
     // Check to see if autocompletion is allowed on this input. We don't expose
     // it for password fields even though the entire password can be remembered
     // for a page if the user asks it to be. However, the kind of autocomplete
     // we're talking here is based on what the user types, where a popup of
     // possible choices comes up.
     nsAutoString autocomplete;
     mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::autocomplete,
                       autocomplete);
--- a/accessible/src/html/HTMLFormControlAccessible.h
+++ b/accessible/src/html/HTMLFormControlAccessible.h
@@ -138,16 +138,21 @@ public:
 
   // Widgets
   virtual bool IsWidget() const;
   virtual Accessible* ContainerWidget() const;
 
 protected:
   // Accessible
   virtual ENameValueFlag NativeName(nsString& aName) MOZ_OVERRIDE;
+
+  /**
+   * Return a XUL widget element this input is part of.
+   */
+  nsIContent* XULWidgetElm() const { return mContent->GetBindingParent(); }
 };
 
 
 /**
  * Accessible for input@type="file" element.
  */
 class HTMLFileInputAccessible : public HyperTextAccessibleWrap
 {
--- a/accessible/src/xul/XULFormControlAccessible.cpp
+++ b/accessible/src/xul/XULFormControlAccessible.cpp
@@ -631,197 +631,8 @@ XULToolbarSeparatorAccessible::NativeRol
   return roles::SEPARATOR;
 }
 
 uint64_t
 XULToolbarSeparatorAccessible::NativeState()
 {
   return 0;
 }
-
-////////////////////////////////////////////////////////////////////////////////
-// XULTextFieldAccessible
-////////////////////////////////////////////////////////////////////////////////
-
-XULTextFieldAccessible::
- XULTextFieldAccessible(nsIContent* aContent, DocAccessible* aDoc) :
- HyperTextAccessibleWrap(aContent, aDoc)
-{
-}
-
-NS_IMPL_ISUPPORTS_INHERITED2(XULTextFieldAccessible,
-                             Accessible,
-                             nsIAccessibleText,
-                             nsIAccessibleEditableText)
-
-////////////////////////////////////////////////////////////////////////////////
-// XULTextFieldAccessible: nsIAccessible
-
-void
-XULTextFieldAccessible::Value(nsString& aValue)
-{
-  aValue.Truncate();
-  if (NativeRole() == roles::PASSWORD_TEXT) // Don't return password text!
-    return;
-
-  nsCOMPtr<nsIDOMXULTextBoxElement> textBox(do_QueryInterface(mContent));
-  if (textBox) {
-    textBox->GetValue(aValue);
-    return;
-  }
-
-  nsCOMPtr<nsIDOMXULMenuListElement> menuList(do_QueryInterface(mContent));
-  if (menuList)
-    menuList->GetLabel(aValue);
-}
-
-void
-XULTextFieldAccessible::ApplyARIAState(uint64_t* aState) const
-{
-  HyperTextAccessibleWrap::ApplyARIAState(aState);
-
-  aria::MapToState(aria::eARIAAutoComplete, mContent->AsElement(), aState);
-}
-
-uint64_t
-XULTextFieldAccessible::NativeState()
-{
-  uint64_t state = HyperTextAccessibleWrap::NativeState();
-
-  nsCOMPtr<nsIContent> inputField(GetInputField());
-  NS_ENSURE_TRUE(inputField, state);
-
-  // Create a temporary accessible from the HTML text field to get
-  // the accessible state from. Doesn't add to cache into document cache.
-  nsRefPtr<HTMLTextFieldAccessible> tempAccessible =
-    new HTMLTextFieldAccessible(inputField, mDoc);
-  if (tempAccessible)
-    return state | tempAccessible->NativeState();
-  return state;
-}
-
-role
-XULTextFieldAccessible::NativeRole()
-{
-  if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
-                            nsGkAtoms::password, eIgnoreCase))
-    return roles::PASSWORD_TEXT;
-  
-  return roles::ENTRY;
-}
-
-/**
-  * Only one actions available
-  */
-uint8_t
-XULTextFieldAccessible::ActionCount()
-{
-  return 1;
-}
-
-/**
-  * Return the name of our only action
-  */
-NS_IMETHODIMP
-XULTextFieldAccessible::GetActionName(uint8_t aIndex, nsAString& aName)
-{
-  if (aIndex == eAction_Click) {
-    aName.AssignLiteral("activate"); 
-    return NS_OK;
-  }
-  return NS_ERROR_INVALID_ARG;
-}
-
-/**
-  * Tell the button to do its action
-  */
-NS_IMETHODIMP
-XULTextFieldAccessible::DoAction(uint8_t index)
-{
-  if (index == 0) {
-    nsCOMPtr<nsIDOMXULElement> element(do_QueryInterface(mContent));
-    if (element)
-    {
-      element->Focus();
-      return NS_OK;
-    }
-    return NS_ERROR_FAILURE;
-  }
-  return NS_ERROR_INVALID_ARG;
-}
-
-bool
-XULTextFieldAccessible::CanHaveAnonChildren()
-{
-  return false;
-}
-
-bool
-XULTextFieldAccessible::IsAcceptableChild(Accessible* aPossibleChild) const
-{
-  // XXX: entry shouldn't contain anything but text leafs. Currently it may
-  // contain a trailing fake HTML br element added for layout needs. We don't
-  // need to expose it since it'd be confusing for AT.
-  return aPossibleChild->IsTextLeaf();
-}
-
-already_AddRefed<nsIEditor>
-XULTextFieldAccessible::GetEditor() const
-{
-  nsCOMPtr<nsIContent> inputField = GetInputField();
-  nsCOMPtr<nsIDOMNSEditableElement> editableElt(do_QueryInterface(inputField));
-  if (!editableElt)
-    return nullptr;
-
-  nsCOMPtr<nsIEditor> editor;
-  editableElt->GetEditor(getter_AddRefs(editor));
-  return editor.forget();
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// XULTextFieldAccessible: Accessible protected
-
-void
-XULTextFieldAccessible::CacheChildren()
-{
-  NS_ENSURE_TRUE_VOID(mDoc);
-  // Create child accessibles for native anonymous content of underlying HTML
-  // input element.
-  nsCOMPtr<nsIContent> inputContent(GetInputField());
-  if (!inputContent)
-    return;
-
-  TreeWalker walker(this, inputContent);
-  while (Accessible* child = walker.NextChild())
-    AppendChild(child);
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// XULTextFieldAccessible: HyperTextAccessible protected
-
-already_AddRefed<nsFrameSelection>
-XULTextFieldAccessible::FrameSelection() const
-{
-  nsCOMPtr<nsIContent> inputContent(GetInputField());
-  NS_ASSERTION(inputContent, "No input content");
-  if (!inputContent)
-    return nullptr;
-
-  nsIFrame* frame = inputContent->GetPrimaryFrame();
-  return frame ? frame->GetFrameSelection() : nullptr;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-// XULTextFieldAccessible protected
-
-already_AddRefed<nsIContent>
-XULTextFieldAccessible::GetInputField() const
-{
-  nsCOMPtr<nsIDOMNode> inputFieldDOMNode;
-  nsCOMPtr<nsIDOMXULTextBoxElement> textBox = do_QueryInterface(mContent);
-  if (textBox)
-    textBox->GetInputField(getter_AddRefs(inputFieldDOMNode));
-
-  NS_ASSERTION(inputFieldDOMNode, "No input field for XULTextFieldAccessible");
-
-  nsCOMPtr<nsIContent> inputField = do_QueryInterface(inputFieldDOMNode);
-  return inputField.forget();
-}
--- a/accessible/src/xul/XULFormControlAccessible.h
+++ b/accessible/src/xul/XULFormControlAccessible.h
@@ -210,54 +210,13 @@ public:
   XULToolbarSeparatorAccessible(nsIContent* aContent,
                                 DocAccessible* aDoc);
 
   // Accessible
   virtual mozilla::a11y::role NativeRole();
   virtual uint64_t NativeState();
 };
 
-/**
- * Used for XUL textbox element.
- */
-class XULTextFieldAccessible : public HyperTextAccessibleWrap
-{
-public:
-  enum { eAction_Click = 0 };
-
-  XULTextFieldAccessible(nsIContent* aContent, DocAccessible* aDoc);
-
-  NS_DECL_ISUPPORTS_INHERITED
-
-  // nsIAccessible
-  NS_IMETHOD GetActionName(uint8_t aIndex, nsAString& aName);
-  NS_IMETHOD DoAction(uint8_t index);
-
-  // HyperTextAccessible
-  virtual already_AddRefed<nsIEditor> GetEditor() const;
-
-  // Accessible
-  virtual void Value(nsString& aValue);
-  virtual void ApplyARIAState(uint64_t* aState) const;
-  virtual mozilla::a11y::role NativeRole();
-  virtual uint64_t NativeState();
-  virtual bool CanHaveAnonChildren();
-  virtual bool IsAcceptableChild(Accessible* aPossibleChild) const MOZ_OVERRIDE;
-
-  // ActionAccessible
-  virtual uint8_t ActionCount();
-
-protected:
-  // Accessible
-  virtual void CacheChildren();
-
-  // HyperTextAccessible
-  virtual already_AddRefed<nsFrameSelection> FrameSelection() const;
-
-  // nsXULTextFieldAccessible
-  already_AddRefed<nsIContent> GetInputField() const;
-};
-
 } // namespace a11y
 } // namespace mozilla
 
 #endif
 
--- a/accessible/src/xul/XULListboxAccessible.cpp
+++ b/accessible/src/xul/XULListboxAccessible.cpp
@@ -630,17 +630,17 @@ XULListitemAccessible::NativeName(nsStri
   if (childContent) {
     if (childContent->NodeInfo()->Equals(nsGkAtoms::listcell,
                                          kNameSpaceID_XUL)) {
       childContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName);
       return eNameOK;
     }
   }
 
-  return GetXULName(aName);
+  return Accessible::NativeName(aName);
 }
 
 role
 XULListitemAccessible::NativeRole()
 {
   Accessible* list = GetListAccessible();
   if (!list) {
     NS_ERROR("No list accessible for listitem accessible!");
--- a/accessible/tests/mochitest/a11y.ini
+++ b/accessible/tests/mochitest/a11y.ini
@@ -16,21 +16,18 @@ support-files =
   name.js
   pivot.js
   relations.js
   role.js
   selectable.js
   states.js
   table.js
   value.js
-  testTextboxes.js
   text.js
   treeview.css
   treeview.js
 
 [test_aria_token_attrs.html]
 [test_bug420863.html]
 [test_descr.html]
 [test_nsIAccessibleDocument.html]
 [test_nsIAccessibleImage.html]
 [test_OuterDocAccessible.html]
-[test_textboxes.html]
-[test_textboxes.xul]
--- a/accessible/tests/mochitest/actions.js
+++ b/accessible/tests/mochitest/actions.js
@@ -86,16 +86,33 @@ function testActions(aArray)
     var invoker = new actionInvoker(accOrElmOrID, actionIndex, actionName,
                                     eventSeq);
     gActionsQueue.push(invoker);
   }
 
   gActionsQueue.invoke();
 }
 
+/**
+ * Test action names and descriptions.
+ */
+function testActionNames(aID, aActions)
+{
+  var actions = (typeof aActions == "string") ?
+    [ aActions ] : (aActions || []);
+
+  var acc = getAccessible(aID);
+  is(acc.actionCount, actions.length, "Wong number of actions.");
+  for (var i = 0; i < actions.length; i++ ) {
+    is(acc.getActionName(i), actions[i], "Wrong action name at " + i + " index.");
+    is(acc.getActionDescription(0), gActionDescrMap[actions[i]],
+       "Wrong action description at " + i + "index.");
+  }
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Private
 
 var gActionsQueue = null;
 
 function actionInvoker(aAccOrElmOrId, aActionIndex, aActionName, aEventSeq)
 {
   this.invoke = function actionInvoker_invoke()
@@ -146,8 +163,25 @@ function checkerOfActionInvoker(aType, a
   }
 
   this.check = function check(aEvent)
   {
     if (aActionObj && "checkOnClickEvent" in aActionObj)
       aActionObj.checkOnClickEvent(aEvent);
   }
 }
+
+var gActionDescrMap =
+{
+  jump: "Jump",
+  press: "Press",
+  check: "Check",
+  uncheck: "Uncheck",
+  select: "Select",
+  open: "Open",
+  close: "Close",
+  switch: "Switch",
+  click: "Click",
+  collapse: "Collapse",
+  expand: "Expand",
+  activate: "Activate",
+  cycle: "Cycle"
+};
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -324,16 +324,24 @@ function getTabDocAccessible(aAccOrElmOr
  */
 function getApplicationAccessible()
 {
   return gAccRetrieval.getApplicationAccessible().
     QueryInterface(nsIAccessibleApplication);
 }
 
 /**
+ * A version of accessible tree testing, doesn't fail if tree is not complete.
+ */
+function testElm(aID, aTreeObj)
+{
+  testAccessibleTree(aID, aTreeObj, kSkipTreeFullCheck);
+}
+
+/**
  * Flags used for testAccessibleTree
  */
 const kSkipTreeFullCheck = 1;
 
 /**
  * Compare expected and actual accessibles trees.
  *
  * @param  aAccOrElmOrID  [in] accessible identifier
@@ -365,21 +373,17 @@ function testAccessibleTree(aAccOrElmOrI
   }
 
   // Test accessible properties.
   for (var prop in accTree) {
     var msg = "Wrong value of property '" + prop + "' for " + prettyName(acc) + ".";
 
     switch (prop) {
     case "actions": {
-      var actions = (typeof accTree.actions == "string") ?
-        [ accTree.actions ] : (accTree.actions || []);
-      is(acc.actionCount, actions.length, "Wong number of actions.");
-      for (var i = 0; i < actions.length; i++ )
-        is(acc.getActionName(i), actions[i], "Wrong action name at " + i + " index.");
+      testActionNames(acc, accTree.actions);
       break;
     }
 
     case "attributes":
       testAttrs(acc, accTree[prop], true);
       break;
 
     case "absentAttributes":
--- a/accessible/tests/mochitest/elm/test_HTMLSpec.html
+++ b/accessible/tests/mochitest/elm/test_HTMLSpec.html
@@ -6,32 +6,29 @@
         href="chrome://mochikit/content/tests/SimpleTest/test.css" />
 
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 
   <script type="application/javascript"
           src="../common.js"></script>
   <script type="application/javascript"
+          src="../actions.js"></script>
+  <script type="application/javascript"
           src="../role.js"></script>
   <script type="application/javascript"
           src="../states.js"></script>
   <script type="application/javascript"
           src="../attributes.js"></script>
   <script type="application/javascript"
           src="../relations.js"></script>
   <script type="application/javascript"
           src="../name.js"></script>
 
   <script type="application/javascript">
-    function testElm(aID, aTreeObj)
-    {
-      testAccessibleTree(aID, aTreeObj, kSkipTreeFullCheck);
-    }
-
     function doTest()
     {
       //////////////////////////////////////////////////////////////////////////
       // HTML:a@href
 
       var obj = {
         role: ROLE_LINK,
         states: STATE_LINKED,
--- a/accessible/tests/mochitest/events/test_caretmove.xul
+++ b/accessible/tests/mochitest/events/test_caretmove.xul
@@ -23,30 +23,30 @@
 
     //gA11yEventDumpID = "eventdump"; // debug stuff
     //gA11yEventDumpToConsole = true;
 
     var gQueue = null;
 
     function doTests()
     {
-
       if (MAC) {
         todo(false, "Make these tests pass on OSX (bug 650294)");
         SimpleTest.finish();
         return;
       }
 
       gQueue = new eventQueue(EVENT_TEXT_CARET_MOVED);
 
       var id = "textbox";
-      gQueue.push(new synthFocus(id, new caretMoveChecker(5, id)));
-      gQueue.push(new synthSelectAll(id, new caretMoveChecker(5, id)));
-      gQueue.push(new synthHomeKey(id, new caretMoveChecker(0, id)));
-      gQueue.push(new synthRightKey(id, new caretMoveChecker(1, id)));
+      var input = getNode(id).inputField;
+      gQueue.push(new synthFocus(id, new caretMoveChecker(5, input)));
+      gQueue.push(new synthSelectAll(id, new caretMoveChecker(5, input)));
+      gQueue.push(new synthHomeKey(id, new caretMoveChecker(0, input)));
+      gQueue.push(new synthRightKey(id, new caretMoveChecker(1, input)));
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   </script>
 
--- a/accessible/tests/mochitest/events/test_focus_general.xul
+++ b/accessible/tests/mochitest/events/test_focus_general.xul
@@ -33,18 +33,20 @@
     //gA11yEventDumpToConsole = true; // debug stuff
 
     var gQueue = null;
     function doTests()
     {
       // Test focus events.
       gQueue = new eventQueue();
 
-      gQueue.push(new synthFocus("textbox"));
-      gQueue.push(new synthFocus("textbox_multiline"));
+      gQueue.push(new synthFocus("textbox",
+                                 new focusChecker(getNode("textbox").inputField)));
+      gQueue.push(new synthFocus("textbox_multiline",
+                                 new focusChecker(getNode("textbox_multiline").inputField)));
       gQueue.push(new synthFocus("scale"));
       gQueue.push(new synthFocusOnFrame("editabledoc"));
       gQueue.push(new synthFocus("radioclothes",
                                  new focusChecker("radiosweater")));
       gQueue.push(new synthDownKey("radiosweater",
                                    new focusChecker("radiojacket")));
       gQueue.push(new synthFocus("checkbox"));
       gQueue.push(new synthFocus("button"));
--- a/accessible/tests/mochitest/events/test_focus_listcontrols.xul
+++ b/accessible/tests/mochitest/events/test_focus_listcontrols.xul
@@ -17,17 +17,17 @@
           src="../role.js" />
   <script type="application/javascript"
           src="../states.js" />
   <script type="application/javascript"
           src="../events.js" />
 
   <script type="application/javascript">
     //gA11yEventDumpID = "eventdump"; // debug stuff
-    //gA11yEventDumpToConsole = true; // debug stuff
+    gA11yEventDumpToConsole = true; // debug stuff
 
     var gQueue = null;
     function doTests()
     {
       // Test focus events.
       gQueue = new eventQueue();
 
       gQueue.push(new synthFocus("listbox", new focusChecker("lb_item1")));
--- a/accessible/tests/mochitest/events/test_focus_tabbox.xul
+++ b/accessible/tests/mochitest/events/test_focus_tabbox.xul
@@ -31,20 +31,21 @@
         todo(false, "Tests disabled because of imminent failure.");
         SimpleTest.finish();
         return;
       }
 
       // Test focus events.
       gQueue = new eventQueue();
 
+      var textbox = getNode("textbox").inputField;
       gQueue.push(new synthClick("tab1", new focusChecker("tab1")));
       gQueue.push(new synthTab("tab1", new focusChecker("checkbox1")));
       gQueue.push(new synthKey("tab1", "VK_TAB", { ctrlKey: true },
-                               new focusChecker("textbox")));
+                               new focusChecker(textbox)));
       gQueue.push(new synthKey("tab2", "VK_TAB", { ctrlKey: true },
                                new focusChecker("tab3")));
       gQueue.push(new synthKey("tab3", "VK_TAB", { ctrlKey: true },
                                new focusChecker("tab1")));
 
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
--- a/accessible/tests/mochitest/name/test_general.xul
+++ b/accessible/tests/mochitest/name/test_general.xul
@@ -28,17 +28,17 @@
       // Simple label provided via ARIA
       testName("btn_simple_aria_label", "I am a button");
 
       // aria-label and aria-labelledby, expect aria-labelledby
       testName("btn_both_aria_labels", "text I am a button, two");
 
       //////////////////////////////////////////////////////////////////////////
       // aria-labelledby
-      
+
       // Single relation. The value of 'aria-labelledby' contains the ID of
       // an element. Gets the name from text node of that element.
       testName("btn_labelledby_text", "text");
 
       // Multiple relations. The value of 'aria-labelledby' contains the IDs
       // of elements. Gets the name from text nodes of those elements.
       testName("btn_labelledby_texts", "text1 text2");
 
--- a/accessible/tests/mochitest/states/test_textbox.xul
+++ b/accessible/tests/mochitest/states/test_textbox.xul
@@ -13,94 +13,99 @@
           src="../common.js" />
   <script type="application/javascript"
           src="../role.js" />
   <script type="application/javascript"
           src="../states.js" />
 
   <script type="application/javascript">
   <![CDATA[
+    function getInput(aID)
+    {
+      return getNode(aID).inputField;
+    }
+
     function doTest()
     {
       //////////////////////////////////////////////////////////////////////////
       // Ordinary textbox
-      testStates("textbox",
+      testStates(getInput("textbox"),
                  STATE_FOCUSABLE,
                  EXT_STATE_EDITABLE,
                  STATE_PROTECTED | STATE_UNAVAILABLE,
                  EXT_STATE_SUPPORTS_AUTOCOMPLETION,
                  "ordinary textbox");
 
       //////////////////////////////////////////////////////////////////////////
       // Password textbox
-      testStates("password",
+      testStates(getInput("password"),
                  STATE_FOCUSABLE | STATE_PROTECTED,
                  EXT_STATE_EDITABLE,
                  STATE_UNAVAILABLE,
                  EXT_STATE_SUPPORTS_AUTOCOMPLETION,
                  "password textbox");
 
       //////////////////////////////////////////////////////////////////////////
       // Textarea
-      testStates("textarea",
+      testStates(getInput("textarea"),
                  STATE_FOCUSABLE,
                  EXT_STATE_EDITABLE | EXT_STATE_MULTI_LINE,
                  STATE_PROTECTED | STATE_UNAVAILABLE,
                  EXT_STATE_SUPPORTS_AUTOCOMPLETION,
                  "multiline textbox");
 
       //////////////////////////////////////////////////////////////////////////
       // Readonly textbox
-      testStates("readonly_textbox",
+      testStates(getInput("readonly_textbox"),
                  STATE_FOCUSABLE | STATE_READONLY,
                  EXT_STATE_EDITABLE,
                  STATE_PROTECTED | STATE_UNAVAILABLE,
                  EXT_STATE_SUPPORTS_AUTOCOMPLETION,
                  "readonly textbox");
 
       //////////////////////////////////////////////////////////////////////////
       // Disabled textbox
-      testStates("disabled_textbox",
+      testStates(getInput("disabled_textbox"),
                  STATE_UNAVAILABLE,
                  EXT_STATE_EDITABLE,
                  STATE_FOCUSABLE | STATE_PROTECTED,
                  EXT_STATE_SUPPORTS_AUTOCOMPLETION,
                  "readonly textbox");
 
       //////////////////////////////////////////////////////////////////////////
       // Readonly textarea
-      testStates("readonly_textarea",
+      testStates(getInput("readonly_textarea"),
                  STATE_FOCUSABLE | STATE_READONLY,
                  EXT_STATE_EDITABLE | EXT_STATE_MULTI_LINE,
                  STATE_PROTECTED | STATE_UNAVAILABLE,
                  EXT_STATE_SUPPORTS_AUTOCOMPLETION,
                  "readonly multiline textbox");
 
       //////////////////////////////////////////////////////////////////////////
       // Disabled textarea
-      testStates("disabled_textarea",
+      testStates(getInput("disabled_textarea"),
                  STATE_UNAVAILABLE,
                  EXT_STATE_EDITABLE| EXT_STATE_MULTI_LINE,
                  STATE_PROTECTED | STATE_FOCUSABLE,
                  EXT_STATE_SUPPORTS_AUTOCOMPLETION,
                  "readonly multiline textbox");
 
       //////////////////////////////////////////////////////////////////////////
       // Search textbox without search button, searches as you type and filters
       // a separate control.
-      testStates("searchbox",
+      testStates(getInput("searchbox"),
                  STATE_FOCUSABLE,
                  EXT_STATE_EDITABLE | EXT_STATE_SUPPORTS_AUTOCOMPLETION,
                  STATE_PROTECTED | STATE_UNAVAILABLE,
                  0,
                  "searchbox");
 
       //////////////////////////////////////////////////////////////////////////
       // Search textbox with search button, does not support autoCompletion.
-      testStates("searchfield",
+      testStates(getInput("searchfield"),
                  STATE_FOCUSABLE,
                  EXT_STATE_EDITABLE,
                  STATE_PROTECTED | STATE_UNAVAILABLE,
                  EXT_STATE_SUPPORTS_AUTOCOMPLETION,
                  "searchfield");
 
       SimpleTest.finish();
     }
deleted file mode 100644
--- a/accessible/tests/mochitest/testTextboxes.js
+++ /dev/null
@@ -1,33 +0,0 @@
-function testValue(aID, aAcc, aValue, aRole)
-{
-  is(aAcc.value, aValue, "Wrong value for " + aID + "!");
-}
-
-function testAction(aID, aAcc, aActionCount, aActionName, aActionDescription)
-{
-  var actionCount = aAcc.actionCount;
-  is(actionCount, aActionCount, "Wrong number of actions for " + aID + "!");
-
-  if (actionCount != 0) {
-    // Test first action. Normally only 1 should be present.
-    is(aAcc.getActionName(0), aActionName,
-       "Wrong name of action for " + aID + "!");
-    is(aAcc.getActionDescription(0), aActionDescription,
-       "Wrong description of action for " + aID + "!");
-  }
-}
-
-function testThis(aID, aName, aValue, aDescription, aRole,
-                  aActionCount, aActionName, aActionDescription)
-{
-  var acc = getAccessible(aID);
-  if (!acc)
-    return;
-
-  is(acc.name, aName, "Wrong name for " + aID + "!");
-  testValue(aID, acc, aValue, aRole);
-  is(acc.description, aDescription, "Wrong description for " + aID + "!");
-  testRole(aID, aRole);
-
-  testAction(aID, acc, aActionCount, aActionName, aActionDescription);
-}
deleted file mode 100644
--- a/accessible/tests/mochitest/test_textboxes.html
+++ /dev/null
@@ -1,164 +0,0 @@
-<!DOCTYPE html>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=442648
--->
-<head>
-  <title>nsIAccessible textboxes chrome tests</title>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
-
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-
-  <script type="application/javascript"
-          src="common.js"></script>
-  <script type="application/javascript"
-          src="role.js"></script>
-  <script type="application/javascript"
-          src="states.js"></script>
-
-  <script type="application/javascript"
-          src="testTextboxes.js"></script>
-
-  <script type="application/javascript">
-    function doTest()
-    {
-      //////////////////////////////////////////////////////////////////////////
-      // normal textbox without content and with no proper label
-      testThis("unlabelled_Textbox", // ID
-               null, // name
-               "", // value
-               "", // description
-               ROLE_ENTRY, // role
-               1, // actionCount
-               "activate",  // ActionName
-               "Activate"); // ActionDescription
-
-      //////////////////////////////////////////////////////////////////////////
-      // normal textbox without content and with a proper label
-      testThis("labelled_textbox", // ID
-               "Second textbox:", // name
-               "", // value
-               "", // description
-               ROLE_ENTRY, // role
-               1, // actionCount
-               "activate",  // ActionName
-               "Activate"); // ActionDescription
-
-      //////////////////////////////////////////////////////////////////////////
-      // normal textbox with content and with a proper label
-      testThis("prefilled_textbox", // ID
-               "Textbox with predefined value:", // name
-               "I have some text", // value
-               "", // description
-               ROLE_ENTRY, // role
-               1, // actionCount
-               "activate",  // ActionName
-               "Activate"); // ActionDescription
-
-      //////////////////////////////////////////////////////////////////////////
-      // password textbox with a proper label
-      testThis("password_textbox", // ID
-               "Enter some password here:", // name
-               "", // value
-               "", // description
-               ROLE_PASSWORD_TEXT, // role
-               1, // actionCount
-               "activate",  // ActionName
-               "Activate"); // ActionDescription
-
-      //////////////////////////////////////////////////////////////////////////
-      // textarea without content and label
-      testThis("unlabelled_Textarea", // ID
-               null, // name
-               "", // value
-               "", // description
-               ROLE_ENTRY, // role
-               1, // actionCount
-               "activate",  // ActionName
-               "Activate"); // ActionDescription
-
-      //////////////////////////////////////////////////////////////////////////
-      // textarea without content and with proper label
-      testThis("labelled_textarea", // ID
-               "Labelled textarea:", // name
-               "", // value
-               "", // description
-               ROLE_ENTRY, // role
-               1, // actionCount
-               "activate",  // ActionName
-               "Activate"); // ActionDescription
-
-      //////////////////////////////////////////////////////////////////////////
-      // textarea with content and with proper label
-      testThis("prefilled_textarea", // ID
-               "Pre-filled textarea:", // name
-               "    I also have some text.\n    ", // value
-               "", // description
-               ROLE_ENTRY, // role
-               1, // actionCount
-               "activate",  // ActionName
-               "Activate"); // ActionDescription
-
-      //////////////////////////////////////////////////////////////////////////
-      // readonly textbox with content and with proper label
-      testThis("readonly_textbox", // ID
-               "The following is a read-only textbox:", // name
-               "You cannot change me.", // value
-               "", // description
-               ROLE_ENTRY, // role
-               1, // actionCount
-               "activate",  // ActionName
-               "Activate"); // ActionDescription
-
-      //////////////////////////////////////////////////////////////////////////
-      // readonly textarea with content and with proper label
-      testThis("readonly_textarea", // ID
-               "This textarea is readonly, too:", // name
-               "    You cannot change me, either.\n    ", // value
-               "", // description
-               ROLE_ENTRY, // role
-               1, // actionCount
-               "activate",  // ActionName
-               "Activate"); // ActionDescription
-
-      SimpleTest.finish();
-    }
-
-    SimpleTest.waitForExplicitFinish();
-    addA11yLoadEvent(doTest);
-  </script>
-</head>
-<body>
-
-  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=442648">Mozilla Bug 442648</a>
-  <p id="display"></p>
-  <div id="content" style="display: none"></div>
-  <pre id="test">
-  </pre>
-  <form action="test.php" method="post">
-    Text before input without labelling it:
-    <input type="text" name="unlabelled_Textbox" id="unlabelled_Textbox" size="50"/>
-    <br><label for="labelled_textbox">Second textbox:</label>
-    <input type="text" name="labelled_Textbox" id="labelled_textbox"/>
-    <br><label for="prefilled_textbox">Textbox with predefined value:</label>
-    <input type="text" name="prefilled_Textbox" id="prefilled_textbox" value="I have some text" size="80"/>
-    <br><label for="password_textbox">Enter some password here:</label>
-    <input type="password" name="password_Textbox" id="password_textbox"/>
-    <br>Textarea without label:<br>
-    <textarea id="unlabelled_Textarea" name="unlabelled_Textarea" cols="80" rows="5"></textarea>
-    <br><label for="labelled_textarea">Labelled textarea:</label><br>
-    <textarea id="labelled_textarea" name="labelled_Textarea" cols="80" rows="5"></textarea>
-    <br><label for="prefilled_textarea">Pre-filled textarea:</label><br>
-    <textarea id="prefilled_textarea" name="prefilled_Textarea" cols="80" rows="5">
-    I also have some text.
-    </textarea>
-    <br><label for="readonly_textbox">The following is a read-only textbox:</label>
-    <input type="text" readonly="true" name="readonly_Textbox" id="readonly_textbox" value="You cannot change me."/>
-    <br><label for="readonly_textarea">This textarea is readonly, too:</label><br>
-    <textarea name="readonly_Textarea" id="readonly_textarea" readonly="true" cols="80" rows="5">
-    You cannot change me, either.
-    </textarea>
-  </form>
-</body>
-</html>
deleted file mode 100644
--- a/accessible/tests/mochitest/test_textboxes.xul
+++ /dev/null
@@ -1,217 +0,0 @@
-<?xml version="1.0"?>
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
-<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
-                 type="text/css"?>
-
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        title="nsIAccessible XUL textboxes chrome tests">
-
-  <script type="application/javascript"
-          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js" />
-
-  <script type="application/javascript"
-          src="common.js" />
-  <script type="application/javascript"
-          src="role.js" />
-  <script type="application/javascript"
-          src="states.js" />
-
-  <script type="application/javascript"
-          src="testTextboxes.js" />
-
-  <script type="application/javascript">
-  <![CDATA[
-    function doTest()
-    {
-      //////////////////////////////////////////////////////////////////////////
-      // normal textbox without content and with no proper label
-      testThis("unlabelled_Textbox", // ID
-               null, // name
-               "", // value
-               "", // description
-               ROLE_ENTRY, // role
-               1, // actionCount
-               "activate",  // ActionName
-               "Activate"); // ActionDescription
-
-      //////////////////////////////////////////////////////////////////////////
-      // normal textbox without content and with a proper label
-      testThis("labelled_textbox", // ID
-               "Second textbox:", // name
-               "", // value
-               "", // description
-               ROLE_ENTRY, // role
-               1, // actionCount
-               "activate",  // ActionName
-               "Activate"); // ActionDescription
-
-      //////////////////////////////////////////////////////////////////////////
-      // normal textbox with content and with a proper label
-      testThis("prefilled_textbox", // ID
-               "Textbox with predefined value:", // name
-               "I have some text", // value
-               "", // description
-               ROLE_ENTRY, // role
-               1, // actionCount
-               "activate",  // ActionName
-               "Activate"); // ActionDescription
-
-      //////////////////////////////////////////////////////////////////////////
-      // password textbox with a proper label
-      testThis("password_textbox", // ID
-               "Enter some password here:", // name
-               "", // value
-               "", // description
-               ROLE_PASSWORD_TEXT, // role
-               1, // actionCount
-               "activate",  // ActionName
-               "Activate"); // ActionDescription
-
-      //////////////////////////////////////////////////////////////////////////
-      // textarea without content and label
-      testThis("unlabelled_Textarea", // ID
-               null, // name
-               "", // value
-               "", // description
-               ROLE_ENTRY, // role
-               1, // actionCount
-               "activate",  // ActionName
-               "Activate"); // ActionDescription
-
-      //////////////////////////////////////////////////////////////////////////
-      // textarea without content and with proper label
-      testThis("labelled_textarea", // ID
-               "Labelled textarea:", // name
-               "", // value
-               "", // description
-               ROLE_ENTRY, // role
-               1, // actionCount
-               "activate",  // ActionName
-               "Activate"); // ActionDescription
-
-      //////////////////////////////////////////////////////////////////////////
-      // textarea with content and with proper label
-      testThis("prefilled_textarea", // ID
-               "Pre-filled textarea:", // name
-               "I also have some text.", // value
-               "", // description
-               ROLE_ENTRY, // role
-               1, // actionCount
-               "activate",  // ActionName
-               "Activate"); // ActionDescription
-
-      //////////////////////////////////////////////////////////////////////////
-      // readonly textbox with content and with proper label
-      testThis("readonly_textbox", // ID
-               "The following is a read-only textbox:", // name
-               "You cannot change me.", // value
-               "", // description
-               ROLE_ENTRY, // role
-               1, // actionCount
-               "activate",  // ActionName
-               "Activate"); // ActionDescription
-
-      //////////////////////////////////////////////////////////////////////////
-      // readonly textarea with content and with proper label
-      testThis("readonly_textarea", // ID
-               "This textarea is readonly, too:", // name
-               "You cannot change me, either.", // value
-               "", // description
-               ROLE_ENTRY, // role
-               1, // actionCount
-               "activate",  // ActionName
-               "Activate"); // ActionDescription
-
-      //////////////////////////////////////////////////////////////////////////
-      // Search textbox without search button, searches as you type and filters
-      // a separate control.
-      testThis("search-box", // ID
-               "Search History:", // name
-               "", // value
-               "", // description
-               ROLE_ENTRY, // role
-               1, // actionCount
-               "activate",  // ActionName
-               "Activate"); // ActionDescription
-
-      //////////////////////////////////////////////////////////////////////////
-      // Search textbox with search button, does not support autoCompletion.
-      testThis("searchfield", // ID
-               "Search all add-ons", // name
-               "", // value
-               "", // description
-               ROLE_ENTRY, // role
-               1, // actionCount
-               "activate",  // ActionName
-               "Activate"); // ActionDescription
-      testStates("searchfield", 0, 0, 0, EXT_STATE_SUPPORTS_AUTOCOMPLETION);
-
-      SimpleTest.finish();
-    }
-
-    SimpleTest.waitForExplicitFinish();
-    addA11yLoadEvent(doTest);
-  ]]>
-  </script>
-
-  <body xmlns="http://www.w3.org/1999/xhtml">
-    <a target="_blank"
-       href="https://bugzilla.mozilla.org/show_bug.cgi?id=442648">
-      Mozilla Bug 442648
-    </a>
-    <p id="display"></p>
-    <div id="content" style="display: none"></div>
-    <pre id="test">
-    </pre>
-  </body>
-
-  <vbox>
-    <hbox>
-      <label value="Text before input without labelling it:"/>
-      <textbox id="unlabelled_Textbox" size="50"/>
-    </hbox>
-    <hbox>
-      <label control="labelled_textbox">Second textbox:</label>
-      <textbox id="labelled_textbox"/>
-    </hbox>
-    <hbox>
-      <label control="prefilled_textbox">Textbox with predefined value:</label>
-      <textbox id="prefilled_textbox" value="I have some text" size="80"/>
-    </hbox>
-    <hbox>
-      <label control="password_textbox">Enter some password here:</label>
-      <textbox type="password" id="password_textbox"/>
-    </hbox>
-    <vbox>
-      <label value="Textarea without label:"/>
-      <textbox multiline="true" id="unlabelled_Textarea" cols="80" rows="5"/>
-    </vbox>
-    <vbox>
-      <label control="labelled_textarea">Labelled textarea:</label>
-      <textbox multiline="true" id="labelled_textarea" cols="80" rows="5"/>
-    </vbox>
-    <vbox>
-      <label control="prefilled_textarea">Pre-filled textarea:</label>
-      <textbox multiline="true" id="prefilled_textarea" cols="80" rows="5"
-               value="I also have some text."/>
-    </vbox>
-    <hbox>
-      <label control="readonly_textbox">The following is a read-only textbox:</label>
-      <textbox readonly="true" id="readonly_textbox"
-               value="You cannot change me."/>
-    </hbox>
-    <vbox>
-      <label control="readonly_textarea">This textarea is readonly, too:</label>
-      <textbox multiline="true" id="readonly_textarea" readonly="true" cols="80"
-               rows="5" value="You cannot change me, either."/>
-    </vbox>
-    <hbox>
-      <label value="Search History:" accesskey="S" 
-             control="search-box"/>
-	  <textbox id="search-box" flex="1" type="search"
-	           results="historyTree"/>
-    </hbox>
-    <textbox id="searchfield" placeholder="Search all add-ons"
-             type="search" searchbutton="true"/>
-  </vbox>
-</window>
--- a/accessible/tests/mochitest/text/test_general.xul
+++ b/accessible/tests/mochitest/text/test_general.xul
@@ -38,17 +38,17 @@
 
       testCharAfterOffset(ids, 0, "e", 1, 2);
       testCharBeforeOffset(ids, 1, "H", 0, 1);
       testCharAtOffset(ids, 1, "e", 1, 2);
 
       //////////////////////////////////////////////////////////////////////////
       // XUL textbox
 
-      testTextAtOffset([ "tbox1" ], BOUNDARY_LINE_START,
+      testTextAtOffset([ getNode("tbox1").inputField ], BOUNDARY_LINE_START,
                        [ [ 0, 4, "test", 0, 4 ] ]);
 
       SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTests);
   ]]>
--- a/accessible/tests/mochitest/tree/test_txtctrl.xul
+++ b/accessible/tests/mochitest/tree/test_txtctrl.xul
@@ -18,74 +18,78 @@
 
   <script type="application/javascript">
   <![CDATA[
     ////////////////////////////////////////////////////////////////////////////
     // Test
 
     function doTest()
     {
-      ////////////////////
-      // textbox
-      ////////////////////
-      var accTree = {
-        role: ROLE_ENTRY,
-        children: [
-          {
-            role: ROLE_TEXT_LEAF,
-            children: []
-          }
-        ]
-      };
+      //////////////////////////////////////////////////////////////////////////
+      // textboxes
+
+      var accTree =
+        { SECTION: [
+          { ENTRY: [ { TEXT_LEAF: [] } ] },
+          { MENUPOPUP: [] }
+        ] };
 
       // default textbox
-      testAccessibleTree("txc1", accTree);
+      testAccessibleTree("txc", accTree);
 
-      // number textbox
-      testAccessibleTree("txc2", accTree);
+      // multiline
+      testAccessibleTree("txc_multiline", accTree);
 
+      //////////////////////////////////////////////////////////////////////////
       // search textbox
-      testAccessibleTree("txc3", accTree);
+
+      if (MAC) {
+        accTree =
+          { SECTION: [
+            { ENTRY: [ { TEXT_LEAF: [] } ] },
+            { MENUPOPUP: [] }
+          ] };
+      } else {
+        accTree =
+          { SECTION: [
+            { ENTRY: [ { TEXT_LEAF: [] } ] },
+            { PUSHBUTTON: [] },
+            { MENUPOPUP: [] }
+          ] };
+      }
 
-      // timed textbox
-      testAccessibleTree("txc4_deprecated", accTree);
+      testAccessibleTree("txc_search", accTree);
+
+      //////////////////////////////////////////////////////////////////////////
+      // number textbox
 
-      ////////////////////
+      accTree =
+        { SECTION: [
+          { ENTRY: [ { TEXT_LEAF: [] } ] },
+          { MENUPOPUP: [] },
+          { PUSHBUTTON: [] },
+          { PUSHBUTTON: [] }
+        ] };
+
+      testAccessibleTree("txc_number", accTree);
+
+      //////////////////////////////////////////////////////////////////////////
       // password textbox
-      ////////////////////
-      accTree = {
-        role: ROLE_PASSWORD_TEXT,
-        children: [
-          {
-            role: ROLE_TEXT_LEAF,
-            children: []
-          }
-        ]
-      };
-
-      testAccessibleTree("txc5", accTree);
 
-      ////////////////////
-      // multiline textbox
-      ////////////////////
-      accTree = {
-        role: ROLE_ENTRY,
-        children: [
-          {
-            role: ROLE_TEXT_LEAF,
-            children: []
-          }
-        ]
-      };
+      accTree =
+        { SECTION: [
+          { PASSWORD_TEXT: [ { TEXT_LEAF: [] } ] },
+          { MENUPOPUP: [] }
+        ] };
 
-      testAccessibleTree("txc6", accTree);
+      testAccessibleTree("txc_password", accTree);
 
-      ////////////////////
+      //////////////////////////////////////////////////////////////////////////
       // autocomplete textbox
-      ////////////////////
+
       accTree = {
         // textbox
         role: ROLE_AUTOCOMPLETE,
         children: [
           {
             // html:input
             role: ROLE_ENTRY,
             children: [
@@ -99,24 +103,24 @@
           {
             // xul:menupopup
             role: ROLE_COMBOBOX_LIST,
             children: []
           }
         ]
       };
 
-      function test_txc7() {
-        testAccessibleTree("txc7", accTree);
+      function test_AutocompleteControl() {
+        testAccessibleTree("txc_autocomplete", accTree);
         SimpleTest.finish();
       }
 
       // XPFE and Toolkit autocomplete widgets differ.
-      var txc7 = document.getElementById("txc7");
-      if ("clearResults" in txc7) {
+      var txc = document.getElementById("txc_autocomplete");
+      if ("clearResults" in txc) {
         SimpleTest.ok(true, "Testing (Old) XPFE autocomplete widget.");
 
         // Popup is always created. (See code below.)
 
         accTree.children.push(
           {
             // xul:panel
             role: ROLE_COMBOBOX_LIST,
@@ -136,24 +140,24 @@
                       }
                     ]
                   }
                 ]
               }
             ]
           }
         );
-        test_txc7();
+        test_AutocompleteControl();
 
       } else {
         SimpleTest.ok(true, "Testing (New) Toolkit autocomplete widget.");
 
         // Dumb access to trigger popup lazy creation.
-        waitForEvent(EVENT_REORDER, txc7, test_txc7);
-        txc7.popup;
+        waitForEvent(EVENT_REORDER, txc, test_AutocompleteControl);
+        txc.popup;
 
         accTree.children.push(
           {
             role: ROLE_LIST,
             children: [
               {
                 role: ROLE_LIST,
                 children: [
@@ -184,21 +188,18 @@
       <p id="display"></p>
       <div id="content" style="display: none">
       </div>
       <pre id="test">
       </pre>
     </body>
 
     <vbox flex="1">
-      <textbox id="txc1" value="hello"/>
-      <textbox id="txc2" type="number" value="44"/>
-      <textbox id="txc3" type="search" value="hello"/>
-      <!-- This textbox triggers "Warning: Timed textboxes are deprecated. Consider using type="search" instead.".
-           Yet let's test it too as long as it's (still) supported. -->
-      <textbox id="txc4_deprecated" type="timed" value="hello"/>
-      <textbox id="txc5" type="password" value="hello"/>
-      <textbox id="txc6" multiline="true" value="hello"/>
-      <textbox id="txc7" type="autocomplete" value="hello"/>
+      <textbox id="txc" value="hello"/>
+      <textbox id="txc_search" type="search" value="hello"/>
+      <textbox id="txc_number" type="number" value="44"/>
+      <textbox id="txc_password" type="password" value="hello"/>
+      <textbox id="txc_multiline" multiline="true" value="hello"/>
+      <textbox id="txc_autocomplete" type="autocomplete" value="hello"/>
     </vbox>
   </hbox>
 
 </window>
--- a/b2g/components/ContentPermissionPrompt.js
+++ b/b2g/components/ContentPermissionPrompt.js
@@ -1,28 +1,31 @@
 /* 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/. */
 
 "use strict"
 
 function debug(str) {
-  //dump("-*- ContentPermissionPrompt: " + s + "\n");
+  //dump("-*- ContentPermissionPrompt: " + str + "\n");
 }
 
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 const Cc = Components.classes;
 
-const PROMPT_FOR_UNKNOWN    = ["geolocation", "desktop-notification",
-                               "audio-capture"];
+const PROMPT_FOR_UNKNOWN = ["audio-capture",
+                            "desktop-notification",
+                            "geolocation",
+                            "video-capture"];
 // Due to privary issue, permission requests like GetUserMedia should prompt
 // every time instead of providing session persistence.
-const PERMISSION_NO_SESSION = ["audio-capture"];
+const PERMISSION_NO_SESSION = ["audio-capture", "video-capture"];
+const ALLOW_MULTIPLE_REQUESTS = ["audio-capture", "video-capture"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Webapps.jsm");
 Cu.import("resource://gre/modules/AppsUtils.jsm");
 Cu.import("resource://gre/modules/PermissionsInstaller.jsm");
 Cu.import("resource://gre/modules/PermissionsTable.jsm");
 
@@ -36,206 +39,314 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "@mozilla.org/permissionSettings;1",
                                    "nsIDOMPermissionSettings");
 
 XPCOMUtils.defineLazyServiceGetter(this,
                                    "AudioManager",
                                    "@mozilla.org/telephony/audiomanager;1",
                                    "nsIAudioManager");
 
-function rememberPermission(aPermission, aPrincipal, aSession)
+/**
+ * aTypesInfo is an array of {permission, access, action, deny} which keeps
+ * the information of each permission. This arrary is initialized in
+ * ContentPermissionPrompt.prompt and used among functions.
+ *
+ * aTypesInfo[].permission : permission name
+ * aTypesInfo[].access     : permission name + request.access
+ * aTypesInfo[].action     : the default action of this permission
+ * aTypesInfo[].deny       : true if security manager denied this app's origin
+ *                           principal.
+ * Note:
+ *   aTypesInfo[].permission will be sent to prompt only when
+ *   aTypesInfo[].action is PROMPT_ACTION and aTypesInfo[].deny is false.
+ */
+function rememberPermission(aTypesInfo, aPrincipal, aSession)
 {
   function convertPermToAllow(aPerm, aPrincipal)
   {
     let type =
       permissionManager.testExactPermissionFromPrincipal(aPrincipal, aPerm);
     if (type == Ci.nsIPermissionManager.PROMPT_ACTION ||
         (type == Ci.nsIPermissionManager.UNKNOWN_ACTION &&
-        PROMPT_FOR_UNKNOWN.indexOf(aPermission) >= 0)) {
+        PROMPT_FOR_UNKNOWN.indexOf(aPerm) >= 0)) {
+      debug("add " + aPerm + " to permission manager with ALLOW_ACTION");
       if (!aSession) {
         permissionManager.addFromPrincipal(aPrincipal,
                                            aPerm,
                                            Ci.nsIPermissionManager.ALLOW_ACTION);
-      } else if (PERMISSION_NO_SESSION.indexOf(aPermission) < 0) {
+      } else if (PERMISSION_NO_SESSION.indexOf(aPerm) < 0) {
         permissionManager.addFromPrincipal(aPrincipal,
                                            aPerm,
                                            Ci.nsIPermissionManager.ALLOW_ACTION,
                                            Ci.nsIPermissionManager.EXPIRE_SESSION, 0);
       }
     }
   }
 
-  // Expand the permission to see if we have multiple access properties to convert
-  let access = PermissionsTable[aPermission].access;
-  if (access) {
-    for (let idx in access) {
-      convertPermToAllow(aPermission + "-" + access[idx], aPrincipal);
+  for (let i in aTypesInfo) {
+    // Expand the permission to see if we have multiple access properties
+    // to convert
+    let perm = aTypesInfo[i].permission;
+    let access = PermissionsTable[perm].access;
+    if (access) {
+      for (let idx in access) {
+        convertPermToAllow(perm + "-" + access[idx], aPrincipal);
+      }
+    } else {
+      convertPermToAllow(perm, aPrincipal);
     }
-  } else {
-    convertPermToAllow(aPermission, aPrincipal);
   }
 }
 
 function ContentPermissionPrompt() {}
 
 ContentPermissionPrompt.prototype = {
 
-  handleExistingPermission: function handleExistingPermission(request) {
-    let access = (request.access && request.access !== "unused") ? request.type + "-" + request.access :
-                                                                   request.type;
-    let result = Services.perms.testExactPermissionFromPrincipal(request.principal, access);
-    if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
+  handleExistingPermission: function handleExistingPermission(request,
+                                                              typesInfo) {
+    typesInfo.forEach(function(type) {
+      type.action =
+        Services.perms.testExactPermissionFromPrincipal(request.principal,
+                                                        type.access);
+      if (type.action == Ci.nsIPermissionManager.UNKNOWN_ACTION &&
+          PROMPT_FOR_UNKNOWN.indexOf(type.access) >= 0) {
+        type.action = Ci.nsIPermissionManager.PROMPT_ACTION;
+      }
+    });
+
+    // If all permissions are allowed already, call allow() without prompting.
+    let checkAllowPermission = function(type) {
+      if (type.action == Ci.nsIPermissionManager.ALLOW_ACTION) {
+        return true;
+      }
+      return false;
+    }
+    if (typesInfo.every(checkAllowPermission)) {
+      debug("all permission requests are allowed");
       request.allow();
       return true;
     }
-    if (result == Ci.nsIPermissionManager.DENY_ACTION ||
-        result == Ci.nsIPermissionManager.UNKNOWN_ACTION && PROMPT_FOR_UNKNOWN.indexOf(access) < 0) {
+
+    // If all permissions are DENY_ACTION or UNKNOWN_ACTION, call cancel()
+    // without prompting.
+    let checkDenyPermission = function(type) {
+      if (type.action == Ci.nsIPermissionManager.DENY_ACTION ||
+          type.action == Ci.nsIPermissionManager.UNKNOWN_ACTION) {
+        return true;
+      }
+      return false;
+    }
+    if (typesInfo.every(checkDenyPermission)) {
+      debug("all permission requests are denied");
       request.cancel();
       return true;
     }
     return false;
   },
 
-  handledByApp: function handledByApp(request) {
+  // multiple requests should be audio and video
+  checkMultipleRequest: function checkMultipleRequest(typesInfo) {
+    if (typesInfo.length == 1) {
+      return true;
+    } else if (typesInfo.length > 1) {
+      let checkIfAllowMultiRequest = function(type) {
+        return (ALLOW_MULTIPLE_REQUESTS.indexOf(type.access) !== -1);
+      }
+      if (typesInfo.every(checkIfAllowMultiRequest)) {
+        debug("legal multiple requests");
+        return true;
+      }
+    }
+
+    return false;
+  },
+
+  handledByApp: function handledByApp(request, typesInfo) {
     if (request.principal.appId == Ci.nsIScriptSecurityManager.NO_APP_ID ||
         request.principal.appId == Ci.nsIScriptSecurityManager.UNKNOWN_APP_ID) {
       // This should not really happen
       request.cancel();
       return true;
     }
 
     let appsService = Cc["@mozilla.org/AppsService;1"]
                         .getService(Ci.nsIAppsService);
     let app = appsService.getAppByLocalId(request.principal.appId);
 
-    let url = Services.io.newURI(app.origin, null, null);
-    let principal = secMan.getAppCodebasePrincipal(url, request.principal.appId,
-                                                   /*mozbrowser*/false);
-    let access = (request.access && request.access !== "unused") ? request.type + "-" + request.access :
-                                                                   request.type;
-    let result = Services.perms.testExactPermissionFromPrincipal(principal, access);
+    // Check each permission if it's denied by permission manager with app's
+    // URL.
+    let notDenyAppPrincipal = function(type) {
+      let url = Services.io.newURI(app.origin, null, null);
+      let principal = secMan.getAppCodebasePrincipal(url,
+                                                     request.principal.appId,
+                                                     /*mozbrowser*/false);
+      let result = Services.perms.testExactPermissionFromPrincipal(principal,
+                                                                   type.access);
 
-    if (result == Ci.nsIPermissionManager.ALLOW_ACTION ||
-        result == Ci.nsIPermissionManager.PROMPT_ACTION) {
-      return false;
+      if (result == Ci.nsIPermissionManager.ALLOW_ACTION ||
+          result == Ci.nsIPermissionManager.PROMPT_ACTION) {
+        type.deny = false;
+      }
+      return !type.deny;
+    }
+    if (typesInfo.filter(notDenyAppPrincipal).length === 0) {
+      request.cancel();
+      return true;
     }
 
-    request.cancel();
-    return true;
+    return false;
   },
 
-  handledByPermissionType: function handledByPermissionType(request) {
-    return permissionSpecificChecker.hasOwnProperty(request.type)
-             ? permissionSpecificChecker[request.type](request)
-             : false;
+  handledByPermissionType: function handledByPermissionType(request, typesInfo) {
+    for (let i in typesInfo) {
+      if (permissionSpecificChecker.hasOwnProperty(typesInfo[i].permission) &&
+          permissionSpecificChecker[typesInfo[i].permission](request)) {
+        return true;
+      }
+    }
+
+    return false;
   },
 
   _id: 0,
   prompt: function(request) {
     if (secMan.isSystemPrincipal(request.principal)) {
       request.allow();
-      return true;
+      return;
     }
 
-    if (this.handledByApp(request) ||
-        this.handledByPermissionType(request)) {
+    // Initialize the typesInfo and set the default value.
+    let typesInfo = [];
+    let perms = request.types.QueryInterface(Ci.nsIArray);
+    for (let idx = 0; idx < perms.length; idx++) {
+      let perm = perms.queryElementAt(idx, Ci.nsIContentPermissionType);
+      let tmp = {
+        permission: perm.type,
+        access: (perm.access && perm.access !== "unused") ?
+                  perm.type + "-" + perm.access : perm.type,
+        deny: true,
+        action: Ci.nsIPermissionManager.UNKNOWN_ACTION
+      };
+      typesInfo.push(tmp);
+    }
+    if (typesInfo.length == 0) {
+      request.cancel();
+      return;
+    }
+
+    if(!this.checkMultipleRequest(typesInfo)) {
+      request.cancel();
+      return;
+    }
+
+    if (this.handledByApp(request, typesInfo) ||
+        this.handledByPermissionType(request, typesInfo)) {
       return;
     }
 
     // returns true if the request was handled
-    if (this.handleExistingPermission(request))
+    if (this.handleExistingPermission(request, typesInfo)) {
        return;
+    }
+
+    // prompt PROMPT_ACTION request only.
+    typesInfo.forEach(function(aType, aIndex) {
+      if (aType.action != Ci.nsIPermissionManager.PROMPT_ACTION || aType.deny) {
+        typesInfo.splice(aIndex);
+      }
+    });
 
     let frame = request.element;
     let requestId = this._id++;
 
     if (!frame) {
-      this.delegatePrompt(request, requestId);
+      this.delegatePrompt(request, requestId, typesInfo);
       return;
     }
 
     frame = frame.wrappedJSObject;
     var cancelRequest = function() {
       frame.removeEventListener("mozbrowservisibilitychange", onVisibilityChange);
       request.cancel();
     }
 
     var self = this;
     var onVisibilityChange = function(evt) {
       if (evt.detail.visible === true)
         return;
 
-      self.cancelPrompt(request, requestId);
+      self.cancelPrompt(request, requestId, typesInfo);
       cancelRequest();
     }
 
     // If the request was initiated from a hidden iframe
     // we don't forward it to content and cancel it right away
     let domRequest = frame.getVisible();
     domRequest.onsuccess = function gv_success(evt) {
       if (!evt.target.result) {
         cancelRequest();
         return;
       }
 
       // Monitor the frame visibility and cancel the request if the frame goes
       // away but the request is still here.
       frame.addEventListener("mozbrowservisibilitychange", onVisibilityChange);
 
-      self.delegatePrompt(request, requestId, function onCallback() {
+      self.delegatePrompt(request, requestId, typesInfo, function onCallback() {
         frame.removeEventListener("mozbrowservisibilitychange", onVisibilityChange);
       });
     };
 
     // Something went wrong. Let's cancel the request just in case.
     domRequest.onerror = function gv_error() {
       cancelRequest();
     }
   },
 
-  cancelPrompt: function(request, requestId) {
-    this.sendToBrowserWindow("cancel-permission-prompt", request, requestId);
+  cancelPrompt: function(request, requestId, typesInfo) {
+    this.sendToBrowserWindow("cancel-permission-prompt", request, requestId,
+                             typesInfo);
   },
 
-  delegatePrompt: function(request, requestId, callback) {
-    let access = (request.access && request.access !== "unused") ? request.type + "-" + request.access :
-                                                                   request.type;
-    let principal = request.principal;
+  delegatePrompt: function(request, requestId, typesInfo, callback) {
 
-    this._permission = access;
-    this._uri = principal.URI.spec;
-    this._origin = principal.origin;
-
-    this.sendToBrowserWindow("permission-prompt", request, requestId, function(type, remember) {
+    this.sendToBrowserWindow("permission-prompt", request, requestId, typesInfo,
+                             function(type, remember) {
       if (type == "permission-allow") {
-        rememberPermission(request.type, principal, !remember);
+        rememberPermission(typesInfo, request.principal, !remember);
         if (callback) {
           callback();
         }
         request.allow();
         return;
       }
 
-      if (remember) {
-        Services.perms.addFromPrincipal(principal, access,
-                                        Ci.nsIPermissionManager.DENY_ACTION);
-      } else {
-        Services.perms.addFromPrincipal(principal, access,
-                                        Ci.nsIPermissionManager.DENY_ACTION,
-                                        Ci.nsIPermissionManager.EXPIRE_SESSION, 0);
+      let addDenyPermission = function(type) {
+        debug("add " + type.permission +
+              " to permission manager with DENY_ACTION");
+        if (remember) {
+          Services.perms.addFromPrincipal(request.principal, type.access,
+                                          Ci.nsIPermissionManager.DENY_ACTION);
+        } else if (PERMISSION_NO_SESSION.indexOf(aPerm) < 0) {
+          Services.perms.addFromPrincipal(request.principal, type.access,
+                                          Ci.nsIPermissionManager.DENY_ACTION,
+                                          Ci.nsIPermissionManager.EXPIRE_SESSION,
+                                          0);
+        }
       }
+      typesInfo.forEach(addDenyPermission);
 
       if (callback) {
         callback();
       }
       request.cancel();
     });
   },
 
-  sendToBrowserWindow: function(type, request, requestId, callback) {
+  sendToBrowserWindow: function(type, request, requestId, typesInfo, callback) {
     let browser = Services.wm.getMostRecentWindow("navigator:browser");
     let content = browser.getContentWindow();
     if (!content)
       return;
 
     if (callback) {
       content.addEventListener("mozContentEvent", function contentEvent(evt) {
         let detail = evt.detail;
@@ -248,20 +359,25 @@ ContentPermissionPrompt.prototype = {
     }
 
     let principal = request.principal;
     let isApp = principal.appStatus != Ci.nsIPrincipal.APP_STATUS_NOT_INSTALLED;
     let remember = (principal.appStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED ||
                     principal.appStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED)
                     ? true
                     : request.remember;
+    let permissions = {};
+    for (let i in typesInfo) {
+      debug("prompt " + typesInfo[i].permission);
+      permissions[typesInfo[i].permission] = [];
+    }
 
     let details = {
       type: type,
-      permission: request.type,
+      permissions: permissions,
       id: requestId,
       origin: principal.origin,
       isApp: isApp,
       remember: remember
     };
 
     if (!isApp) {
       browser.shell.sendChromeEvent(details);
@@ -284,11 +400,10 @@ ContentPermissionPrompt.prototype = {
       request.cancel();
       return true;
     } else {
       return false;
     }
   };
 })();
 
-
 //module initialization
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ContentPermissionPrompt]);
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "961def304c067c112f9f3c149431dba70887c5e0", 
+    "revision": "7343851c97c278a338434d2632098a263e19bbb6", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/confvars.sh
+++ b/b2g/confvars.sh
@@ -51,12 +51,14 @@ MOZ_EXTENSION_MANAGER=1
 MOZ_TIME_MANAGER=1
 
 MOZ_B2G_CERTDATA=1
 MOZ_PAY=1
 MOZ_TOOLKIT_SEARCH=
 MOZ_PLACES=
 MOZ_B2G=1
 
-#MOZ_NUWA_PROCESS=1
+if test "$OS_TARGET" = "Android"; then
+MOZ_NUWA_PROCESS=1
+fi
 MOZ_FOLD_LIBS=1
 
 MOZ_JSDOWNLOADS=1
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -2025,60 +2025,68 @@ ContentPermissionPrompt.prototype = {
     }
 
     this._showPrompt(aRequest, message, "pointerLock", actions, "pointerLock",
                      "pointerLock-notification-icon", null);
   },
 
   prompt: function CPP_prompt(request) {
 
+    // Only allow exactly one permission rquest here.
+    let types = request.types.QueryInterface(Ci.nsIArray);
+    if (types.length != 1) {
+      request.cancel();
+      return;
+    }
+    let perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
+
     const kFeatureKeys = { "geolocation" : "geo",
                            "desktop-notification" : "desktop-notification",
                            "pointerLock" : "pointerLock",
                          };
 
     // Make sure that we support the request.
-    if (!(request.type in kFeatureKeys)) {
+    if (!(perm.type in kFeatureKeys)) {
         return;
     }
 
     var requestingPrincipal = request.principal;
     var requestingURI = requestingPrincipal.URI;
 
     // Ignore requests from non-nsIStandardURLs
     if (!(requestingURI instanceof Ci.nsIStandardURL))
       return;
 
     var autoAllow = false;
-    var permissionKey = kFeatureKeys[request.type];
+    var permissionKey = kFeatureKeys[perm.type];
     var result = Services.perms.testExactPermissionFromPrincipal(requestingPrincipal, permissionKey);
 
     if (result == Ci.nsIPermissionManager.DENY_ACTION) {
       request.cancel();
       return;
     }
 
     if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
       autoAllow = true;
       // For pointerLock, we still want to show a warning prompt.
-      if (request.type != "pointerLock") {
+      if (perm.type != "pointerLock") {
         request.allow();
         return;
       }
     }
 
     var browser = this._getBrowserForRequest(request);
     var chromeWin = browser.ownerDocument.defaultView;
     if (!chromeWin.PopupNotifications)
       // Ignore requests from browsers hosted in windows that don't support
       // PopupNotifications.
       return;
 
     // Show the prompt.
-    switch (request.type) {
+    switch (perm.type) {
     case "geolocation":
       this._promptGeo(request);
       break;
     case "desktop-notification":
       this._promptWebNotifications(request);
       break;
     case "pointerLock":
       this._promptPointerLock(request, autoAllow);
--- a/browser/metro/base/content/helperui/IndexedDB.js
+++ b/browser/metro/base/content/helperui/IndexedDB.js
@@ -39,33 +39,35 @@ let IndexedDB = {
     } else if (topic == this._quotaCancel) {
       payload.permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
       browser.messageManager.sendAsyncMessage("IndexedDB:Response", payload);
       // XXX Need to actually save this?
       return;
     }
 
     let prompt = Cc["@mozilla.org/content-permission/prompt;1"].createInstance(Ci.nsIContentPermissionPrompt);
+    let types = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+    types.appendElement({type: type, access: "unused"}, false);
 
     // If the user waits a long time before responding, we default to UNKNOWN_ACTION.
     let timeoutId = setTimeout(function() {
       payload.permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
       browser.messageManager.sendAsyncMessage("IndexedDB:Response", payload);
       timeoutId = null;
     }, 30000);
  
     function checkTimeout() {
       if (timeoutId === null) return true;
       clearTimeout(timeoutId);
       timeoutId = null;
       return false;
     }
 
     prompt.prompt({
-      type: type,
+      types: types,
       uri: Services.io.newURI(payload.location, null, null),
       window: null,
       element: aMessage.target,
 
       cancel: function() {
         if (checkTimeout()) return;
         payload.permission = Ci.nsIPermissionManager.DENY_ACTION;
         browser.messageManager.sendAsyncMessage("IndexedDB:Response", payload);
--- a/browser/metro/components/ContentPermissionPrompt.js
+++ b/browser/metro/components/ContentPermissionPrompt.js
@@ -51,78 +51,86 @@ ContentPermissionPrompt.prototype = {
       let requestingWindow = request.window.top;
       let windowID = request.window.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
       let browser = chromeWin.Browser.getBrowserForWindowId(windowID);
       return chromeWin.getNotificationBox(browser);
     }
     return chromeWin.Browser.getNotificationBox(request.element);
   },
 
-  handleExistingPermission: function handleExistingPermission(request) {
-    let result = Services.perms.testExactPermissionFromPrincipal(request.principal, request.type);
+  handleExistingPermission: function handleExistingPermission(request, type) {
+    let result = Services.perms.testExactPermissionFromPrincipal(request.principal, type);
     if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
       request.allow();
       return true;
     }
     if (result == Ci.nsIPermissionManager.DENY_ACTION) {
       request.cancel();
       return true;
     }
     return false;
   },
 
   prompt: function(request) {
+    // Only allow exactly one permission rquest here.
+    let types = request.types.QueryInterface(Ci.nsIArray);
+    if (types.length != 1) {
+      request.cancel();
+      return;
+    }
+    let perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
+
     // returns true if the request was handled
-    if (this.handleExistingPermission(request))
+    if (this.handleExistingPermission(request, perm.type))
        return;
 
     let pm = Services.perms;
     let notificationBox = this.getNotificationBoxForRequest(request);
     let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
     
-    let notification = notificationBox.getNotificationWithValue(request.type);
+    let notification = notificationBox.getNotificationWithValue(perm.type);
     if (notification)
       return;
 
-    let entityName = kEntities[request.type];
-    let icon = kIcons[request.type] || "";
+    let entityName = kEntities[perm.type];
+    let icon = kIcons[perm.type] || "";
 
     let buttons = [{
       label: browserBundle.GetStringFromName(entityName + ".allow"),
       accessKey: "",
       callback: function(notification) {
         request.allow();
       }
     },
     {
       label: browserBundle.GetStringFromName("contentPermissions.alwaysForSite"),
       accessKey: "",
       callback: function(notification) {
-        Services.perms.addFromPrincipal(request.principal, request.type, Ci.nsIPermissionManager.ALLOW_ACTION);
+        Services.perms.addFromPrincipal(request.principal, perm.type, Ci.nsIPermissionManager.ALLOW_ACTION);
         request.allow();
       }
     },
     {
       label: browserBundle.GetStringFromName("contentPermissions.neverForSite"),
       accessKey: "",
       callback: function(notification) {
-        Services.perms.addFromPrincipal(request.principal, request.type, Ci.nsIPermissionManager.DENY_ACTION);
+        Services.perms.addFromPrincipal(request.principal, perm.type, Ci.nsIPermissionManager.DENY_ACTION);
         request.cancel();
       }
     }];
 
     let message = browserBundle.formatStringFromName(entityName + ".wantsTo",
                                                      [request.principal.URI.host], 1);
     let newBar = notificationBox.appendNotification(message,
-                                                    request.type,
+                                                    perm.type,
                                                     icon,
                                                     notificationBox.PRIORITY_WARNING_MEDIUM,
                                                     buttons);
 
-    if (request.type == "geolocation") {
+    if (perm.type == "geolocation") {
       // Add the "learn more" link.
       let link = newBar.ownerDocument.createElement("label");
       link.setAttribute("value", browserBundle.GetStringFromName("geolocation.learnMore"));
       link.setAttribute("class", "text-link notification-link");
       newBar.insertBefore(link, newBar.firstChild);
 
       let win = this.getChromeWindowForRequest(request);
       link.addEventListener("click", function() {
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -212,16 +212,18 @@
 #include "mozilla/css/Rule.h"
 #include "nsIDOMLocation.h"
 #include "nsIHttpChannelInternal.h"
 #include "nsISecurityConsoleMessage.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "mozilla/dom/XPathEvaluator.h"
 #include "nsIDocumentEncoder.h"
 #include "nsIStructuredCloneContainer.h"
+#include "nsIMutableArray.h"
+#include "nsContentPermissionHelper.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 typedef nsTArray<Link*> LinkArray;
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo* gDocumentLeakPRLog;
@@ -10724,27 +10726,21 @@ public:
   bool mUserInputOrChromeCaller;
 };
 
 NS_IMPL_ISUPPORTS_INHERITED1(nsPointerLockPermissionRequest,
                              nsRunnable,
                              nsIContentPermissionRequest)
 
 NS_IMETHODIMP
-nsPointerLockPermissionRequest::GetType(nsACString& aType)
-{
-  aType = "pointerLock";
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsPointerLockPermissionRequest::GetAccess(nsACString& aAccess)
-{
-  aAccess = "unused";
-  return NS_OK;
+nsPointerLockPermissionRequest::GetTypes(nsIArray** aTypes)
+{
+  return CreatePermissionArray(NS_LITERAL_CSTRING("pointerLock"),
+                               NS_LITERAL_CSTRING("unused"),
+                               aTypes);
 }
 
 NS_IMETHODIMP
 nsPointerLockPermissionRequest::GetPrincipal(nsIPrincipal** aPrincipal)
 {
   nsCOMPtr<nsIDocument> d = do_QueryReferent(mDocument);
   if (d) {
     NS_ADDREF(*aPrincipal = d->NodePrincipal());
--- a/content/media/webrtc/MediaEngine.h
+++ b/content/media/webrtc/MediaEngine.h
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MEDIAENGINE_H_
 #define MEDIAENGINE_H_
 
+#include "mozilla/RefPtr.h"
 #include "nsIDOMFile.h"
 #include "DOMMediaStream.h"
 #include "MediaStreamGraph.h"
 
 namespace mozilla {
 
 /**
  * Abstract interface for managing audio and video devices. Each platform
@@ -30,17 +31,17 @@ enum MediaEngineState {
 };
 
 // We only support 1 audio and 1 video track for now.
 enum {
   kVideoTrack = 1,
   kAudioTrack = 2
 };
 
-class MediaEngine
+class MediaEngine : public RefCounted<MediaEngine>
 {
 public:
   virtual ~MediaEngine() {}
 
   static const int DEFAULT_VIDEO_FPS = 30;
   static const int DEFAULT_VIDEO_MIN_FPS = 10;
   static const int DEFAULT_VIDEO_WIDTH = 640;
   static const int DEFAULT_VIDEO_HEIGHT = 480;
--- a/content/media/webrtc/MediaEngineWebRTC.cpp
+++ b/content/media/webrtc/MediaEngineWebRTC.cpp
@@ -96,17 +96,17 @@ MediaEngineWebRTC::EnumerateVideoDevices
     }
 
     nsRefPtr<MediaEngineWebRTCVideoSource> vSource;
     NS_ConvertUTF8toUTF16 uuid(cameraName);
     if (mVideoSources.Get(uuid, getter_AddRefs(vSource))) {
       // We've already seen this device, just append.
       aVSources->AppendElement(vSource.get());
     } else {
-      vSource = new MediaEngineWebRTCVideoSource(mCameraManager, i, mWindowId);
+      vSource = new MediaEngineWebRTCVideoSource(mCameraManager, i);
       mVideoSources.Put(uuid, vSource); // Hashtable takes ownership.
       aVSources->AppendElement(vSource);
     }
   }
 
   return;
 #else
   webrtc::ViEBase* ptrViEBase;
--- a/content/media/webrtc/MediaEngineWebRTC.h
+++ b/content/media/webrtc/MediaEngineWebRTC.h
@@ -47,16 +47,17 @@
 #include "webrtc/video_engine/include/vie_capture.h"
 #ifdef MOZ_B2G_CAMERA
 #include "CameraPreviewMediaStream.h"
 #include "DOMCameraManager.h"
 #include "GonkCameraControl.h"
 #include "ImageContainer.h"
 #include "nsGlobalWindow.h"
 #include "prprf.h"
+#include "nsProxyRelease.h"
 #endif
 
 #include "NullTransport.h"
 
 namespace mozilla {
 
 #ifdef MOZ_B2G_CAMERA
 class CameraAllocateRunnable;
@@ -68,17 +69,17 @@ class GetCameraNameRunnable;
  *
  * On B2G platform, member data may accessed from different thread after construction:
  *
  * MediaThread:
  *   mState, mImage, mWidth, mHeight, mCapability, mPrefs, mDeviceName, mUniqueId, mInitDone,
  *   mSources, mImageContainer, mSources, mState, mImage, mLastCapture
  *
  * MainThread:
- *   mDOMCameraControl, mCaptureIndex, mCameraThread, mWindowId, mCameraManager,
+ *   mDOMCameraControl, mCaptureIndex, mCameraThread, mCameraManager,
  *   mNativeCameraControl, mPreviewStream, mState, mLastCapture, mWidth, mHeight
  *
  * Where mWidth, mHeight, mImage are protected by mMonitor
  *       mState, mLastCapture is protected by mCallbackMonitor
  * Other variable is accessed only from single thread
  */
 class MediaEngineWebRTCVideoSource : public MediaEngineVideoSource
                                    , public nsRunnable
@@ -91,21 +92,20 @@ class MediaEngineWebRTCVideoSource : pub
                                    , public CameraPreviewFrameCallback
 #else
                                    , public webrtc::ExternalRenderer
 #endif
 {
 public:
 #ifdef MOZ_B2G_CAMERA
   MediaEngineWebRTCVideoSource(nsDOMCameraManager* aCameraManager,
-    int aIndex, uint64_t aWindowId)
+    int aIndex)
     : mCameraManager(aCameraManager)
     , mNativeCameraControl(nullptr)
     , mPreviewStream(nullptr)
-    , mWindowId(aWindowId)
     , mCallbackMonitor("WebRTCCamera.CallbackMonitor")
     , mCaptureIndex(aIndex)
     , mMonitor("WebRTCCamera.Monitor")
     , mWidth(0)
     , mHeight(0)
     , mInitDone(false)
     , mInSnapshotMode(false)
     , mSnapshotPath(nullptr)
@@ -218,17 +218,16 @@ private:
   // We need raw pointer here since such DOM-object should not addref/release on
   // any thread other than main thread, but we must use this object for now. To
   // avoid any bad thing do to addref/release DOM-object on other thread, we use
   // raw-pointer for now.
   nsDOMCameraManager* mCameraManager;
   nsRefPtr<nsDOMCameraControl> mDOMCameraControl;
   nsRefPtr<nsGonkCameraControl> mNativeCameraControl;
   nsRefPtr<DOMCameraPreview> mPreviewStream;
-  uint64_t mWindowId;
   mozilla::ReentrantMonitor mCallbackMonitor; // Monitor for camera callback handling
   nsRefPtr<nsIThread> mCameraThread;
   nsRefPtr<nsIDOMFile> mLastCapture;
 #else
   webrtc::VideoEngine* mVideoEngine; // Weak reference, don't free.
   webrtc::ViEBase* mViEBase;
   webrtc::ViECapture* mViECapture;
   webrtc::ViERender* mViERender;
@@ -347,25 +346,24 @@ private:
 
   NullTransport *mNullTransport;
 };
 
 class MediaEngineWebRTC : public MediaEngine
 {
 public:
 #ifdef MOZ_B2G_CAMERA
-  MediaEngineWebRTC(nsDOMCameraManager* aCameraManager, uint64_t aWindowId)
+  MediaEngineWebRTC(nsDOMCameraManager* aCameraManager)
     : mMutex("mozilla::MediaEngineWebRTC")
     , mVideoEngine(nullptr)
     , mVoiceEngine(nullptr)
     , mVideoEngineInit(false)
     , mAudioEngineInit(false)
+    , mHasTabVideoSource(false)
     , mCameraManager(aCameraManager)
-    , mWindowId(aWindowId)
-    , mHasTabVideoSource(false)
   {
     AsyncLatencyLogger::Get(true)->AddRef();
     mLoadMonitor = new LoadMonitor();
     mLoadMonitor->Init(mLoadMonitor);
   }
 #else
   MediaEngineWebRTC(MediaEnginePrefs &aPrefs);
 #endif
@@ -396,25 +394,26 @@ private:
   bool mHasTabVideoSource;
 
   // Store devices we've already seen in a hashtable for quick return.
   // Maps UUID to MediaEngineSource (one set for audio, one for video).
   nsRefPtrHashtable<nsStringHashKey, MediaEngineWebRTCVideoSource > mVideoSources;
   nsRefPtrHashtable<nsStringHashKey, MediaEngineWebRTCAudioSource > mAudioSources;
 
 #ifdef MOZ_B2G_CAMERA
+  // XXX Should use nsMainThreadPtrHandle/etc
+
   // MediaEngine hold this DOM object, and the MediaEngine is hold by Navigator
   // Their life time is always much longer than this object. Use a raw-pointer
   // here should be safe.
   // We need raw pointer here since such DOM-object should not addref/release on
   // any thread other than main thread, but we must use this object for now. To
   // avoid any bad thing do to addref/release DOM-object on other thread, we use
   // raw-pointer for now.
   nsDOMCameraManager* mCameraManager;
-  uint64_t mWindowId;
 #endif
 
    nsRefPtr<LoadMonitor> mLoadMonitor;
 };
 
 }
 
 #endif /* NSMEDIAENGINEWEBRTC_H_ */
--- a/content/media/webrtc/MediaEngineWebRTCVideo.cpp
+++ b/content/media/webrtc/MediaEngineWebRTCVideo.cpp
@@ -307,17 +307,16 @@ MediaEngineWebRTCVideoSource::Deallocate
   }
   return NS_OK;
 }
 
 nsresult
 MediaEngineWebRTCVideoSource::Start(SourceMediaStream* aStream, TrackID aID)
 {
   LOG((__FUNCTION__));
-  int error = 0;
   if (!mInitDone || !aStream) {
     return NS_ERROR_FAILURE;
   }
 
   mSources.AppendElement(aStream);
 
   aStream->AddTrack(aID, USECS_PER_S, 0, new VideoSegment());
   aStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
@@ -336,17 +335,17 @@ MediaEngineWebRTCVideoSource::Start(Sour
                                        &MediaEngineWebRTCVideoSource::StartImpl,
                                        mCapability));
   mCallbackMonitor.Wait();
   if (mState != kStarted) {
     return NS_ERROR_FAILURE;
   }
 #else
   mState = kStarted;
-  error = mViERender->AddRenderer(mCaptureIndex, webrtc::kVideoI420, (webrtc::ExternalRenderer*)this);
+  int error = mViERender->AddRenderer(mCaptureIndex, webrtc::kVideoI420, (webrtc::ExternalRenderer*)this);
   if (error == -1) {
     return NS_ERROR_FAILURE;
   }
 
   error = mViERender->StartRender(mCaptureIndex);
   if (error == -1) {
     return NS_ERROR_FAILURE;
   }
@@ -487,30 +486,28 @@ MediaEngineWebRTCVideoSource::Shutdown()
 
 #ifdef MOZ_B2G_CAMERA
 
 // All these functions must be run on MainThread!
 void
 MediaEngineWebRTCVideoSource::AllocImpl() {
   MOZ_ASSERT(NS_IsMainThread());
 
-  mDOMCameraControl = new nsDOMCameraControl(mCaptureIndex,
-                                             mCameraThread,
-                                             this,
-                                             this,
-                                             nsGlobalWindow::GetInnerWindowWithId(mWindowId));
-  mCameraManager->Register(mDOMCameraControl);
+  ErrorResult rv;
+  mDOMCameraControl = mCameraManager->GetCameraControl(mCaptureIndex,
+                                                       this, this, rv);
 }
 
 void
 MediaEngineWebRTCVideoSource::DeallocImpl() {
   MOZ_ASSERT(NS_IsMainThread());
 
   mNativeCameraControl->ReleaseHardware(this, this);
   mNativeCameraControl = nullptr;
+  mDOMCameraControl = nullptr;
 }
 
 void
 MediaEngineWebRTCVideoSource::StartImpl(webrtc::CaptureCapability aCapability) {
   MOZ_ASSERT(NS_IsMainThread());
 
   idl::CameraSize size;
   size.width = aCapability.width;
--- a/dom/apps/src/PermissionsTable.jsm
+++ b/dom/apps/src/PermissionsTable.jsm
@@ -318,16 +318,21 @@ this.PermissionsTable =  { geolocation: 
                              privileged: ALLOW_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "downloads": {
                              app: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
+                           "video-capture": {
+                             app: PROMPT_ACTION,
+                             privileged: PROMPT_ACTION,
+                             certified: PROMPT_ACTION
+                           },
                          };
 
 /**
  * Append access modes to the permission name as suffixes.
  *   e.g. permission name 'contacts' with ['read', 'write'] =
  *   ['contacts-read', contacts-write']
  * @param string aPermName
  * @param array aAccess
--- a/dom/base/nsContentPermissionHelper.cpp
+++ b/dom/base/nsContentPermissionHelper.cpp
@@ -1,49 +1,183 @@
 /* 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/. */
 
 #ifdef MOZ_WIDGET_GONK
 #include "GonkPermission.h"
 #include "mozilla/dom/ContentParent.h"
 #endif // MOZ_WIDGET_GONK
-#include "nsContentPermissionHelper.h"
-#include "nsIContentPermissionPrompt.h"
 #include "nsCOMPtr.h"
 #include "nsIDOMElement.h"
 #include "nsIPrincipal.h"
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/PContentPermission.h"
+#include "mozilla/dom/PermissionMessageUtils.h"
+#include "mozilla/dom/PContentPermissionRequestParent.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/unused.h"
 #include "nsComponentManagerUtils.h"
+#include "nsArrayUtils.h"
+#include "nsIMutableArray.h"
+#include "nsContentPermissionHelper.h"
 
 using mozilla::unused;          // <snicker>
 using namespace mozilla::dom;
 using namespace mozilla;
 
+namespace mozilla {
+namespace dom {
+
+class ContentPermissionRequestParent : public PContentPermissionRequestParent
+{
+ public:
+  ContentPermissionRequestParent(const nsTArray<PermissionRequest>& aRequests,
+                                 Element* element,
+                                 const IPC::Principal& principal);
+  virtual ~ContentPermissionRequestParent();
+
+  bool IsBeingDestroyed();
+
+  nsCOMPtr<nsIPrincipal> mPrincipal;
+  nsCOMPtr<Element> mElement;
+  nsCOMPtr<nsContentPermissionRequestProxy> mProxy;
+  nsTArray<PermissionRequest> mRequests;
+
+ private:
+  virtual bool Recvprompt();
+  virtual void ActorDestroy(ActorDestroyReason why);
+};
+
+ContentPermissionRequestParent::ContentPermissionRequestParent(const nsTArray<PermissionRequest>& aRequests,
+                                                               Element* aElement,
+                                                               const IPC::Principal& aPrincipal)
+{
+  MOZ_COUNT_CTOR(ContentPermissionRequestParent);
+
+  mPrincipal = aPrincipal;
+  mElement   = aElement;
+  mRequests  = aRequests;
+}
+
+ContentPermissionRequestParent::~ContentPermissionRequestParent()
+{
+  MOZ_COUNT_DTOR(ContentPermissionRequestParent);
+}
+
+bool
+ContentPermissionRequestParent::Recvprompt()
+{
+  mProxy = new nsContentPermissionRequestProxy();
+  NS_ASSERTION(mProxy, "Alloc of request proxy failed");
+  if (NS_FAILED(mProxy->Init(mRequests, this))) {
+    mProxy->Cancel();
+  }
+  return true;
+}
+
+void
+ContentPermissionRequestParent::ActorDestroy(ActorDestroyReason why)
+{
+  if (mProxy) {
+    mProxy->OnParentDestroyed();
+  }
+}
+
+bool
+ContentPermissionRequestParent::IsBeingDestroyed()
+{
+  // When TabParent::Destroy() is called, we are being destroyed. It's unsafe
+  // to send out any message now.
+  TabParent* tabParent = static_cast<TabParent*>(Manager());
+  return tabParent->IsDestroyed();
+}
+
+NS_IMPL_ISUPPORTS1(ContentPermissionType, nsIContentPermissionType)
+
+ContentPermissionType::ContentPermissionType(const nsACString& aType,
+                                             const nsACString& aAccess)
+{
+  mType = aType;
+  mAccess = aAccess;
+}
+
+ContentPermissionType::~ContentPermissionType()
+{
+}
+
+NS_IMETHODIMP
+ContentPermissionType::GetType(nsACString& aType)
+{
+  aType = mType;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+ContentPermissionType::GetAccess(nsACString& aAccess)
+{
+  aAccess = mAccess;
+  return NS_OK;
+}
+
+uint32_t
+ConvertPermissionRequestToArray(nsTArray<PermissionRequest>& aSrcArray,
+                                nsIMutableArray* aDesArray)
+{
+  uint32_t len = aSrcArray.Length();
+  for (uint32_t i = 0; i < len; i++) {
+    nsRefPtr<ContentPermissionType> cpt =
+      new ContentPermissionType(aSrcArray[i].type(), aSrcArray[i].access());
+    aDesArray->AppendElement(cpt, false);
+  }
+  return len;
+}
+
+nsresult
+CreatePermissionArray(const nsACString& aType,
+                      const nsACString& aAccess,
+                      nsIArray** aTypesArray)
+{
+  nsCOMPtr<nsIMutableArray> types = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  nsRefPtr<ContentPermissionType> permType = new ContentPermissionType(aType,
+                                                                       aAccess);
+  types->AppendElement(permType, false);
+  types.forget(aTypesArray);
+
+  return NS_OK;
+}
+
+PContentPermissionRequestParent*
+CreateContentPermissionRequestParent(const nsTArray<PermissionRequest>& aRequests,
+                                     Element* element,
+                                     const IPC::Principal& principal)
+{
+  return new ContentPermissionRequestParent(aRequests, element, principal);
+}
+
+} // namespace dom
+} // namespace mozilla
+
 nsContentPermissionRequestProxy::nsContentPermissionRequestProxy()
 {
   MOZ_COUNT_CTOR(nsContentPermissionRequestProxy);
 }
 
 nsContentPermissionRequestProxy::~nsContentPermissionRequestProxy()
 {
   MOZ_COUNT_DTOR(nsContentPermissionRequestProxy);
 }
 
 nsresult
-nsContentPermissionRequestProxy::Init(const nsACString & type,
-                                      const nsACString & access,
+nsContentPermissionRequestProxy::Init(const nsTArray<PermissionRequest>& requests,
                                       ContentPermissionRequestParent* parent)
 {
   NS_ASSERTION(parent, "null parent");
   mParent = parent;
-  mType   = type;
-  mAccess = access;
+  mPermissionRequests = requests;
 
   nsCOMPtr<nsIContentPermissionPrompt> prompt = do_CreateInstance(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
   if (!prompt) {
     return NS_ERROR_FAILURE;
   }
 
   prompt->Prompt(this);
   return NS_OK;
@@ -53,27 +187,24 @@ void
 nsContentPermissionRequestProxy::OnParentDestroyed()
 {
   mParent = nullptr;
 }
 
 NS_IMPL_ISUPPORTS1(nsContentPermissionRequestProxy, nsIContentPermissionRequest)
 
 NS_IMETHODIMP
-nsContentPermissionRequestProxy::GetType(nsACString & aType)
+nsContentPermissionRequestProxy::GetTypes(nsIArray** aTypes)
 {
-  aType = mType;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsContentPermissionRequestProxy::GetAccess(nsACString & aAccess)
-{
-  aAccess = mAccess;
-  return NS_OK;
+  nsCOMPtr<nsIMutableArray> types = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  if (ConvertPermissionRequestToArray(mPermissionRequests, types)) {
+    types.forget(aTypes);
+    return NS_OK;
+  }
+  return NS_ERROR_FAILURE;
 }
 
 NS_IMETHODIMP
 nsContentPermissionRequestProxy::GetWindow(nsIDOMWindow * *aRequestingWindow)
 {
   NS_ENSURE_ARG_POINTER(aRequestingWindow);
   *aRequestingWindow = nullptr; // ipc doesn't have a window
   return NS_OK;
@@ -131,71 +262,27 @@ nsContentPermissionRequestProxy::Allow()
 
   // Don't send out the delete message when the managing protocol (PBrowser) is
   // being destroyed and PContentPermissionRequest will soon be.
   if (mParent->IsBeingDestroyed()) {
     return NS_ERROR_FAILURE;
   }
 
 #ifdef MOZ_WIDGET_GONK
-  if (mType.Equals("audio-capture")) {
-    GonkPermissionService::GetInstance()->addGrantInfo(
-      "android.permission.RECORD_AUDIO",
-      static_cast<TabParent*>(mParent->Manager())->Manager()->Pid());
+  uint32_t len = mPermissionRequests.Length();
+  for (uint32_t i = 0; i < len; i++) {
+    if (mPermissionRequests[i].type().Equals("audio-capture")) {
+      GonkPermissionService::GetInstance()->addGrantInfo(
+        "android.permission.RECORD_AUDIO",
+        static_cast<TabParent*>(mParent->Manager())->Manager()->Pid());
+    }
+    if (mPermissionRequests[i].type().Equals("video-capture")) {
+      GonkPermissionService::GetInstance()->addGrantInfo(
+        "android.permission.CAMERA",
+        static_cast<TabParent*>(mParent->Manager())->Manager()->Pid());
+    }
   }
 #endif
 
   unused << ContentPermissionRequestParent::Send__delete__(mParent, true);
   mParent = nullptr;
   return NS_OK;
 }
-
-namespace mozilla {
-namespace dom {
-
-ContentPermissionRequestParent::ContentPermissionRequestParent(const nsACString& aType,
-                                                               const nsACString& aAccess,
-                                                               Element* aElement,
-                                                               const IPC::Principal& aPrincipal)
-{
-  MOZ_COUNT_CTOR(ContentPermissionRequestParent);
-
-  mPrincipal = aPrincipal;
-  mElement   = aElement;
-  mType      = aType;
-  mAccess    = aAccess;
-}
-
-ContentPermissionRequestParent::~ContentPermissionRequestParent()
-{
-  MOZ_COUNT_DTOR(ContentPermissionRequestParent);
-}
-
-bool
-ContentPermissionRequestParent::Recvprompt()
-{
-  mProxy = new nsContentPermissionRequestProxy();
-  NS_ASSERTION(mProxy, "Alloc of request proxy failed");
-  if (NS_FAILED(mProxy->Init(mType, mAccess, this))) {
-    mProxy->Cancel();
-  }
-  return true;
-}
-
-void
-ContentPermissionRequestParent::ActorDestroy(ActorDestroyReason why)
-{
-  if (mProxy) {
-    mProxy->OnParentDestroyed();
-  }
-}
-
-bool
-ContentPermissionRequestParent::IsBeingDestroyed()
-{
-  // When TabParent::Destroy() is called, we are being destroyed. It's unsafe
-  // to send out any message now.
-  TabParent* tabParent = static_cast<TabParent*>(Manager());
-  return tabParent->IsDestroyed();
-}
-
-} // namespace dom
-} // namespace mozilla
--- a/dom/base/nsContentPermissionHelper.h
+++ b/dom/base/nsContentPermissionHelper.h
@@ -1,65 +1,80 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsContentPermissionHelper_h
 #define nsContentPermissionHelper_h
 
 #include "nsIContentPermissionPrompt.h"
-#include "nsString.h"
-
-#include "mozilla/dom/PermissionMessageUtils.h"
-#include "mozilla/dom/PContentPermissionRequestParent.h"
+#include "nsTArray.h"
+#include "nsIMutableArray.h"
 
 class nsContentPermissionRequestProxy;
 
+// Forward declare IPC::Principal here which is defined in
+// PermissionMessageUtils.h. Include this file will transitively includes
+// "windows.h" and it defines
+//   #define CreateEvent CreateEventW
+//   #define LoadImage LoadImageW
+// That will mess up windows build.
+namespace IPC {
+class Principal;
+}
+
 namespace mozilla {
 namespace dom {
 
 class Element;
+class PermissionRequest;
+class ContentPermissionRequestParent;
+class PContentPermissionRequestParent;
 
-class ContentPermissionRequestParent : public PContentPermissionRequestParent
+class ContentPermissionType : public nsIContentPermissionType
 {
- public:
-  ContentPermissionRequestParent(const nsACString& type,
-                                 const nsACString& access,
-                                 Element* element,
-                                 const IPC::Principal& principal);
-  virtual ~ContentPermissionRequestParent();
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSICONTENTPERMISSIONTYPE
 
-  bool IsBeingDestroyed();
+  ContentPermissionType(const nsACString& aType, const nsACString& aAccess);
+  virtual ~ContentPermissionType();
 
-  nsCOMPtr<nsIPrincipal> mPrincipal;
-  nsCOMPtr<Element> mElement;
-  nsCOMPtr<nsContentPermissionRequestProxy> mProxy;
+protected:
   nsCString mType;
   nsCString mAccess;
+};
 
- private:
-  virtual bool Recvprompt();
-  virtual void ActorDestroy(ActorDestroyReason why);
-};
+uint32_t ConvertPermissionRequestToArray(nsTArray<PermissionRequest>& aSrcArray,
+                                         nsIMutableArray* aDesArray);
+
+nsresult CreatePermissionArray(const nsACString& aType,
+                               const nsACString& aAccess,
+                               nsIArray** aTypesArray);
+
+PContentPermissionRequestParent*
+CreateContentPermissionRequestParent(const nsTArray<PermissionRequest>& aRequests,
+                                     Element* element,
+                                     const IPC::Principal& principal);
 
 } // namespace dom
 } // namespace mozilla
 
 class nsContentPermissionRequestProxy : public nsIContentPermissionRequest
 {
  public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSICONTENTPERMISSIONREQUEST
+
   nsContentPermissionRequestProxy();
   virtual ~nsContentPermissionRequestProxy();
 
-  nsresult Init(const nsACString& type, const nsACString& access, mozilla::dom::ContentPermissionRequestParent* parent);
+  nsresult Init(const nsTArray<mozilla::dom::PermissionRequest>& requests,
+                mozilla::dom::ContentPermissionRequestParent* parent);
   void OnParentDestroyed();
 
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSICONTENTPERMISSIONREQUEST
-
  private:
   // Non-owning pointer to the ContentPermissionRequestParent object which owns this proxy.
   mozilla::dom::ContentPermissionRequestParent* mParent;
-  nsCString mType;
-  nsCString mAccess;
+  nsTArray<mozilla::dom::PermissionRequest> mPermissionRequests;
 };
+
 #endif // nsContentPermissionHelper_h
-
--- a/dom/camera/DOMCameraManager.cpp
+++ b/dom/camera/DOMCameraManager.cpp
@@ -100,43 +100,56 @@ nsDOMCameraManager::CreateInstance(nsPID
     new nsDOMCameraManager(aWindow);
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   obs->AddObserver(cameraManager, "xpcom-shutdown", true);
 
   return cameraManager.forget();
 }
 
+nsDOMCameraControl*
+nsDOMCameraManager::GetCameraControl(uint32_t aDeviceNum,
+                                     nsICameraGetCameraCallback* onSuccess,
+                                     nsICameraErrorCallback* onError,
+                                     ErrorResult& aRv)
+{
+  aRv = NS_OK;
+
+  // reuse the same camera thread to conserve resources
+  if (!mCameraThread) {
+    aRv = NS_NewThread(getter_AddRefs(mCameraThread));
+    if (aRv.Failed()) {
+      return nullptr;
+    }
+  }
+
+  // Creating this object will trigger the onSuccess handler
+  nsDOMCameraControl* cameraControl =  new nsDOMCameraControl(aDeviceNum, mCameraThread,
+                                                              onSuccess, onError, mWindow);
+  if (cameraControl) {
+    Register(cameraControl);
+  }
+  return cameraControl;
+}
+
 void
 nsDOMCameraManager::GetCamera(const CameraSelector& aOptions,
                               nsICameraGetCameraCallback* onSuccess,
                               const Optional<nsICameraErrorCallback*>& onError,
                               ErrorResult& aRv)
 {
   uint32_t cameraId = 0;  // back (or forward-facing) camera by default
   if (aOptions.mCamera.EqualsLiteral("front")) {
     cameraId = 1;
   }
 
-  // reuse the same camera thread to conserve resources
-  if (!mCameraThread) {
-    aRv = NS_NewThread(getter_AddRefs(mCameraThread));
-    if (aRv.Failed()) {
-      return;
-    }
-  }
-
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
 
-  // Creating this object will trigger the onSuccess handler
   nsRefPtr<nsDOMCameraControl> cameraControl =
-    new nsDOMCameraControl(cameraId, mCameraThread,
-                           onSuccess, onError.WasPassed() ? onError.Value() : nullptr, mWindow);
-
-  Register(cameraControl);
+    GetCameraControl(cameraId, onSuccess, onError.WasPassed() ? onError.Value() : nullptr, aRv);
 }
 
 void
 nsDOMCameraManager::Register(nsDOMCameraControl* aDOMCameraControl)
 {
   DOM_CAMERA_LOGI(">>> Register( aDOMCameraControl = %p ) mWindowId = 0x%llx\n", aDOMCameraControl, mWindowId);
   MOZ_ASSERT(NS_IsMainThread());
 
--- a/dom/camera/DOMCameraManager.h
+++ b/dom/camera/DOMCameraManager.h
@@ -44,16 +44,21 @@ public:
                                                          nsIObserver)
   NS_DECL_NSIOBSERVER
 
   static bool CheckPermission(nsPIDOMWindow* aWindow);
   static already_AddRefed<nsDOMCameraManager>
     CreateInstance(nsPIDOMWindow* aWindow);
   static bool IsWindowStillActive(uint64_t aWindowId);
 
+  // Build us an nsDOMCameraControl
+  mozilla::nsDOMCameraControl* GetCameraControl(uint32_t aDeviceNum,
+                                                nsICameraGetCameraCallback* onSuccess,
+                                                nsICameraErrorCallback* onError,
+                                                mozilla::ErrorResult& aRv);
   void Register(mozilla::nsDOMCameraControl* aDOMCameraControl);
   void OnNavigation(uint64_t aWindowId);
 
   nsresult GetNumberOfCameras(int32_t& aDeviceCount);
   nsresult GetCameraName(uint32_t aDeviceNum, nsCString& aDeviceName);
 
   // WebIDL
   void GetCamera(const mozilla::dom::CameraSelector& aOptions,
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -44,16 +44,17 @@
 #include "nsIObserverService.h"
 #include "GeneratedEvents.h"
 #include "nsIMIMEService.h"
 #include "nsCExternalHandlerService.h"
 #include "nsIPermissionManager.h"
 #include "nsIStringBundle.h"
 #include "nsIDocument.h"
 #include <algorithm>
+#include "nsContentPermissionHelper.h"
 
 #include "mozilla/dom/DeviceStorageBinding.h"
 
 // Microsoft's API Name hackery sucks
 #undef CreateEvent
 
 #ifdef MOZ_WIDGET_GONK
 #include "nsIVolume.h"
@@ -1728,27 +1729,24 @@ nsDOMDeviceStorageCursor::~nsDOMDeviceSt
 
 void
 nsDOMDeviceStorageCursor::GetStorageType(nsAString & aType)
 {
   aType = mFile->mStorageType;
 }
 
 NS_IMETHODIMP
-nsDOMDeviceStorageCursor::GetType(nsACString & aType)
+nsDOMDeviceStorageCursor::GetTypes(nsIArray** aTypes)
 {
-  return DeviceStorageTypeChecker::GetPermissionForType(mFile->mStorageType,
-                                                        aType);
-}
-
-NS_IMETHODIMP
-nsDOMDeviceStorageCursor::GetAccess(nsACString & aAccess)
-{
-  aAccess = NS_LITERAL_CSTRING("read");
-  return NS_OK;
+  nsCString type;
+  nsresult rv =
+    DeviceStorageTypeChecker::GetPermissionForType(mFile->mStorageType, type);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return CreatePermissionArray(type, NS_LITERAL_CSTRING("read"), aTypes);
 }
 
 NS_IMETHODIMP
 nsDOMDeviceStorageCursor::GetPrincipal(nsIPrincipal * *aRequestingPrincipal)
 {
   NS_IF_ADDREF(*aRequestingPrincipal = mPrincipal);
   return NS_OK;
 }
@@ -2246,51 +2244,50 @@ public:
         return rv;
       }
       nsCString access;
       rv = DeviceStorageTypeChecker::GetAccessForRequest(
         DeviceStorageRequestType(mRequestType), access);
       if (NS_FAILED(rv)) {
         return rv;
       }
+      nsTArray<PermissionRequest> permArray;
+      permArray.AppendElement(PermissionRequest(type, access));
       child->SendPContentPermissionRequestConstructor(
-        this, type, access, IPC::Principal(mPrincipal));
+        this, permArray, IPC::Principal(mPrincipal));
 
       Sendprompt();
       return NS_OK;
     }
 
     nsCOMPtr<nsIContentPermissionPrompt> prompt
       = do_CreateInstance(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
     if (prompt) {
       prompt->Prompt(this);
     }
     return NS_OK;
   }
 
-  NS_IMETHOD GetType(nsACString & aType)
+  NS_IMETHODIMP GetTypes(nsIArray** aTypes)
   {
     nsCString type;
-    nsresult rv
-      = DeviceStorageTypeChecker::GetPermissionForType(mFile->mStorageType,
-                                                       aType);
+    nsresult rv =
+      DeviceStorageTypeChecker::GetPermissionForType(mFile->mStorageType, type);
     if (NS_FAILED(rv)) {
       return rv;
     }
-    return NS_OK;
-  }
-
-  NS_IMETHOD GetAccess(nsACString & aAccess)
-  {
-    nsresult rv = DeviceStorageTypeChecker::GetAccessForRequest(
-      DeviceStorageRequestType(mRequestType), aAccess);
+
+    nsCString access;
+    rv = DeviceStorageTypeChecker::GetAccessForRequest(
+      DeviceStorageRequestType(mRequestType), access);
     if (NS_FAILED(rv)) {
       return rv;
     }
-    return NS_OK;
+
+    return CreatePermissionArray(type, access, aTypes);
   }
 
   NS_IMETHOD GetPrincipal(nsIPrincipal * *aRequestingPrincipal)
   {
     NS_IF_ADDREF(*aRequestingPrincipal = mPrincipal);
     return NS_OK;
   }
 
@@ -3294,18 +3291,20 @@ nsDOMDeviceStorage::EnumerateInternal(co
     // Corresponding release occurs in DeallocPContentPermissionRequest.
     r->AddRef();
 
     nsCString type;
     aRv = DeviceStorageTypeChecker::GetPermissionForType(mStorageType, type);
     if (aRv.Failed()) {
       return nullptr;
     }
-    child->SendPContentPermissionRequestConstructor(r, type,
-                                                    NS_LITERAL_CSTRING("read"),
+    nsTArray<PermissionRequest> permArray;
+    permArray.AppendElement(PermissionRequest(type, NS_LITERAL_CSTRING("read")));
+    child->SendPContentPermissionRequestConstructor(r,
+                                                    permArray,
                                                     IPC::Principal(mPrincipal));
 
     r->Sendprompt();
 
     return cursor.forget();
   }
 
   nsCOMPtr<nsIContentPermissionPrompt> prompt
--- a/dom/interfaces/base/nsIContentPermissionPrompt.idl
+++ b/dom/interfaces/base/nsIContentPermissionPrompt.idl
@@ -2,38 +2,50 @@
  * 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 "nsISupports.idl"
 
 interface nsIPrincipal;
 interface nsIDOMWindow;
 interface nsIDOMElement;
+interface nsIArray;
 
 /**
- * Interface allows access to a content to request
- * permission to perform a privileged operation such as
- * geolocation.
+ *  Interface provides the request type and its access.
  */
-[scriptable, uuid(1de67000-2de8-11e2-81c1-0800200c9a66)]
-interface nsIContentPermissionRequest : nsISupports {
-
+[scriptable, builtinclass, uuid(384b6cc4-a66b-4bea-98e0-eb10562a9ba4)]
+interface nsIContentPermissionType : nsISupports {
   /**
    *  The type of the permission request, such as
    *  "geolocation".
    */
   readonly attribute ACString type;
 
   /**
    *  The access of the permission request, such as
    *  "read".
    */
   readonly attribute ACString access;
+};
 
+/**
+ * Interface allows access to a content to request
+ * permission to perform a privileged operation such as
+ * geolocation.
+ */
+[scriptable, uuid(69a39d88-d1c4-4ba9-9b19-bafc7a1bb783)]
+interface nsIContentPermissionRequest : nsISupports {
   /**
+   *  The array will include the request types. Elements of this array are
+   *  nsIContentPermissionType object.
+   */
+  readonly attribute nsIArray types;
+
+  /*
    *  The principal of the permission request.
    */
   readonly attribute nsIPrincipal principal;
 
   /**
    *  The window or element that the permission request was
    *  originated in.  Typically the element will be non-null
    *  in when using out of process content.  window or
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1343,46 +1343,52 @@ OnFinishNuwaPreparation ()
 static void
 PreloadSlowThings()
 {
     // This fetches and creates all the built-in stylesheets.
     nsLayoutStylesheetCache::UserContentSheet();
 
     TabChild::PreloadSlowThings();
 
-#ifdef MOZ_NUWA_PROCESS
-    // After preload of slow things, start freezing threads.
-    if (IsNuwaProcess()) {
-        // Perform GC before freezing the Nuwa process to reduce memory usage.
-        ContentChild::GetSingleton()->RecvGarbageCollect();
-
-        MessageLoop::current()->
-                PostTask(FROM_HERE,
-                         NewRunnableFunction(OnFinishNuwaPreparation));
-    }
-#endif
 }
 
 bool
 ContentChild::RecvAppInfo(const nsCString& version, const nsCString& buildID,
                           const nsCString& name, const nsCString& UAName)
 {
     mAppInfo.version.Assign(version);
     mAppInfo.buildID.Assign(buildID);
     mAppInfo.name.Assign(name);
     mAppInfo.UAName.Assign(UAName);
+
+    if (!Preferences::GetBool("dom.ipc.processPrelaunch.enabled", false)) {
+        return true;
+    }
+
     // If we're part of the mozbrowser machinery, go ahead and start
     // preloading things.  We can only do this for mozbrowser because
     // PreloadSlowThings() may set the docshell of the first TabChild
     // inactive, and we can only safely restore it to active from
     // BrowserElementChild.js.
-    if ((mIsForApp || mIsForBrowser) &&
-        Preferences::GetBool("dom.ipc.processPrelaunch.enabled", false)) {
+    if ((mIsForApp || mIsForBrowser)
+#ifdef MOZ_NUWA_PROCESS
+        && !IsNuwaProcess()
+#endif
+       ) {
         PreloadSlowThings();
     }
+
+#ifdef MOZ_NUWA_PROCESS
+    if (IsNuwaProcess()) {
+        ContentChild::GetSingleton()->RecvGarbageCollect();
+        MessageLoop::current()->PostTask(
+            FROM_HERE, NewRunnableFunction(OnFinishNuwaPreparation));
+    }
+#endif
+
     return true;
 }
 
 bool
 ContentChild::RecvLastPrivateDocShellDestroyed()
 {
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     obs->NotifyObservers(nullptr, "last-pb-context-exited", nullptr);
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1244,103 +1244,23 @@ ContentParent::ContentParent(mozIApplica
     std::vector<std::string> extraArgs;
     if (aIsNuwaProcess) {
         extraArgs.push_back("-nuwa");
     }
     mSubprocess->LaunchAndWaitForProcessHandle(extraArgs);
 
     Open(mSubprocess->GetChannel(), mSubprocess->GetOwnedChildProcessHandle());
 
-    // Set the subprocess's priority.  We do this early on because we're likely
-    // /lowering/ the process's CPU and memory priority, which it has inherited
-    // from this process.
-    //
-    // This call can cause us to send IPC messages to the child process, so it
-    // must come after the Open() call above.
-    ProcessPriorityManager::SetProcessPriority(this, aInitialPriority);
-
-    // NB: internally, this will send an IPC message to the child
-    // process to get it to create the CompositorChild.  This
-    // message goes through the regular IPC queue for this
-    // channel, so delivery will happen-before any other messages
-    // we send.  The CompositorChild must be created before any
-    // PBrowsers are created, because they rely on the Compositor
-    // already being around.  (Creation is async, so can't happen
-    // on demand.)
-    bool useOffMainThreadCompositing = !!CompositorParent::CompositorLoop();
-    if (useOffMainThreadCompositing) {
-        DebugOnly<bool> opened = PCompositor::Open(this);
-        MOZ_ASSERT(opened);
-
-        if (Preferences::GetBool("layers.async-video.enabled",false)) {
-            opened = PImageBridge::Open(this);
-            MOZ_ASSERT(opened);
-        }
-    }
-
-    nsCOMPtr<nsIChromeRegistry> registrySvc = nsChromeRegistry::GetService();
-    nsChromeRegistryChrome* chromeRegistry =
-        static_cast<nsChromeRegistryChrome*>(registrySvc.get());
-    chromeRegistry->SendRegisteredChrome(this);
-    mMessageManager = nsFrameMessageManager::NewProcessMessageManager(this);
-
-    if (gAppData) {
-        nsCString version(gAppData->version);
-        nsCString buildID(gAppData->buildID);
-        nsCString name(gAppData->name);
-        nsCString UAName(gAppData->UAName);
-
-        // Sending all information to content process.
-        unused << SendAppInfo(version, buildID, name, UAName);
-    }
-
-    nsStyleSheetService *sheetService = nsStyleSheetService::GetInstance();
-    if (sheetService) {
-        // This looks like a lot of work, but in a normal browser session we just
-        // send two loads.
-
-        nsCOMArray<nsIStyleSheet>& agentSheets = *sheetService->AgentStyleSheets();
-        for (uint32_t i = 0; i < agentSheets.Length(); i++) {
-            URIParams uri;
-            SerializeURI(agentSheets[i]->GetSheetURI(), uri);
-            unused << SendLoadAndRegisterSheet(uri, nsIStyleSheetService::AGENT_SHEET);
-        }
-
-        nsCOMArray<nsIStyleSheet>& userSheets = *sheetService->UserStyleSheets();
-        for (uint32_t i = 0; i < userSheets.Length(); i++) {
-            URIParams uri;
-            SerializeURI(userSheets[i]->GetSheetURI(), uri);
-            unused << SendLoadAndRegisterSheet(uri, nsIStyleSheetService::USER_SHEET);
-        }
-
-        nsCOMArray<nsIStyleSheet>& authorSheets = *sheetService->AuthorStyleSheets();
-        for (uint32_t i = 0; i < authorSheets.Length(); i++) {
-            URIParams uri;
-            SerializeURI(authorSheets[i]->GetSheetURI(), uri);
-            unused << SendLoadAndRegisterSheet(uri, nsIStyleSheetService::AUTHOR_SHEET);
-        }
-    }
-
-#ifdef MOZ_CONTENT_SANDBOX
-    // Bug 921817.  We enable the sandbox in RecvSetProcessPrivileges,
-    // which is where a preallocated process drops unnecessary privileges,
-    // but a non-preallocated process will already have changed its
-    // uid/gid/etc immediately after forking.  Thus, we send this message,
-    // which is otherwise a no-op, to sandbox it at an appropriate point
-    // during startup.
-    if (aOSPrivileges != base::PRIVILEGES_INHERIT) {
-        if (!SendSetProcessPrivileges(base::PRIVILEGES_INHERIT)) {
-            KillHard();
-        }
-    }
-#endif
+    InitInternal(aInitialPriority,
+                 true, /* Setup off-main thread compositing */
+                 true  /* Send registered chrome */);
 }
 
 #ifdef MOZ_NUWA_PROCESS
-static const FileDescriptor*
+static const mozilla::ipc::FileDescriptor*
 FindFdProtocolFdMapping(const nsTArray<ProtocolFdMapping>& aFds,
                         ProtocolId aProtoId)
 {
     for (unsigned int i = 0; i < aFds.Length(); i++) {
         if (aFds[i].protocolId() == aProtoId) {
             return &aFds[i].fd();
         }
     }
@@ -1396,18 +1316,19 @@ ContentParent::ContentParent(ContentPare
     // memory priority, which it has inherited from this process.
     ProcessPriority priority;
     if (IsPreallocated()) {
         priority = PROCESS_PRIORITY_BACKGROUND;
     } else {
         priority = PROCESS_PRIORITY_FOREGROUND;
     }
 
-    ProcessPriorityManager::SetProcessPriority(this, priority);
-    mMessageManager = nsFrameMessageManager::NewProcessMessageManager(this);
+    InitInternal(priority,
+                 false, /* Setup Off-main thread compositing */
+                 false  /* Send registered chrome */);
 }
 #endif  // MOZ_NUWA_PROCESS
 
 ContentParent::~ContentParent()
 {
     if (mForceKillTask) {
         mForceKillTask->Cancel();
     }
@@ -1427,16 +1348,111 @@ ContentParent::~ContentParent()
         // nullptr.  But it could be that we created another ContentParent for
         // this app after we did this->ActorDestroy(), so the right check is
         // that sAppContentParents->Get(mAppManifestURL) != this.
         MOZ_ASSERT(!sAppContentParents ||
                    sAppContentParents->Get(mAppManifestURL) != this);
     }
 }
 
+void
+ContentParent::InitInternal(ProcessPriority aInitialPriority,
+                            bool aSetupOffMainThreadCompositing,
+                            bool aSendRegisteredChrome)
+{
+    // Set the subprocess's priority.  We do this early on because we're likely
+    // /lowering/ the process's CPU and memory priority, which it has inherited
+    // from this process.
+    //
+    // This call can cause us to send IPC messages to the child process, so it
+    // must come after the Open() call above.
+    ProcessPriorityManager::SetProcessPriority(this, aInitialPriority);
+
+    if (aSetupOffMainThreadCompositing) {
+        // NB: internally, this will send an IPC message to the child
+        // process to get it to create the CompositorChild.  This
+        // message goes through the regular IPC queue for this
+        // channel, so delivery will happen-before any other messages
+        // we send.  The CompositorChild must be created before any
+        // PBrowsers are created, because they rely on the Compositor
+        // already being around.  (Creation is async, so can't happen
+        // on demand.)
+        bool useOffMainThreadCompositing = !!CompositorParent::CompositorLoop();
+        if (useOffMainThreadCompositing) {
+            DebugOnly<bool> opened = PCompositor::Open(this);
+            MOZ_ASSERT(opened);
+
+            if (Preferences::GetBool("layers.async-video.enabled",false)) {
+                opened = PImageBridge::Open(this);
+                MOZ_ASSERT(opened);
+            }
+        }
+    }
+
+    if (aSendRegisteredChrome) {
+        nsCOMPtr<nsIChromeRegistry> registrySvc = nsChromeRegistry::GetService();
+        nsChromeRegistryChrome* chromeRegistry =
+            static_cast<nsChromeRegistryChrome*>(registrySvc.get());
+        chromeRegistry->SendRegisteredChrome(this);
+    }
+
+    mMessageManager = nsFrameMessageManager::NewProcessMessageManager(this);
+
+    if (gAppData) {
+        nsCString version(gAppData->version);
+        nsCString buildID(gAppData->buildID);
+        nsCString name(gAppData->name);
+        nsCString UAName(gAppData->UAName);
+
+        // Sending all information to content process.
+        unused << SendAppInfo(version, buildID, name, UAName);
+    }
+
+    nsStyleSheetService *sheetService = nsStyleSheetService::GetInstance();
+    if (sheetService) {
+        // This looks like a lot of work, but in a normal browser session we just
+        // send two loads.
+
+        nsCOMArray<nsIStyleSheet>& agentSheets = *sheetService->AgentStyleSheets();
+        for (uint32_t i = 0; i < agentSheets.Length(); i++) {
+            URIParams uri;
+            SerializeURI(agentSheets[i]->GetSheetURI(), uri);
+            unused << SendLoadAndRegisterSheet(uri, nsIStyleSheetService::AGENT_SHEET);
+        }
+
+        nsCOMArray<nsIStyleSheet>& userSheets = *sheetService->UserStyleSheets();
+        for (uint32_t i = 0; i < userSheets.Length(); i++) {
+            URIParams uri;
+            SerializeURI(userSheets[i]->GetSheetURI(), uri);
+            unused << SendLoadAndRegisterSheet(uri, nsIStyleSheetService::USER_SHEET);
+        }
+
+        nsCOMArray<nsIStyleSheet>& authorSheets = *sheetService->AuthorStyleSheets();
+        for (uint32_t i = 0; i < authorSheets.Length(); i++) {
+            URIParams uri;
+            SerializeURI(authorSheets[i]->GetSheetURI(), uri);
+            unused << SendLoadAndRegisterSheet(uri, nsIStyleSheetService::AUTHOR_SHEET);
+        }
+    }
+
+#ifdef MOZ_CONTENT_SANDBOX
+    // Bug 921817.  We enable the sandbox in RecvSetProcessPrivileges,
+    // which is where a preallocated process drops unnecessary privileges,
+    // but a non-preallocated process will already have changed its
+    // uid/gid/etc immediately after forking.  Thus, we send this message,
+    // which is otherwise a no-op, to sandbox it at an appropriate point
+    // during startup.
+    if (mOSPrivileges != base::PRIVILEGES_INHERIT) {
+        if (!SendSetProcessPrivileges(base::PRIVILEGES_INHERIT)) {
+            KillHard();
+        }
+    }
+#endif
+}
+
 bool
 ContentParent::IsAlive()
 {
     return mIsAlive;
 }
 
 bool
 ContentParent::IsForApp()
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -260,16 +260,21 @@ private:
                   base::ProcessHandle aPid,
                   const nsTArray<ProtocolFdMapping>& aFds,
                   ChildPrivileges aOSPrivileges = base::PRIVILEGES_DEFAULT);
 #endif
 
     // The common initialization for the constructors.
     void InitializeMembers();
 
+    // The common initialization logic shared by all constuctors.
+    void InitInternal(ProcessPriority aPriority,
+                      bool aSetupOffMainThreadCompositing,
+                      bool aSendRegisteredChrome);
+
     virtual ~ContentParent();
 
     void Init();
 
     // If the frame element indicates that the child process is "critical" and
     // has a pending system message, this function acquires the CPU wake lock on
     // behalf of the child.  We'll release the lock when the system message is
     // handled or after a timeout, whichever comes first.
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -11,16 +11,17 @@ include protocol PContentDialog;
 include protocol PDocumentRenderer;
 include protocol PContentPermissionRequest;
 include protocol PRenderFrame;
 include protocol POfflineCacheUpdate;
 include protocol PIndexedDB;
 include DOMTypes;
 include JavaScriptTypes;
 include URIParams;
+include PContentPermission;
 
 
 using class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h";
 using struct gfxMatrix from "gfxMatrix.h";
 using struct gfxSize from "gfxPoint.h";
 using CSSRect from "Units.h";
 using struct mozilla::layers::FrameMetrics from "FrameMetrics.h";
 using struct mozilla::layers::ScrollableLayerGuid from "FrameMetrics.h";
@@ -201,28 +202,26 @@ parent:
      * Nowadays this is mainly used for link locations on hover.
      */
     SetStatus(uint32_t type, nsString status);
 
     /**
      * Initiates an asynchronous request for permission for the
      * provided principal.
      *
-     * @param aType
-     *   The type of permission to request.
-     * @param aAccess
-     *   Access type. "read" for example.
+     * @param aRequests
+     *   The array of permissions to request.
      * @param aPrincipal
      *   The principal of the request.
      *
      * NOTE: The principal is untrusted in the parent process. Only
      *       principals that can live in the content process should
      *       provided.
      */
-    PContentPermissionRequest(nsCString aType, nsCString aAccess, Principal principal);
+    PContentPermissionRequest(PermissionRequest[] aRequests, Principal aPrincipal);
 
     PContentDialog(uint32_t aType, nsCString aName, nsCString aFeatures,
                    int32_t[] aIntParams, nsString[] aStringParams);
 
     /**
      * Create a layout frame (encapsulating a remote layer tree) for
      * the page that is currently loaded in the <browser>.
      */
new file mode 100644
--- /dev/null
+++ b/dom/ipc/PContentPermission.ipdlh
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+namespace mozilla {
+namespace dom {
+
+struct PermissionRequest {
+  nsCString type;
+  nsCString access;
+};
+
+} // namespace dom
+} // namespace mozilla
--- a/dom/ipc/PreallocatedProcessManager.cpp
+++ b/dom/ipc/PreallocatedProcessManager.cpp
@@ -276,16 +276,25 @@ PreallocatedProcessManagerImpl::GetSpare
 /**
  * Publish a ContentParent to spare process list.
  */
 void
 PreallocatedProcessManagerImpl::PublishSpareProcess(ContentParent* aContent)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  if (Preferences::GetBool("dom.ipc.processPriorityManager.testMode")) {
+    AutoJSContext cx;
+    nsCOMPtr<nsIMessageBroadcaster> ppmm =
+      do_GetService("@mozilla.org/parentprocessmessagemanager;1");
+    nsresult rv = ppmm->BroadcastAsyncMessage(
+      NS_LITERAL_STRING("TEST-ONLY:nuwa-add-new-process"),
+      JSVAL_NULL, JSVAL_NULL, cx, 1);
+  }
+
   if (!mNuwaForkWaitTasks.IsEmpty()) {
     mNuwaForkWaitTasks.ElementAt(0)->Cancel();
     mNuwaForkWaitTasks.RemoveElementAt(0);
   }
 
   mSpareProcesses.AppendElement(aContent);
 }
 
@@ -307,16 +316,24 @@ PreallocatedProcessManagerImpl::MaybeFor
 
 void
 PreallocatedProcessManagerImpl::OnNuwaReady()
 {
   NS_ASSERTION(!mIsNuwaReady, "Multiple Nuwa processes created!");
   ProcessPriorityManager::SetProcessPriority(mPreallocatedAppProcess,
                                              hal::PROCESS_PRIORITY_FOREGROUND);
   mIsNuwaReady = true;
+  if (Preferences::GetBool("dom.ipc.processPriorityManager.testMode")) {
+    AutoJSContext cx;
+    nsCOMPtr<nsIMessageBroadcaster> ppmm =
+      do_GetService("@mozilla.org/parentprocessmessagemanager;1");
+    nsresult rv = ppmm->BroadcastAsyncMessage(
+      NS_LITERAL_STRING("TEST-ONLY:nuwa-ready"),
+      JSVAL_NULL, JSVAL_NULL, cx, 1);
+  }
   NuwaFork();
 }
 
 void
 PreallocatedProcessManagerImpl::OnNuwaForkTimeout()
 {
   if (!mNuwaForkWaitTasks.IsEmpty()) {
     mNuwaForkWaitTasks.RemoveElementAt(0);
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1152,22 +1152,21 @@ TabChild::ArraysToParams(const Infallibl
       aParams->SetString(j, aStringParams[j].get());
     }
   }
 }
 
 #ifdef DEBUG
 PContentPermissionRequestChild*
 TabChild:: SendPContentPermissionRequestConstructor(PContentPermissionRequestChild* aActor,
-                                                    const nsCString& aType,
-                                                    const nsCString& aAccess,
+                                                    const InfallibleTArray<PermissionRequest>& aRequests,
                                                     const IPC::Principal& aPrincipal)
 {
   PCOMContentPermissionRequestChild* child = static_cast<PCOMContentPermissionRequestChild*>(aActor);
-  PContentPermissionRequestChild* request = PBrowserChild::SendPContentPermissionRequestConstructor(aActor, aType, aAccess, aPrincipal);
+  PContentPermissionRequestChild* request = PBrowserChild::SendPContentPermissionRequestConstructor(aActor, aRequests, aPrincipal);
   child->mIPCOpen = true;
   return request;
 }
 #endif /* DEBUG */
 
 void
 TabChild::DestroyWindow()
 {
@@ -2009,17 +2008,18 @@ TabChild::AllocPContentDialogChild(const
 bool
 TabChild::DeallocPContentDialogChild(PContentDialogChild* aDialog)
 {
   delete aDialog;
   return true;
 }
 
 PContentPermissionRequestChild*
-TabChild::AllocPContentPermissionRequestChild(const nsCString& aType, const nsCString& aAccess, const IPC::Principal&)
+TabChild::AllocPContentPermissionRequestChild(const InfallibleTArray<PermissionRequest>& aRequests,
+                                              const IPC::Principal& aPrincipal)
 {
   NS_RUNTIMEABORT("unused");
   return nullptr;
 }
 
 bool
 TabChild::DeallocPContentPermissionRequestChild(PContentPermissionRequestChild* actor)
 {
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -273,23 +273,21 @@ public:
                                InfallibleTArray<nsString>& aStringParams);
     static void ArraysToParams(const InfallibleTArray<int>& aIntParams,
                                const InfallibleTArray<nsString>& aStringParams,
                                nsIDialogParamBlock* aParams);
 
 #ifdef DEBUG
     virtual PContentPermissionRequestChild*
     SendPContentPermissionRequestConstructor(PContentPermissionRequestChild* aActor,
-                                             const nsCString& aType,
-                                             const nsCString& aAccess,
+                                             const InfallibleTArray<PermissionRequest>& aRequests,
                                              const IPC::Principal& aPrincipal);
 #endif /* DEBUG */
 
-    virtual PContentPermissionRequestChild* AllocPContentPermissionRequestChild(const nsCString& aType,
-                                                                                const nsCString& aAccess,
+    virtual PContentPermissionRequestChild* AllocPContentPermissionRequestChild(const InfallibleTArray<PermissionRequest>& aRequests,
                                                                                 const IPC::Principal& aPrincipal);
     virtual bool DeallocPContentPermissionRequestChild(PContentPermissionRequestChild* actor);
 
     virtual POfflineCacheUpdateChild* AllocPOfflineCacheUpdateChild(
             const URIParams& manifestURI,
             const URIParams& documentURI,
             const bool& stickDocument);
     virtual bool DeallocPOfflineCacheUpdateChild(POfflineCacheUpdateChild* offlineCacheUpdate);
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -10,16 +10,17 @@
 
 #include "AppProcessChecker.h"
 #include "IDBFactory.h"
 #include "IndexedDBParent.h"
 #include "mozIApplication.h"
 #include "mozilla/BrowserElementParent.h"
 #include "mozilla/docshell/OfflineCacheUpdateParent.h"
 #include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/PContentPermissionRequestParent.h"
 #include "mozilla/Hal.h"
 #include "mozilla/ipc/DocumentRendererParent.h"
 #include "mozilla/layers/CompositorParent.h"
 #include "mozilla/layout/RenderFrameParent.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/TouchEvents.h"
@@ -574,19 +575,20 @@ TabParent::AllocPDocumentRendererParent(
 bool
 TabParent::DeallocPDocumentRendererParent(PDocumentRendererParent* actor)
 {
     delete actor;
     return true;
 }
 
 PContentPermissionRequestParent*
-TabParent::AllocPContentPermissionRequestParent(const nsCString& type, const nsCString& access, const IPC::Principal& principal)
+TabParent::AllocPContentPermissionRequestParent(const InfallibleTArray<PermissionRequest>& aRequests,
+                                                const IPC::Principal& aPrincipal)
 {
-  return new ContentPermissionRequestParent(type, access, mFrameElement, principal);
+  return CreateContentPermissionRequestParent(aRequests, mFrameElement, aPrincipal);
 }
 
 bool
 TabParent::DeallocPContentPermissionRequestParent(PContentPermissionRequestParent* actor)
 {
   delete actor;
   return true;
 }
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -220,17 +220,18 @@ public:
     virtual PDocumentRendererParent*
     AllocPDocumentRendererParent(const nsRect& documentRect, const gfxMatrix& transform,
                                  const nsString& bgcolor,
                                  const uint32_t& renderFlags, const bool& flushLayout,
                                  const nsIntSize& renderSize);
     virtual bool DeallocPDocumentRendererParent(PDocumentRendererParent* actor);
 
     virtual PContentPermissionRequestParent*
-    AllocPContentPermissionRequestParent(const nsCString& aType, const nsCString& aAccess, const IPC::Principal& aPrincipal);
+    AllocPContentPermissionRequestParent(const InfallibleTArray<PermissionRequest>& aRequests,
+                                         const IPC::Principal& aPrincipal);
     virtual bool DeallocPContentPermissionRequestParent(PContentPermissionRequestParent* actor);
 
     virtual POfflineCacheUpdateParent* AllocPOfflineCacheUpdateParent(
             const URIParams& aManifestURI,
             const URIParams& aDocumentURI,
             const bool& stickDocument) MOZ_OVERRIDE;
     virtual bool DeallocPOfflineCacheUpdateParent(POfflineCacheUpdateParent* actor);
 
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -63,16 +63,17 @@ SOURCES += [
 
 IPDL_SOURCES += [
     'DOMTypes.ipdlh',
     'PBlob.ipdl',
     'PBlobStream.ipdl',
     'PBrowser.ipdl',
     'PContent.ipdl',
     'PContentDialog.ipdl',
+    'PContentPermission.ipdlh',
     'PContentPermissionRequest.ipdl',
     'PCrashReporter.ipdl',
     'PDocumentRenderer.ipdl',
     'PMemoryReportRequest.ipdl',
     'PTabContext.ipdlh',
 ]
 
 FAIL_ON_WARNINGS = True
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/mochitest.ini
@@ -0,0 +1,2 @@
+[test_NuwaProcessCreation.html]
+run-if = toolkit == 'gonk'
--- a/dom/ipc/tests/moz.build
+++ b/dom/ipc/tests/moz.build
@@ -1,8 +1,9 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 MOCHITEST_CHROME_MANIFESTS += ['chrome.ini']
+MOCHITEST_MANIFESTS += ['mochitest.ini']
 
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/test_NuwaProcessCreation.html
@@ -0,0 +1,99 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test if Nuwa process created successfully.
+-->
+<head>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="application/javascript;version=1.7">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+function TestLoader() {}
+
+TestLoader.prototype = {
+  _waitingTask: 0,
+  onTestReady: null,
+  unlockTestReady: function() {
+    this._waitingTask--;
+    this._maybeLoadTest();
+  },
+  lockTestReady: function() {
+    this._waitingTask++;
+  },
+  _maybeLoadTest: function() {
+    if (this._waitingTask == 0) {
+      this.onTestReady();
+    }
+  }
+}
+
+var testLoader = new TestLoader();
+testLoader.lockTestReady();
+window.addEventListener('load', function() {
+  testLoader.unlockTestReady();
+});
+
+function setPref(pref, value) {
+  testLoader.lockTestReady();
+  if (value !== undefined && value !== null) {
+    SpecialPowers.pushPrefEnv({'set': [[pref, value]]}, function() { testLoader.unlockTestReady(); });
+  } else {
+    SpecialPowers.pushPrefEnv({'clear': [[pref]]}, function() { testLoader.unlockTestReady(); });
+  }
+}
+
+setPref('dom.ipc.processPriorityManager.testMode', true);
+setPref('dom.ipc.processPriorityManager.enabled', true);
+setPref('dom.ipc.processPriorityManager.backgroundLRUPoolLevels', 2);
+
+function runTest()
+{
+  // Shutdown preallocated process.
+  SpecialPowers.setBoolPref('dom.ipc.processPrelaunch.enabled', false);
+  let cpmm = SpecialPowers.Cc["@mozilla.org/childprocessmessagemanager;1"]
+                          .getService(SpecialPowers.Ci.nsISyncMessageSender);
+  let seenNuwaReady = false;
+  let msgHandler = {
+    receiveMessage: function receiveMessage(msg) {
+      msg = SpecialPowers.wrap(msg);
+      if (msg.name == 'TEST-ONLY:nuwa-ready') {
+        ok(true, "Got nuwa-ready");
+        is(seenNuwaReady, false, "Already received nuwa ready");
+        seenNuwaReady = true;
+      } else if (msg.name == 'TEST-ONLY:nuwa-add-new-process') {
+        ok(true, "Got nuwa-add-new-process");
+        is(seenNuwaReady, true, "Receive nuwa-add-new-process before nuwa-ready");
+        testEnd();
+      }
+    }
+  };
+  let timeout = setTimeout(function() {
+    ok(false, "Nuwa process is not launched");
+    testEnd();
+  }, 60000);
+
+  function testEnd() {
+    cpmm.removeMessageListener("TEST-ONLY:nuwa-ready", msgHandler);
+    cpmm.removeMessageListener("TEST-ONLY:nuwa-add-new-process", msgHandler);
+    clearTimeout(timeout);
+    SimpleTest.finish();
+  }
+
+  cpmm.addMessageListener("TEST-ONLY:nuwa-ready", msgHandler);
+  cpmm.addMessageListener("TEST-ONLY:nuwa-add-new-process", msgHandler);
+
+
+  // Setting this pref to true should cause us to prelaunch a process.
+  SpecialPowers.setBoolPref('dom.ipc.processPrelaunch.enabled', true);
+}
+
+testLoader.onTestReady = runTest;
+</script>
+</body>
+</html>
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -36,17 +36,17 @@
 #include "mozilla/Preferences.h"
 
 /* Using WebRTC backend on Desktops (Mac, Windows, Linux), otherwise default */
 #include "MediaEngineDefault.h"
 #if defined(MOZ_WEBRTC)
 #include "MediaEngineWebRTC.h"
 #endif
 
-#ifdef MOZ_WIDGET_GONK
+#ifdef MOZ_B2G
 #include "MediaPermissionGonk.h"
 #endif
 
 // GetCurrentTime is defined in winbase.h as zero argument macro forwarding to
 // GetTickCount() and conflicts with MediaStream::GetCurrentTime.
 #ifdef GetCurrentTime
 #undef GetCurrentTime
 #endif
@@ -751,17 +751,17 @@ public:
     MediaEnginePrefs &aPrefs)
     : mConstraints(aConstraints)
     , mSuccess(aSuccess)
     , mError(aError)
     , mWindowID(aWindowID)
     , mListener(aListener)
     , mPrefs(aPrefs)
     , mDeviceChosen(false)
-    , mBackendChosen(false)
+    , mBackend(nullptr)
     , mManager(MediaManager::GetInstance())
   {}
 
   /**
    * The caller can also choose to provide their own backend instead of
    * using the one provided by MediaManager::GetBackend.
    */
   GetUserMediaRunnable(
@@ -773,40 +773,37 @@ public:
     MediaEngine* aBackend)
     : mConstraints(aConstraints)
     , mSuccess(aSuccess)
     , mError(aError)
     , mWindowID(aWindowID)
     , mListener(aListener)
     , mPrefs(aPrefs)
     , mDeviceChosen(false)
-    , mBackendChosen(true)
     , mBackend(aBackend)
     , mManager(MediaManager::GetInstance())
   {}
 
   ~GetUserMediaRunnable() {
-    if (mBackendChosen) {
-      delete mBackend;
-    }
   }
 
   NS_IMETHOD
   Run()
   {
     NS_ASSERTION(!NS_IsMainThread(), "Don't call on main thread");
 
+    MediaEngine* backend = mBackend;
     // Was a backend provided?
-    if (!mBackendChosen) {
-      mBackend = mManager->GetBackend(mWindowID);
+    if (!backend) {
+      backend = mManager->GetBackend(mWindowID);
     }
 
     // Was a device provided?
     if (!mDeviceChosen) {
-      nsresult rv = SelectDevice();
+      nsresult rv = SelectDevice(backend);
       if (rv != NS_OK) {
         return rv;
       }
     }
 
     // It is an error if audio or video are requested along with picture.
     if (mConstraints.mPicture && (mConstraints.mAudio || mConstraints.mVideo)) {
       NS_DispatchToMainThread(new ErrorCallbackRunnable(
@@ -868,34 +865,34 @@ public:
   SetVideoDevice(MediaDevice* aVideoDevice)
   {
     mVideoDevice = aVideoDevice;
     mDeviceChosen = true;
     return NS_OK;
   }
 
   nsresult
-  SelectDevice()
+  SelectDevice(MediaEngine* backend)
   {
     if (mConstraints.mPicture || mConstraints.mVideo) {
-      ScopedDeletePtr<SourceSet> sources (GetSources(mBackend,
+      ScopedDeletePtr<SourceSet> sources (GetSources(backend,
           mConstraints.mVideom, &MediaEngine::EnumerateVideoDevices));
 
       if (!sources->Length()) {
         NS_DispatchToMainThread(new ErrorCallbackRunnable(
           mSuccess, mError, NS_LITERAL_STRING("NO_DEVICES_FOUND"), mWindowID));
         return NS_ERROR_FAILURE;
       }
       // Pick the first available device.
       mVideoDevice = do_QueryObject((*sources)[0]);
       LOG(("Selected video device"));
     }
 
     if (mConstraints.mAudio) {
-      ScopedDeletePtr<SourceSet> sources (GetSources(mBackend,
+      ScopedDeletePtr<SourceSet> sources (GetSources(backend,
           mConstraints.mAudiom, &MediaEngine::EnumerateAudioDevices));
 
       if (!sources->Length()) {
         NS_DispatchToMainThread(new ErrorCallbackRunnable(
           mSuccess, mError, NS_LITERAL_STRING("NO_DEVICES_FOUND"), mWindowID));
         return NS_ERROR_FAILURE;
       }
       // Pick the first available device.
@@ -979,19 +976,18 @@ private:
   already_AddRefed<nsIDOMGetUserMediaErrorCallback> mError;
   uint64_t mWindowID;
   nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener;
   nsRefPtr<MediaDevice> mAudioDevice;
   nsRefPtr<MediaDevice> mVideoDevice;
   MediaEnginePrefs mPrefs;
 
   bool mDeviceChosen;
-  bool mBackendChosen;
 
-  MediaEngine* mBackend;
+  RefPtr<MediaEngine> mBackend;
   nsRefPtr<MediaManager> mManager; // get ref to this when creating the runnable
 };
 
 /**
  * Similar to GetUserMediaRunnable, but used for the chrome-only
  * GetUserMediaDevices function. Enumerates a list of audio & video devices,
  * wraps them up in nsIMediaDevice objects and returns it to the success
  * callback.
@@ -1261,20 +1257,20 @@ MediaManager::GetUserMedia(JSContext* aC
   }
 #endif
 
   static bool created = false;
   if (!created) {
     // Force MediaManager to startup before we try to access it from other threads
     // Hack: should init singleton earlier unless it's expensive (mem or CPU)
     (void) MediaManager::Get();
-#ifdef MOZ_WIDGET_GONK
+#ifdef MOZ_B2G
     // Initialize MediaPermissionManager before send out any permission request.
     (void) MediaPermissionManager::GetInstance();
-#endif //MOZ_WIDGET_GONK
+#endif //MOZ_B2G
   }
 
   // Store the WindowID in a hash table and mark as active. The entry is removed
   // when this window is closed or navigated away from.
   uint64_t windowID = aWindow->WindowID();
   nsRefPtr<GetUserMediaRunnable> gUMRunnable;
   // This is safe since we're on main-thread, and the windowlist can only
   // be invalidated from the main-thread (see OnNavigation)
@@ -1410,21 +1406,21 @@ MediaEngine*
 MediaManager::GetBackend(uint64_t aWindowId)
 {
   // Plugin backends as appropriate. The default engine also currently
   // includes picture support for Android.
   // This IS called off main-thread.
   MutexAutoLock lock(mMutex);
   if (!mBackend) {
 #if defined(MOZ_WEBRTC)
-  #ifndef MOZ_B2G_CAMERA
+#ifndef MOZ_B2G_CAMERA
     mBackend = new MediaEngineWebRTC(mPrefs);
-  #else
-    mBackend = new MediaEngineWebRTC(mCameraManager, aWindowId);
-  #endif
+#else
+    mBackend = new MediaEngineWebRTC(mCameraManager);
+#endif
 #else
     mBackend = new MediaEngineDefault();
 #endif
   }
   return mBackend;
 }
 
 void
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -508,35 +508,33 @@ private:
                const char *aData, int32_t *aVal);
   void GetPrefBool(nsIPrefBranch *aBranch, const char *aPref,
                    const char *aData, bool *aVal);
   void GetPrefs(nsIPrefBranch *aBranch, const char *aData);
 
   // Make private because we want only one instance of this class
   MediaManager();
 
-  ~MediaManager() {
-    delete mBackend;
-  }
+  ~MediaManager() {}
 
   nsresult MediaCaptureWindowStateInternal(nsIDOMWindow* aWindow, bool* aVideo,
                                            bool* aAudio);
 
   void StopMediaStreams();
 
   // ONLY access from MainThread so we don't need to lock
   WindowTable mActiveWindows;
   nsRefPtrHashtable<nsStringHashKey, nsRunnable> mActiveCallbacks;
   // Always exists
   nsCOMPtr<nsIThread> mMediaThread;
 
   Mutex mMutex;
   // protected with mMutex:
-  MediaEngine* mBackend;
+  RefPtr<MediaEngine> mBackend;
 
   static StaticRefPtr<MediaManager> sSingleton;
 
-#ifdef MOZ_WIDGET_GONK
+#ifdef MOZ_B2G_CAMERA
   nsRefPtr<nsDOMCameraManager> mCameraManager;
 #endif
 };
 
 } // namespace mozilla
--- a/dom/media/MediaPermissionGonk.cpp
+++ b/dom/media/MediaPermissionGonk.cpp
@@ -15,24 +15,46 @@
 #include "nsTArray.h"
 #include "GetUserMediaRequest.h"
 #include "PCOMContentPermissionRequestChild.h"
 #include "mozilla/dom/PBrowserChild.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/MediaStreamTrackBinding.h"
 #include "nsISupportsPrimitives.h"
 #include "nsServiceManagerUtils.h"
+#include "nsArrayUtils.h"
+#include "nsContentPermissionHelper.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
 
 #define AUDIO_PERMISSION_NAME "audio-capture"
+#define VIDEO_PERMISSION_NAME "video-capture"
+
+using namespace mozilla::dom;
 
 namespace mozilla {
 
 static MediaPermissionManager *gMediaPermMgr = nullptr;
 
+static uint32_t
+ConvertArrayToPermissionRequest(nsIArray* aSrcArray,
+                                nsTArray<PermissionRequest>& aDesArray)
+{
+  uint32_t len = 0;
+  aSrcArray->GetLength(&len);
+  for (uint32_t i = 0; i < len; i++) {
+    nsCOMPtr<nsIContentPermissionType> cpt = do_QueryElementAt(aSrcArray, i);
+    nsAutoCString type;
+    nsAutoCString access;
+    cpt->GetType(type);
+    cpt->GetAccess(access);
+    aDesArray.AppendElement(PermissionRequest(type, access));
+  }
+  return len;
+}
+
 // Helper function for notifying permission granted
 static nsresult
 NotifyPermissionAllow(const nsAString &aCallID, nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices)
 {
   nsresult rv;
   nsCOMPtr<nsISupportsArray> array;
   rv = NS_NewISupportsArray(getter_AddRefs(array));
   NS_ENSURE_SUCCESS(rv, rv);
@@ -88,77 +110,85 @@ public:
   // It will be called when prompt dismissed.
   virtual bool Recv__delete__(const bool &allow) MOZ_OVERRIDE;
   virtual void IPDLRelease() MOZ_OVERRIDE { Release(); }
 
   already_AddRefed<nsPIDOMWindow> GetOwner();
 
 private:
   bool mAudio; // Request for audio permission
+  bool mVideo; // Request for video permission
   nsRefPtr<dom::GetUserMediaRequest> mRequest;
   nsTArray<nsCOMPtr<nsIMediaDevice> > mDevices; // candiate device list
 };
 
 // MediaPermissionRequest
 NS_IMPL_ISUPPORTS1(MediaPermissionRequest, nsIContentPermissionRequest)
 
 MediaPermissionRequest::MediaPermissionRequest(nsRefPtr<dom::GetUserMediaRequest> &aRequest,
                                                nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices)
   : mRequest(aRequest)
 {
   dom::MediaStreamConstraintsInternal constraints;
   mRequest->GetConstraints(constraints);
 
   mAudio = constraints.mAudio;
+  mVideo = constraints.mVideo;
 
   for (uint32_t i = 0; i < aDevices.Length(); ++i) {
     nsCOMPtr<nsIMediaDevice> device(aDevices[i]);
     nsAutoString deviceType;
     device->GetType(deviceType);
     if (mAudio && deviceType.EqualsLiteral("audio")) {
       mDevices.AppendElement(device);
     }
+    if (mVideo && deviceType.EqualsLiteral("video")) {
+      mDevices.AppendElement(device);
+    }
   }
 }
 
 // nsIContentPermissionRequest methods
 NS_IMETHODIMP
+MediaPermissionRequest::GetTypes(nsIArray** aTypes)
+{
+  nsCOMPtr<nsIMutableArray> types = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  if (mAudio) {
+    nsCOMPtr<ContentPermissionType> AudioType =
+      new ContentPermissionType(NS_LITERAL_CSTRING(AUDIO_PERMISSION_NAME),
+                                NS_LITERAL_CSTRING("unused"));
+    types->AppendElement(AudioType, false);
+  }
+  if (mVideo) {
+    nsCOMPtr<ContentPermissionType> VideoType =
+      new ContentPermissionType(NS_LITERAL_CSTRING(VIDEO_PERMISSION_NAME),
+                                NS_LITERAL_CSTRING("unused"));
+    types->AppendElement(VideoType, false);
+  }
+  NS_IF_ADDREF(*aTypes = types);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 MediaPermissionRequest::GetPrincipal(nsIPrincipal **aRequestingPrincipal)
 {
   NS_ENSURE_ARG_POINTER(aRequestingPrincipal);
 
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(mRequest->GetParentObject());
   NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
 
   nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
   NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE);
 
   NS_ADDREF(*aRequestingPrincipal = doc->NodePrincipal());
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MediaPermissionRequest::GetType(nsACString &aType)
-{
-  if (mAudio) {
-    aType = AUDIO_PERMISSION_NAME;
-    return NS_OK;
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-MediaPermissionRequest::GetAccess(nsACString &aAccess)
-{
-  aAccess = "unused";
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 MediaPermissionRequest::GetWindow(nsIDOMWindow** aRequestingWindow)
 {
   NS_ENSURE_ARG_POINTER(aRequestingWindow);
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(mRequest->GetParentObject());
   window.forget(aRequestingWindow);
   return NS_OK;
 }
 
@@ -273,32 +303,30 @@ MediaDeviceSuccessCallback::DoPrompt(nsR
     nsresult rv;
 
     nsCOMPtr<nsPIDOMWindow> window(req->GetOwner());
     NS_ENSURE_TRUE(window, NS_ERROR_FAILURE);
 
     dom::TabChild* child = dom::TabChild::GetFrom(window->GetDocShell());
     NS_ENSURE_TRUE(child, NS_ERROR_FAILURE);
 
-    nsAutoCString type;
-    rv = req->GetType(type);
+    nsCOMPtr<nsIArray> typeArray;
+    rv = req->GetTypes(getter_AddRefs(typeArray));
     NS_ENSURE_SUCCESS(rv, rv);
 
-    nsAutoCString access;
-    rv = req->GetAccess(access);
-    NS_ENSURE_SUCCESS(rv, rv);
+    nsTArray<PermissionRequest> permArray;
+    ConvertArrayToPermissionRequest(typeArray, permArray);
 
     nsCOMPtr<nsIPrincipal> principal;
     rv = req->GetPrincipal(getter_AddRefs(principal));
     NS_ENSURE_SUCCESS(rv, rv);
 
     req->AddRef();
     child->SendPContentPermissionRequestConstructor(req,
-                                                    type,
-                                                    access,
+                                                    permArray,
                                                     IPC::Principal(principal));
 
     req->Sendprompt();
     return NS_OK;
   }
 
   // for chrome process
   nsCOMPtr<nsIContentPermissionPrompt> prompt =
--- a/dom/src/geolocation/nsGeolocation.cpp
+++ b/dom/src/geolocation/nsGeolocation.cpp
@@ -9,16 +9,17 @@
 
 #include "nsISettingsService.h"
 
 #include "nsGeolocation.h"
 #include "nsDOMClassInfoID.h"
 #include "nsComponentManagerUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsContentUtils.h"
+#include "nsContentPermissionHelper.h"
 #include "nsCxPusher.h"
 #include "nsIDocument.h"
 #include "nsIObserverService.h"
 #include "nsPIDOMWindow.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 #include "mozilla/Preferences.h"
@@ -380,27 +381,21 @@ nsGeolocationRequest::GetPrincipal(nsIPr
 
   nsCOMPtr<nsIPrincipal> principal = mLocator->GetPrincipal();
   principal.forget(aRequestingPrincipal);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsGeolocationRequest::GetType(nsACString & aType)
+nsGeolocationRequest::GetTypes(nsIArray** aTypes)
 {
-  aType = "geolocation";
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsGeolocationRequest::GetAccess(nsACString & aAccess)
-{
-  aAccess = "unused";
-  return NS_OK;
+  return CreatePermissionArray(NS_LITERAL_CSTRING("geolocation"),
+                               NS_LITERAL_CSTRING("unused"),
+                               aTypes);
 }
 
 NS_IMETHODIMP
 nsGeolocationRequest::GetWindow(nsIDOMWindow * *aRequestingWindow)
 {
   NS_ENSURE_ARG_POINTER(aRequestingWindow);
 
   nsCOMPtr<nsIDOMWindow> window = do_QueryReferent(mLocator->GetOwner());
@@ -1447,22 +1442,25 @@ Geolocation::RegisterRequestWithPrompt(n
 
     // because owner implements nsITabChild, we can assume that it is
     // the one and only TabChild.
     TabChild* child = TabChild::GetFrom(window->GetDocShell());
     if (!child) {
       return false;
     }
 
+    nsTArray<PermissionRequest> permArray;
+    permArray.AppendElement(PermissionRequest(NS_LITERAL_CSTRING("geolocation"),
+                                              NS_LITERAL_CSTRING("unused")));
+
     // Retain a reference so the object isn't deleted without IPDL's knowledge.
     // Corresponding release occurs in DeallocPContentPermissionRequest.
     request->AddRef();
     child->SendPContentPermissionRequestConstructor(request,
-                                                    NS_LITERAL_CSTRING("geolocation"),
-                                                    NS_LITERAL_CSTRING("unused"),
+                                                    permArray,
                                                     IPC::Principal(mPrincipal));
 
     request->Sendprompt();
     return true;
   }
 
   nsCOMPtr<nsIRunnable> ev  = new RequestPromptEvent(request);
   NS_DispatchToMainThread(ev);
--- a/dom/src/notification/DesktopNotification.cpp
+++ b/dom/src/notification/DesktopNotification.cpp
@@ -10,16 +10,17 @@
 #include "nsIDOMDesktopNotification.h"
 #include "TabChild.h"
 #include "mozilla/Preferences.h"
 #include "nsGlobalWindow.h"
 #include "nsIAppsService.h"
 #include "PCOMContentPermissionRequestChild.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsServiceManagerUtils.h"
+#include "PermissionMessageUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 /*
  * Simple Request
  */
 class DesktopNotificationRequest : public nsIContentPermissionRequest,
@@ -174,19 +175,22 @@ DesktopNotification::Init()
     // because owner implements nsITabChild, we can assume that it is
     // the one and only TabChild for this docshell.
     TabChild* child = TabChild::GetFrom(GetOwner()->GetDocShell());
 
     // Retain a reference so the object isn't deleted without IPDL's knowledge.
     // Corresponding release occurs in DeallocPContentPermissionRequest.
     nsRefPtr<DesktopNotificationRequest> copy = request;
 
+    nsTArray<PermissionRequest> permArray;
+    permArray.AppendElement(PermissionRequest(
+                            NS_LITERAL_CSTRING("desktop-notification"),
+                            NS_LITERAL_CSTRING("unused")));
     child->SendPContentPermissionRequestConstructor(copy.forget().get(),
-                                                    NS_LITERAL_CSTRING("desktop-notification"),
-                                                    NS_LITERAL_CSTRING("unused"),
+                                                    permArray,
                                                     IPC::Principal(mPrincipal));
 
     request->Sendprompt();
     return;
   }
 
   // otherwise, dispatch it
   NS_DispatchToMainThread(request);
@@ -348,23 +352,17 @@ NS_IMETHODIMP
 DesktopNotificationRequest::Allow()
 {
   nsresult rv = mDesktopNotification->SetAllow(true);
   mDesktopNotification = nullptr;
   return rv;
 }
 
 NS_IMETHODIMP
-DesktopNotificationRequest::GetType(nsACString & aType)
+DesktopNotificationRequest::GetTypes(nsIArray** aTypes)
 {
-  aType = "desktop-notification";
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-DesktopNotificationRequest::GetAccess(nsACString & aAccess)
-{
-  aAccess = "unused";
-  return NS_OK;
+  return CreatePermissionArray(NS_LITERAL_CSTRING("desktop-notification"),
+                               NS_LITERAL_CSTRING("unused"),
+                               aTypes);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/src/notification/Notification.cpp
+++ b/dom/src/notification/Notification.cpp
@@ -19,16 +19,17 @@
 #include "nsIPermissionManager.h"
 #include "nsIUUIDGenerator.h"
 #include "nsServiceManagerUtils.h"
 #include "nsToolkitCompsCID.h"
 #include "nsGlobalWindow.h"
 #include "nsDOMJSUtils.h"
 #include "nsIScriptSecurityManager.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
+#include "nsContentPermissionHelper.h"
 #ifdef MOZ_B2G
 #include "nsIDOMDesktopNotification.h"
 #endif
 
 namespace mozilla {
 namespace dom {
 
 class NotificationStorageCallback MOZ_FINAL : public nsINotificationStorageCallback
@@ -262,19 +263,21 @@ NotificationPermissionRequest::Run()
     if (!child) {
       return NS_ERROR_NOT_AVAILABLE;
     }
 
     // Retain a reference so the object isn't deleted without IPDL's knowledge.
     // Corresponding release occurs in DeallocPContentPermissionRequest.
     AddRef();
 
-    NS_NAMED_LITERAL_CSTRING(type, "desktop-notification");
-    NS_NAMED_LITERAL_CSTRING(access, "unused");
-    child->SendPContentPermissionRequestConstructor(this, type, access,
+    nsTArray<PermissionRequest> permArray;
+    permArray.AppendElement(PermissionRequest(
+                            NS_LITERAL_CSTRING("desktop-notification"),
+                            NS_LITERAL_CSTRING("unused")));
+    child->SendPContentPermissionRequestConstructor(this, permArray,
                                                     IPC::Principal(mPrincipal));
 
     Sendprompt();
     return NS_OK;
   }
 
   nsCOMPtr<nsIContentPermissionPrompt> prompt =
     do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
@@ -337,27 +340,21 @@ nsresult
 NotificationPermissionRequest::CallCallback()
 {
   ErrorResult rv;
   mCallback->Call(mPermission, rv);
   return rv.ErrorCode();
 }
 
 NS_IMETHODIMP
-NotificationPermissionRequest::GetAccess(nsACString& aAccess)
+NotificationPermissionRequest::GetTypes(nsIArray** aTypes)
 {
-  aAccess.AssignLiteral("unused");
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-NotificationPermissionRequest::GetType(nsACString& aType)
-{
-  aType.AssignLiteral("desktop-notification");
-  return NS_OK;
+  return CreatePermissionArray(NS_LITERAL_CSTRING("desktop-notification"),
+                               NS_LITERAL_CSTRING("unused"),
+                               aTypes);
 }
 
 bool
 NotificationPermissionRequest::Recv__delete__(const bool& aAllow)
 {
   if (aAllow) {
     (void) Allow();
   } else {
--- a/dom/webidl/RTCStatsReport.webidl
+++ b/dom/webidl/RTCStatsReport.webidl
@@ -22,17 +22,17 @@ dictionary RTCStats {
   DOMHighResTimeStamp timestamp;
   RTCStatsType type;
   DOMString id;
 };
 
 dictionary RTCRTPStreamStats : RTCStats {
   DOMString ssrc;
   DOMString remoteId;
-  boolean isRemote;
+  boolean isRemote = false;
   DOMString mediaTrackId;
   DOMString transportId;
   DOMString codecId;
 };
 
 dictionary RTCInboundRTPStreamStats : RTCRTPStreamStats {
   unsigned long packetsReceived;
   unsigned long long bytesReceived;
@@ -119,17 +119,17 @@ dictionary RTCCodecStats : RTCStats {
 };
 
 callback RTCStatsReportCallback = void (RTCStatsReport obj);
 
 // This is the internal representation of the report in this implementation
 // to be received from c++
 
 dictionary RTCStatsReportInternal {
-  DOMString                           pcid;
+  DOMString                           pcid = "";
   sequence<RTCRTPStreamStats>         rtpStreamStats;
   sequence<RTCInboundRTPStreamStats>  inboundRTPStreamStats;
   sequence<RTCOutboundRTPStreamStats> outboundRTPStreamStats;
   sequence<RTCMediaStreamTrackStats>  mediaStreamTrackStats;
   sequence<RTCMediaStreamStats>       mediaStreamStats;
   sequence<RTCTransportStats>         transportStats;
   sequence<RTCIceComponentStats>      iceComponentStats;
   sequence<RTCIceCandidatePairStats>  iceCandidatePairStats;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug940635.js
@@ -0,0 +1,7 @@
+function f(y) {
+    return (y > 0) == y;
+}
+assertEq(f(0), true);
+assertEq(f(0), true);
+assertEq(f(null), false);
+assertEq(f(null), false);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug946969.js
@@ -0,0 +1,6 @@
+function f() {
+    return Math.abs(~(Math.tan()));
+}
+
+for (var i=0; i<1000; i++)
+    assertEq(f(i), 1);
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -221,18 +221,30 @@ CanUseInt32Compare(ICStub::Kind kind)
 
 MCompare::CompareType
 BaselineInspector::expectedCompareType(jsbytecode *pc)
 {
     ICStub *first = monomorphicStub(pc), *second = nullptr;
     if (!first && !dimorphicStub(pc, &first, &second))
         return MCompare::Compare_Unknown;
 
-    if (CanUseInt32Compare(first->kind()) && (!second || CanUseInt32Compare(second->kind())))
+    if (CanUseInt32Compare(first->kind()) && (!second || CanUseInt32Compare(second->kind()))) {
+        ICCompare_Int32WithBoolean *coerce =
+            first->isCompare_Int32WithBoolean()
+            ? first->toCompare_Int32WithBoolean()
+            : ((second && second->isCompare_Int32WithBoolean())
+               ? second->toCompare_Int32WithBoolean()
+               : nullptr);
+        if (coerce) {
+            return coerce->lhsIsInt32()
+                   ? MCompare::Compare_Int32MaybeCoerceRHS
+                   : MCompare::Compare_Int32MaybeCoerceLHS;
+        }
         return MCompare::Compare_Int32;
+    }
 
     if (CanUseDoubleCompare(first->kind()) && (!second || CanUseDoubleCompare(second->kind()))) {
         ICCompare_NumberWithUndefined *coerce =
             first->isCompare_NumberWithUndefined()
             ? first->toCompare_NumberWithUndefined()
             : (second && second->isCompare_NumberWithUndefined())
               ? second->toCompare_NumberWithUndefined()
               : nullptr;
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -196,17 +196,18 @@ CodeGenerator::visitValueToInt32(LValueT
             stringRejoin = nullptr;
         }
 
         masm.truncateValueToInt32(operand, input, stringEntry, stringRejoin, oolDouble->entry(),
                                   stringReg, temp, output, &fails);
         masm.bind(oolDouble->rejoin());
     } else {
         masm.convertValueToInt32(operand, input, temp, output, &fails,
-                                 lir->mirNormal()->canBeNegativeZero());
+                                 lir->mirNormal()->canBeNegativeZero(),
+                                 lir->mirNormal()->conversion());
     }
 
     return bailoutFrom(&fails, lir->snapshot());
 }
 
 bool
 CodeGenerator::visitValueToDouble(LValueToDouble *lir)
 {
@@ -3834,17 +3835,17 @@ CodeGenerator::visitMinMaxI(LMinMaxI *in
 bool
 CodeGenerator::visitAbsI(LAbsI *ins)
 {
     Register input = ToRegister(ins->input());
     Label positive;
 
     JS_ASSERT(input == ToRegister(ins->output()));
     masm.test32(input, input);
-    masm.j(Assembler::GreaterThanOrEqual, &positive);
+    masm.j(Assembler::NotSigned, &positive);
     masm.neg32(input);
     if (ins->snapshot() && !bailoutIf(Assembler::Overflow, ins->snapshot()))
         return false;
     masm.bind(&positive);
 
     return true;
 }
 
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -1444,17 +1444,17 @@ jit::ExtractLinearInequality(MTest *test
         return false;
 
     MCompare *compare = test->getOperand(0)->toCompare();
 
     MDefinition *lhs = compare->getOperand(0);
     MDefinition *rhs = compare->getOperand(1);
 
     // TODO: optimize Compare_UInt32
-    if (compare->compareType() != MCompare::Compare_Int32)
+    if (!compare->isInt32Comparison())
         return false;
 
     JS_ASSERT(lhs->type() == MIRType_Int32);
     JS_ASSERT(rhs->type() == MIRType_Int32);
 
     JSOp jsop = compare->jsop();
     if (direction == FALSE_BRANCH)
         jsop = analyze::NegateCompareOp(jsop);
--- a/js/src/jit/IonMacroAssembler.cpp
+++ b/js/src/jit/IonMacroAssembler.cpp
@@ -1558,48 +1558,56 @@ MacroAssembler::convertDoubleToInt(Float
     }
 }
 
 void
 MacroAssembler::convertValueToInt(ValueOperand value, MDefinition *maybeInput,
                                   Label *handleStringEntry, Label *handleStringRejoin,
                                   Label *truncateDoubleSlow,
                                   Register stringReg, FloatRegister temp, Register output,
-                                  Label *fail, IntConversionBehavior behavior)
+                                  Label *fail, IntConversionBehavior behavior,
+                                  IntConversionInputKind conversion)
 {
     Register tag = splitTagForTest(value);
     bool handleStrings = (behavior == IntConversion_Truncate ||
                           behavior == IntConversion_ClampToUint8) &&
                          handleStringEntry &&
                          handleStringRejoin;
     bool zeroObjects = behavior == IntConversion_ClampToUint8;
 
+    JS_ASSERT_IF(handleStrings || zeroObjects, conversion == IntConversion_Any);
+
     Label done, isInt32, isBool, isDouble, isNull, isString;
 
     branchEqualTypeIfNeeded(MIRType_Int32, maybeInput, tag, &isInt32);
-    branchEqualTypeIfNeeded(MIRType_Boolean, maybeInput, tag, &isBool);
+    if (conversion == IntConversion_Any)
+        branchEqualTypeIfNeeded(MIRType_Boolean, maybeInput, tag, &isBool);
     branchEqualTypeIfNeeded(MIRType_Double, maybeInput, tag, &isDouble);
 
-    // If we are not truncating, we fail for anything that's not
-    // null. Otherwise we might be able to handle strings and objects.
-    switch (behavior) {
-      case IntConversion_Normal:
-      case IntConversion_NegativeZeroCheck:
-        branchTestNull(Assembler::NotEqual, tag, fail);
-        break;
+    if (conversion == IntConversion_Any) {
+        // If we are not truncating, we fail for anything that's not
+        // null. Otherwise we might be able to handle strings and objects.
+        switch (behavior) {
+          case IntConversion_Normal:
+          case IntConversion_NegativeZeroCheck:
+            branchTestNull(Assembler::NotEqual, tag, fail);
+            break;
 
-      case IntConversion_Truncate:
-      case IntConversion_ClampToUint8:
-        branchEqualTypeIfNeeded(MIRType_Null, maybeInput, tag, &isNull);
-        if (handleStrings)
-            branchEqualTypeIfNeeded(MIRType_String, maybeInput, tag, &isString);
-        if (zeroObjects)
-            branchEqualTypeIfNeeded(MIRType_Object, maybeInput, tag, &isNull);
-        branchTestUndefined(Assembler::NotEqual, tag, fail);
-        break;
+          case IntConversion_Truncate:
+          case IntConversion_ClampToUint8:
+            branchEqualTypeIfNeeded(MIRType_Null, maybeInput, tag, &isNull);
+            if (handleStrings)
+                branchEqualTypeIfNeeded(MIRType_String, maybeInput, tag, &isString);
+            if (zeroObjects)
+                branchEqualTypeIfNeeded(MIRType_Object, maybeInput, tag, &isNull);
+            branchTestUndefined(Assembler::NotEqual, tag, fail);
+            break;
+        }
+    } else {
+        jump(fail);
     }
 
     // The value is null or undefined in truncation contexts - just emit 0.
     if (isNull.used())
         bind(&isNull);
     mov(ImmWord(0), output);
     jump(&done);
 
--- a/js/src/jit/IonMacroAssembler.h
+++ b/js/src/jit/IonMacroAssembler.h
@@ -1169,31 +1169,37 @@ class MacroAssembler : public MacroAssem
 
     enum IntConversionBehavior {
         IntConversion_Normal,
         IntConversion_NegativeZeroCheck,
         IntConversion_Truncate,
         IntConversion_ClampToUint8,
     };
 
+    enum IntConversionInputKind {
+        IntConversion_NumbersOnly,
+        IntConversion_Any
+    };
+
     //
     // Functions for converting values to int.
     //
     void convertDoubleToInt(FloatRegister src, Register output, FloatRegister temp,
                             Label *truncateFail, Label *fail, IntConversionBehavior behavior);
 
     // Strings may be handled by providing labels to jump to when the behavior
     // is truncation or clamping. The subroutine, usually an OOL call, is
     // passed the unboxed string in |stringReg| and should convert it to a
     // double store into |temp|.
     void convertValueToInt(ValueOperand value, MDefinition *input,
                            Label *handleStringEntry, Label *handleStringRejoin,
                            Label *truncateDoubleSlow,
                            Register stringReg, FloatRegister temp, Register output,
-                           Label *fail, IntConversionBehavior behavior);
+                           Label *fail, IntConversionBehavior behavior,
+                           IntConversionInputKind conversion = IntConversion_Any);
     void convertValueToInt(ValueOperand value, FloatRegister temp, Register output, Label *fail,
                            IntConversionBehavior behavior)
     {
         convertValueToInt(value, nullptr, nullptr, nullptr, nullptr, InvalidReg, temp, output,
                           fail, behavior);
     }
     bool convertValueToInt(JSContext *cx, const Value &v, Register output, Label *fail,
                            IntConversionBehavior behavior);
@@ -1209,22 +1215,23 @@ class MacroAssembler : public MacroAssem
                              bool negativeZeroCheck)
     {
         convertValueToInt(value, temp, output, fail, negativeZeroCheck
                           ? IntConversion_NegativeZeroCheck
                           : IntConversion_Normal);
     }
     void convertValueToInt32(ValueOperand value, MDefinition *input,
                              FloatRegister temp, Register output, Label *fail,
-                             bool negativeZeroCheck)
+                             bool negativeZeroCheck, IntConversionInputKind conversion = IntConversion_Any)
     {
         convertValueToInt(value, input, nullptr, nullptr, nullptr, InvalidReg, temp, output, fail,
                           negativeZeroCheck
                           ? IntConversion_NegativeZeroCheck
-                          : IntConversion_Normal);
+                          : IntConversion_Normal,
+                          conversion);
     }
     bool convertValueToInt32(JSContext *cx, const Value &v, Register output, Label *fail,
                              bool negativeZeroCheck)
     {
         return convertValueToInt(cx, v, output, fail, negativeZeroCheck
                                  ? IntConversion_NegativeZeroCheck
                                  : IntConversion_Normal);
     }
--- a/js/src/jit/Lowering.cpp
+++ b/js/src/jit/Lowering.cpp
@@ -764,30 +764,27 @@ LIRGenerator::visitTest(MTest *test)
             LAllocation rhs = useRegisterOrConstant(right);
             LCompareBAndBranch *lir = new(alloc()) LCompareBAndBranch(rhs, ifTrue, ifFalse);
             if (!useBox(lir, LCompareBAndBranch::Lhs, left))
                 return false;
             return add(lir, comp);
         }
 
         // Compare and branch Int32 or Object pointers.
-        if (comp->compareType() == MCompare::Compare_Int32 ||
+        if (comp->isInt32Comparison() ||
             comp->compareType() == MCompare::Compare_UInt32 ||
             comp->compareType() == MCompare::Compare_Object)
         {
             JSOp op = ReorderComparison(comp->jsop(), &left, &right);
             LAllocation lhs = useRegister(left);
             LAllocation rhs;
-            if (comp->compareType() == MCompare::Compare_Int32 ||
-                comp->compareType() == MCompare::Compare_UInt32)
-            {
+            if (comp->isInt32Comparison() || comp->compareType() == MCompare::Compare_UInt32)
                 rhs = useAnyOrConstant(right);
-            } else {
+            else
                 rhs = useRegister(right);
-            }
             LCompareAndBranch *lir = new(alloc()) LCompareAndBranch(op, lhs, rhs, ifTrue, ifFalse);
             return add(lir, comp);
         }
 
         // Compare and branch doubles.
         if (comp->isDoubleComparison()) {
             LAllocation lhs = useRegister(left);
             LAllocation rhs = useRegister(right);
@@ -953,24 +950,24 @@ LIRGenerator::visitCompare(MCompare *com
 
         LCompareB *lir = new(alloc()) LCompareB(useRegisterOrConstant(right));
         if (!useBox(lir, LCompareB::Lhs, left))
             return false;
         return define(lir, comp);
     }
 
     // Compare Int32 or Object pointers.
-    if (comp->compareType() == MCompare::Compare_Int32 ||
+    if (comp->isInt32Comparison() ||
         comp->compareType() == MCompare::Compare_UInt32 ||
         comp->compareType() == MCompare::Compare_Object)
     {
         JSOp op = ReorderComparison(comp->jsop(), &left, &right);
         LAllocation lhs = useRegister(left);
         LAllocation rhs;
-        if (comp->compareType() == MCompare::Compare_Int32 ||
+        if (comp->isInt32Comparison() ||
             comp->compareType() == MCompare::Compare_UInt32)
         {
             rhs = useAnyOrConstant(right);
         } else {
             rhs = useRegister(right);
         }
         return define(new(alloc()) LCompare(op, lhs, rhs), comp);
     }
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -1718,16 +1718,19 @@ MCompare::inputType()
       case Compare_Undefined:
         return MIRType_Undefined;
       case Compare_Null:
         return MIRType_Null;
       case Compare_Boolean:
         return MIRType_Boolean;
       case Compare_UInt32:
       case Compare_Int32:
+      case Compare_Int32MaybeCoerceBoth:
+      case Compare_Int32MaybeCoerceLHS:
+      case Compare_Int32MaybeCoerceRHS:
         return MIRType_Int32;
       case Compare_Double:
       case Compare_DoubleMaybeCoerceLHS:
       case Compare_DoubleMaybeCoerceRHS:
         return MIRType_Double;
       case Compare_Float32:
         return MIRType_Float32;
       case Compare_String:
@@ -1804,26 +1807,26 @@ MCompare::infer(BaselineInspector *inspe
         compareType_ = Compare_UInt32;
         return;
     }
 
     // Integer to integer or boolean to boolean comparisons may be treated as Int32.
     if ((lhs == MIRType_Int32 && rhs == MIRType_Int32) ||
         (lhs == MIRType_Boolean && rhs == MIRType_Boolean))
     {
-        compareType_ = Compare_Int32;
+        compareType_ = Compare_Int32MaybeCoerceBoth;
         return;
     }
 
     // Loose/relational cross-integer/boolean comparisons may be treated as Int32.
     if (!strictEq &&
         (lhs == MIRType_Int32 || lhs == MIRType_Boolean) &&
         (rhs == MIRType_Int32 || rhs == MIRType_Boolean))
     {
-        compareType_ = Compare_Int32;
+        compareType_ = Compare_Int32MaybeCoerceBoth;
         return;
     }
 
     // Numeric comparisons against a double coerce to double.
     if (IsNumberType(lhs) && IsNumberType(rhs)) {
         compareType_ = Compare_Double;
         return;
     }
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -2187,16 +2187,19 @@ class MCompare
         // String    compared to Boolean
         // Object    compared to Boolean
         // Value     compared to Boolean
         Compare_Boolean,
 
         // Int32   compared to Int32
         // Boolean compared to Boolean
         Compare_Int32,
+        Compare_Int32MaybeCoerceBoth,
+        Compare_Int32MaybeCoerceLHS,
+        Compare_Int32MaybeCoerceRHS,
 
         // Int32 compared as unsigneds
         Compare_UInt32,
 
         // Double compared to Double
         Compare_Double,
 
         Compare_DoubleMaybeCoerceLHS,
@@ -2253,16 +2256,22 @@ class MCompare
     bool tryFold(bool *result);
     bool evaluateConstantOperands(bool *result);
     MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers);
 
     void infer(BaselineInspector *inspector, jsbytecode *pc);
     CompareType compareType() const {
         return compareType_;
     }
+    bool isInt32Comparison() const {
+        return compareType() == Compare_Int32 ||
+               compareType() == Compare_Int32MaybeCoerceBoth ||
+               compareType() == Compare_Int32MaybeCoerceLHS ||
+               compareType() == Compare_Int32MaybeCoerceRHS;
+    }
     bool isDoubleComparison() const {
         return compareType() == Compare_Double ||
                compareType() == Compare_DoubleMaybeCoerceLHS ||
                compareType() == Compare_DoubleMaybeCoerceRHS;
     }
     bool isFloat32Comparison() const {
         return compareType() == Compare_Float32;
     }
@@ -3036,30 +3045,34 @@ class MAsmJSUnsignedToFloat32
 // Converts a primitive (either typed or untyped) to an int32. If the input is
 // not primitive at runtime, a bailout occurs. If the input cannot be converted
 // to an int32 without loss (i.e. "5.5" or undefined) then a bailout occurs.
 class MToInt32
   : public MUnaryInstruction,
     public ToInt32Policy
 {
     bool canBeNegativeZero_;
-
-    MToInt32(MDefinition *def)
+    MacroAssembler::IntConversionInputKind conversion_;
+
+    MToInt32(MDefinition *def, MacroAssembler::IntConversionInputKind conversion)
       : MUnaryInstruction(def),
-        canBeNegativeZero_(true)
+        canBeNegativeZero_(true),
+        conversion_(conversion)
     {
         setResultType(MIRType_Int32);
         setMovable();
     }
 
   public:
     INSTRUCTION_HEADER(ToInt32)
-    static MToInt32 *New(TempAllocator &alloc, MDefinition *def)
-    {
-        return new(alloc) MToInt32(def);
+    static MToInt32 *New(TempAllocator &alloc, MDefinition *def,
+                         MacroAssembler::IntConversionInputKind conversion =
+                             MacroAssembler::IntConversion_Any)
+    {
+        return new(alloc) MToInt32(def, conversion);
     }
 
     MDefinition *foldsTo(TempAllocator &alloc, bool useValueNumbers);
 
     // this only has backwards information flow.
     void analyzeEdgeCasesBackward();
 
     bool canBeNegativeZero() const {
@@ -3068,16 +3081,20 @@ class MToInt32
     void setCanBeNegativeZero(bool negativeZero) {
         canBeNegativeZero_ = negativeZero;
     }
 
     TypePolicy *typePolicy() {
         return this;
     }
 
+    MacroAssembler::IntConversionInputKind conversion() const {
+        return conversion_;
+    }
+
     bool congruentTo(MDefinition *ins) const {
         return congruentIfOperandsEqual(ins);
     }
 
     AliasSet getAliasSet() const {
         return AliasSet::None();
     }
     void computeRange(TempAllocator &alloc);
--- a/js/src/jit/TypePolicy.cpp
+++ b/js/src/jit/TypePolicy.cpp
@@ -136,17 +136,17 @@ ComparePolicy::adjustInputs(TempAllocato
 
     // Compare_Boolean specialization is done for "Anything === Bool"
     // If the LHS is boolean, we set the specialization to Compare_Int32.
     // This matches other comparisons of the form bool === bool and
     // generated code of Compare_Int32 is more efficient.
     if (compare->compareType() == MCompare::Compare_Boolean &&
         def->getOperand(0)->type() == MIRType_Boolean)
     {
-       compare->setCompareType(MCompare::Compare_Int32);
+       compare->setCompareType(MCompare::Compare_Int32MaybeCoerceBoth);
     }
 
     // Compare_Boolean specialization is done for "Anything === Bool"
     // As of previous line Anything can't be Boolean
     if (compare->compareType() == MCompare::Compare_Boolean) {
         // Unbox rhs that is definitely Boolean
         MDefinition *rhs = def->getOperand(1);
         if (rhs->type() != MIRType_Boolean) {
@@ -237,19 +237,38 @@ ComparePolicy::adjustInputs(TempAllocato
             if (in->type() == MIRType_Null ||
                 (in->type() == MIRType_Boolean && convert == MToFloat32::NumbersOnly))
             {
                 in = boxAt(alloc, def, in);
             }
             replace = MToFloat32::New(alloc, in, convert);
             break;
           }
-          case MIRType_Int32:
-            replace = MToInt32::New(alloc, in);
+          case MIRType_Int32: {
+            MacroAssembler::IntConversionInputKind convert = MacroAssembler::IntConversion_NumbersOnly;
+            if (compare->compareType() == MCompare::Compare_Int32MaybeCoerceBoth ||
+                (compare->compareType() == MCompare::Compare_Int32MaybeCoerceLHS && i == 0) ||
+                (compare->compareType() == MCompare::Compare_Int32MaybeCoerceRHS && i == 1))
+            {
+                convert = MacroAssembler::IntConversion_Any;
+            }
+            if (convert == MacroAssembler::IntConversion_NumbersOnly) {
+                if (in->type() != MIRType_Int32 && in->type() != MIRType_Value)
+                    in = boxAt(alloc, def, in);
+            } else {
+                if (in->type() == MIRType_Undefined ||
+                    in->type() == MIRType_String ||
+                    in->type() == MIRType_Object)
+                {
+                    in = boxAt(alloc, def, in);
+                }
+            }
+            replace = MToInt32::New(alloc, in, convert);
             break;
+          }
           case MIRType_Object:
             replace = MUnbox::New(alloc, in, MIRType_Object, MUnbox::Infallible);
             break;
           case MIRType_String:
             replace = MUnbox::New(alloc, in, MIRType_String, MUnbox::Infallible);
             break;
           default:
             MOZ_ASSUME_UNREACHABLE("Unknown compare specialization");
--- a/js/src/jit/arm/Assembler-arm.h
+++ b/js/src/jit/arm/Assembler-arm.h
@@ -1155,17 +1155,17 @@ class Assembler
         Below = CC,
         BelowOrEqual = LS,
         GreaterThan = GT,
         GreaterThanOrEqual = GE,
         LessThan = LT,
         LessThanOrEqual = LE,
         Overflow = VS,
         Signed = MI,
-        Unsigned = PL,
+        NotSigned = PL,
         Zero = EQ,
         NonZero = NE,
         Always  = AL,
 
         VFP_NotEqualOrUnordered = NE,
         VFP_Equal = EQ,
         VFP_Unordered = VS,
         VFP_NotUnordered = VC,
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -1119,19 +1119,19 @@ CodeGeneratorARM::emitTableSwitchDispatc
     // unhandled case is the default case (both out of range high and out of range low)
     // I then insert a branch to default case into the extra slot, which ensures
     // we don't attempt to execute the address table.
     Label *defaultcase = mir->getDefault()->lir()->label();
 
     int32_t cases = mir->numCases();
     // Lower value with low value
     masm.ma_sub(index, Imm32(mir->low()), index, SetCond);
-    masm.ma_rsb(index, Imm32(cases - 1), index, SetCond, Assembler::Unsigned);
+    masm.ma_rsb(index, Imm32(cases - 1), index, SetCond, Assembler::NotSigned);
     AutoForbidPools afp(&masm);
-    masm.ma_ldr(DTRAddr(pc, DtrRegImmShift(index, LSL, 2)), pc, Offset, Assembler::Unsigned);
+    masm.ma_ldr(DTRAddr(pc, DtrRegImmShift(index, LSL, 2)), pc, Offset, Assembler::NotSigned);
     masm.ma_b(defaultcase);
 
     // To fill in the CodeLabels for the case entries, we need to first
     // generate the case entries (we don't yet know their offsets in the
     // instruction stream).
     OutOfLineTableSwitch *ool = new(alloc()) OutOfLineTableSwitch(alloc(), mir);
     for (int32_t i = 0; i < cases; i++) {
         CodeLabel cl;
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -953,17 +953,17 @@ MacroAssemblerARM::ma_mod_mask(Register 
 
     // Extract the bottom bits into lr.
     ma_and(Imm32(mask), ScratchRegister, secondScratchReg_);
     // Add those bits to the accumulator.
     ma_add(secondScratchReg_, dest, dest);
     // Do a trial subtraction, this is the same operation as cmp, but we store the dest
     ma_sub(dest, Imm32(mask), secondScratchReg_, SetCond);
     // If (sum - C) > 0, store sum - C back into sum, thus performing a modulus.
-    ma_mov(secondScratchReg_, dest, NoSetCond, Unsigned);
+    ma_mov(secondScratchReg_, dest, NoSetCond, NotSigned);
     // Get rid of the bits that we extracted before, and set the condition codes
     as_mov(ScratchRegister, lsr(ScratchRegister, shift), SetCond);
     // If the shift produced zero, finish, otherwise, continue in the loop.
     ma_b(&head, NonZero);
     // Check the hold to see if we need to negate the result.  Hold can only be 1 or -1,
     // so this will never set the 0 flag.
     ma_cmp(hold, Imm32(0));
     // If the hold was non-zero, negate the result to be in line with what JS wants
@@ -3879,17 +3879,17 @@ MacroAssemblerARMCompat::floor(FloatRegi
     // the result will still be a negative number
     ma_rsb(output, Imm32(0), output, SetCond);
     // Flip the negated input back to its original value.
     ma_vneg(input, input);
     // If the result looks non-negative, then this value didn't actually fit into
     // the int range, and special handling is required.
     // zero is also caught by this case, but floor of a negative number
     // should never be zero.
-    ma_b(bail, Unsigned);
+    ma_b(bail, NotSigned);
 
     bind(&fin);
 }
 
 void
 MacroAssemblerARMCompat::floorf(FloatRegister input, Register output, Label *bail)
 {
     Label handleZero;
@@ -3931,17 +3931,17 @@ MacroAssemblerARMCompat::floorf(FloatReg
     // the result will still be a negative number
     ma_rsb(output, Imm32(0), output, SetCond);
     // Flip the negated input back to its original value.
     ma_vneg_f32(input, input);
     // If the result looks non-negative, then this value didn't actually fit into
     // the int range, and special handling is required.
     // zero is also caught by this case, but floor of a negative number
     // should never be zero.
-    ma_b(bail, Unsigned);
+    ma_b(bail, NotSigned);
 
     bind(&fin);
 }
 
 CodeOffsetLabel
 MacroAssemblerARMCompat::toggledJump(Label *label)
 {
     // Emit a B that can be toggled to a CMP. See ToggleToJmp(), ToggleToCmp().
@@ -4018,17 +4018,17 @@ MacroAssemblerARMCompat::round(FloatRegi
     ma_sub(output, Imm32(1), output, NoSetCond, Equal);
     // Negate the output.  Since INT_MIN < -INT_MAX, even after adding 1,
     // the result will still be a negative number
     ma_rsb(output, Imm32(0), output, SetCond);
 
     // If the result looks non-negative, then this value didn't actually fit into
     // the int range, and special handling is required, or it was zero, which means
     // the result is actually -0.0 which also requires special handling.
-    ma_b(bail, Unsigned);
+    ma_b(bail, NotSigned);
 
     bind(&fin);
 }
 
 CodeOffsetJump
 MacroAssemblerARMCompat::jumpWithPatch(RepatchLabel *label, Condition cond)
 {
     ARMBuffer::PoolEntry pe;
--- a/js/src/jit/arm/Trampoline-arm.cpp
+++ b/js/src/jit/arm/Trampoline-arm.cpp
@@ -450,17 +450,17 @@ JitRuntime::generateArgumentsRectifier(J
     // Push arguments, |nargs| + 1 times (to include |this|).
     {
         Label copyLoopTop;
         masm.bind(&copyLoopTop);
         masm.ma_dataTransferN(IsLoad, 64, true, r3, Imm32(-8), r4, PostIndex);
         masm.ma_dataTransferN(IsStore, 64, true, sp, Imm32(-8), r4, PreIndex);
 
         masm.ma_sub(r8, Imm32(1), r8, SetCond);
-        masm.ma_b(&copyLoopTop, Assembler::Unsigned);
+        masm.ma_b(&copyLoopTop, Assembler::NotSigned);
     }
 
     // translate the framesize from values into bytes
     masm.ma_add(r6, Imm32(1), r6);
     masm.ma_lsl(Imm32(3), r6, r6);
 
     // Construct sizeDescriptor.
     masm.makeFrameDescriptor(r6, IonFrame_Rectifier);
--- a/layout/generic/nsLineLayout.cpp
+++ b/layout/generic/nsLineLayout.cpp
@@ -1944,39 +1944,37 @@ nsLineLayout::VerticalAlignFrames(PerSpa
       nscoord revisedBaselineY = baselineY - offset;
       pfd->mBounds.y = revisedBaselineY - pfd->mAscent;
       pfd->mVerticalAlign = VALIGN_OTHER;
     }
 
     // Update minY/maxY for frames that we just placed. Do not factor
     // text into the equation.
     if (pfd->mVerticalAlign == VALIGN_OTHER) {
-      // Text frames do not contribute to the min/max Y values for the
-      // line (instead their parent frame's font-size contributes).
+      // Text frames and bullets do not contribute to the min/max Y values for
+      // the line (instead their parent frame's font-size contributes).
       // XXXrbs -- relax this restriction because it causes text frames
       //           to jam together when 'font-size-adjust' is enabled
       //           and layout is using dynamic font heights (bug 20394)
       //        -- Note #1: With this code enabled and with the fact that we are not
       //           using Em[Ascent|Descent] as nsDimensions for text metrics in
       //           GFX mean that the discussion in bug 13072 cannot hold.
       //        -- Note #2: We still don't want empty-text frames to interfere.
       //           For example in quirks mode, avoiding empty text frames prevents
       //           "tall" lines around elements like <hr> since the rules of <hr>
       //           in quirks.css have pseudo text contents with LF in them.
-#if 0
-      if (!pfd->GetFlag(PFD_ISTEXTFRAME)) {
-#else
-      // Only consider non empty text frames when line-height=normal
       bool canUpdate = !pfd->GetFlag(PFD_ISTEXTFRAME);
-      if (!canUpdate && pfd->GetFlag(PFD_ISNONWHITESPACETEXTFRAME)) {
+      if ((!canUpdate && pfd->GetFlag(PFD_ISNONWHITESPACETEXTFRAME)) ||
+          (canUpdate && (pfd->GetFlag(PFD_ISBULLET) ||
+                         nsGkAtoms::bulletFrame == frame->GetType()))) {
+        // Only consider bullet / non-empty text frames when line-height:normal.
         canUpdate =
           frame->StyleText()->mLineHeight.GetUnit() == eStyleUnit_Normal;
       }
       if (canUpdate) {
-#endif
         nscoord yTop, yBottom;
         if (frameSpan) {
           // For spans that were are now placing, use their position
           // plus their already computed min-Y and max-Y values for
           // computing yTop and yBottom.
           yTop = pfd->mBounds.y + frameSpan->mMinY;
           yBottom = pfd->mBounds.y + frameSpan->mMaxY;
         }
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/942017-ref.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+    <meta charset="utf-8">
+    <title>Bug 942017</title>
+
+    <style type="text/css">
+@font-face {
+  font-family: DejaVuSansMono;
+  src: url(../fonts/DejaVuSansMono.woff),url(DejaVuSansMono.woff);
+}
+
+  html,body {
+      color:black; background-color:white; font-size:24px; font-family:DejaVuSansMono; padding:20px; margin:0;
+  }
+
+  div {
+    float: left;
+    padding: 1em 2em;
+    outline: 2px solid black;
+    background: black;
+  }
+
+  div.a { line-height: 0.5em; }
+  div.b { line-height: 2em; }
+  div.i l { margin-left:2.84ch; }
+
+  l { display:block; outline:1px solid green; width:1ch; direction:rtl; white-space:nowrap; }
+  x { display:inline-block; width:2.84ch; height:1px; vertical-align:top; }
+    </style>
+</head>
+<body>
+        <div class="a">
+          <l>X<x></x></l>
+          <l>X<x></x></l>
+        </div>
+
+<br clear="all">
+
+        <div class="b">
+          <l>X<x></x></l>
+          <l>X<x></x></l>
+        </div>
+
+<br clear="all">
+
+        <div class="a i">
+          <l>X<x></x></l>
+          <l>X<x></x></l>
+        </div>
+
+<br clear="all">
+
+        <div class="b i">
+          <l>X<x></x></l>
+          <l>X<x></x></l>
+        </div>
+
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/942017.html
@@ -0,0 +1,65 @@
+<!DOCTYPE HTML>
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<html><head>
+    <meta charset="utf-8">
+    <title>Bug 942017</title>
+
+    <style type="text/css">
+@font-face {
+  font-family: DejaVuSansMono;
+  src: url(../fonts/DejaVuSansMono.woff),url(DejaVuSansMono.woff);
+}
+
+  html,body {
+      color:black; background-color:white; font-size:24px; font-family:DejaVuSansMono; padding:20px; margin:0;
+  }
+
+  div {
+    float: left;
+    padding: 1em 2em;
+    outline: 2px solid black;
+    background: black;
+    list-style-type: decimal;
+  }
+
+  div.a { line-height: 0.5em; }
+  div.b { line-height: 2em; }
+  div.i { list-style-position: inside; }
+
+  li { outline:1px solid green; padding:0; margin:0; letter-spacing:0; }
+
+    </style>
+</head>
+<body>
+
+        <div class="a">
+          <li>X</li>
+          <li>X</li>
+        </div>
+
+<br clear="all">
+
+        <div class="b">
+          <li>X</li>
+          <li>X</li>
+        </div>
+
+<br clear="all">
+
+        <div class="a i">
+          <li>X</li>
+          <li>X</li>
+        </div>
+
+<br clear="all">
+
+        <div class="b i">
+          <li>X</li>
+          <li>X</li>
+        </div>
+
+</body>
+</html>
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1780,9 +1780,10 @@ fuzzy(1,10000) fuzzy-if(Android&&Android
 fuzzy-if(Android,8,400) == 906199-1.html 906199-1-ref.html
 == 921716-1.html 921716-1-ref.html
 fuzzy-if(cocoaWidget,1,40) == 928607-1.html 928607-1-ref.html
 == 931464-1.html 931464-1-ref.html
 == 931853.html 931853-ref.html
 == 931853-quirks.html 931853-quirks-ref.html
 == 936670-1.svg 936670-1-ref.svg
 == 941940-1.html 941940-1-ref.html
+== 942017.html 942017-ref.html
 fails-if(winWidget&&!d2d) == 942672-1.html 942672-1-ref.html
--- a/media/mtransport/third_party/nICEr/src/ice/ice_component.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_component.c
@@ -431,25 +431,31 @@ int nr_ice_component_initialize(struct n
       }
       cand=TAILQ_NEXT(cand,entry_comp);
     }
     _status=0;
  abort:
     return(_status);
   }
 
+static int nr_ice_any_peer_paired(nr_ice_candidate* cand) {
+  nr_ice_peer_ctx* pctx=STAILQ_FIRST(&cand->ctx->peers);
+  while(pctx && pctx->state == NR_ICE_PEER_STATE_UNPAIRED){
+    /* Is it worth actually looking through the check lists? Probably not. */
+    pctx=STAILQ_NEXT(pctx,entry);
+  }
+  return pctx != NULL;
+}
+
 /*
   Compare this newly initialized candidate against the other initialized
   candidates and discard the lower-priority one if they are redundant.
 
    This algorithm combined with the other algorithms, favors
    host > srflx > relay
-
-   This actually won't prune relayed in the very rare
-   case that relayed is the same. Not relevant in practice.
  */
 int nr_ice_component_maybe_prune_candidate(nr_ice_ctx *ctx, nr_ice_component *comp, nr_ice_candidate *c1, int *was_pruned)
   {
     nr_ice_candidate *c2, *tmp = NULL;
 
     *was_pruned = 0;
     c2 = TAILQ_FIRST(&comp->candidates);
     while(c2){
@@ -458,22 +464,23 @@ int nr_ice_component_maybe_prune_candida
          !nr_transport_addr_cmp(&c1->base,&c2->base,NR_TRANSPORT_ADDR_CMP_MODE_ALL) &&
          !nr_transport_addr_cmp(&c1->addr,&c2->addr,NR_TRANSPORT_ADDR_CMP_MODE_ALL)){
 
         if((c1->type == c2->type) ||
            (c1->type==HOST && c2->type == SERVER_REFLEXIVE) ||
            (c2->type==HOST && c1->type == SERVER_REFLEXIVE)){
 
           /*
-             These are redundant. Remove the lower pri one.
+             These are redundant. Remove the lower pri one, or if pairing has
+             already occurred, remove the newest one.
 
              Since this algorithmis run whenever a new candidate
              is initialized, there should at most one duplicate.
            */
-          if (c1->priority < c2->priority) {
+          if ((c1->priority <= c2->priority) || nr_ice_any_peer_paired(c2)) {
             tmp = c1;
             *was_pruned = 1;
           }
           else {
             tmp = c2;
           }
           break;
         }
--- a/media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/ice/ice_peer_ctx.c
@@ -331,26 +331,29 @@ int nr_ice_peer_ctx_pair_candidates(nr_i
 
     r_log(LOG_ICE,LOG_DEBUG,"ICE(%s): peer (%s) pairing candidates",pctx->ctx->label,pctx->label);
 
     if(STAILQ_EMPTY(&pctx->peer_streams)) {
         r_log(LOG_ICE,LOG_ERR,"ICE(%s): peer (%s) received no media stream attributes",pctx->ctx->label,pctx->label);
         ABORT(R_FAILED);
     }
 
+    /* Set this first; if we fail partway through, we do not want to end
+     * up in UNPAIRED after creating some pairs. */
+    pctx->state = NR_ICE_PEER_STATE_PAIRED;
+
     stream=STAILQ_FIRST(&pctx->peer_streams);
     while(stream){
       if(r=nr_ice_media_stream_pair_candidates(pctx, stream->local_stream,
         stream))
         ABORT(r);
 
       stream=STAILQ_NEXT(stream,entry);
     }
 
-    pctx->state = NR_ICE_PEER_STATE_PAIRED;
 
     _status=0;
   abort:
     return(_status);
   }
 
 
 int nr_ice_peer_ctx_pair_new_trickle_candidate(nr_ice_ctx *ctx, nr_ice_peer_ctx *pctx, nr_ice_candidate *cand)
--- a/media/mtransport/third_party/nICEr/src/stun/turn_client_ctx.c
+++ b/media/mtransport/third_party/nICEr/src/stun/turn_client_ctx.c
@@ -375,16 +375,38 @@ nr_turn_client_ctx_destroy(nr_turn_clien
     r_log(NR_LOG_TURN, LOG_DEBUG, "TURN(%s): destroy", ctx->label);
 
   /* Cancel frees the rest of our data */
   RFREE(ctx->label);
   ctx->label = 0;
 
   nr_turn_client_cancel(ctx);
 
+  RFREE(ctx->username);
+  ctx->username = 0;
+  r_data_destroy(&ctx->password);
+  RFREE(ctx->nonce);
+  ctx->nonce = 0;
+  RFREE(ctx->realm);
+  ctx->realm = 0;
+
+  /* Destroy the STUN client ctxs */
+  while (!STAILQ_EMPTY(&ctx->stun_ctxs)) {
+    nr_turn_stun_ctx *stun = STAILQ_FIRST(&ctx->stun_ctxs);
+    STAILQ_REMOVE_HEAD(&ctx->stun_ctxs, entry);
+    nr_turn_stun_ctx_destroy(&stun);
+  }
+
+  /* Destroy the permissions */
+  while (!STAILQ_EMPTY(&ctx->permissions)) {
+    nr_turn_permission *perm = STAILQ_FIRST(&ctx->permissions);
+    STAILQ_REMOVE_HEAD(&ctx->permissions, entry);
+    nr_turn_permission_destroy(&perm);
+  }
+
   RFREE(ctx);
 
   return(0);
 }
 
 static int nr_turn_client_connect(nr_turn_client_ctx *ctx)
 {
   int r,_status;
@@ -452,16 +474,18 @@ static void nr_turn_client_connected_cb(
 abort:
   if (_status) {
     nr_turn_client_failed(ctx);
   }
 }
 
 int nr_turn_client_cancel(nr_turn_client_ctx *ctx)
 {
+  nr_turn_stun_ctx *stun = 0;
+
   if (ctx->state == NR_TURN_CLIENT_STATE_CANCELLED ||
       ctx->state == NR_TURN_CLIENT_STATE_FAILED)
     return 0;
 
   if (ctx->label)
     r_log(NR_LOG_TURN, LOG_INFO, "TURN(%s): cancelling", ctx->label);
 
   /* If we are waiting for connect, we need to stop
@@ -477,39 +501,21 @@ int nr_turn_client_cancel(nr_turn_client
          error, we shouldn't cancel. */
       r_log(NR_LOG_TURN, LOG_ERR, "TURN: Couldn't get internal fd");
     }
     else {
       NR_ASYNC_CANCEL(fd, NR_ASYNC_WAIT_WRITE);
     }
   }
 
-  /* Setting these values to 0 isn't strictly necessary, but
-     it protects us in case we double cancel and for
-     some reason bungle the states above in future.*/
-  RFREE(ctx->username);
-  ctx->username = 0;
-  r_data_destroy(&ctx->password);
-  RFREE(ctx->nonce);
-  ctx->nonce = 0;
-  RFREE(ctx->realm);
-  ctx->realm = 0;
-
-  /* Destroy the STUN client ctxs */
-  while (!STAILQ_EMPTY(&ctx->stun_ctxs)) {
-    nr_turn_stun_ctx *stun = STAILQ_FIRST(&ctx->stun_ctxs);
-    STAILQ_REMOVE_HEAD(&ctx->stun_ctxs, entry);
-    nr_turn_stun_ctx_destroy(&stun);
-  }
-
-  /* Destroy the permissions */
-  while (!STAILQ_EMPTY(&ctx->permissions)) {
-    nr_turn_permission *perm = STAILQ_FIRST(&ctx->permissions);
-    STAILQ_REMOVE_HEAD(&ctx->permissions, entry);
-    nr_turn_permission_destroy(&perm);
+  /* Cancel the STUN client ctxs */
+  stun = STAILQ_FIRST(&ctx->stun_ctxs);
+  while (stun) {
+    nr_stun_client_cancel(stun->stun);
+    stun = STAILQ_NEXT(stun, entry);
   }
 
   /* Cancel the timers, if not already cancelled */
   NR_async_timer_cancel(ctx->connected_timer_handle);
   NR_async_timer_cancel(ctx->refresh_timer_handle);
 
   ctx->state = NR_TURN_CLIENT_STATE_CANCELLED;
 
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
@@ -324,44 +324,48 @@ nsresult MediaPipeline::SendPacket(Trans
 
     MOZ_MTLOG(ML_ERROR, "Failed write on stream");
     return NS_BASE_STREAM_CLOSED;
   }
 
   return NS_OK;
 }
 
-void MediaPipeline::increment_rtp_packets_sent() {
+void MediaPipeline::increment_rtp_packets_sent(int32_t bytes) {
   ++rtp_packets_sent_;
+  rtp_bytes_sent_ += bytes;
 
   if (!(rtp_packets_sent_ % 100)) {
     MOZ_MTLOG(ML_INFO, "RTP sent packet count for " << description_
               << " Pipeline " << static_cast<void *>(this)
               << " Flow : " << static_cast<void *>(rtp_transport_)
-              << ": " << rtp_packets_sent_);
+              << ": " << rtp_packets_sent_
+              << " (" << rtp_bytes_sent_ << " bytes)");
   }
 }
 
 void MediaPipeline::increment_rtcp_packets_sent() {
   ++rtcp_packets_sent_;
   if (!(rtcp_packets_sent_ % 100)) {
     MOZ_MTLOG(ML_INFO, "RTCP sent packet count for " << description_
               << " Pipeline " << static_cast<void *>(this)
               << " Flow : " << static_cast<void *>(rtcp_transport_)
               << ": " << rtcp_packets_sent_);
   }
 }
 
-void MediaPipeline::increment_rtp_packets_received() {
+void MediaPipeline::increment_rtp_packets_received(int32_t bytes) {
   ++rtp_packets_received_;
+  rtp_bytes_received_ += bytes;
   if (!(rtp_packets_received_ % 100)) {
     MOZ_MTLOG(ML_INFO, "RTP received packet count for " << description_
               << " Pipeline " << static_cast<void *>(this)
               << " Flow : " << static_cast<void *>(rtp_transport_)
-              << ": " << rtp_packets_received_);
+              << ": " << rtp_packets_received_
+              << " (" << rtp_bytes_received_ << " bytes)");
   }
 }
 
 void MediaPipeline::increment_rtcp_packets_received() {
   ++rtcp_packets_received_;
   if (!(rtcp_packets_received_ % 100)) {
     MOZ_MTLOG(ML_INFO, "RTCP received packet count for " << description_
               << " Pipeline " << static_cast<void *>(this)
@@ -398,39 +402,40 @@ void MediaPipeline::RtpPacketReceived(Tr
   if (direction_ == TRANSMIT) {
     // Discard any media that is being transmitted to us
     // This will be unnecessary when we have SSRC filtering.
     return;
   }
 
   // TODO(ekr@rtfm.com): filter for DTLS here and in RtcpPacketReceived
   // TODO(ekr@rtfm.com): filter on SSRC for bundle
-  increment_rtp_packets_received();
 
   // Make a copy rather than cast away constness
   ScopedDeletePtr<unsigned char> inner_data(
       new unsigned char[len]);
   memcpy(inner_data, data, len);
-  int out_len;
+  int out_len = 0;
   nsresult res = rtp_recv_srtp_->UnprotectRtp(inner_data,
                                               len, len, &out_len);
   if (!NS_SUCCEEDED(res)) {
     char tmp[16];
 
     PR_snprintf(tmp, sizeof(tmp), "%.2x %.2x %.2x %.2x",
                 inner_data[0],
                 inner_data[1],
                 inner_data[2],
                 inner_data[3]);
 
     MOZ_MTLOG(ML_NOTICE, "Error unprotecting RTP in " << description_
               << "len= " << len << "[" << tmp << "...]");
 
     return;
   }
+  increment_rtp_packets_received(out_len);
+
   (void)conduit_->ReceivedRTPPacket(inner_data, out_len);  // Ignore error codes
 }
 
 void MediaPipeline::RtcpPacketReceived(TransportLayer *layer,
                                        const unsigned char *data,
                                        size_t len) {
   if (!transport_->pipeline()) {
     MOZ_MTLOG(ML_DEBUG, "Discarding incoming packet; transport disconnected");
@@ -607,17 +612,17 @@ nsresult MediaPipeline::PipelineTranspor
   int out_len;
   nsresult res = pipeline_->rtp_send_srtp_->ProtectRtp(inner_data,
                                                        data->len(),
                                                        max_len,
                                                        &out_len);
   if (!NS_SUCCEEDED(res))
     return res;
 
-  pipeline_->increment_rtp_packets_sent();
+  pipeline_->increment_rtp_packets_sent(out_len);
   return pipeline_->SendPacket(pipeline_->rtp_transport_, inner_data,
                                out_len);
 }
 
 nsresult MediaPipeline::PipelineTransport::SendRtcpPacket(
     const void *data, int len) {
 
     nsAutoPtr<DataBuffer> buf(new DataBuffer(static_cast<const uint8_t *>(data),
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.h
@@ -87,16 +87,18 @@ class MediaPipeline : public sigslot::ha
         rtp_send_srtp_(),
         rtcp_send_srtp_(),
         rtp_recv_srtp_(),
         rtcp_recv_srtp_(),
         rtp_packets_sent_(0),
         rtcp_packets_sent_(0),
         rtp_packets_received_(0),
         rtcp_packets_received_(0),
+        rtp_bytes_sent_(0),
+        rtp_bytes_received_(0),
         pc_(pc),
         description_() {
       // To indicate rtcp-mux rtcp_transport should be nullptr.
       // Therefore it's an error to send in the same flow for
       // both rtp and rtcp.
       MOZ_ASSERT(rtp_transport_ != rtcp_transport_);
 
       if (!rtcp_transport_) {
@@ -118,27 +120,30 @@ class MediaPipeline : public sigslot::ha
     if (stream_) {
       DetachMediaStream();
     }
   }
 
   virtual nsresult Init();
 
   virtual Direction direction() const { return direction_; }
+  virtual TrackID trackid() const { return track_id_; }
 
   bool IsDoingRtcpMux() const {
     return (rtp_transport_ == rtcp_transport_);
   }
 
-  int rtp_packets_sent() const { return rtp_packets_sent_; }
-  int rtcp_packets_sent() const { return rtcp_packets_sent_; }
-  int rtp_packets_received() const { return rtp_packets_received_; }
-  int rtcp_packets_received() const { return rtcp_packets_received_; }
+  int32_t rtp_packets_sent() const { return rtp_packets_sent_; }
+  int64_t rtp_bytes_sent() const { return rtp_bytes_sent_; }
+  int32_t rtcp_packets_sent() const { return rtcp_packets_sent_; }
+  int32_t rtp_packets_received() const { return rtp_packets_received_; }
+  int64_t rtp_bytes_received() const { return rtp_bytes_received_; }
+  int32_t rtcp_packets_received() const { return rtcp_packets_received_; }
 
-  MediaSessionConduit *Conduit() { return conduit_; }
+  MediaSessionConduit *Conduit() const { return conduit_; }
 
   // Thread counting
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaPipeline)
 
  protected:
   virtual void DetachMediaStream() {}
 
   // Separate class to allow ref counting
@@ -162,19 +167,19 @@ class MediaPipeline : public sigslot::ha
     MediaPipeline *pipeline_;  // Raw pointer to avoid cycles
     nsCOMPtr<nsIEventTarget> sts_thread_;
   };
   friend class PipelineTransport;
 
   virtual nsresult TransportFailed_s(TransportFlow *flow);  // The transport is down
   virtual nsresult TransportReady_s(TransportFlow *flow);   // The transport is ready
 
-  void increment_rtp_packets_sent();
+  void increment_rtp_packets_sent(int bytes);
   void increment_rtcp_packets_sent();
-  void increment_rtp_packets_received();
+  void increment_rtp_packets_received(int bytes);
   void increment_rtcp_packets_received();
 
   virtual nsresult SendPacket(TransportFlow *flow, const void* data, int len);
 
   // Process slots on transports
   void StateChange(TransportFlow *flow, TransportLayer::State);
   void RtpPacketReceived(TransportLayer *layer, const unsigned char *data,
                          size_t len);
@@ -211,20 +216,22 @@ class MediaPipeline : public sigslot::ha
   RefPtr<SrtpFlow> rtp_send_srtp_;
   RefPtr<SrtpFlow> rtcp_send_srtp_;
   RefPtr<SrtpFlow> rtp_recv_srtp_;
   RefPtr<SrtpFlow> rtcp_recv_srtp_;
 
   // Written only on STS thread. May be read on other
   // threads but since there is no mutex, the values
   // will only be approximate.
-  int rtp_packets_sent_;
-  int rtcp_packets_sent_;
-  int rtp_packets_received_;
-  int rtcp_packets_received_;
+  int32_t rtp_packets_sent_;
+  int32_t rtcp_packets_sent_;
+  int32_t rtp_packets_received_;
+  int32_t rtcp_packets_received_;
+  int64_t rtp_bytes_sent_;
+  int64_t rtp_bytes_received_;
 
   // Written on Init. Read on STS thread.
   std::string pc_;
   std::string description_;
 
  private:
   nsresult Init_s();
 
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -58,16 +58,18 @@
 #include "nsIDOMDataChannel.h"
 #include "mozilla/dom/RTCConfigurationBinding.h"
 #include "mozilla/dom/RTCStatsReportBinding.h"
 #include "mozilla/dom/RTCPeerConnectionBinding.h"
 #include "mozilla/dom/PeerConnectionImplBinding.h"
 #include "mozilla/dom/DataChannelBinding.h"
 #include "MediaStreamList.h"
 #include "MediaStreamTrack.h"
+#include "AudioStreamTrack.h"
+#include "VideoStreamTrack.h"
 #include "nsIScriptGlobalObject.h"
 #include "DOMMediaStream.h"
 #include "rlogringbuffer.h"
 #endif
 
 #ifndef USE_FAKE_MEDIA_STREAMS
 #include "MediaSegment.h"
 #endif
@@ -1791,36 +1793,61 @@ PeerConnectionImpl::IceGatheringStateCha
                              &PeerConnectionObserver::OnStateChange,
                              PCObserverStateType::IceGatheringState,
                              rv, static_cast<JSCompartment*>(nullptr)),
                 NS_DISPATCH_NORMAL);
   return NS_OK;
 }
 
 #ifdef MOZILLA_INTERNAL_API
-void PeerConnectionImpl::GetStats_s(
-    uint32_t trackId,
-    bool internalStats,
-    DOMHighResTimeStamp now) {
+class RTCStatsReportInternalConstruct : public RTCStatsReportInternal {
+public:
+  RTCStatsReportInternalConstruct(const nsString &pcid, DOMHighResTimeStamp now) {
+    mPcid = pcid;
+    mInboundRTPStreamStats.Construct();
+    mOutboundRTPStreamStats.Construct();
+    mMediaStreamTrackStats.Construct();
+    mMediaStreamStats.Construct();
+    mTransportStats.Construct();
+    mIceComponentStats.Construct();
+    mIceCandidatePairStats.Construct();
+    mIceCandidateStats.Construct();
+    mCodecStats.Construct();
+  }
+};
 
-  nsresult result = NS_OK;
-  nsAutoPtr<RTCStatsReportInternal> report(new RTCStatsReportInternal);
-  if (!report) {
-    result = NS_ERROR_FAILURE;
-  }
+nsresult PeerConnectionImpl::GetStatsImpl_s(
+    TrackID trackId,
+    bool internalStats,
+    DOMHighResTimeStamp now,
+    RTCStatsReportInternal *report) {
+  if (mMedia) {
+    nsresult rv;
+
+    // Gather stats from media pipeline (can't touch stream itself on STS)
 
-  report->mPcid.Construct(NS_ConvertASCIItoUTF16(mHandle.c_str()));
-  if (mMedia) {
-    RefPtr<NrIceMediaStream> mediaStream(
-        mMedia->ice_media_stream(trackId));
+    for (int i = 0, len = mMedia->LocalStreamsLength(); i < len; i++) {
+      rv = mMedia->GetLocalStream(i)->GetPipelineStats(now, trackId,
+          &report->mInboundRTPStreamStats.Value(),
+          &report->mOutboundRTPStreamStats.Value());
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+    for (int i = 0, len = mMedia->RemoteStreamsLength(); i < len; i++) {
+      rv = mMedia->GetRemoteStream(i)->GetPipelineStats(now, trackId,
+          &report->mInboundRTPStreamStats.Value(),
+          &report->mOutboundRTPStreamStats.Value());
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    // Gather stats from ICE
+
+    RefPtr<NrIceMediaStream> mediaStream(mMedia->ice_media_stream(trackId));
     if (mediaStream) {
       std::vector<NrIceCandidatePair> candPairs;
       mediaStream->GetCandidatePairs(&candPairs);
-      report->mIceCandidatePairStats.Construct();
-      report->mIceCandidateStats.Construct();
       NS_ConvertASCIItoUTF16 componentId(mediaStream->name().c_str());
       for (auto p = candPairs.begin(); p != candPairs.end(); ++p) {
         NS_ConvertASCIItoUTF16 codeword(p->codeword.c_str());
         NS_ConvertASCIItoUTF16 localCodeword(p->local.codeword.c_str());
         NS_ConvertASCIItoUTF16 remoteCodeword(p->remote.codeword.c_str());
         // Only expose candidate-pair statistics to chrome, until we've thought
         // through the implications of exposing it to content.
 
@@ -1864,29 +1891,42 @@ void PeerConnectionImpl::GetStats_s(
           remote.mIpAddress.Construct(
               NS_ConvertASCIItoUTF16(p->remote.cand_addr.host.c_str()));
           remote.mPortNumber.Construct(p->remote.cand_addr.port);
           report->mIceCandidateStats.Value().AppendElement(remote);
         }
       }
     }
   }
+  return NS_OK;
+}
+
+void PeerConnectionImpl::GetStats_s(
+    TrackID trackId,
+    bool internalStats,
+    DOMHighResTimeStamp now) {
+
+  nsAutoPtr<RTCStatsReportInternal> report(new RTCStatsReportInternalConstruct(
+      NS_ConvertASCIItoUTF16(mHandle.c_str()), now));
+
+  nsresult rv = report ? GetStatsImpl_s(trackId, internalStats, now, report)
+                       : NS_ERROR_UNEXPECTED;
 
   nsRefPtr<PeerConnectionImpl> pc(this);
   RUN_ON_THREAD(mThread,
                 WrapRunnable(pc,
                              &PeerConnectionImpl::OnStatsReport_m,
                              trackId,
-                             result,
+                             rv,
                              report),
                 NS_DISPATCH_NORMAL);
 }
 
 void PeerConnectionImpl::OnStatsReport_m(
-    uint32_t trackId,
+    TrackID trackId,
     nsresult result,
     nsAutoPtr<RTCStatsReportInternal> report) {
   nsRefPtr<PeerConnectionObserver> pco = do_QueryObjectReferent(mPCObserver);
   if (pco) {
     JSErrorResult rv;
     if (NS_SUCCEEDED(result)) {
       pco->OnGetStatsSuccess(*report, rv);
     } else {
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -20,16 +20,17 @@
 #include "nricectx.h"
 #include "nricemediastream.h"
 #include "nsComponentManagerUtils.h"
 #include "nsPIDOMWindow.h"
 #include "nsIThread.h"
 
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/PeerConnectionImplEnumsBinding.h"
+#include "StreamBuffer.h"
 
 #ifdef MOZILLA_INTERNAL_API
 #include "mozilla/TimeStamp.h"
 #include "mozilla/net/DataChannel.h"
 #include "VideoUtils.h"
 #include "VideoSegment.h"
 #include "nsNSSShutDown.h"
 #endif
@@ -523,22 +524,27 @@ private:
   // ICE callbacks run on the right thread.
   nsresult IceConnectionStateChange_m(
       mozilla::dom::PCImplIceConnectionState aState);
   nsresult IceGatheringStateChange_m(
       mozilla::dom::PCImplIceGatheringState aState);
 
 #ifdef MOZILLA_INTERNAL_API
   // Fills in an RTCStatsReportInternal. Must be run on STS.
-  void GetStats_s(uint32_t trackId,
+  void GetStats_s(mozilla::TrackID trackId,
                   bool internalStats,
                   DOMHighResTimeStamp now);
 
+  nsresult GetStatsImpl_s(mozilla::TrackID trackId,
+                          bool internalStats,
+                          DOMHighResTimeStamp now,
+                          mozilla::dom::RTCStatsReportInternal *report);
+
   // Sends an RTCStatsReport to JS. Must run on main thread.
-  void OnStatsReport_m(uint32_t trackId,
+  void OnStatsReport_m(mozilla::TrackID trackId,
                        nsresult result,
                        nsAutoPtr<mozilla::dom::RTCStatsReportInternal> report);
 
   // Fetches logs matching pattern from RLogRingBuffer. Must be run on STS.
   void GetLogging_s(const std::string& pattern);
 
   // Sends logging to JS. Must run on main thread.
   void OnGetLogging_m(const std::string& pattern,
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.cpp
@@ -15,19 +15,21 @@
 #include "AudioConduit.h"
 #include "VideoConduit.h"
 #include "runnable_utils.h"
 
 #ifdef MOZILLA_INTERNAL_API
 #include "MediaStreamList.h"
 #include "nsIScriptGlobalObject.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/dom/RTCStatsReportBinding.h"
 #endif
 
 using namespace mozilla;
+using namespace mozilla::dom;
 
 namespace sipcc {
 
 static const char* logTag = "PeerConnectionMedia";
 static const mozilla::TrackID TRACK_AUDIO = 0;
 static const mozilla::TrackID TRACK_VIDEO = 1;
 
 /* If the ExpectAudio hint is on we will add a track at the default first
@@ -460,16 +462,68 @@ SourceStreamInfo::GetPipeline(int aTrack
 
   if (it == mPipelines.end()) {
     return nullptr;
   }
 
   return it->second;
 }
 
+// This methods gathers statistics for the getStats API.
+// aTrack == 0 means gather stats for all tracks.
+
+nsresult
+SourceStreamInfo::GetPipelineStats(DOMHighResTimeStamp now, int aTrack,
+                                   Sequence<RTCInboundRTPStreamStats > *inbound,
+                                   Sequence<RTCOutboundRTPStreamStats > *outbound)
+{
+#ifdef MOZILLA_INTERNAL_API
+  ASSERT_ON_THREAD(mParent->GetSTSThread());
+  // walk through all the MediaPipelines and gather stats
+  for (std::map<int, RefPtr<MediaPipeline> >::iterator it = mPipelines.begin();
+       it != mPipelines.end();
+       ++it) {
+    if (!aTrack || aTrack == it->first) {
+      const MediaPipeline &mp = *it->second;
+      nsString idstr = (mp.Conduit()->type() == MediaSessionConduit::AUDIO) ?
+          NS_LITERAL_STRING("audio_") : NS_LITERAL_STRING("video_");
+      idstr.AppendInt(mp.trackid());
+
+      switch (mp.direction()) {
+        case MediaPipeline::TRANSMIT: {
+          RTCOutboundRTPStreamStats s;
+          s.mTimestamp.Construct(now);
+          s.mId.Construct(NS_LITERAL_STRING("outbound_rtp_") + idstr);
+          s.mType.Construct(RTCStatsType::Outboundrtp);
+          // TODO: Get SSRC
+          // int channel = mp.Conduit()->GetChannel();
+          s.mSsrc.Construct(NS_LITERAL_STRING("123457"));
+          s.mPacketsSent.Construct(mp.rtp_packets_sent());
+          s.mBytesSent.Construct(mp.rtp_bytes_sent());
+          outbound->AppendElement(s);
+          break;
+        }
+        case MediaPipeline::RECEIVE: {
+          RTCInboundRTPStreamStats s;
+          s.mTimestamp.Construct(now);
+          s.mId.Construct(NS_LITERAL_STRING("inbound_rtp_") + idstr);
+          s.mType.Construct(RTCStatsType::Inboundrtp);
+          s.mSsrc.Construct(NS_LITERAL_STRING("123457"));
+          s.mPacketsReceived.Construct(mp.rtp_packets_received());
+          s.mBytesReceived.Construct(mp.rtp_bytes_received());
+          inbound->AppendElement(s);
+          break;
+        }
+      }
+    }
+  }
+#endif
+  return NS_OK;
+}
+
 void
 LocalSourceStreamInfo::StorePipeline(int aTrack,
   mozilla::RefPtr<mozilla::MediaPipeline> aPipeline)
 {
   MOZ_ASSERT(mPipelines.find(aTrack) == mPipelines.end());
   if (mPipelines.find(aTrack) != mPipelines.end()) {
     CSFLogError(logTag, "%s: Storing duplicate track", __FUNCTION__);
     return;
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionMedia.h
@@ -24,21 +24,25 @@
 
 #include "AudioSegment.h"
 
 #ifdef MOZILLA_INTERNAL_API
 #include "Layers.h"
 #include "VideoUtils.h"
 #include "ImageLayers.h"
 #include "VideoSegment.h"
-#else
+#endif
+
 namespace mozilla {
-  class DataChannel;
+class DataChannel;
+namespace dom {
+class RTCInboundRTPStreamStats;
+class RTCOutboundRTPStreamStats;
 }
-#endif
+}
 
 #include "nricectx.h"
 #include "nriceresolver.h"
 #include "nricemediastream.h"
 #include "MediaPipeline.h"
 
 namespace sipcc {
 
@@ -176,17 +180,19 @@ public:
   SourceStreamInfo(already_AddRefed<DOMMediaStream> aMediaStream,
                   PeerConnectionMedia *aParent)
       : mMediaStream(aMediaStream),
         mParent(aParent) {
     MOZ_ASSERT(mMediaStream);
   }
 
   mozilla::RefPtr<mozilla::MediaPipeline> GetPipeline(int aTrack);
-
+  nsresult GetPipelineStats(DOMHighResTimeStamp now, int aTrack,
+    mozilla::dom::Sequence<mozilla::dom::RTCInboundRTPStreamStats > *inbound,
+    mozilla::dom::Sequence<mozilla::dom::RTCOutboundRTPStreamStats > *outbound);
 protected:
   std::map<int, mozilla::RefPtr<mozilla::MediaPipeline> > mPipelines;
   nsRefPtr<DOMMediaStream> mMediaStream;
   PeerConnectionMedia *mParent;
 };
 
 // TODO(ekr@rtfm.com): Refactor {Local,Remote}SourceStreamInfo
 // bug 837539.
--- a/media/webrtc/signaling/test/mediapipeline_unittest.cpp
+++ b/media/webrtc/signaling/test/mediapipeline_unittest.cpp
@@ -320,17 +320,18 @@ class MediaPipelineTest : public ::testi
 
     p2_.Start();
     p1_.Start();
 
     // wait for some RTP/RTCP tx and rx to happen
     PR_Sleep(10000);
 
     ASSERT_GE(p1_.GetAudioRtpCount(), 40);
-    ASSERT_GE(p2_.GetAudioRtpCount(), 40);
+// TODO: Fix to not fail or crash (Bug 947663)
+//    ASSERT_GE(p2_.GetAudioRtpCount(), 40);
     ASSERT_GE(p1_.GetAudioRtcpCount(), 1);
     ASSERT_GE(p2_.GetAudioRtcpCount(), 1);
 
     p1_.Stop();
     p2_.Stop();
   }
 
 protected:
--- a/mobile/android/components/ContentPermissionPrompt.js
+++ b/mobile/android/components/ContentPermissionPrompt.js
@@ -16,28 +16,28 @@ const kEntities = { "geolocation": "geol
 
 function ContentPermissionPrompt() {}
 
 ContentPermissionPrompt.prototype = {
   classID: Components.ID("{C6E8C44D-9F39-4AF7-BCC0-76E38A8310F5}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionPrompt]),
 
-  handleExistingPermission: function handleExistingPermission(request, isApp) {
-    let result = Services.perms.testExactPermissionFromPrincipal(request.principal, request.type);
+  handleExistingPermission: function handleExistingPermission(request, type, isApp) {
+    let result = Services.perms.testExactPermissionFromPrincipal(request.principal, type);
     if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
       request.allow();
       return true;
     }
     if (result == Ci.nsIPermissionManager.DENY_ACTION) {
       request.cancel();
       return true;
     }
 
-    if (isApp && (result == Ci.nsIPermissionManager.UNKNOWN_ACTION && !!kEntities[request.type])) {
+    if (isApp && (result == Ci.nsIPermissionManager.UNKNOWN_ACTION && !!kEntities[type])) {
       request.cancel();
       return true;
     }
 
     return false;
   },
 
   getChromeWindow: function getChromeWindow(aWindow) {
@@ -57,48 +57,56 @@ ContentPermissionPrompt.prototype = {
       return this.getChromeWindow(requestingWindow).wrappedJSObject;
     }
     return request.element.ownerDocument.defaultView;
   },
 
   prompt: function(request) {
     let isApp = request.principal.appId !== Ci.nsIScriptSecurityManager.NO_APP_ID && request.principal.appId !== Ci.nsIScriptSecurityManager.UNKNOWN_APP_ID;
 
+    // Only allow exactly one permission rquest here.
+    let types = request.types.QueryInterface(Ci.nsIArray);
+    if (types.length != 1) {
+      request.cancel();
+      return;
+    }
+    let perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
+
     // Returns true if the request was handled
-    if (this.handleExistingPermission(request, isApp))
+    if (this.handleExistingPermission(request, perm.type, isApp))
        return;
 
     let chromeWin = this.getChromeForRequest(request);
     let tab = chromeWin.BrowserApp.getTabForWindow(request.window.top);
     if (!tab)
       return;
 
     let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
-    let entityName = kEntities[request.type];
+    let entityName = kEntities[perm.type];
 
     let buttons = [{
       label: browserBundle.GetStringFromName(entityName + ".allow"),
       callback: function(aChecked) {
         // If the user checked "Don't ask again", make a permanent exception
         if (aChecked) {
-          Services.perms.addFromPrincipal(request.principal, request.type, Ci.nsIPermissionManager.ALLOW_ACTION);
+          Services.perms.addFromPrincipal(request.principal, perm.type, Ci.nsIPermissionManager.ALLOW_ACTION);
         } else if (isApp || entityName == "desktopNotification") {
           // Otherwise allow the permission for the current session (if the request comes from an app or if it's a desktop-notification request)
-          Services.perms.addFromPrincipal(request.principal, request.type, Ci.nsIPermissionManager.ALLOW_ACTION, Ci.nsIPermissionManager.EXPIRE_SESSION);
+          Services.perms.addFromPrincipal(request.principal, perm.type, Ci.nsIPermissionManager.ALLOW_ACTION, Ci.nsIPermissionManager.EXPIRE_SESSION);
         }
 
         request.allow();
       }
     },
     {
       label: browserBundle.GetStringFromName(entityName + ".dontAllow"),
       callback: function(aChecked) {
         // If the user checked "Don't ask again", make a permanent exception
         if (aChecked)
-          Services.perms.addFromPrincipal(request.principal, request.type, Ci.nsIPermissionManager.DENY_ACTION);
+          Services.perms.addFromPrincipal(request.principal, perm.type, Ci.nsIPermissionManager.DENY_ACTION);
 
         request.cancel();
       }
     }];
 
     let requestor = chromeWin.BrowserApp.manifest ? "'" + chromeWin.BrowserApp.manifest.name + "'" : request.principal.URI.host;
     let message = browserBundle.formatStringFromName(entityName + ".ask", [requestor], 1);
     let options = { checkbox: browserBundle.GetStringFromName(entityName + ".dontAskAgain") };
--- a/netwerk/base/src/nsPACMan.cpp
+++ b/netwerk/base/src/nsPACMan.cpp
@@ -9,16 +9,19 @@
 #include "nsIAuthPrompt.h"
 #include "nsIPromptFactory.h"
 #include "nsIHttpChannel.h"
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h"
 #include "nsNetUtil.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "nsISystemProxySettings.h"
+#ifdef MOZ_NUWA_PROCESS
+#include "ipc/Nuwa.h"
+#endif
 
 //-----------------------------------------------------------------------------
 using namespace mozilla;
 using namespace mozilla::net;
 
 #if defined(PR_LOGGING)
 #endif
 #undef LOG
@@ -669,16 +672,23 @@ nsPACMan::AsyncOnChannelRedirect(nsIChan
   return NS_OK;
 }
 
 void
 nsPACMan::NamePACThread()
 {
   NS_ABORT_IF_FALSE(!NS_IsMainThread(), "wrong thread");
   PR_SetCurrentThreadName("Proxy Resolution");
+#ifdef MOZ_NUWA_PROCESS
+  if (IsNuwaProcess()) {
+    NS_ASSERTION(NuwaMarkCurrentThread != nullptr,
+                 "NuwaMarkCurrentThread is undefined!");
+    NuwaMarkCurrentThread(nullptr, nullptr);
+  }
+#endif
 }
 
 nsresult
 nsPACMan::Init(nsISystemProxySettings *systemProxySettings)
 {
   mSystemProxySettings = systemProxySettings;
 
   nsresult rv = NS_NewThread(getter_AddRefs(mPACThread), nullptr);
--- a/testing/specialpowers/content/MockPermissionPrompt.jsm
+++ b/testing/specialpowers/content/MockPermissionPrompt.jsm
@@ -29,19 +29,28 @@ var newFactory = {
   },
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory])
 };
 
 this.MockPermissionPrompt = {
   init: function() {
     this.reset();
     if (!registrar.isCIDRegistered(newClassID)) {
-      oldClassID = registrar.contractIDToCID(CONTRACT_ID);
-      oldFactory = Cm.getClassObject(Cc[CONTRACT_ID], Ci.nsIFactory);
-      registrar.unregisterFactory(oldClassID, oldFactory);
+      try {
+        oldClassID = registrar.contractIDToCID(CONTRACT_ID);
+        oldFactory = Cm.getClassObject(Cc[CONTRACT_ID], Ci.nsIFactory);
+      } catch (ex) {
+        oldClassID = "";
+        oldFactory = null;
+        dump("TEST-INFO | can't get permission prompt registered component, " +
+            "assuming there is none");
+      }
+      if (oldFactory) {
+        registrar.unregisterFactory(oldClassID, oldFactory);
+      }
       registrar.registerFactory(newClassID, "", CONTRACT_ID, newFactory);
     }
   },
   
   reset: function() {
   },
   
   cleanup: function() {
@@ -56,24 +65,27 @@ this.MockPermissionPrompt = {
 function MockPermissionPromptInstance() { };
 MockPermissionPromptInstance.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIContentPermissionPrompt]),
 
   promptResult: Ci.nsIPermissionManager.UNKNOWN_ACTION,
 
   prompt: function(request) {
 
-    this.promptResult = Services.perms.testExactPermissionFromPrincipal(request.principal,
-                                                                        request.type);
-    if (this.promptResult == Ci.nsIPermissionManager.ALLOW_ACTION) {
-      request.allow();
+    let perms = request.types.QueryInterface(Ci.nsIArray);
+    for (let idx = 0; idx < perms.length; idx++) {
+      let perm = perms.queryElementAt(idx, Ci.nsIContentPermissionType);
+      if (Services.perms.testExactPermissionFromPrincipal(
+           request.principal, perm.type) != Ci.nsIPermissionManager.ALLOW_ACTION) {
+        request.cancel();
+        return;
+      }
     }
-    else {
-      request.cancel();
-    }
+
+    request.allow();
   }
 };
 
 // Expose everything to content. We call reset() here so that all of the relevant
 // lazy expandos get added.
 MockPermissionPrompt.reset();
 function exposeAll(obj) {
   var props = {};
--- a/webapprt/ContentPermission.js
+++ b/webapprt/ContentPermission.js
@@ -25,80 +25,88 @@ ContentPermission.prototype = {
       .QueryInterface(Ci.nsIDocShellTreeItem)
       .rootTreeItem
       .QueryInterface(Ci.nsIInterfaceRequestor)
       .getInterface(Ci.nsIDOMWindow)
       .QueryInterface(Ci.nsIDOMChromeWindow);
   },
 
   prompt: function(request) {
+    // Only allow exactly one permission rquest here.
+    let types = request.types.QueryInterface(Ci.nsIArray);
+    if (types.length != 1) {
+      request.cancel();
+      return;
+    }
+    let perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
+
     // Reuse any remembered permission preferences
     let result =
       Services.perms.testExactPermissionFromPrincipal(request.principal,
-                                                      request.type);
+                                                      perm.type);
 
     // We used to use the name "geo" for the geolocation permission, now we're
     // using "geolocation".  We need to check both to support existing
     // installations.
     if ((result == Ci.nsIPermissionManager.UNKNOWN_ACTION ||
          result == Ci.nsIPermissionManager.PROMPT_ACTION) &&
-        request.type == "geolocation") {
+        perm.type == "geolocation") {
       let geoResult = Services.perms.testExactPermission(request.principal.URI,
                                                          "geo");
       // We override the result only if the "geo" permission was allowed or
       // denied.
       if (geoResult == Ci.nsIPermissionManager.ALLOW_ACTION ||
           geoResult == Ci.nsIPermissionManager.DENY_ACTION) {
         result = geoResult;
       }
     }
 
     if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
       request.allow();
       return;
     } else if (result == Ci.nsIPermissionManager.DENY_ACTION ||
                (result == Ci.nsIPermissionManager.UNKNOWN_ACTION &&
-                UNKNOWN_FAIL.indexOf(request.type) >= 0)) {
+                UNKNOWN_FAIL.indexOf(perm.type) >= 0)) {
       request.cancel();
       return;
     }
 
     // Display a prompt at the top level
     let {name} = WebappRT.localeManifest;
     let requestingWindow = request.window.top;
     let chromeWin = this._getChromeWindow(requestingWindow);
     let bundle = Services.strings.createBundle("chrome://webapprt/locale/webapp.properties");
 
     // Construct a prompt with share/don't and remember checkbox
     let remember = {value: false};
     let choice = Services.prompt.confirmEx(
       chromeWin,
-      bundle.formatStringFromName(request.type + ".title", [name], 1),
-      bundle.GetStringFromName(request.type + ".description"),
+      bundle.formatStringFromName(perm.type + ".title", [name], 1),
+      bundle.GetStringFromName(perm.type + ".description"),
       // Set both buttons to strings with the cancel button being default
       Ci.nsIPromptService.BUTTON_POS_1_DEFAULT |
         Ci.nsIPromptService.BUTTON_TITLE_IS_STRING * Ci.nsIPromptService.BUTTON_POS_0 |
         Ci.nsIPromptService.BUTTON_TITLE_IS_STRING * Ci.nsIPromptService.BUTTON_POS_1,
-      bundle.GetStringFromName(request.type + ".allow"),
-      bundle.GetStringFromName(request.type + ".deny"),
+      bundle.GetStringFromName(perm.type + ".allow"),
+      bundle.GetStringFromName(perm.type + ".deny"),
       null,
-      bundle.GetStringFromName(request.type + ".remember"),
+      bundle.GetStringFromName(perm.type + ".remember"),
       remember);
 
     let action = Ci.nsIPermissionManager.ALLOW_ACTION;
     if (choice != 0) {
       action = Ci.nsIPermissionManager.DENY_ACTION;
     }
 
     if (remember.value) {
       // Persist the choice if the user wants to remember
-      Services.perms.addFromPrincipal(request.principal, request.type, action);
+      Services.perms.addFromPrincipal(request.principal, perm.type, action);
     } else {
       // Otherwise allow the permission for the current session
-      Services.perms.addFromPrincipal(request.principal, request.type, action,
+      Services.perms.addFromPrincipal(request.principal, perm.type, action,
                                       Ci.nsIPermissionManager.EXPIRE_SESSION);
     }
 
     // Trigger the selected choice
     if (choice == 0) {
       request.allow();
     }
     else {
--- a/xpcom/glue/nsBaseHashtable.h
+++ b/xpcom/glue/nsBaseHashtable.h
@@ -53,16 +53,18 @@ class nsBaseHashtable :
   protected nsTHashtable< nsBaseHashtableET<KeyClass,DataType> >
 {
   typedef mozilla::fallible_t fallible_t;
 
 public:
   typedef typename KeyClass::KeyType KeyType;
   typedef nsBaseHashtableET<KeyClass,DataType> EntryType;
 
+  using nsTHashtable<EntryType>::Contains;
+
   nsBaseHashtable()
   {
   }
   explicit nsBaseHashtable(uint32_t aInitSize)
     : nsTHashtable<EntryType>(aInitSize)
   {
   }
 
--- a/xpcom/threads/BackgroundHangMonitor.cpp
+++ b/xpcom/threads/BackgroundHangMonitor.cpp
@@ -6,16 +6,19 @@
 #include "mozilla/BackgroundHangMonitor.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/Move.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/ThreadHangStats.h"
 #include "mozilla/ThreadLocal.h"
+#ifdef MOZ_NUWA_PROCESS
+#include "ipc/Nuwa.h"
+#endif
 
 #include "prinrval.h"
 #include "prthread.h"
 #include "ThreadStackHelper.h"
 
 #include <algorithm>
 
 namespace mozilla {
@@ -26,16 +29,25 @@ namespace mozilla {
  */
 class BackgroundHangManager : public AtomicRefCounted<BackgroundHangManager>
 {
 private:
   // Background hang monitor thread function
   static void MonitorThread(void* aData)
   {
     PR_SetCurrentThreadName("BgHangManager");
+
+#ifdef MOZ_NUWA_PROCESS
+    if (IsNuwaProcess()) {
+      NS_ASSERTION(NuwaMarkCurrentThread != nullptr,
+                   "NuwaMarkCurrentThread is undefined!");
+      NuwaMarkCurrentThread(nullptr, nullptr);
+    }
+#endif
+
     /* We do not hold a reference to BackgroundHangManager here
        because the monitor thread only exists as long as the
        BackgroundHangManager instance exists. We stop the monitor
        thread in the BackgroundHangManager destructor, and we can
        only get to the destructor if we don't hold a reference here. */
     static_cast<BackgroundHangManager*>(aData)->RunMonitorThread();
   }