Merge the last green PGO changeset of mozilla-inbound to mozilla-central
authorEhsan Akhgari <ehsan@mozilla.com>
Mon, 04 Jun 2012 11:31:44 -0400
changeset 95748 fc94f4339da76d758d62aa447eb7b420bd1caaba
parent 95709 ed00ab13fee9e1ff4055e9403d2e25ada75d202a (current diff)
parent 95747 778ba119ded663eb5520f57fdf5df13785db69d2 (diff)
child 95749 12809c23e838dd1e332ddda38a003a7f2d1f0e2b
child 95776 1f4824e46f2391999a816f576469c4ebd7e6bdff
push idunknown
push userunknown
push dateunknown
milestone15.0a1
Merge the last green PGO changeset of mozilla-inbound to mozilla-central
browser/installer/package-manifest.in
testing/xpcshell/xpcshell.ini
--- a/accessible/src/base/filters.cpp
+++ b/accessible/src/base/filters.cpp
@@ -15,17 +15,17 @@ bool
 filters::GetSelected(Accessible* aAccessible)
 {
   return aAccessible->State() & states::SELECTED;
 }
 
 bool
 filters::GetSelectable(Accessible* aAccessible)
 {
-  return aAccessible->State() & states::SELECTABLE;
+  return aAccessible->InteractiveState() & states::SELECTABLE;
 }
 
 bool
 filters::GetRow(Accessible* aAccessible)
 {
   return aAccessible->Role() == roles::ROW;
 }
 
--- a/accessible/src/generic/Accessible.cpp
+++ b/accessible/src/generic/Accessible.cpp
@@ -648,43 +648,26 @@ PRUint64
 Accessible::NativeState()
 {
   PRUint64 state = 0;
 
   DocAccessible* document = Document();
   if (!document || !document->IsInDocument(this))
     state |= states::STALE;
 
-  bool disabled = false;
   if (mContent->IsElement()) {
     nsEventStates elementState = mContent->AsElement()->State();
 
     if (elementState.HasState(NS_EVENT_STATE_INVALID))
       state |= states::INVALID;
 
     if (elementState.HasState(NS_EVENT_STATE_REQUIRED))
       state |= states::REQUIRED;
 
-    disabled = mContent->IsHTML() ? 
-      (elementState.HasState(NS_EVENT_STATE_DISABLED)) :
-      (mContent->AttrValueIs(kNameSpaceID_None,
-                             nsGkAtoms::disabled,
-                             nsGkAtoms::_true,
-                             eCaseMatters));
-  }
-
-  // Set unavailable state based on disabled state, otherwise set focus states
-  if (disabled) {
-    state |= states::UNAVAILABLE;
-  }
-  else if (mContent->IsElement()) {
-    nsIFrame* frame = GetFrame();
-    if (frame && frame->IsFocusable())
-      state |= states::FOCUSABLE;
-
+    state |= NativeInteractiveState();
     if (FocusMgr()->IsFocused(this))
       state |= states::FOCUSED;
   }
 
   // Gather states::INVISIBLE and states::OFFSCREEN flags for this object.
   state |= VisibilityState();
 
   nsIFrame *frame = GetFrame();
@@ -700,22 +683,48 @@ Accessible::NativeState()
   if (!mRoleMapEntry || mRoleMapEntry->roleRule == kUseNativeRole ||
       mRoleMapEntry->role == roles::LINK)
     state |= NativeLinkState();
 
   return state;
 }
 
 PRUint64
+Accessible::NativeInteractiveState() const
+{
+  if (!mContent->IsElement())
+    return 0;
+
+  if (NativelyUnavailable())
+    return states::UNAVAILABLE;
+
+  nsIFrame* frame = GetFrame();
+  if (frame && frame->IsFocusable())
+    return states::FOCUSABLE;
+
+  return 0;
+}
+
+PRUint64
 Accessible::NativeLinkState() const
 {
   // Expose linked state for simple xlink.
   return nsCoreUtils::IsXLink(mContent) ? states::LINKED : 0;
 }
 
+bool
+Accessible::NativelyUnavailable() const
+{
+  if (mContent->IsHTML())
+    return mContent->AsElement()->State().HasState(NS_EVENT_STATE_DISABLED);
+
+  return mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+                               nsGkAtoms::_true, eCaseMatters);
+}
+
   /* readonly attribute boolean focusedChild; */
 NS_IMETHODIMP
 Accessible::GetFocusedChild(nsIAccessible** aChild)
 {
   NS_ENSURE_ARG_POINTER(aChild);
   *aChild = nsnull;
 
   if (IsDefunct())
@@ -1833,81 +1842,83 @@ Accessible::GetActionCount(PRUint8* aAct
 
   *aActionCount = ActionCount();
   return NS_OK;
 }
 
 PRUint8
 Accessible::ActionCount()
 {
-  return GetActionRule(State()) == eNoAction ? 0 : 1;
+  return GetActionRule() == eNoAction ? 0 : 1;
 }
 
 /* DOMString getAccActionName (in PRUint8 index); */
 NS_IMETHODIMP
 Accessible::GetActionName(PRUint8 aIndex, nsAString& aName)
 {
   aName.Truncate();
 
   if (aIndex != 0)
     return NS_ERROR_INVALID_ARG;
 
   if (IsDefunct())
     return NS_ERROR_FAILURE;
 
-  PRUint64 states = State();
-  PRUint32 actionRule = GetActionRule(states);
+  PRUint32 actionRule = GetActionRule();
 
  switch (actionRule) {
    case eActivateAction:
      aName.AssignLiteral("activate");
      return NS_OK;
 
    case eClickAction:
      aName.AssignLiteral("click");
      return NS_OK;
 
    case ePressAction:
      aName.AssignLiteral("press");
      return NS_OK;
 
    case eCheckUncheckAction:
-     if (states & states::CHECKED)
+   {
+     PRUint64 state = State();
+     if (state & states::CHECKED)
        aName.AssignLiteral("uncheck");
-     else if (states & states::MIXED)
+     else if (state & states::MIXED)
        aName.AssignLiteral("cycle");
      else
        aName.AssignLiteral("check");
      return NS_OK;
+   }
 
    case eJumpAction:
      aName.AssignLiteral("jump");
      return NS_OK;
 
    case eOpenCloseAction:
-     if (states & states::COLLAPSED)
+     if (State() & states::COLLAPSED)
        aName.AssignLiteral("open");
      else
        aName.AssignLiteral("close");
      return NS_OK;
 
    case eSelectAction:
      aName.AssignLiteral("select");
      return NS_OK;
 
    case eSwitchAction:
      aName.AssignLiteral("switch");
      return NS_OK;
-     
+
    case eSortAction:
      aName.AssignLiteral("sort");
      return NS_OK;
-   
+
    case eExpandAction:
-     if (states & states::COLLAPSED)
+     if (State() & states::COLLAPSED)
        aName.AssignLiteral("expand");
      else
        aName.AssignLiteral("collapse");
      return NS_OK;
   }
 
   return NS_ERROR_INVALID_ARG;
 }
@@ -1930,17 +1941,17 @@ NS_IMETHODIMP
 Accessible::DoAction(PRUint8 aIndex)
 {
   if (aIndex != 0)
     return NS_ERROR_INVALID_ARG;
 
   if (IsDefunct())
     return NS_ERROR_FAILURE;
 
-  if (GetActionRule(State()) != eNoAction) {
+  if (GetActionRule() != eNoAction) {
     DoCommand();
     return NS_OK;
   }
 
   return NS_ERROR_INVALID_ARG;
 }
 
 /* DOMString getHelp (); */
@@ -3137,21 +3148,21 @@ Accessible::GetAttrValue(nsIAtom *aPrope
   double value = attrValue.ToDouble(&error);
   if (NS_SUCCEEDED(error))
     *aValue = value;
 
   return NS_OK;
 }
 
 PRUint32
-Accessible::GetActionRule(PRUint64 aStates)
+Accessible::GetActionRule()
 {
-  if (aStates & states::UNAVAILABLE)
+  if (InteractiveState() & states::UNAVAILABLE)
     return eNoAction;
-  
+
   // Check if it's simple xlink.
   if (nsCoreUtils::IsXLink(mContent))
     return eJumpAction;
 
   // Return "click" action on elements that have an attached popup menu.
   if (mContent->IsXUL())
     if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::popup))
       return eClickAction;
--- a/accessible/src/generic/Accessible.h
+++ b/accessible/src/generic/Accessible.h
@@ -203,42 +203,63 @@ public:
   virtual mozilla::a11y::role NativeRole();
 
   /**
    * Return all states of accessible (including ARIA states).
    */
   virtual PRUint64 State();
 
   /**
+   * Return interactive states present on the accessible
+   * (@see NativeInteractiveState).
+   */
+  PRUint64 InteractiveState() const
+  {
+    PRUint64 state = NativeInteractiveState();
+    ApplyARIAState(&state);
+    return state;
+  }
+
+  /**
    * Return link states present on the accessible.
    */
   PRUint64 LinkState() const
   {
     PRUint64 state = NativeLinkState();
     ApplyARIAState(&state);
     return state;
   }
 
   /**
    * Return the states of accessible, not taking into account ARIA states.
    * Use State() to get complete set of states.
    */
   virtual PRUint64 NativeState();
 
   /**
+   * Return native interactice state (unavailable, focusable or selectable).
+   */
+  virtual PRUint64 NativeInteractiveState() const;
+
+  /**
    * Return native link states present on the accessible.
    */
   virtual PRUint64 NativeLinkState() const;
 
   /**
    * Return bit set of invisible and offscreen states.
    */
   PRUint64 VisibilityState();
 
   /**
+   * Return true if native unavailable state present.
+   */
+  virtual bool NativelyUnavailable() const;
+
+  /**
    * Returns attributes for accessible without explicitly setted ARIA
    * attributes.
    */
   virtual nsresult GetAttributesInternal(nsIPersistentProperties *aAttributes);
 
   /**
    * Return group position (level, position in set and set size).
    */
@@ -819,20 +840,18 @@ protected:
    *
    * @return - NS_OK_NO_ARIA_VALUE if there is no setted ARIA attribute
    */
   nsresult GetAttrValue(nsIAtom *aAriaProperty, double *aValue);
 
   /**
    * Return the action rule based on ARIA enum constants EActionRule
    * (see nsARIAMap.h). Used by ActionCount() and GetActionName().
-   *
-   * @param aStates  [in] states of the accessible
    */
-  PRUint32 GetActionRule(PRUint64 aStates);
+  PRUint32 GetActionRule();
 
   /**
    * Return group info.
    */
   AccGroupInfo* GetGroupInfo();
 
   /**
    * Fires platform accessible event. It's notification method only. It does
--- a/accessible/src/generic/DocAccessible.cpp
+++ b/accessible/src/generic/DocAccessible.cpp
@@ -280,17 +280,17 @@ PRUint64
 DocAccessible::NativeState()
 {
   // The root content of the document might be removed so that mContent is
   // out of date.
   PRUint64 state = (mContent->GetCurrentDoc() == mDocument) ?
     0 : states::STALE;
 
   // Document is always focusable.
-  state |= states::FOCUSABLE;
+  state |= states::FOCUSABLE; // keep in sync with NativeIteractiveState() impl
   if (FocusMgr()->IsFocused(this))
     state |= states::FOCUSED;
 
   // Expose stale state until the document is ready (DOM is loaded and tree is
   // constructed).
   if (!HasLoadState(eReady))
     state |= states::STALE;
 
@@ -306,16 +306,29 @@ DocAccessible::NativeState()
   }
 
   nsCOMPtr<nsIEditor> editor = GetEditor();
   state |= editor ? states::EDITABLE : states::READONLY;
 
   return state;
 }
 
+PRUint64
+DocAccessible::NativeInteractiveState() const
+{
+  // Document is always focusable.
+  return states::FOCUSABLE;
+}
+
+bool
+DocAccessible::NativelyUnavailable() const
+{
+  return false;
+}
+
 // Accessible public method
 void
 DocAccessible::ApplyARIAState(PRUint64* aState) const
 {
   // Combine with states from outer doc
   // 
   Accessible::ApplyARIAState(aState);
 
--- a/accessible/src/generic/DocAccessible.h
+++ b/accessible/src/generic/DocAccessible.h
@@ -79,16 +79,18 @@ public:
   virtual nsIDocument* GetDocumentNode() const { return mDocument; }
 
   // Accessible
   virtual mozilla::a11y::ENameValueFlag Name(nsString& aName);
   virtual void Description(nsString& aDescription);
   virtual Accessible* FocusedChild();
   virtual mozilla::a11y::role NativeRole();
   virtual PRUint64 NativeState();
+  virtual PRUint64 NativeInteractiveState() const;
+  virtual bool NativelyUnavailable() const;
   virtual void ApplyARIAState(PRUint64* aState) const;
 
   virtual void SetRoleMapEntry(nsRoleMapEntry* aRoleMapEntry);
 
 #ifdef DEBUG
   virtual nsresult HandleAccEvent(AccEvent* aEvent);
 #endif
 
--- a/accessible/src/generic/HyperTextAccessible.cpp
+++ b/accessible/src/generic/HyperTextAccessible.cpp
@@ -1536,17 +1536,17 @@ HyperTextAccessible::GetEditor() const
 
 /**
   * =================== Caret & Selection ======================
   */
 
 nsresult
 HyperTextAccessible::SetSelectionRange(PRInt32 aStartPos, PRInt32 aEndPos)
 {
-  bool isFocusable = State() & states::FOCUSABLE;
+  bool isFocusable = InteractiveState() & states::FOCUSABLE;
 
   // If accessible is focusable then focus it before setting the selection to
   // neglect control's selection changes on focus if any (for example, inputs
   // that do select all on focus).
   // some input controls
   if (isFocusable)
     TakeFocus();
 
@@ -1607,17 +1607,17 @@ HyperTextAccessible::GetCaretOffset(PRIn
   *aCaretOffset = -1;
 
   if (IsDefunct())
     return NS_ERROR_FAILURE;
 
   // Not focused focusable accessible except document accessible doesn't have
   // a caret.
   if (!IsDoc() && !FocusMgr()->IsFocused(this) &&
-      (State() & states::FOCUSABLE)) {
+      (InteractiveState() & states::FOCUSABLE)) {
     return NS_OK;
   }
 
   // No caret if the focused node is not inside this DOM node and this DOM node
   // is not inside of focused node.
   FocusManager::FocusDisposition focusDisp =
     FocusMgr()->IsInOrContainsFocus(this);
   if (focusDisp == FocusManager::eNone)
--- a/accessible/src/html/HTMLListAccessible.cpp
+++ b/accessible/src/html/HTMLListAccessible.cpp
@@ -183,21 +183,17 @@ role
 HTMLListBulletAccessible::NativeRole()
 {
   return roles::STATICTEXT;
 }
 
 PRUint64
 HTMLListBulletAccessible::NativeState()
 {
-  PRUint64 state = nsLeafAccessible::NativeState();
-
-  state &= ~states::FOCUSABLE;
-  state |= states::READONLY;
-  return state;
+  return nsLeafAccessible::NativeState() | states::READONLY;
 }
 
 void
 HTMLListBulletAccessible::AppendTextTo(nsAString& aText, PRUint32 aStartOffset,
                                        PRUint32 aLength)
 {
   nsAutoString bulletText;
   nsBlockFrame* blockFrame = do_QueryFrame(mContent->GetPrimaryFrame());
--- a/accessible/src/html/nsHTMLLinkAccessible.cpp
+++ b/accessible/src/html/nsHTMLLinkAccessible.cpp
@@ -37,28 +37,17 @@ role
 nsHTMLLinkAccessible::NativeRole()
 {
   return roles::LINK;
 }
 
 PRUint64
 nsHTMLLinkAccessible::NativeState()
 {
-  PRUint64 states = HyperTextAccessibleWrap::NativeState();
-
-  states  &= ~states::READONLY;
-
-  if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::name)) {
-    // This is how we indicate it is a named anchor
-    // In other words, this anchor can be selected as a location :)
-    // There is no other better state to use to indicate this.
-    states |= states::SELECTABLE;
-  }
-
-  return states;
+  return HyperTextAccessibleWrap::NativeState() & ~states::READONLY;
 }
 
 PRUint64
 nsHTMLLinkAccessible::NativeLinkState() const
 {
   nsEventStates eventState = mContent->AsElement()->State();
   if (eventState.HasState(NS_EVENT_STATE_UNVISITED))
     return states::LINKED;
@@ -67,16 +56,30 @@ nsHTMLLinkAccessible::NativeLinkState() 
     return states::LINKED | states::TRAVERSED;
 
   // This is a either named anchor (a link with also a name attribute) or
   // it doesn't have any attributes. Check if 'click' event handler is
   // registered, otherwise bail out.
   return nsCoreUtils::HasClickListener(mContent) ? states::LINKED : 0;
 }
 
+PRUint64
+nsHTMLLinkAccessible::NativeInteractiveState() const
+{
+  PRUint64 state = HyperTextAccessibleWrap::NativeInteractiveState();
+
+  // This is how we indicate it is a named anchor. In other words, this anchor
+  // can be selected as a location :) There is no other better state to use to
+  // indicate this.
+  if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::name))
+    state |= states::SELECTABLE;
+
+  return state;
+}
+
 void
 nsHTMLLinkAccessible::Value(nsString& aValue)
 {
   aValue.Truncate();
 
   HyperTextAccessible::Value(aValue);
   if (aValue.IsEmpty())
     nsContentUtils::GetLinkLocation(mContent->AsElement(), aValue);
--- a/accessible/src/html/nsHTMLLinkAccessible.h
+++ b/accessible/src/html/nsHTMLLinkAccessible.h
@@ -19,16 +19,17 @@ public:
   NS_IMETHOD GetActionName(PRUint8 aIndex, nsAString& aName);
   NS_IMETHOD DoAction(PRUint8 aIndex);
 
   // Accessible
   virtual void Value(nsString& aValue);
   virtual mozilla::a11y::role NativeRole();
   virtual PRUint64 NativeState();
   virtual PRUint64 NativeLinkState() const;
+  virtual PRUint64 NativeInteractiveState() const;
 
   // ActionAccessible
   virtual PRUint8 ActionCount();
 
   // HyperLinkAccessible
   virtual bool IsLink();
   virtual already_AddRefed<nsIURI> AnchorURIAt(PRUint32 aAnchorIndex);
 
--- a/accessible/src/html/nsHTMLSelectAccessible.cpp
+++ b/accessible/src/html/nsHTMLSelectAccessible.cpp
@@ -232,20 +232,16 @@ nsHTMLSelectOptionAccessible::NativeStat
   Accessible* select = GetSelect();
   if (!select)
     return state;
 
   PRUint64 selectState = select->State();
   if (selectState & states::INVISIBLE)
     return state;
 
-  // Focusable and selectable
-  if (!(state & states::UNAVAILABLE))
-    state |= (states::FOCUSABLE | states::SELECTABLE);
-
   // Are we selected?
   bool isSelected = false;
   nsCOMPtr<nsIDOMHTMLOptionElement> option(do_QueryInterface(mContent));
   if (option) {
     option->GetSelected(&isSelected);
     if (isSelected)
       state |= states::SELECTED;
   }
@@ -280,16 +276,23 @@ nsHTMLSelectOptionAccessible::NativeStat
         state |= states::OFFSCREEN;
       }
     }
   }
  
   return state;
 }
 
+PRUint64
+nsHTMLSelectOptionAccessible::NativeInteractiveState() const
+{
+  return NativelyUnavailable() ?
+    states::UNAVAILABLE : states::FOCUSABLE | states::SELECTABLE;
+}
+
 PRInt32
 nsHTMLSelectOptionAccessible::GetLevelInternal()
 {
   nsIContent *parentContent = mContent->GetParent();
 
   PRInt32 level =
     parentContent->NodeInfo()->Equals(nsGkAtoms::optgroup) ? 2 : 1;
 
@@ -371,23 +374,19 @@ nsHTMLSelectOptGroupAccessible::
 
 role
 nsHTMLSelectOptGroupAccessible::NativeRole()
 {
   return roles::HEADING;
 }
 
 PRUint64
-nsHTMLSelectOptGroupAccessible::NativeState()
+nsHTMLSelectOptGroupAccessible::NativeInteractiveState() const
 {
-  PRUint64 state = nsHTMLSelectOptionAccessible::NativeState();
-
-  state &= ~(states::FOCUSABLE | states::SELECTABLE);
-
-  return state;
+  return NativelyUnavailable() ? states::UNAVAILABLE : 0;
 }
 
 NS_IMETHODIMP nsHTMLSelectOptGroupAccessible::DoAction(PRUint8 index)
 {
   return NS_ERROR_NOT_IMPLEMENTED;
 }
 
 NS_IMETHODIMP nsHTMLSelectOptGroupAccessible::GetActionName(PRUint8 aIndex, nsAString& aName)
--- a/accessible/src/html/nsHTMLSelectAccessible.h
+++ b/accessible/src/html/nsHTMLSelectAccessible.h
@@ -81,16 +81,17 @@ public:
   NS_IMETHOD DoAction(PRUint8 index);
   NS_IMETHOD GetActionName(PRUint8 aIndex, nsAString& aName);
   NS_IMETHOD SetSelected(bool aSelect);
 
   // Accessible
   virtual nsresult GetNameInternal(nsAString& aName);
   virtual mozilla::a11y::role NativeRole();
   virtual PRUint64 NativeState();
+  virtual PRUint64 NativeInteractiveState() const;
 
   virtual PRInt32 GetLevelInternal();
   virtual void GetBoundsRect(nsRect& aTotalBounds, nsIFrame** aBoundingFrame);
 
   // ActionAccessible
   virtual PRUint8 ActionCount();
 
   // Widgets
@@ -136,17 +137,17 @@ public:
   virtual ~nsHTMLSelectOptGroupAccessible() {}
 
   // nsIAccessible
   NS_IMETHOD DoAction(PRUint8 index);  
   NS_IMETHOD GetActionName(PRUint8 aIndex, nsAString& aName);
 
   // Accessible
   virtual mozilla::a11y::role NativeRole();
-  virtual PRUint64 NativeState();
+  virtual PRUint64 NativeInteractiveState() const;
 
   // ActionAccessible
   virtual PRUint8 ActionCount();
 
 protected:
   // Accessible
   virtual void CacheChildren();
 };
--- a/accessible/src/html/nsHTMLTableAccessible.cpp
+++ b/accessible/src/html/nsHTMLTableAccessible.cpp
@@ -67,25 +67,28 @@ nsHTMLTableCellAccessible::NativeRole()
 PRUint64
 nsHTMLTableCellAccessible::NativeState()
 {
   PRUint64 state = HyperTextAccessibleWrap::NativeState();
 
   nsIFrame *frame = mContent->GetPrimaryFrame();
   NS_ASSERTION(frame, "No frame for valid cell accessible!");
 
-  if (frame) {
-    state |= states::SELECTABLE;
-    if (frame->IsSelected())
-      state |= states::SELECTED;
-  }
+  if (frame && frame->IsSelected())
+    state |= states::SELECTED;
 
   return state;
 }
 
+PRUint64
+nsHTMLTableCellAccessible::NativeInteractiveState() const
+{
+  return HyperTextAccessibleWrap::NativeInteractiveState() | states::SELECTABLE;
+}
+
 nsresult
 nsHTMLTableCellAccessible::GetAttributesInternal(nsIPersistentProperties *aAttributes)
 {
   if (IsDefunct())
     return NS_ERROR_FAILURE;
 
   nsresult rv = HyperTextAccessibleWrap::GetAttributesInternal(aAttributes);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/accessible/src/html/nsHTMLTableAccessible.h
+++ b/accessible/src/html/nsHTMLTableAccessible.h
@@ -27,16 +27,17 @@ public:
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIAccessibleTableCell
   NS_DECL_NSIACCESSIBLETABLECELL
 
   // Accessible
   virtual mozilla::a11y::role NativeRole();
   virtual PRUint64 NativeState();
+  virtual PRUint64 NativeInteractiveState() const;
   virtual nsresult GetAttributesInternal(nsIPersistentProperties *aAttributes);
 
 protected:
   /**
    * Return host table accessible.
    */
   already_AddRefed<nsIAccessibleTable> GetTableAccessible();
 
--- a/accessible/src/jsat/AccessFu.jsm
+++ b/accessible/src/jsat/AccessFu.jsm
@@ -346,16 +346,21 @@ var AccessFu = {
           this.presenters.forEach(
             function(p) {
               p.textChanged(isInserted, event.start, event.length, text, event.modifiedText);
             }
           );
         }
         break;
       }
+      case Ci.nsIAccessibleEvent.EVENT_SCROLLING_START:
+      {
+        VirtualCursorController.moveCursorToObject(aEvent.accessible);
+        break;
+      }
       default:
         break;
     }
   },
 
   /**
    * Check if accessible is a top-level content document (i.e. a child of a XUL
    * browser node).
--- a/accessible/src/jsat/VirtualCursorController.jsm
+++ b/accessible/src/jsat/VirtualCursorController.jsm
@@ -160,16 +160,32 @@ var VirtualCursorController = {
     }
   },
 
   getVirtualCursor: function getVirtualCursor(document) {
     return gAccRetrieval.getAccessibleFor(document).
       QueryInterface(Ci.nsIAccessibleCursorable).virtualCursor;
   },
 
+  moveCursorToObject: function moveCursorToObject(aAccessible, aRule) {
+    let doc = aAccessible.document;
+    while (doc) {
+      let vc = null;
+      try {
+        vc = doc.QueryInterface(Ci.nsIAccessibleCursorable).virtualCursor;
+      } catch (x) {
+        doc = doc.parentDocument;
+        continue;
+      }
+      if (vc)
+        vc.moveNext(aRule || this.SimpleTraversalRule, aAccessible, true);
+      break;
+    }
+  },
+
   SimpleTraversalRule: {
     getMatchRoles: function SimpleTraversalRule_getmatchRoles(aRules) {
       aRules.value = this._matchRoles;
       return this._matchRoles.length;
     },
 
     preFilter: Ci.nsIAccessibleTraversalRule.PREFILTER_DEFUNCT |
       Ci.nsIAccessibleTraversalRule.PREFILTER_INVISIBLE,
--- a/accessible/src/mac/mozAccessible.mm
+++ b/accessible/src/mac/mozAccessible.mm
@@ -551,33 +551,33 @@ GetNativeFromGeckoAccessible(nsIAccessib
 
   return [NSString stringWithFormat:@"(%p) %@", self, [self role]];
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NIL;
 }
 
 - (BOOL)isFocused
 {
-  return (mGeckoAccessible->State() & states::FOCUSED) != 0;
+  return FocusMgr()->IsFocused(mGeckoAccessible);
 }
 
 - (BOOL)canBeFocused
 {
-  return mGeckoAccessible->State() & states::FOCUSABLE;
+  return mGeckoAccessible->InteractiveState() & states::FOCUSABLE;
 }
 
 - (BOOL)focus
 {
   nsresult rv = mGeckoAccessible->TakeFocus();
   return NS_SUCCEEDED(rv);
 }
 
 - (BOOL)isEnabled
 {
-  return (mGeckoAccessible->State() & states::UNAVAILABLE) == 0;
+  return (mGeckoAccessible->InteractiveState() & states::UNAVAILABLE) == 0;
 }
 
 // The root accessible calls this when the focused node was
 // changed to us.
 - (void)didReceiveFocus
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
--- a/accessible/src/msaa/nsHTMLWin32ObjectAccessible.cpp
+++ b/accessible/src/msaa/nsHTMLWin32ObjectAccessible.cpp
@@ -37,22 +37,22 @@ nsHTMLWin32ObjectOwnerAccessible::Shutdo
 // nsHTMLWin32ObjectOwnerAccessible: Accessible implementation
 
 role
 nsHTMLWin32ObjectOwnerAccessible::NativeRole()
 {
   return roles::EMBEDDED_OBJECT;
 }
 
-PRUint64
-nsHTMLWin32ObjectOwnerAccessible::NativeState()
+bool
+nsHTMLWin32ObjectOwnerAccessible::NativelyUnavailable() const
 {
   // XXX: No HWND means this is windowless plugin which is not accessible in
   // the meantime.
-  return mHwnd ? AccessibleWrap::NativeState() : states::UNAVAILABLE;
+  return !mHwnd;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsHTMLWin32ObjectOwnerAccessible: Accessible protected implementation
 
 void
 nsHTMLWin32ObjectOwnerAccessible::CacheChildren()
 {
--- a/accessible/src/msaa/nsHTMLWin32ObjectAccessible.h
+++ b/accessible/src/msaa/nsHTMLWin32ObjectAccessible.h
@@ -23,17 +23,17 @@ public:
                                    DocAccessible* aDoc, void* aHwnd);
   virtual ~nsHTMLWin32ObjectOwnerAccessible() {}
 
   // nsAccessNode
   virtual void Shutdown();
 
   // Accessible
   virtual mozilla::a11y::role NativeRole();
-  virtual PRUint64 NativeState();
+  virtual bool NativelyUnavailable() const;
 
 protected:
 
   // Accessible
   virtual void CacheChildren();
 
   void* mHwnd;
   nsRefPtr<Accessible> mNativeAccessible;
--- a/accessible/src/xforms/nsXFormsAccessible.cpp
+++ b/accessible/src/xforms/nsXFormsAccessible.cpp
@@ -114,49 +114,55 @@ nsXFormsAccessible::CacheSelectChildren(
 
 PRUint64
 nsXFormsAccessible::NativeState()
 {
   NS_ENSURE_TRUE(sXFormsService, 0);
 
   nsCOMPtr<nsIDOMNode> DOMNode(do_QueryInterface(mContent));
 
-  bool isRelevant = false;
-  nsresult rv = sXFormsService->IsRelevant(DOMNode, &isRelevant);
-  NS_ENSURE_SUCCESS(rv, 0);
-
   bool isReadonly = false;
-  rv = sXFormsService->IsReadonly(DOMNode, &isReadonly);
+  nsresult rv = sXFormsService->IsReadonly(DOMNode, &isReadonly);
   NS_ENSURE_SUCCESS(rv, 0);
 
   bool isRequired = false;
   rv = sXFormsService->IsRequired(DOMNode, &isRequired);
   NS_ENSURE_SUCCESS(rv, 0);
 
   bool isValid = false;
   rv = sXFormsService->IsValid(DOMNode, &isValid);
   NS_ENSURE_SUCCESS(rv, 0);
 
   PRUint64 states = HyperTextAccessibleWrap::NativeState();
 
-  if (!isRelevant)
+  if (NativelyUnavailable())
     states |= states::UNAVAILABLE;
 
   if (isReadonly)
     states |= states::READONLY;
 
   if (isRequired)
     states |= states::REQUIRED;
 
   if (!isValid)
     states |= states::INVALID;
 
   return states;
 }
 
+bool
+nsXFormsAccessible::NativelyUnavailable() const
+{
+  nsCOMPtr<nsIDOMNode> DOMNode(do_QueryInterface(mContent));
+
+  bool isRelevant = false;
+  sXFormsService->IsRelevant(DOMNode, &isRelevant);
+  return !isRelevant;
+}
+
 nsresult
 nsXFormsAccessible::GetNameInternal(nsAString& aName)
 {
   // search the xforms:label element
   return GetBoundChildElementValue(NS_LITERAL_STRING("label"), aName);
 }
 
 void
--- a/accessible/src/xforms/nsXFormsAccessible.h
+++ b/accessible/src/xforms/nsXFormsAccessible.h
@@ -47,16 +47,17 @@ public:
   virtual void Value(nsString& aValue);
 
   // Returns value of child xforms 'label' element.
   virtual nsresult GetNameInternal(nsAString& aName);
 
   // Returns state of xforms element taking into account state of instance node
   // that it is bound to.
   virtual PRUint64 NativeState();
+  virtual bool NativelyUnavailable() const;
 
   // Denies accessible nodes in anonymous content of xforms element by
   // always returning false value.
   virtual bool CanHaveAnonChildren();
 
 protected:
   // Returns value of first child xforms element by tagname that is bound to
   // instance node.
--- a/accessible/src/xforms/nsXFormsFormControlsAccessible.cpp
+++ b/accessible/src/xforms/nsXFormsFormControlsAccessible.cpp
@@ -550,17 +550,23 @@ nsXFormsSelectComboboxAccessible::Native
   nsresult rv = sXFormsService->IsDropmarkerOpen(DOMNode, &isOpen);
   NS_ENSURE_SUCCESS(rv, state);
 
   if (isOpen)
     state |= states::EXPANDED;
   else
     state |= states::COLLAPSED;
 
-  return state | states::HASPOPUP | states::FOCUSABLE;
+  return state | states::HASPOPUP;
+}
+
+PRUint64
+nsXFormsSelectComboboxAccessible::NativeInteractiveState() const
+{
+  return NativelyUnavailable() ? states::UNAVAILABLE : states::FOCUSABLE;
 }
 
 bool
 nsXFormsSelectComboboxAccessible::CanHaveAnonChildren()
 {
   return true;
 }
 
@@ -580,27 +586,29 @@ nsXFormsItemComboboxAccessible::NativeRo
 {
   return roles::LISTITEM;
 }
 
 PRUint64
 nsXFormsItemComboboxAccessible::NativeState()
 {
   PRUint64 state = nsXFormsSelectableItemAccessible::NativeState();
-
-  if (state & states::UNAVAILABLE)
-    return state;
-
-  state |= states::SELECTABLE;
   if (IsSelected())
     state |= states::SELECTED;
 
   return state;
 }
 
+PRUint64
+nsXFormsItemComboboxAccessible::NativeInteractiveState() const
+{
+  return NativelyUnavailable() ?
+    states::UNAVAILABLE : states::FOCUSABLE | states::SELECTABLE;
+}
+
 NS_IMETHODIMP
 nsXFormsItemComboboxAccessible::GetActionName(PRUint8 aIndex, nsAString& aName)
 {
   if (aIndex != eAction_Click)
     return NS_ERROR_INVALID_ARG;
 
   aName.AssignLiteral("select");
   return NS_OK;
--- a/accessible/src/xforms/nsXFormsFormControlsAccessible.h
+++ b/accessible/src/xforms/nsXFormsFormControlsAccessible.h
@@ -256,16 +256,17 @@ class nsXFormsSelectComboboxAccessible :
 {
 public:
   nsXFormsSelectComboboxAccessible(nsIContent* aContent,
                                    DocAccessible* aDoc);
 
   // Accessible
   virtual mozilla::a11y::role NativeRole();
   virtual PRUint64 NativeState();
+  virtual PRUint64 NativeInteractiveState() const;
   virtual bool CanHaveAnonChildren();
 };
 
 
 /**
  * Accessible object for xforms:item element when it is represented by a
  * listitem. This occurs when the item is contained in a xforms:select with
  * minimal appearance. Such a xforms:select is represented by a combobox.
@@ -278,12 +279,13 @@ public:
                                  DocAccessible* aDoc);
 
   // nsIAccessible
   NS_IMETHOD GetActionName(PRUint8 aIndex, nsAString& aName);
 
   // Accessible
   virtual mozilla::a11y::role NativeRole();
   virtual PRUint64 NativeState();
+  virtual PRUint64 NativeInteractiveState() const;
 };
 
 #endif
 
--- a/accessible/src/xforms/nsXFormsWidgetsAccessible.cpp
+++ b/accessible/src/xforms/nsXFormsWidgetsAccessible.cpp
@@ -114,26 +114,30 @@ nsXFormsComboboxPopupWidgetAccessible::N
 {
   PRUint64 state = nsXFormsAccessible::NativeState();
 
   bool isOpen = false;
   nsCOMPtr<nsIDOMNode> DOMNode(do_QueryInterface(mContent));
   nsresult rv = sXFormsService->IsDropmarkerOpen(DOMNode, &isOpen);
   NS_ENSURE_SUCCESS(rv, state);
 
-  state |= states::FOCUSABLE;
-
   if (isOpen)
     state = states::FLOATING;
   else
     state = states::INVISIBLE;
 
   return state;
 }
 
+PRUint64
+nsXFormsComboboxPopupWidgetAccessible::NativeInteractiveState() const
+{
+  return NativelyUnavailable() ? states::UNAVAILABLE : states::FOCUSABLE;
+}
+
 nsresult
 nsXFormsComboboxPopupWidgetAccessible::GetNameInternal(nsAString& aName)
 {
   // Override nsXFormsAccessible::GetName() to prevent name calculation by
   // XForms rules.
   return NS_OK;
 }
 
--- a/accessible/src/xforms/nsXFormsWidgetsAccessible.h
+++ b/accessible/src/xforms/nsXFormsWidgetsAccessible.h
@@ -59,15 +59,16 @@ public:
                                         DocAccessible* aDoc);
 
   // Accessible
   virtual void Description(nsString& aDescription);
   virtual void Value(nsString& aValue);
   virtual nsresult GetNameInternal(nsAString& aName);
   virtual mozilla::a11y::role NativeRole();
   virtual PRUint64 NativeState();
+  virtual PRUint64 NativeInteractiveState() const;
 
 protected:
   // Accessible
   virtual void CacheChildren();
 };
 
 #endif
--- a/accessible/src/xul/XULFormControlAccessible.cpp
+++ b/accessible/src/xul/XULFormControlAccessible.cpp
@@ -90,26 +90,16 @@ XULButtonAccessible::NativeRole()
 PRUint64
 XULButtonAccessible::NativeState()
 {
   // Possible states: focused, focusable, unavailable(disabled).
 
   // get focus and disable status from base class
   PRUint64 state = Accessible::NativeState();
 
-  bool disabled = false;
-  nsCOMPtr<nsIDOMXULControlElement> xulFormElement(do_QueryInterface(mContent));
-  if (xulFormElement) {
-    xulFormElement->GetDisabled(&disabled);
-    if (disabled)
-      state |= states::UNAVAILABLE;
-    else 
-      state |= states::FOCUSABLE;
-  }
-
   // Buttons can be checked -- they simply appear pressed in rather than checked
   nsCOMPtr<nsIDOMXULButtonElement> xulButtonElement(do_QueryInterface(mContent));
   if (xulButtonElement) {
     nsAutoString type;
     xulButtonElement->GetType(type);
     if (type.EqualsLiteral("checkbox") || type.EqualsLiteral("radio")) {
       state |= states::CHECKABLE;
       bool checked = false;
@@ -476,32 +466,35 @@ XULRadioButtonAccessible::
 }
 
 PRUint64
 XULRadioButtonAccessible::NativeState()
 {
   PRUint64 state = nsLeafAccessible::NativeState();
   state |= states::CHECKABLE;
 
-  if (!(state & states::UNAVAILABLE))
-    state |= states::FOCUSABLE;
-
   nsCOMPtr<nsIDOMXULSelectControlItemElement> radioButton =
     do_QueryInterface(mContent);
   if (radioButton) {
     bool selected = false;   // Radio buttons can be selected
     radioButton->GetSelected(&selected);
     if (selected) {
       state |= states::CHECKED;
     }
   }
 
   return state;
 }
 
+PRUint64
+XULRadioButtonAccessible::NativeInteractiveState() const
+{
+  return NativelyUnavailable() ? states::UNAVAILABLE : states::FOCUSABLE;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // XULRadioButtonAccessible: Widgets
 
 Accessible*
 XULRadioButtonAccessible::ContainerWidget() const
 {
   return mParent;
 }
@@ -528,22 +521,22 @@ XULRadioGroupAccessible::
 
 role
 XULRadioGroupAccessible::NativeRole()
 {
   return roles::GROUPING;
 }
 
 PRUint64
-XULRadioGroupAccessible::NativeState()
+XULRadioGroupAccessible::NativeInteractiveState() const
 {
   // The radio group is not focusable. Sometimes the focus controller will
   // report that it is focused. That means that the actual selected radio button
   // should be considered focused.
-  return Accessible::NativeState() & ~(states::FOCUSABLE | states::FOCUSED);
+  return NativelyUnavailable() ? states::UNAVAILABLE : 0;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // XULRadioGroupAccessible: Widgets
 
 bool
 XULRadioGroupAccessible::IsWidget() const
 {
--- a/accessible/src/xul/XULFormControlAccessible.h
+++ b/accessible/src/xul/XULFormControlAccessible.h
@@ -127,32 +127,33 @@ public:
 class XULRadioButtonAccessible : public RadioButtonAccessible
 {
 
 public:
   XULRadioButtonAccessible(nsIContent* aContent, DocAccessible* aDoc);
 
   // Accessible
   virtual PRUint64 NativeState();
+  virtual PRUint64 NativeInteractiveState() const;
 
   // Widgets
   virtual Accessible* ContainerWidget() const;
 };
 
 /**
  * Used for XUL radiogroup element.
  */
 class XULRadioGroupAccessible : public XULSelectControlAccessible
 {
 public:
   XULRadioGroupAccessible(nsIContent* aContent, DocAccessible* aDoc);
 
   // Accessible
   virtual mozilla::a11y::role NativeRole();
-  virtual PRUint64 NativeState();
+  virtual PRUint64 NativeInteractiveState() const;
 
   // Widgets
   virtual bool IsWidget() const;
   virtual bool IsActiveWidget() const;
   virtual bool AreItemsOperable() const;
 };
 
 /**
--- a/accessible/src/xul/nsXULColorPickerAccessible.cpp
+++ b/accessible/src/xul/nsXULColorPickerAccessible.cpp
@@ -47,25 +47,29 @@ nsXULColorPickerTileAccessible::NativeRo
 {
   return roles::PUSHBUTTON;
 }
 
 PRUint64
 nsXULColorPickerTileAccessible::NativeState()
 {
   PRUint64 state = AccessibleWrap::NativeState();
-  if (!(state & states::UNAVAILABLE))
-    state |= states::FOCUSABLE | states::SELECTABLE;
-
   if (mContent->HasAttr(kNameSpaceID_None, nsGkAtoms::selected))
     state |= states::SELECTED;
 
   return state;
 }
 
+PRUint64
+nsXULColorPickerTileAccessible::NativeInteractiveState() const
+{
+  return NativelyUnavailable() ?
+    states::UNAVAILABLE : states::FOCUSABLE | states::SELECTABLE;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // nsXULColorPickerTileAccessible: Widgets
 
 Accessible*
 nsXULColorPickerTileAccessible::ContainerWidget() const
 {
   Accessible* parent = Parent();
   if (parent) {
@@ -88,24 +92,18 @@ nsXULColorPickerAccessible::
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsXULColorPickerAccessible: Accessible
 
 PRUint64
 nsXULColorPickerAccessible::NativeState()
 {
-  // Possible states: focused, focusable, unavailable(disabled).
-
-  // get focus and disable status from base class
-  PRUint64 states = AccessibleWrap::NativeState();
-
-  states |= states::FOCUSABLE | states::HASPOPUP;
-
-  return states;
+  PRUint64 state = AccessibleWrap::NativeState();
+  return state | states::HASPOPUP;
 }
 
 role
 nsXULColorPickerAccessible::NativeRole()
 {
   return roles::BUTTONDROPDOWNGRID;
 }
 
--- a/accessible/src/xul/nsXULColorPickerAccessible.h
+++ b/accessible/src/xul/nsXULColorPickerAccessible.h
@@ -16,16 +16,17 @@ class nsXULColorPickerTileAccessible : p
 public:
   nsXULColorPickerTileAccessible(nsIContent* aContent,
                                  DocAccessible* aDoc);
 
   // Accessible
   virtual void Value(nsString& aValue);
   virtual mozilla::a11y::role NativeRole();
   virtual PRUint64 NativeState();
+  virtual PRUint64 NativeInteractiveState() const;
 
   // Widgets
   virtual Accessible* ContainerWidget() const;
 };
 
 
 /**
  * Used for colorpicker button (xul:colorpicker@type="button").
--- a/accessible/src/xul/nsXULComboboxAccessible.cpp
+++ b/accessible/src/xul/nsXULComboboxAccessible.cpp
@@ -45,33 +45,29 @@ nsXULComboboxAccessible::NativeState()
   // As a nsComboboxAccessible we can have the following states:
   //     STATE_FOCUSED
   //     STATE_FOCUSABLE
   //     STATE_HASPOPUP
   //     STATE_EXPANDED
   //     STATE_COLLAPSED
 
   // Get focus status from base class
-  PRUint64 states = Accessible::NativeState();
+  PRUint64 state = Accessible::NativeState();
 
   nsCOMPtr<nsIDOMXULMenuListElement> menuList(do_QueryInterface(mContent));
   if (menuList) {
-    bool isOpen;
+    bool isOpen = false;
     menuList->GetOpen(&isOpen);
-    if (isOpen) {
-      states |= states::EXPANDED;
-    }
-    else {
-      states |= states::COLLAPSED;
-    }
+    if (isOpen)
+      state |= states::EXPANDED;
+    else
+      state |= states::COLLAPSED;
   }
 
-  states |= states::HASPOPUP | states::FOCUSABLE;
-
-  return states;
+  return state | states::HASPOPUP;
 }
 
 void
 nsXULComboboxAccessible::Description(nsString& aDescription)
 {
   aDescription.Truncate();
   // Use description of currently focused option
   nsCOMPtr<nsIDOMXULMenuListElement> menuListElm(do_QueryInterface(mContent));
--- a/accessible/src/xul/nsXULListboxAccessible.cpp
+++ b/accessible/src/xul/nsXULListboxAccessible.cpp
@@ -864,17 +864,17 @@ nsXULListitemAccessible::NativeRole()
 }
 
 PRUint64
 nsXULListitemAccessible::NativeState()
 {
   if (mIsCheckbox)
     return nsXULMenuitemAccessible::NativeState();
 
-  PRUint64 states = states::FOCUSABLE | states::SELECTABLE;
+  PRUint64 states = NativeInteractiveState();
 
   nsCOMPtr<nsIDOMXULSelectControlItemElement> listItem =
     do_QueryInterface(mContent);
 
   if (listItem) {
     bool isSelected;
     listItem->GetSelected(&isSelected);
     if (isSelected)
@@ -882,16 +882,23 @@ nsXULListitemAccessible::NativeState()
 
     if (FocusMgr()->IsFocused(this))
       states |= states::FOCUSED;
   }
 
   return states;
 }
 
+PRUint64
+nsXULListitemAccessible::NativeInteractiveState() const
+{
+  return NativelyUnavailable() || mParent->NativelyUnavailable() ?
+    states::UNAVAILABLE : states::FOCUSABLE | states::SELECTABLE;
+}
+
 NS_IMETHODIMP nsXULListitemAccessible::GetActionName(PRUint8 aIndex, nsAString& aName)
 {
   if (aIndex == eAction_Click && mIsCheckbox) {
     // check or uncheck
     PRUint64 states = NativeState();
 
     if (states & states::CHECKED)
       aName.AssignLiteral("uncheck");
--- a/accessible/src/xul/nsXULListboxAccessible.h
+++ b/accessible/src/xul/nsXULListboxAccessible.h
@@ -112,16 +112,17 @@ public:
   NS_IMETHOD GetActionName(PRUint8 index, nsAString& aName);
   // Don't use XUL menuitems's description attribute
 
   // Accessible
   virtual void Description(nsString& aDesc);
   virtual nsresult GetNameInternal(nsAString& aName);
   virtual mozilla::a11y::role NativeRole();
   virtual PRUint64 NativeState();
+  virtual PRUint64 NativeInteractiveState() const;
   virtual bool CanHaveAnonChildren();
 
   // Widgets
   virtual Accessible* ContainerWidget() const;
 
 protected:
   /**
    * Return listbox accessible for the listitem.
--- a/accessible/src/xul/nsXULMenuAccessible.cpp
+++ b/accessible/src/xul/nsXULMenuAccessible.cpp
@@ -105,34 +105,38 @@ nsXULMenuitemAccessible::NativeState()
         state &= ~(states::OFFSCREEN | states::INVISIBLE);
         state |= (grandParentState & states::OFFSCREEN) |
                  (grandParentState & states::INVISIBLE) |
                  (grandParentState & states::OPAQUE1);
       } // isCollapsed
     } // isSelected
   } // ROLE_COMBOBOX_OPTION
 
-  // Set focusable and selectable for items that are available
-  // and whose metric setting does allow disabled items to be focused.
-  if (state & states::UNAVAILABLE) {
-    // Honour the LookAndFeel metric.
-    PRInt32 skipDisabledMenuItems =
-      LookAndFeel::GetInt(LookAndFeel::eIntID_SkipNavigatingDisabledMenuItem);
-    // We don't want the focusable and selectable states for combobox items,
-    // so exclude them here as well.
-    if (skipDisabledMenuItems || isComboboxOption) {
-      return state;
+  return state;
+}
+
+PRUint64
+nsXULMenuitemAccessible::NativeInteractiveState() const
+{
+  if (NativelyUnavailable()) {
+    // Note: keep in sinc with nsXULPopupManager::IsValidMenuItem() logic.
+    bool skipNavigatingDisabledMenuItem = true;
+    nsMenuFrame* menuFrame = do_QueryFrame(GetFrame());
+    if (!menuFrame->IsOnMenuBar()) {
+      skipNavigatingDisabledMenuItem = LookAndFeel::
+        GetInt(LookAndFeel::eIntID_SkipNavigatingDisabledMenuItem, 0) != 0;
     }
+
+    if (skipNavigatingDisabledMenuItem)
+      return states::UNAVAILABLE;
+
+    return states::UNAVAILABLE | states::FOCUSABLE | states::SELECTABLE;
   }
 
-  state |= (states::FOCUSABLE | states::SELECTABLE);
-  if (FocusMgr()->IsFocused(this))
-    state |= states::FOCUSED;
-
-  return state;
+  return states::FOCUSABLE | states::SELECTABLE;
 }
 
 nsresult
 nsXULMenuitemAccessible::GetNameInternal(nsAString& aName)
 {
   mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, aName);
   return NS_OK;
 }
@@ -550,27 +554,16 @@ nsXULMenupopupAccessible::ContainerWidge
 ////////////////////////////////////////////////////////////////////////////////
 
 nsXULMenubarAccessible::
   nsXULMenubarAccessible(nsIContent* aContent, DocAccessible* aDoc) :
   AccessibleWrap(aContent, aDoc)
 {
 }
 
-PRUint64
-nsXULMenubarAccessible::NativeState()
-{
-  PRUint64 state = Accessible::NativeState();
-
-  // Menu bar itself is not actually focusable
-  state &= ~states::FOCUSABLE;
-  return state;
-}
-
-
 nsresult
 nsXULMenubarAccessible::GetNameInternal(nsAString& aName)
 {
   aName.AssignLiteral("Application");
   return NS_OK;
 }
 
 role
--- a/accessible/src/xul/nsXULMenuAccessible.h
+++ b/accessible/src/xul/nsXULMenuAccessible.h
@@ -24,16 +24,17 @@ public:
   NS_IMETHOD DoAction(PRUint8 index);
   NS_IMETHOD GetActionName(PRUint8 aIndex, nsAString& aName);
 
   // Accessible
   virtual void Description(nsString& aDescription);
   virtual nsresult GetNameInternal(nsAString& aName);
   virtual mozilla::a11y::role NativeRole();
   virtual PRUint64 NativeState();
+  virtual PRUint64 NativeInteractiveState() const;
   virtual PRInt32 GetLevelInternal();
 
   virtual bool CanHaveAnonChildren();
 
   // ActionAccessible
   virtual PRUint8 ActionCount();
   virtual KeyBinding AccessKey() const;
   virtual KeyBinding KeyboardShortcut() const;
@@ -93,17 +94,16 @@ public:
 class nsXULMenubarAccessible : public AccessibleWrap
 {
 public:
   nsXULMenubarAccessible(nsIContent* aContent, DocAccessible* aDoc);
 
   // Accessible
   virtual nsresult GetNameInternal(nsAString& aName);
   virtual mozilla::a11y::role NativeRole();
-  virtual PRUint64 NativeState();
 
   // Widget
   virtual bool IsActiveWidget() const;
   virtual bool AreItemsOperable() const;
   virtual Accessible* CurrentItem();
   virtual void SetCurrentItem(Accessible* aItem);
 };
 
--- a/accessible/src/xul/nsXULSliderAccessible.cpp
+++ b/accessible/src/xul/nsXULSliderAccessible.cpp
@@ -33,32 +33,36 @@ NS_IMPL_ISUPPORTS_INHERITED1(nsXULSlider
 
 role
 nsXULSliderAccessible::NativeRole()
 {
   return roles::SLIDER;
 }
 
 PRUint64
-nsXULSliderAccessible::NativeState()
-{
-  PRUint64 state = AccessibleWrap::NativeState();
+nsXULSliderAccessible::NativeInteractiveState() const
+ {
+  if (NativelyUnavailable())
+    return states::UNAVAILABLE;
 
   nsIContent* sliderElm = GetSliderElement();
-  if (!sliderElm)
-    return state;
+  if (sliderElm) {
+    nsIFrame* frame = sliderElm->GetPrimaryFrame();
+    if (frame && frame->IsFocusable())
+      return states::FOCUSABLE;
+  }
 
-  nsIFrame *frame = sliderElm->GetPrimaryFrame();
-  if (frame && frame->IsFocusable())
-    state |= states::FOCUSABLE;
+  return 0;
+}
 
-  if (FocusMgr()->IsFocused(this))
-    state |= states::FOCUSED;
-
-  return state;
+bool
+nsXULSliderAccessible::NativelyUnavailable() const
+{
+  return mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+                               nsGkAtoms::_true, eCaseMatters);
 }
 
 // nsIAccessible
 
 void
 nsXULSliderAccessible::Value(nsString& aValue)
 {
   GetSliderAttr(nsGkAtoms::curpos, aValue);
@@ -160,17 +164,17 @@ nsXULSliderAccessible::CanHaveAnonChildr
 {
   // Do not allow anonymous xul:slider be accessible.
   return false;
 }
 
 // Utils
 
 nsIContent*
-nsXULSliderAccessible::GetSliderElement()
+nsXULSliderAccessible::GetSliderElement() const
 {
   if (!mSliderNode) {
     // XXX: we depend on anonymous content.
     mSliderNode = mContent->OwnerDoc()->
       GetAnonymousElementByAttribute(mContent, nsGkAtoms::anonid,
                                      NS_LITERAL_STRING("slider"));
   }
 
--- a/accessible/src/xul/nsXULSliderAccessible.h
+++ b/accessible/src/xul/nsXULSliderAccessible.h
@@ -26,36 +26,37 @@ public:
   NS_IMETHOD DoAction(PRUint8 aIndex);
 
   // nsIAccessibleValue
   NS_DECL_NSIACCESSIBLEVALUE
 
   // Accessible
   virtual void Value(nsString& aValue);
   virtual mozilla::a11y::role NativeRole();
-  virtual PRUint64 NativeState();
+  virtual PRUint64 NativeInteractiveState() const;
+  virtual bool NativelyUnavailable() const;
   virtual bool CanHaveAnonChildren();
 
   // ActionAccessible
   virtual PRUint8 ActionCount();
 
 protected:
   /**
    * Return anonymous slider element.
    */
-  nsIContent* GetSliderElement();
+  nsIContent* GetSliderElement() const;
 
   nsresult GetSliderAttr(nsIAtom *aName, nsAString& aValue);
   nsresult SetSliderAttr(nsIAtom *aName, const nsAString& aValue);
 
   nsresult GetSliderAttr(nsIAtom *aName, double *aValue);
   nsresult SetSliderAttr(nsIAtom *aName, double aValue);
 
 private:
-  nsCOMPtr<nsIContent> mSliderNode;
+  mutable nsCOMPtr<nsIContent> mSliderNode;
 };
 
 
 /**
  * Used for slider's thumb element.
  */
 class nsXULThumbAccessible : public AccessibleWrap
 {
--- a/accessible/src/xul/nsXULTabAccessible.cpp
+++ b/accessible/src/xul/nsXULTabAccessible.cpp
@@ -77,40 +77,33 @@ nsXULTabAccessible::NativeRole()
 PRUint64
 nsXULTabAccessible::NativeState()
 {
   // Possible states: focused, focusable, unavailable(disabled), offscreen.
 
   // get focus and disable status from base class
   PRUint64 state = AccessibleWrap::NativeState();
 
-  // In the past, tabs have been focusable in classic theme
-  // They may be again in the future
-  // Check style for -moz-user-focus: normal to see if it's focusable
-  state &= ~states::FOCUSABLE;
-
-  nsIFrame *frame = mContent->GetPrimaryFrame();
-  if (frame) {
-    const nsStyleUserInterface* ui = frame->GetStyleUserInterface();
-    if (ui->mUserFocus == NS_STYLE_USER_FOCUS_NORMAL)
-      state |= states::FOCUSABLE;
-  }
-
   // Check whether the tab is selected
-  state |= states::SELECTABLE;
-  state &= ~states::SELECTED;
   nsCOMPtr<nsIDOMXULSelectControlItemElement> tab(do_QueryInterface(mContent));
   if (tab) {
     bool selected = false;
     if (NS_SUCCEEDED(tab->GetSelected(&selected)) && selected)
       state |= states::SELECTED;
   }
   return state;
 }
 
+PRUint64
+nsXULTabAccessible::NativeInteractiveState() const
+{
+  PRUint64 state = Accessible::NativeInteractiveState();
+  return (state & states::UNAVAILABLE) ? state : state | states::SELECTABLE;
+}
+
 // nsIAccessible
 Relation
 nsXULTabAccessible::RelationByType(PRUint32 aType)
 {
   Relation rel = AccessibleWrap::RelationByType(aType);
   if (aType != nsIAccessibleRelation::RELATION_LABEL_FOR)
     return rel;
 
--- a/accessible/src/xul/nsXULTabAccessible.h
+++ b/accessible/src/xul/nsXULTabAccessible.h
@@ -23,16 +23,17 @@ public:
 
   // nsIAccessible
   NS_IMETHOD GetActionName(PRUint8 aIndex, nsAString& aName);
   NS_IMETHOD DoAction(PRUint8 index);
 
   // Accessible
   virtual mozilla::a11y::role NativeRole();
   virtual PRUint64 NativeState();
+  virtual PRUint64 NativeInteractiveState() const;
   virtual Relation RelationByType(PRUint32 aType);
 
   // ActionAccessible
   virtual PRUint8 ActionCount();
 };
 
 
 /**
--- a/accessible/src/xul/nsXULTextAccessible.cpp
+++ b/accessible/src/xul/nsXULTextAccessible.cpp
@@ -82,21 +82,17 @@ nsXULTooltipAccessible::
   nsXULTooltipAccessible(nsIContent* aContent, DocAccessible* aDoc) :
   nsLeafAccessible(aContent, aDoc)
 {
 }
 
 PRUint64
 nsXULTooltipAccessible::NativeState()
 {
-  PRUint64 states = nsLeafAccessible::NativeState();
-
-  states &= ~states::FOCUSABLE;
-  states |= states::READONLY;
-  return states;
+  return nsLeafAccessible::NativeState() | states::READONLY;
 }
 
 role
 nsXULTooltipAccessible::NativeRole()
 {
   return roles::TOOLTIP;
 }
 
--- a/accessible/src/xul/nsXULTreeAccessible.cpp
+++ b/accessible/src/xul/nsXULTreeAccessible.cpp
@@ -943,17 +943,17 @@ nsXULTreeItemAccessibleBase::GroupPositi
 
 PRUint64
 nsXULTreeItemAccessibleBase::NativeState()
 {
   if (!mTreeView)
     return states::DEFUNCT;
 
   // focusable and selectable states
-  PRUint64 state = states::FOCUSABLE | states::SELECTABLE;
+  PRUint64 state = NativeInteractiveState();
 
   // expanded/collapsed state
   if (IsExpandable()) {
     bool isContainerOpen;
     mTreeView->IsContainerOpen(mRow, &isContainerOpen);
     state |= isContainerOpen ? states::EXPANDED : states::COLLAPSED;
   }
 
@@ -976,16 +976,22 @@ nsXULTreeItemAccessibleBase::NativeState
   mTree->GetFirstVisibleRow(&firstVisibleRow);
   mTree->GetLastVisibleRow(&lastVisibleRow);
   if (mRow < firstVisibleRow || mRow > lastVisibleRow)
     state |= states::INVISIBLE;
 
   return state;
 }
 
+PRUint64
+nsXULTreeItemAccessibleBase::NativeInteractiveState() const
+{
+  return states::FOCUSABLE | states::SELECTABLE;
+}
+
 PRInt32
 nsXULTreeItemAccessibleBase::IndexInParent() const
 {
   return mParent ? mParent->ContentChildCount() + mRow : -1;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsXULTreeItemAccessibleBase: Widgets
--- a/accessible/src/xul/nsXULTreeAccessible.h
+++ b/accessible/src/xul/nsXULTreeAccessible.h
@@ -151,16 +151,17 @@ public:
 
   // nsAccessNode
   virtual void Shutdown();
   virtual bool IsPrimaryForNode() const;
 
   // Accessible
   virtual mozilla::a11y::GroupPos GroupPosition();
   virtual PRUint64 NativeState();
+  virtual PRUint64 NativeInteractiveState() const;
   virtual PRInt32 IndexInParent() const;
   virtual Relation RelationByType(PRUint32 aType);
   virtual Accessible* FocusedChild();
 
   // ActionAccessible
   virtual PRUint8 ActionCount();
 
   // Widgets
--- a/accessible/src/xul/nsXULTreeGridAccessible.cpp
+++ b/accessible/src/xul/nsXULTreeGridAccessible.cpp
@@ -1098,17 +1098,17 @@ nsXULTreeGridCellAccessible::NativeRole(
 
 PRUint64
 nsXULTreeGridCellAccessible::NativeState()
 {
   if (!mTreeView)
     return states::DEFUNCT;
 
   // selectable/selected state
-  PRUint64 states = states::SELECTABLE;
+  PRUint64 states = states::SELECTABLE; // keep in sync with NativeInteractiveState
 
   nsCOMPtr<nsITreeSelection> selection;
   mTreeView->GetSelection(getter_AddRefs(selection));
   if (selection) {
     bool isSelected = false;
     selection->IsSelected(mRow, &isSelected);
     if (isSelected)
       states |= states::SELECTED;
@@ -1123,16 +1123,22 @@ nsXULTreeGridCellAccessible::NativeState
     mTreeView->GetCellValue(mRow, mColumn, checked);
     if (checked.EqualsIgnoreCase("true"))
       states |= states::CHECKED;
   }
 
   return states;
 }
 
+PRUint64
+nsXULTreeGridCellAccessible::NativeInteractiveState() const
+{
+  return states::SELECTABLE;
+}
+
 PRInt32
 nsXULTreeGridCellAccessible::IndexInParent() const
 {
   return GetColumnIndex();
 }
 
 Relation
 nsXULTreeGridCellAccessible::RelationByType(PRUint32 aType)
--- a/accessible/src/xul/nsXULTreeGridAccessible.h
+++ b/accessible/src/xul/nsXULTreeGridAccessible.h
@@ -137,16 +137,17 @@ public:
   // Accessible
   virtual mozilla::a11y::ENameValueFlag Name(nsString& aName);
   virtual Accessible* FocusedChild();
   virtual nsresult GetAttributesInternal(nsIPersistentProperties *aAttributes);
   virtual PRInt32 IndexInParent() const;
   virtual Relation RelationByType(PRUint32 aType);
   virtual mozilla::a11y::role NativeRole();
   virtual PRUint64 NativeState();
+  virtual PRUint64 NativeInteractiveState() const;
 
   // ActionAccessible
   virtual PRUint8 ActionCount();
 
   // nsXULTreeGridCellAccessible
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_XULTREEGRIDCELLACCESSIBLE_IMPL_CID)
 
   /**
--- a/accessible/tests/mochitest/events/test_docload.xul
+++ b/accessible/tests/mochitest/events/test_docload.xul
@@ -94,16 +94,20 @@
     /**
      * Load the document having sub document. No document loading events for
      * nested document.
      */
     function loadNestedDocURIInvoker(aNestedDocURI)
     {
       this.__proto__ = new loadURIInvoker(aNestedDocURI);
 
+      // Remove reorder event checker since the event is likely coalesced by
+      // reorder event on Firefox UI (refer to bug 759670 for details).
+      this.eventSeq.shift();
+
       this.unexpectedEventSeq = [
         new invokerChecker(EVENT_DOCUMENT_LOAD_COMPLETE, getNestedDoc),
         new invokerChecker(EVENT_STATE_CHANGE, getNestedDoc)
       ];
 
       function getNestedDoc()
       {
         return currentTabDocument().getElementsByTagName("iframe")[0].firstChild;
@@ -178,40 +182,38 @@
       {
         return "load error page: '" + aURLDescr + "'";
       }
     }
 
     ////////////////////////////////////////////////////////////////////////////
     // Tests
 
-    gA11yEventDumpToConsole = true; // debug
-    gA11yEventDumpFeature = "parentchain:reorder";
+    //gA11yEventDumpToConsole = true; // debug
+    //gA11yEventDumpFeature = "parentchain:reorder";
 
     var gQueue = null;
     function doTests()
     {
-      enableLogging("events");
-
       gQueue = new eventQueue();
 
       var dataURL =
         "data:text/html,<html><body><iframe src='http://example.com'></iframe></body></html>";
       gQueue.push(new loadNestedDocURIInvoker(dataURL));
 
       gQueue.push(new loadURIInvoker("about:"));
       gQueue.push(new userReloadInvoker());
       gQueue.push(new loadURIInvoker("about:mozilla"));
       gQueue.push(new reloadInvoker());
       gQueue.push(new loadErrorPageInvoker("www.wronguri.wronguri",
                                            "Server not found"));
       gQueue.push(new loadErrorPageInvoker("https://nocert.example.com:443",
                                           "Untrusted Connection"));
 
-      gQueue.onFinish = function() { closeBrowserWindow(); disableLogging(); }
+      gQueue.onFinish = function() { closeBrowserWindow(); }
       gQueue.invoke();
     }
 
     SimpleTest.waitForExplicitFinish();
     openBrowserWindow(doTests);
   ]]>
   </script>
 
--- a/accessible/tests/mochitest/states/test_controls.xul
+++ b/accessible/tests/mochitest/states/test_controls.xul
@@ -10,66 +10,173 @@
           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="../events.js" />
 
   <script type="application/javascript">
   <![CDATA[
+    function openColorpicker(aID)
+    {
+      this.popupNode = getNode(aID).mPicker.parentNode;
+      this.popup = getAccessible(this.popupNode);
 
+      this.eventSeq = [
+        new invokerChecker(EVENT_REORDER, this.popupNode)
+      ];
+
+      this.invoke = function openColorpicker_invoke()
+      {
+        getNode(aID).showPopup();
+      }
+
+      this.finalCheck = function openColorpicker_finalCheck()
+      {
+        testStates(this.popup.firstChild,
+                   STATE_FOCUSABLE | STATE_SELECTABLE, 0,
+                   STATE_UNAVAILABLE);
+      }
+
+      this.getID = function openColorpicker_getID()
+      {
+        return "open colorpicker";
+      }
+    }
+
+    var gQueue = null;
     function doTest()
     {
       testStates("checkbox", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE);
       testStates("checkbox2", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE);
-      testStates("radio-group", 0, 0, STATE_UNAVAILABLE);
-      testStates("orange", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE);
-      testStates("violet", 0, 0, STATE_UNAVAILABLE);
-      testStates("radio-group2", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE);
-      testStates("orange2", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE);
+      testStates("radiogroup", 0, 0, STATE_FOCUSABLE | STATE_UNAVAILABLE);
+      testStates("radio", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE);
+      testStates("radio-disabled", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE);
+      testStates("radiogroup-disabled", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE);
+      testStates("radio-disabledradiogroup", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE);
+      testStates("button", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE);
+      testStates("button-disabled", STATE_UNAVAILABLE, 0 , STATE_FOCUSABLE);
+      testStates("colorpicker", STATE_FOCUSABLE | STATE_HASPOPUP, 0, STATE_UNAVAILABLE);
+      testStates("colorpicker-disabled", STATE_HASPOPUP, 0, STATE_FOCUSABLE);
+      testStates("combobox", STATE_FOCUSABLE | STATE_HASPOPUP, 0, STATE_UNAVAILABLE);
+      testStates("combobox-disabled", STATE_UNAVAILABLE | STATE_HASPOPUP, 0, STATE_FOCUSABLE);
+      testStates("listbox", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE);
+      testStates("listitem", STATE_FOCUSABLE | STATE_SELECTABLE, 0, STATE_UNAVAILABLE);
+      testStates("listbox-disabled", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE | STATE_SELECTABLE);
+      testStates("listitem-disabledlistbox", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE | STATE_SELECTABLE);
+      testStates("menubar", 0, 0, STATE_FOCUSABLE);
+      testStates("menu", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE);
+      testStates("menu-disabled", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE | STATE_SELECTABLE);
+      testStates("scale", STATE_FOCUSABLE, 0, STATE_UNAVAILABLE);
+      testStates("scale-disabled", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE);
+      testStates("tab", STATE_FOCUSABLE | STATE_SELECTABLE | STATE_SELECTED, 0, STATE_UNAVAILABLE);
+      testStates("tab-disabled", STATE_UNAVAILABLE, 0, STATE_FOCUSABLE | STATE_SELECTABLE | STATE_SELECTED);
 
-      SimpleTest.finish()
+      gQueue = new eventQueue();
+      gQueue.push(new openColorpicker("colorpicker"));
+      gQueue.invoke(); // Will call SimpleTest.finish()
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   ]]>
   </script>
 
   <hbox flex="1" style="overflow: auto;">
     <body xmlns="http://www.w3.org/1999/xhtml">
      <a target="_blank"
          href="https://bugzilla.mozilla.org/show_bug.cgi?id=599163"
          title="check disabled state instead of attribute">
         Mozilla Bug 599163
-     </a><br/>
+     </a>
+     <a target="_blank"
+         href="https://bugzilla.mozilla.org/show_bug.cgi?id=756983"
+         title="Isolate focusable and unavailable states from State()">
+        Mozilla Bug 756983
+     </a>
       <p id="display"></p>
       <div id="content" style="display: none">
       </div>
       <pre id="test">
       </pre>
     </body>
 
     <vbox flex="1">
 
     <checkbox id="checkbox" checked="true" label="Steak"/>
     <checkbox id="checkbox2" checked="true" label="Salad" disabled="true"/>
 
-    <radiogroup id="radio-group">
-      <radio id="orange" label="Orange" disabled="true"/>
-      <radio id="violet" selected="true" label="Violet"/>
-      <radio id="yellow" label="Yellow"/>
+    <radiogroup id="radiogroup">
+      <radio id="radio" label="Orange"/>
+      <radio id="radio-disabled" selected="true" label="Violet" disabled="true"/>
+    </radiogroup>
+
+    <radiogroup id="radiogroup-disabled" disabled="true">
+      <radio id="radio-disabledradiogroup" label="Orange"/>
+      <radio id="violet2" selected="true" label="Violet"/>
     </radiogroup>
 
-    <radiogroup id="radio-group2" disabled="true">
-      <radio id="orange2" label="Orange"/>
-      <radio id="violet2" selected="true" label="Violet"/>
-      <radio id="yellow2" label="Yellow"/>
-    </radiogroup>
+    <button id="button" value="button"/>
+    <button id="button-disabled" disabled="true" value="button"/>
+
+    <colorpicker id="colorpicker" type="button"/>
+    <colorpicker id="colorpicker-disabled" type="button" disabled="true"/>
+
+    <menulist id="combobox">
+      <menupopup>
+        <menuitem label="item1"/>
+      </menupopup>
+    </menulist>
+
+    <menulist id="combobox-disabled" disabled="true">
+      <menupopup>
+        <menuitem label="item1"/>
+      </menupopup>
+    </menulist>
+
+    <listbox id="listbox">
+      <listitem id="listitem" label="list item"/>
+    </listbox>
+
+    <listbox id="listbox-disabled" disabled="true">
+      <listitem id="listitem-disabledlistbox" label="list item"/>
+    </listbox>
 
+    <toolbox>
+      <menubar id="menubar">
+        <menu id="menu" label="menu1">
+          <menupopup>
+            <menuitem id="menu1-item1" label="menuitem1.1"/>
+          </menupopup>
+        </menu>
+        <menu id="menu-disabled" label="menu2" disabled="true">
+          <menupopup>
+            <menuitem id="menu-disabled-item1" label="menuitem2.1"/>
+          </menupopup>
+        </menu>
+      </menubar>
+    </toolbox>
+
+    <scale id="scale" min="1" max="10"/>
+    <scale id="scale-disabled" min="1" max="10" disabled="true"/>
+
+    <tabbox>
+      <tabs>
+        <tab id="tab" label="tab1" tooltip="tooltip"/>
+        <tab id="tab-disabled" label="tab1" disabled="true"/>
+      </tabs>
+      <tabpanels>
+        <tabpanel/>
+        <tabpanel/>
+      </tabpanels>
+    </tabbox>
+
+    <tooltip id="tooltip"><description>tooltip</description></tooltip>
     </vbox>
   </hbox>
 
 </window>
 
--- a/accessible/tests/mochitest/states/test_selects.html
+++ b/accessible/tests/mochitest/states/test_selects.html
@@ -69,31 +69,63 @@
       testStates(opt1, STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE,
                  EXT_STATE_ACTIVE, STATE_FOCUSED, 0);
 
       var opt2 = comboboxList.lastChild;
       testStates(opt2, STATE_SELECTABLE | STATE_FOCUSABLE, 0, STATE_SELECTED, 0,
                  STATE_FOCUSED, EXT_STATE_ACTIVE);
 
       // listbox
-      var listbox = getAccessible("listbox");
-      testStates(listbox, STATE_FOCUSABLE, 0,
+      testStates("listbox",
+                 STATE_FOCUSABLE, 0,
                  STATE_HASPOPUP | STATE_COLLAPSED | STATE_FOCUSED);
 
-      testStates(listbox.firstChild, STATE_SELECTABLE, EXT_STATE_ACTIVE,
-                 STATE_SELECTED | STATE_FOCUSED | STATE_FOCUSED);
+      testStates("listitem-active",
+                 STATE_FOCUSABLE | STATE_SELECTABLE, EXT_STATE_ACTIVE,
+                 STATE_SELECTED | STATE_FOCUSED);
+
+      testStates("listitem",
+                 STATE_FOCUSABLE | STATE_SELECTABLE, 0,
+                 STATE_SELECTED | STATE_FOCUSED, EXT_STATE_ACTIVE);
+
+      testStates("listitem-disabled",
+                 STATE_UNAVAILABLE, 0,
+                 STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE,
+                 EXT_STATE_ACTIVE);
+
+      testStates("listgroup",
+                 0, 0,
+                 STATE_UNAVAILABLE | STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE,
+                 EXT_STATE_ACTIVE);
 
-      testStates(listbox.lastChild, STATE_SELECTABLE, 0,
-                 STATE_SELECTED | STATE_FOCUSED | STATE_FOCUSED,
-                 0, 0, EXT_STATE_ACTIVE);
+      testStates("listgroup-disabled",
+                 STATE_UNAVAILABLE, 0,
+                 STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE,
+                 EXT_STATE_ACTIVE);
+
+      todo(false, "no unavailable state on option in disabled group (bug 759666)");
+//      testStates("listitem-disabledgroup",
+//                 STATE_UNAVAILABLE, 0,
+//                 STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE,
+//                 EXT_STATE_ACTIVE);
+
+      testStates("listbox-disabled",
+                 STATE_UNAVAILABLE, 0,
+                 STATE_FOCUSABLE);
+
+      todo(false, "no unavailable state on option in disabled select (bug 759666)");
+//      testStates("listitem-disabledlistbox",
+//                 STATE_UNAVAILABLE, 0,
+//                 STATE_SELECTABLE | STATE_SELECTED | STATE_FOCUSABLE,
+//                 EXT_STATE_ACTIVE);
 
       // open combobox
       gQueue = new eventQueue();
       gQueue.push(new openComboboxNCheckStates("combobox"));
-      gQueue.invoke(); // Will call SimpleTest.finish();
+      gQueue.invoke(); // Will call */SimpleTest.finish();
     }
 
     SimpleTest.waitForExplicitFinish();
     addA11yLoadEvent(doTest);
   </script>
 
 </head>
 
@@ -108,27 +140,41 @@
      title="mochitest for selects and lists">
     Mozilla Bug 640716
   </a>
   <a target="_blank"
      href="https://bugzilla.mozilla.org/show_bug.cgi?id=689847"
      title="Expose active state on current item of selectable widgets">
     Mozilla Bug 689847
   </a>
+  <a target="_blank"
+     href="https://bugzilla.mozilla.org/show_bug.cgi?id=756983"
+     title="Isolate focusable and unavailable states from State()">
+    Mozilla Bug 756983
+  </a>
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
   <select id="combobox">
     <option>item 1</option>
     <option>item 2</option>
   </select>
 
   <select id="listbox" name="component" size="3">
-    <option>Build</option>
-    <option>Disability Access APIs</option>
-    <option>General</option>
-    <option>UI</option>
+    <option id="listitem-active">Build</option>
+    <option id="listitem">Disability Access APIs</option>
+    <option id="listitem-disabled" disabled>General</option>
+    <optgroup id="listgroup" label="group">
+      <option>option</option>
+    </optgroup>
+    <optgroup id="listgroup-disabled" disabled label="group2">
+      <option id="listitem-disabledgroup">UI</option>
+    </optgroup>
+  </select>
+
+  <select id="listbox-disabled" size="3" disabled>
+    <option id="listitem-disabledlistbox">option</option>
   </select>
 
 </body>
 </html>
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -646,15 +646,23 @@ SettingsListener.observe('language.curre
 
 (function RILSettingsToPrefs() {
   ['ril.data.enabled', 'ril.data.roaming.enabled'].forEach(function(key) {
     SettingsListener.observe(key, false, function(value) {
       Services.prefs.setBoolPref(key, value);
     });
   });
 
-  ['ril.data.apn', 'ril.data.user', 'ril.data.passwd'].forEach(function(key) {
+  let strPrefs = ['ril.data.apn', 'ril.data.user', 'ril.data.passwd',
+                  'ril.data.mmsc', 'ril.data.mmsproxy'];
+  strPrefs.forEach(function(key) {
     SettingsListener.observe(key, false, function(value) {
       Services.prefs.setCharPref(key, value);
     });
   });
+
+  ['ril.data.mmsport'].forEach(function(key) {
+    SettingsListener.observe(key, false, function(value) {
+      Services.prefs.setIntPref(key, value);
+    });
+  });
 })();
 
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -169,16 +169,19 @@
 @BINPATH@/components/dom_geolocation.xpt
 @BINPATH@/components/dom_media.xpt
 @BINPATH@/components/dom_network.xpt
 @BINPATH@/components/dom_notification.xpt
 @BINPATH@/components/dom_html.xpt
 @BINPATH@/components/dom_indexeddb.xpt
 @BINPATH@/components/dom_offline.xpt
 @BINPATH@/components/dom_json.xpt
+#ifdef MOZ_B2G_RIL
+@BINPATH@/components/dom_mms.xpt
+#endif
 @BINPATH@/components/dom_power.xpt
 @BINPATH@/components/dom_range.xpt
 @BINPATH@/components/dom_settings.xpt
 @BINPATH@/components/dom_sidebar.xpt
 @BINPATH@/components/dom_sms.xpt
 @BINPATH@/components/dom_storage.xpt
 @BINPATH@/components/dom_stylesheets.xpt
 @BINPATH@/components/dom_threads.xpt
@@ -420,16 +423,18 @@
 @BINPATH@/components/SettingsService.manifest
 @BINPATH@/components/nsFilePicker.js
 @BINPATH@/components/nsFilePicker.manifest
 #ifdef MOZ_B2G_RIL
 @BINPATH@/components/NetworkManager.manifest
 @BINPATH@/components/NetworkManager.js
 @BINPATH@/components/RadioInterfaceLayer.manifest
 @BINPATH@/components/RadioInterfaceLayer.js
+@BINPATH@/components/MmsService.manifest
+@BINPATH@/components/MmsService.js
 @BINPATH@/components/RILContentHelper.js
 @BINPATH@/components/SmsDatabaseService.manifest
 @BINPATH@/components/SmsDatabaseService.js
 @BINPATH@/components/WifiWorker.js
 @BINPATH@/components/WifiWorker.manifest
 @BINPATH@/components/DOMWifiManager.js
 @BINPATH@/components/DOMWifiManager.manifest
 #endif
--- a/browser/confvars.sh
+++ b/browser/confvars.sh
@@ -10,20 +10,16 @@ MOZ_PHOENIX=1
 
 if test "$OS_ARCH" = "WINNT"; then
   if ! test "$HAVE_64BIT_OS"; then
     MOZ_VERIFY_MAR_SIGNATURE=1
     MOZ_MAINTENANCE_SERVICE=1
   fi
 fi
 
-if (test "$OS_TARGET" = "WINNT" -o "$OS_TARGET" = "Darwin"); then
-  MOZ_WEBAPP_RUNTIME=1
-fi
-
 MOZ_CHROME_FILE_FORMAT=omni
 MOZ_SAFE_BROWSING=1
 MOZ_SERVICES_SYNC=1
 MOZ_APP_VERSION=$FIREFOX_VERSION
 MOZ_EXTENSIONS_DEFAULT=" gnomevfs"
 # MOZ_APP_DISPLAYNAME will be set by branding/configure.sh
 # Changing MOZ_*BRANDING_DIRECTORY requires a clobber to ensure correct results,
 # because branding dependencies are broken.
@@ -39,8 +35,9 @@ MOZ_APP_ID={ec8030f7-c20a-464f-9b0e-13a3
 # If more than one ID is needed, then you should use a comma separated list
 # of values.
 ACCEPTED_MAR_CHANNEL_IDS=firefox-mozilla-central
 # The MAR_CHANNEL_ID must not contain the following 3 characters: ",\t "
 MAR_CHANNEL_ID=firefox-mozilla-central
 MOZ_PROFILE_MIGRATOR=1
 MOZ_EXTENSION_MANAGER=1
 MOZ_APP_STATIC_INI=1
+MOZ_WEBAPP_RUNTIME=1
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -174,16 +174,19 @@
 @BINPATH@/components/dom_geolocation.xpt
 @BINPATH@/components/dom_media.xpt
 @BINPATH@/components/dom_network.xpt
 @BINPATH@/components/dom_notification.xpt
 @BINPATH@/components/dom_html.xpt
 @BINPATH@/components/dom_indexeddb.xpt
 @BINPATH@/components/dom_offline.xpt
 @BINPATH@/components/dom_json.xpt
+#ifdef MOZ_B2G_RIL
+@BINPATH@/components/dom_mms.xpt
+#endif
 @BINPATH@/components/dom_power.xpt
 @BINPATH@/components/dom_range.xpt
 @BINPATH@/components/dom_settings.xpt
 @BINPATH@/components/dom_sidebar.xpt
 @BINPATH@/components/dom_sms.xpt
 @BINPATH@/components/dom_storage.xpt
 @BINPATH@/components/dom_stylesheets.xpt
 @BINPATH@/components/dom_traversal.xpt
@@ -403,16 +406,18 @@
 @BINPATH@/components/nsInputListAutoComplete.js
 @BINPATH@/components/contentSecurityPolicy.manifest
 @BINPATH@/components/contentSecurityPolicy.js
 @BINPATH@/components/contentAreaDropListener.manifest
 @BINPATH@/components/contentAreaDropListener.js
 #ifdef MOZ_B2G_RIL
 @BINPATH@/components/RadioInterfaceLayer.manifest
 @BINPATH@/components/RadioInterfaceLayer.js
+@BINPATH@/components/MmsService.manifest
+@BINPATH@/components/MmsService.js
 @BINPATH@/components/RILContentHelper.js
 @BINPATH@/components/SmsDatabaseService.manifest
 @BINPATH@/components/SmsDatabaseService.js
 @BINPATH@/components/WifiWorker.js
 @BINPATH@/components/WifiWorker.manifest
 @BINPATH@/components/DOMWifiManager.js
 @BINPATH@/components/DOMWifiManager.manifest
 #endif
--- a/browser/modules/WebappsInstaller.jsm
+++ b/browser/modules/WebappsInstaller.jsm
@@ -27,34 +27,36 @@ let WebappsInstaller = {
         return true;
       }
     } catch (ex) {}
 
 #ifdef XP_WIN
     let shell = new WinNativeApp(aData);
 #elifdef XP_MACOSX
     let shell = new MacNativeApp(aData);
+#elifdef XP_UNIX
+    let shell = new LinuxNativeApp(aData);
 #else
     return false;
 #endif
 
     try {
       shell.install();
     } catch (ex) {
       Cu.reportError("Error installing app: " + ex);
       return false;
     }
 
     return true;
   }
 }
 
 /**
- * This function implements the common constructor for both
- * the Windows and Mac native app shells. It reads and parses
+ * This function implements the common constructor for
+ * the Windows, Mac and Linux native app shells. It reads and parses
  * the data from the app manifest and stores it in the NativeApp
  * object. It's meant to be called as NativeApp.call(this, aData)
  * from the platform-specific constructor.
  *
  * @param aData the data object provided by the web app with
  *              all the app settings and specifications.
  *
  */
@@ -108,16 +110,18 @@ function NativeApp(aData) {
   this.manifest = app.manifest;
 
   this.profileFolder = Services.dirsvc.get("ProfD", Ci.nsIFile);
 
   this.webappJson = {
     "registryDir": this.profileFolder.path,
     "app": app
   };
+
+  this.processFolder = Services.dirsvc.get("CurProcD", Ci.nsIFile);
 }
 
 #ifdef XP_WIN
 /*************************************
  * Windows app installer
  *
  * The Windows installation process will generate the following files:
  *
@@ -183,48 +187,49 @@ WinNativeApp.prototype = {
 
     this.appNameAsFilename = this.appNameAsFilename.replace(filenameRE, "");
     if (this.appNameAsFilename == "") {
       this.appNameAsFilename = "webapp";
     }
 
     // The ${InstallDir} format is as follows:
     //  host of the app origin + ";" +
-    //  protocol + ";" +
-    //  port (-1 for default port)
+    //  protocol
+    //  + ";" + port (only if port is not default)
     this.installDir = Services.dirsvc.get("AppData", Ci.nsIFile);
-    this.installDir.append(this.launchURI.host + ";" + 
-                           this.launchURI.scheme + ";" +
-                           this.launchURI.port);
+    let installDirLeaf = this.launchURI.scheme
+                       + ";"
+                       + this.launchURI.host;
+    if (this.launchURI.port != -1) {
+      installDirLeaf += ";" + this.launchURI.port;
+    }
+    this.installDir.append(installDirLeaf);
 
     this.uninstallDir = this.installDir.clone();
     this.uninstallDir.append("uninstall");
 
     this.uninstallerFile = this.uninstallDir.clone();
     this.uninstallerFile.append("webapp-uninstaller.exe");
 
     this.iconFile = this.installDir.clone();
     this.iconFile.append("chrome");
     this.iconFile.append("icons");
     this.iconFile.append("default");
     this.iconFile.append("default.ico");
 
-    this.processFolder = Services.dirsvc.get("CurProcD", Ci.nsIFile);
-
     this.desktopShortcut = Services.dirsvc.get("Desk", Ci.nsILocalFile);
     this.desktopShortcut.append(this.appNameAsFilename + ".lnk");
     this.desktopShortcut.followLinks = false;
 
     this.startMenuShortcut = Services.dirsvc.get("Progs", Ci.nsILocalFile);
     this.startMenuShortcut.append(this.appNameAsFilename + ".lnk");
     this.startMenuShortcut.followLinks = false;
 
     this.uninstallSubkeyStr = this.launchURI.scheme + "://" +
-                              this.launchURI.host + ":" +
-                              this.launchURI.port;
+                              this.launchURI.hostPort;
   },
 
   /**
    * Remove the current installation
    */
   _removeInstallation : function() {
     let uninstallKey;
     try {
@@ -477,18 +482,16 @@ MacNativeApp.prototype = {
     this.macOSDir = this.contentsDir.clone();
     this.macOSDir.append("MacOS");
 
     this.resourcesDir = this.contentsDir.clone();
     this.resourcesDir.append("Resources");
 
     this.iconFile = this.resourcesDir.clone();
     this.iconFile.append("appicon.icns");
-
-    this.processFolder = Services.dirsvc.get("CurProcD", Ci.nsIFile);
   },
 
   install: function() {
     this._removeInstallation(true);
     try {
       this._createDirectoryStructure();
       this._copyPrebuiltFiles();
       this._createConfigFiles();
@@ -635,16 +638,164 @@ MacNativeApp.prototype = {
       throw(e);
     } finally {
       aCallback.call(this);
     }
   }
 
 }
 
+#elifdef XP_UNIX
+
+function LinuxNativeApp(aData) {
+  NativeApp.call(this, aData);
+  this._init();
+}
+
+LinuxNativeApp.prototype = {
+  _init: function() {
+    // The ${InstallDir} and desktop entry filename format is as follows:
+    // host of the app origin + ";" +
+    // protocol
+    // + ";" + port (only if port is not default)
+
+    this.uniqueName = this.launchURI.scheme + ";" + this.launchURI.host;
+    if (this.launchURI.port != -1)
+      this.uniqueName += ";" + this.launchURI.port;
+
+    this.installDir = Services.dirsvc.get("Home", Ci.nsIFile);
+    this.installDir.append("." + this.uniqueName);
+
+    this.iconFile = this.installDir.clone();
+    this.iconFile.append(this.uniqueName + ".png");
+
+    this.webapprt = this.installDir.clone();
+    this.webapprt.append("webapprt-stub");
+
+    let env = Cc["@mozilla.org/process/environment;1"]
+                .getService(Ci.nsIEnvironment);
+    let xdg_data_home_env = env.get("XDG_DATA_HOME");
+    if (xdg_data_home_env != "") {
+      this.desktopINI = Cc["@mozilla.org/file/local;1"]
+                          .createInstance(Ci.nsILocalFile);
+      this.desktopINI.initWithPath(xdg_data_home_env);
+    }
+    else {
+      this.desktopINI = Services.dirsvc.get("Home", Ci.nsIFile);
+      this.desktopINI.append(".local");
+      this.desktopINI.append("share");
+    }
+
+    this.desktopINI.append("applications");
+    this.desktopINI.append("owa-" + this.uniqueName + ".desktop");
+  },
+
+  install: function() {
+    this._removeInstallation();
+
+    try {
+      this._createDirectoryStructure();
+      this._copyPrebuiltFiles();
+      this._createConfigFiles();
+    } catch (ex) {
+      this._removeInstallation();
+      throw(ex);
+    }
+
+    getIconForApp(this, function() {});
+  },
+
+  _removeInstallation: function() {
+    try {
+      if (this.installDir.exists())
+        this.installDir.remove(true);
+
+      if (this.desktopINI.exists())
+        this.desktopINI.remove(false);
+    } catch(ex) {
+    }
+  },
+
+  _createDirectoryStructure: function() {
+    this.installDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755);
+  },
+
+  _copyPrebuiltFiles: function() {
+    let webapprtPre = this.processFolder.clone();
+    webapprtPre.append(this.webapprt.leafName);
+    webapprtPre.copyTo(this.installDir, this.webapprt.leafName);
+  },
+
+  _createConfigFiles: function() {
+    // ${InstallDir}/webapp.json
+    let configJson = this.installDir.clone();
+    configJson.append("webapp.json");
+    writeToFile(configJson, JSON.stringify(this.webappJson), function() {});
+
+    // ${InstallDir}/webapp.ini
+    let webappINI = this.installDir.clone();
+    webappINI.append("webapp.ini");
+
+    let factory = Cc["@mozilla.org/xpcom/ini-processor-factory;1"]
+                    .getService(Ci.nsIINIParserFactory);
+
+    let writer = factory.createINIParser(webappINI).QueryInterface(Ci.nsIINIParserWriter);
+    writer.setString("Webapp", "Name", this.appName);
+    writer.setString("Webapp", "Profile", this.uniqueName);
+    writer.setString("WebappRT", "InstallDir", this.processFolder.path);
+    writer.writeFile();
+
+    // $XDG_DATA_HOME/applications/owa-<webappuniquename>.desktop
+    this.desktopINI.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0755);
+
+    writer = factory.createINIParser(this.desktopINI).QueryInterface(Ci.nsIINIParserWriter);
+    writer.setString("Desktop Entry", "Name", this.appName);
+    writer.setString("Desktop Entry", "Comment", this.shortDescription);
+    writer.setString("Desktop Entry", "Exec", '"'+this.webapprt.path+'"');
+    writer.setString("Desktop Entry", "Icon", this.iconFile.path);
+    writer.setString("Desktop Entry", "Type", "Application");
+    writer.setString("Desktop Entry", "Terminal", "false");
+    writer.writeFile();
+  },
+
+  /**
+   * This variable specifies if the icon retrieval process should
+   * use a temporary file in the system or a binary stream. This
+   * is accessed by a common function in WebappsIconHelpers.js and
+   * is different for each platform.
+   */
+  useTmpForIcon: false,
+
+  /**
+   * Process the icon from the imageStream as retrieved from
+   * the URL by getIconForApp().
+   *
+   * @param aMimeType     ahe icon mimetype
+   * @param aImageStream  the stream for the image data
+   * @param aCallback     a callback function to be called
+   *                      after the process finishes
+   */
+  processIcon: function(aMimeType, aImageStream, aCallback) {
+    let iconStream;
+    try {
+      let imgTools = Cc["@mozilla.org/image/tools;1"]
+                       .createInstance(Ci.imgITools);
+      let imgContainer = { value: null };
+
+      imgTools.decodeImageData(aImageStream, aMimeType, imgContainer);
+      iconStream = imgTools.encodeImage(imgContainer.value, "image/png");
+    } catch (e) {
+      throw("processIcon - Failure converting icon (" + e + ")");
+    }
+
+    let outputStream = FileUtils.openSafeFileOutputStream(this.iconFile);
+    NetUtil.asyncCopy(iconStream, outputStream);
+  }
+}
+
 #endif
 
 /* Helper Functions */
 
 /**
  * Async write a data string into a file
  *
  * @param aFile     the nsIFile to write to
--- a/configure.in
+++ b/configure.in
@@ -6408,16 +6408,19 @@ fi
 
 dnl ========================================================
 dnl Web App Runtime
 dnl ========================================================
 MOZ_ARG_DISABLE_BOOL(webapp-runtime,
 [  --disable-webapp-runtime  Disable Web App Runtime],
     MOZ_WEBAPP_RUNTIME=,
     MOZ_WEBAPP_RUNTIME=1)
+if test "$MOZ_WIDGET_TOOLKIT" != "windows" -a "$MOZ_WIDGET_TOOLKIT" != "cocoa" -a "$MOZ_WIDGET_TOOLKIT" != "gtk2"; then
+    MOZ_WEBAPP_RUNTIME=
+fi
 if test "$OS_ARCH" = "WINNT" -a -z "$MAKENSISU" -a -n "$CROSS_COMPILE"; then
     MOZ_WEBAPP_RUNTIME=
 fi
 AC_SUBST(MOZ_WEBAPP_RUNTIME)
 if test "$MOZ_WEBAPP_RUNTIME"; then
     AC_DEFINE(MOZ_WEBAPP_RUNTIME)
 fi
 
--- a/content/base/src/nsInProcessTabChildGlobal.cpp
+++ b/content/base/src/nsInProcessTabChildGlobal.cpp
@@ -111,16 +111,25 @@ nsInProcessTabChildGlobal::Init()
                    "Couldn't initialize nsInProcessTabChildGlobal");
   mMessageManager = new nsFrameMessageManager(false,
                                               SendSyncMessageToParent,
                                               SendAsyncMessageToParent,
                                               nsnull,
                                               this,
                                               nsnull,
                                               mCx);
+
+  // Set the location information for the new global, so that tools like
+  // about:memory may use that information.
+  JSObject *global;
+  nsIURI* docURI = mOwner->OwnerDoc()->GetDocumentURI();
+  if (mGlobal && NS_SUCCEEDED(mGlobal->GetJSObject(&global)) && docURI) {
+    xpc::SetLocationForGlobal(global, docURI);
+  }
+
   return NS_OK;
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsInProcessTabChildGlobal)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsInProcessTabChildGlobal,
                                                 nsDOMEventTargetHelper)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mMessageManager)
--- a/content/media/VideoUtils.h
+++ b/content/media/VideoUtils.h
@@ -113,16 +113,17 @@ static const PRInt32 MAX_VIDEO_WIDTH = 4
 static const PRInt32 MAX_VIDEO_HEIGHT = 3000;
 
 // Scales the display rect aDisplay by aspect ratio aAspectRatio.
 // Note that aDisplay must be validated by nsVideoInfo::ValidateVideoRegion()
 // before being used!
 void ScaleDisplayByAspectRatio(nsIntSize& aDisplay, float aAspectRatio);
 
 // The amount of virtual memory reserved for thread stacks.
-#if defined(XP_WIN) || defined(XP_MACOSX) || defined(LINUX)
+#if (defined(XP_WIN) || defined(XP_MACOSX) || defined(LINUX)) && \
+    !defined(MOZ_ASAN)
 #define MEDIA_THREAD_STACK_SIZE (128 * 1024)
 #else
 // All other platforms use their system defaults.
 #define MEDIA_THREAD_STACK_SIZE nsIThreadManager::DEFAULT_STACK_SIZE
 #endif
 
 #endif
--- a/dom/Makefile.in
+++ b/dom/Makefile.in
@@ -50,16 +50,17 @@ DIRS += \
   battery \
   contacts \
   devicestorage \
   file \
   media \
   power \
   settings \
   sms \
+  mms \
   src \
   locales \
   network \
   plugins/base \
   plugins/ipc \
   indexedDB \
   system \
   ipc \
new file mode 100644
--- /dev/null
+++ b/dom/mms/Makefile.in
@@ -0,0 +1,22 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH            = ../..
+topsrcdir        = @top_srcdir@
+srcdir           = @srcdir@
+VPATH            = @srcdir@
+
+relativesrcdir   = dom/mms
+
+include $(DEPTH)/config/autoconf.mk
+
+PARALLEL_DIRS = interfaces src
+
+ifdef MOZ_B2G_RIL
+ifdef ENABLE_TESTS
+XPCSHELL_TESTS = tests
+endif
+endif
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/mms/interfaces/Makefile.in
@@ -0,0 +1,19 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH            = ../../..
+topsrcdir        = @top_srcdir@
+srcdir           = @srcdir@
+VPATH            = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+XPIDL_MODULE = dom_mms
+
+XPIDLSRCS = \
+  nsIMmsService.idl \
+  nsIWapPushApplication.idl \
+  $(NULL)
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/mms/interfaces/nsIMmsService.idl
@@ -0,0 +1,11 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(217ddd76-75db-4210-955d-8806cd8d87f9)]
+interface nsIMmsService : nsISupports
+{
+  boolean        hasSupport();
+};
new file mode 100644
--- /dev/null
+++ b/dom/mms/interfaces/nsIWapPushApplication.idl
@@ -0,0 +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/. */
+
+#include "nsISupports.idl"
+
+/**
+ * Handle WAP Push notifications.
+ */
+[scriptable, uuid(fd6f7f6b-a67e-4892-930d-fca864df8fe7)]
+interface nsIWapPushApplication : nsISupports
+{
+  /**
+   * Receive WAP Push message.
+   *
+   * @param aData
+   *        An array containing raw PDU data.
+   * @param aLength
+   *        Length of aData.
+   * @param aOffset
+   *        Start offset of aData containing message body of the Push PDU.
+   * @param options
+   *        An object containing various attributes from lower transport layer.
+   */
+  [implicit_jscontext]
+  void receiveWapPush([array, size_is(aLength)] in octet aData,
+                      in unsigned long                   aLength,
+                      in unsigned long                   aOffset,
+                      in jsval                           aOptions);
+};
+
new file mode 100644
--- /dev/null
+++ b/dom/mms/src/Makefile.in
@@ -0,0 +1,30 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DEPTH            = ../../..
+topsrcdir        = @top_srcdir@
+srcdir           = @srcdir@
+VPATH            = \
+  $(srcdir) \
+  $(NULL)
+
+include $(DEPTH)/config/autoconf.mk
+
+ifdef MOZ_B2G_RIL
+EXTRA_COMPONENTS = \
+  ril/MmsService.js \
+  ril/MmsService.manifest \
+  $(NULL)
+
+EXTRA_JS_MODULES = \
+  ril/mms_consts.js \
+  ril/MmsPduHelper.jsm \
+  ril/wap_consts.js \
+  ril/WapPushManager.js \
+  ril/WspPduHelper.jsm \
+  $(NULL)
+endif
+
+include $(topsrcdir)/config/config.mk
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/mms/src/ril/MmsPduHelper.jsm
@@ -0,0 +1,1337 @@
+/* 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";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+let WSP = {};
+Cu.import("resource://gre/modules/WspPduHelper.jsm", WSP);
+
+Cu.import("resource://gre/modules/mms_consts.js");
+
+let DEBUG; // set to true to see debug messages
+
+function translatePduErrorToStatus(error) {
+  switch (error) {
+    case MMS_PDU_ERROR_OK:
+      return MMS_PDU_STATUS_RETRIEVED;
+    case MMS_PDU_ERROR_TRANSIENT_FAILURE:
+    case MMS_PDU_ERROR_TRANSIENT_MESSAGE_NOT_FOUND:
+    case MMS_PDU_ERROR_TRANSIENT_NETWORK_PROBLEM:
+      return MMS_PDU_STATUS_DEFERRED;
+    default:
+      return MMS_PDU_STATUS_UNRECOGNISED;
+  }
+}
+
+/**
+ * Internal decoding function for boolean values.
+ *
+ * Boolean-value = Yes | No
+ * Yes = <Octet 128>
+ * No = <Octet 129>
+ */
+let BooleanValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Boolean true or false.
+   *
+   * @throws CodeError if read octet equals to neither 128 nor 129.
+   */
+  decode: function decode(data) {
+    let value = WSP.Octet.decode(data);
+    if ((value != 128) && (value != 129)) {
+      throw new WSP.CodeError("Boolean-value: invalid value " + value);
+    }
+
+    return value == 128;
+  },
+
+  /**
+   * @param data
+   *        A wrapped object to store encoded raw data.
+   * @param value
+   *        A boolean value to be encoded.
+   */
+  encode: function encode(data, value) {
+    WSP.Octet.encode(data, value ? 128 : 129);
+  },
+};
+
+/**
+ * MMS Address
+ *
+ * address = email | device-address | alphanum-shortcode | num-shortcode
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A section 8
+ */
+let Address = {
+  decode: function (data) {
+    let str = EncodedStringValue.decode(data);
+
+    let result;
+    if (((result = str.match(/^(\+?[\d.-]+)\/TYPE=(PLMN)$/)) != null)
+        || ((result = str.match(/^(\d{1,3}(?:\.\d{1,3}){3})\/TYPE=(IPv4)$/)) != null)
+        || ((result = str.match(/^([\da-fA-F]{4}(?::[\da-fA-F]{4}){7})\/TYPE=(IPv6)$/)) != null)
+        || ((result = str.match(/^([\w\+\-.%]+)\/TYPE=(\w+)$/)) != null)) {
+      return {address: result[1], type: result[2]};
+    }
+
+    let type;
+    if (str.match(/^[\+*#]\d+$/)) {
+      type = "num";
+    } else if (str.match(/^\w+$/)) {
+      type = "alphanum";
+    } else {
+      type = "unknown";
+    }
+
+    return {address: str, type: type};
+  },
+};
+
+/**
+ * Header-field = MMS-header | Application-header
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.2
+ */
+let HeaderField = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param options
+   *        Extra context for decoding.
+   *
+   * @return A decoded object containing `name` and `value` properties or null
+   *         in case of a failed parsing. The `name` property must be a string,
+   *         but the `value` property can be many different types depending on
+   *         `name`.
+   */
+  decode: function decode(data, options) {
+    return WSP.decodeAlternatives(data, options,
+                                  MmsHeader, WSP.ApplicationHeader);
+  },
+
+  /**
+   * @param data
+   *        A wrapped object to store encoded raw data.
+   * @param octet
+   *        Octet value to be encoded.
+   * @param options
+   *        Extra context for encoding.
+   */
+  encode: function encode(data, value, options) {
+    WSP.encodeAlternatives(data, value, options,
+                           MmsHeader, WSP.ApplicationHeader);
+  },
+};
+
+/**
+ * MMS-header = MMS-field-name MMS-value
+ * MMS-field-name = Short-integer
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.2
+ */
+let MmsHeader = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param options
+   *        Extra context for decoding.
+   *
+   * @return A decoded object containing `name` and `value` properties or null
+   *         in case of a failed parsing. The `name` property must be a string,
+   *         but the `value` property can be many different types depending on
+   *         `name`.
+   *
+   * @throws NotWellKnownEncodingError if decoded well-known header field
+   *         number is not registered or supported.
+   */
+  decode: function decode(data, options) {
+    let index = WSP.ShortInteger.decode(data);
+
+    let entry = MMS_HEADER_FIELDS[index];
+    if (!entry) {
+      throw new WSP.NotWellKnownEncodingError(
+        "MMS-header: not well known header " + index);
+    }
+
+    let cur = data.offset, value;
+    try {
+      value = entry.coder.decode(data, options);
+    } catch (e) {
+      data.offset = cur;
+
+      value = WSP.skipValue(data);
+      debug("Skip malformed well known header: "
+            + JSON.stringify({name: entry.name, value: value}));
+
+      return null;
+    }
+
+    return {
+      name: entry.name,
+      value: value,
+    };
+  },
+
+  /**
+   * @param data
+   *        A wrapped object to store encoded raw data.
+   * @param header
+   *        An object containing two attributes: a string-typed `name` and a
+   *        `value` of arbitrary type.
+   *
+   * @throws CodeError if got an empty header name.
+   * @throws NotWellKnownEncodingError if the well-known header field number is
+   *         not registered or supported.
+   */
+  encode: function encode(data, header) {
+    if (!header.name) {
+      throw new WSP.CodeError("MMS-header: empty header name");
+    }
+
+    let entry = MMS_HEADER_FIELDS[header.name.toLowerCase()];
+    if (!entry) {
+      throw new WSP.NotWellKnownEncodingError(
+        "MMS-header: not well known header " + header.name);
+    }
+
+    WSP.ShortInteger.encode(data, entry.number);
+    entry.coder.encode(data, header.value);
+  },
+};
+
+/**
+ * Content-class-value = text | image-basic| image-rich | video-basic |
+ *                       video-rich | megapixel | content-basic | content-rich
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.9
+ */
+let ContentClassValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A integer value for each class.
+   *
+   * @throws CodeError if decoded value is not in range 128..135.
+   */
+  decode: function decode(data) {
+    let value = WSP.Octet.decode(data);
+    if ((value >= 128) && (value <= 135)) {
+      return value;
+    }
+
+    throw new WSP.CodeError("Content-class-value: invalid class " + value);
+  },
+};
+
+/**
+ * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf:
+ *
+ *   Content-location-value = Uri-value
+ *
+ * When used in the M-Mbox-Delete.conf and M-Delete.conf PDU:
+ *
+ *   Content-location-Del-value = Value-length Status-count-value Content-location-value
+ *   Status-count-value = Integer-value
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.10
+ */
+let ContentLocationValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param options
+   *        Extra context for decoding.
+   *
+   * @return A decoded object containing `uri` and conditional `statusCount`
+   *         properties.
+   */
+  decode: function decode(data, options) {
+    let type = WSP.ensureHeader(options, "x-mms-message-type");
+
+    let result = {};
+    if ((type == MMS_PDU_TYPE_MBOX_DELETE_CONF)
+        || (type == MMS_PDU_TYPE_DELETE_CONF)) {
+      let length = WSP.ValueLength.decode(data);
+      let end = data.offset + length;
+
+      result.statusCount = WSP.IntegerValue.decode(data);
+      result.uri = WSP.UriValue.decode(data);
+
+      if (data.offset != end) {
+        data.offset = end;
+      }
+    } else {
+      result.uri = WSP.UriValue.decode(data);
+    }
+
+    return result;
+  },
+};
+
+/**
+ * Element-Descriptor-value = Value-length Content-Reference-value *(Parameter)
+ * Content-Reference-value = Text-string
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.18
+ */
+let ElementDescriptorValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded object containing a string property `contentReference`
+   *         and an optinal `params` name-value map.
+   */
+  decode: function decode(data) {
+    let length = WSP.ValueLength.decode(data);
+    let end = data.offset + length;
+
+    let result = {};
+    result.contentReference = WSP.TextString.decode(data);
+    if (data.offset < end) {
+      result.params = Parameter.decodeMultiple(data, end);
+    }
+
+    if (data.offset != end) {
+      // Explicitly seek to end in case of skipped parameters.
+      data.offset = end;
+    }
+
+    return result;
+  },
+};
+
+/**
+ * OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.18:
+ * `For well-known parameter names binary tokens MUST be used as defined in
+ * Table 27.` So we can't reuse that of WSP.
+ *
+ *   Parameter = Parameter-name Parameter-value
+ *   Parameter-name = Short-integer | Text-string
+ *   Parameter-value = Constrained-encoding | Text-string
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.18
+ */
+let Parameter = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded string.
+   *
+   * @throws NotWellKnownEncodingError if decoded well-known parameter number
+   *         is not registered or supported.
+   */
+  decodeParameterName: function decodeParameterName(data) {
+    let begin = data.offset;
+    let number;
+    try {
+      number = WSP.ShortInteger.decode(data);
+    } catch (e) {
+      data.offset = begin;
+      return WSP.TextString.decode(data).toLowerCase();
+    }
+
+    let entry = MMS_WELL_KNOWN_PARAMS[number];
+    if (!entry) {
+      throw new WSP.NotWellKnownEncodingError(
+        "Parameter-name: not well known parameter " + number);
+    }
+
+    return entry.name;
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded object containing `name` and `value` properties or null
+   *         in case of a failed parsing. The `name` property must be a string,
+   *         but the `value` property can be many different types depending on
+   *         `name`.
+   */
+  decode: function decode(data) {
+    let name = this.decodeParameterName(data);
+    let value = WSP.decodeAlternatives(data, null,
+                                       WSP.ConstrainedEncoding, WSP.TextString);
+    return {
+      name: name,
+      value: value,
+    };
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param end
+   *        Ending offset of following parameters.
+   *
+   * @return An array of decoded objects.
+   */
+  decodeMultiple: function decodeMultiple(data, end) {
+    let params, param;
+
+    while (data.offset < end) {
+      try {
+        param = this.decode(data);
+      } catch (e) {
+        break;
+      }
+      if (param) {
+        if (!params) {
+          params = {};
+        }
+        params[param.name] = param.value;
+      }
+    }
+
+    return params;
+  },
+};
+
+/**
+ * Encoded-string-value = Text-string | Value-length Char-set Text-string
+ * The Char-set values are registered by IANA as MIBEnum value.
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.19
+ */
+let EncodedStringValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded string.
+   */
+  decode: function decode(data) {
+    return WSP.decodeAlternatives(data, null,
+                                  WSP.TextString, CharsetEncodedString);
+  },
+};
+
+/**
+ * Charset-encoded-string = Value-length Char-set Text-string
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.19
+ */
+let CharsetEncodedString = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded string.
+   *
+   * @throws CodeError if the raw octets cannot be converted.
+   * @throws NotWellKnownEncodingError if decoded well-known charset number is
+   *         not registered or supported.
+   */
+  decode: function decode(data) {
+    let length = WSP.ValueLength.decode(data);
+    let end = data.offset + length;
+
+    let charset = WSP.ShortInteger.decode(data);
+    let entry = WSP.WSP_WELL_KNOWN_CHARSETS[charset];
+    if (!entry) {
+      throw new WSP.NotWellKnownEncodingError(
+        "Charset-encoded-string: not well known charset " + charset);
+    }
+
+    let str;
+    if (entry.converter) {
+      // Read a possible string quote(<Octet 127>).
+      let begin = data.offset;
+      if (WSP.Octet.decode(data) != 127) {
+        data.offset = begin;
+      }
+
+      let raw = WSP.Octet.decodeMultiple(data, end - 1);
+      // Read NUL character.
+      WSP.Octet.decodeEqualTo(data, 0);
+
+      if (!raw) {
+        str = "";
+      } else {
+        let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+                   .createInstance(Ci.nsIScriptableUnicodeConverter);
+        conv.charset = entry.converter;
+        try {
+          str = conv.convertFromByteArray(raw, raw.length);
+        } catch (e) {
+          throw new WSP.CodeError("Charset-encoded-string: " + e.message);
+        }
+      }
+    } else {
+      str = WSP.TextString.decode(data);
+    }
+
+    if (data.offset != end) {
+      data.offset = end;
+    }
+
+    return str;
+  },
+};
+
+/**
+ * Expiry-value = Value-length (Absolute-token Date-value | Relative-token Delta-seconds-value)
+ * Address-token = <Octet 128>
+ * Relative-token = <Octet 129>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.20
+ */
+let ExpiryValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A Date object for absolute expiry or an integer for relative one.
+   *
+   * @throws CodeError if decoded token equals to neither 128 nor 129.
+   */
+  decode: function decode(data) {
+    let length = WSP.ValueLength.decode(data);
+    let end = data.offset + length;
+
+    let token = WSP.Octet.decode(data);
+    if ((token != 128) && (token != 129)) {
+      throw new WSP.CodeError("Expiry-value: invalid token " + token);
+    }
+
+    let result;
+    if (token == 128) {
+      result = WSP.DateValue.decode(data);
+    } else {
+      result = WSP.DeltaSecondsValue.decode(data);
+    }
+
+    if (data.offset != end) {
+      data.offset = end;
+    }
+
+    return result;
+  },
+};
+
+/**
+ * From-value = Value-length (Address-present-token Address | Insert-address-token)
+ * Address-present-token = <Octet 128>
+ * Insert-address-token = <Octet 129>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.21
+ */
+let FromValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded string or null for MMS Proxy-Relay Insert-Address mode.
+   *
+   * @throws CodeError if decoded token equals to neither 128 nor 129.
+   */
+  decode: function decode(data) {
+    let length = WSP.ValueLength.decode(data);
+    let end = data.offset + length;
+
+    let token = WSP.Octet.decode(data);
+    if ((token != 128) && (token != 129)) {
+      throw new WSP.CodeError("From-value: invalid token " + token);
+    }
+
+    let result = null;
+    if (token == 128) {
+      result = Address.decode(data);
+    }
+
+    if (data.offset != end) {
+      data.offset = end;
+    }
+
+    return result;
+  },
+};
+
+/**
+ * Previously-sent-by-value = Value-length Forwarded-count-value Address
+ * Forwarded-count-value = Integer-value
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.23
+ */
+let PreviouslySentByValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded object containing an integer `forwardedCount` and an
+   *         string-typed `originator` attributes.
+   */
+  decode: function decode(data) {
+    let length = WSP.ValueLength.decode(data);
+    let end = data.offset + length;
+
+    let result = {};
+    result.forwardedCount = WSP.IntegerValue.decode(data);
+    result.originator = Address.decode(data);
+
+    if (data.offset != end) {
+      data.offset = end;
+    }
+
+    return result;
+  },
+};
+
+/**
+ * Previously-sent-date-value = Value-length Forwarded-count-value Date-value
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.23
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.24
+ */
+let PreviouslySentDateValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded object containing an integer `forwardedCount` and an
+   *         Date-typed `timestamp` attributes.
+   */
+  decode: function decode(data) {
+    let length = WSP.ValueLength.decode(data);
+    let end = data.offset + length;
+
+    let result = {};
+    result.forwardedCount = WSP.IntegerValue.decode(data);
+    result.timestamp = WSP.DateValue.decode(data);
+
+    if (data.offset != end) {
+      data.offset = end;
+    }
+
+    return result;
+  },
+};
+
+/**
+ * Message-class-value = Class-identifier | Token-text
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.27
+ */
+let MessageClassValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded string.
+   */
+  decode: function decode(data) {
+    return WSP.decodeAlternatives(data, null,
+                                  ClassIdentifier, WSP.TokenText);
+  },
+};
+
+/**
+ * Class-identifier = Personal | Advertisement | Informational | Auto
+ * Personal = <Octet 128>
+ * Advertisement = <Octet 129>
+ * Informational = <Octet 130>
+ * Auto = <Octet 131>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.27
+ */
+let ClassIdentifier = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded string.
+   *
+   * @throws CodeError if decoded value is not in the range 128..131.
+   */
+  decode: function decode(data) {
+    let value = WSP.Octet.decode(data);
+    switch (value) {
+      case 128: return "personal";
+      case 129: return "advertisement";
+      case 130: return "informational";
+      case 131: return "auto";
+    }
+
+    throw new WSP.CodeError("Class-identifier: invalid id " + value);
+  },
+};
+
+ /**
+ * Message-type-value = <Octet 128..151>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.30
+ */
+let MessageTypeValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded integer.
+   *
+   * @throws CodeError if decoded value is not in the range 128..151.
+   */
+  decode: function decode(data) {
+    let type = WSP.Octet.decode(data);
+    if ((type >= 128) && (type <= 151)) {
+      return type;
+    }
+
+    throw new WSP.CodeError("Message-type-value: invalid type " + type);
+  },
+
+  /**
+   * @param data
+   *        A wrapped object to store encoded raw data.
+   * @param type
+   *        A numeric message type value to be encoded.
+   *
+   * @throws CodeError if the value is not in the range 128..151.
+   */
+  encode: function encode(data, type) {
+    if ((type < 128) || (type > 151)) {
+      throw new WSP.CodeError("Message-type-value: invalid type " + type);
+    }
+
+    WSP.Octet.encode(data, type);
+  },
+};
+
+/**
+ * MM-flags-value = Value-length ( Add-token | Remove-token | Filter-token ) Encoded-string-value
+ * Add-token = <Octet 128>
+ * Remove-token = <Octet 129>
+ * Filter-token = <Octet 130>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.32
+ */
+let MmFlagsValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded object containing an integer `type` and an string-typed
+   *         `address` attributes.
+   *
+   * @throws CodeError if decoded value is not in the range 128..130.
+   */
+  decode: function decode(data) {
+    let length = WSP.ValueLength.decode(data);
+    let end = data.offset + length;
+
+    let result = {};
+    result.type = WSP.Octet.decode(data);
+    if ((result.type < 128) || (result.type > 130)) {
+      throw new WSP.CodeError("MM-flags-value: invalid type " + result.type);
+    }
+    result.text = EncodedStringValue.decode(data);
+
+    if (data.offset != end) {
+      data.offset = end;
+    }
+
+    return result;
+  },
+};
+
+/**
+ * MM-state-value = Draft | Sent | New | Retrieved | Forwarded
+ * Draft = <Octet 128>
+ * Sent = <Octet 129>
+ * New = <Octet 130>
+ * Retrieved = <Octet 131>
+ * Forwarded = <Octet 132>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.33
+ */
+let MmStateValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded integer.
+   *
+   * @throws CodeError if decoded value is not in the range 128..132.
+   */
+  decode: function decode(data) {
+    let state = WSP.Octet.decode(data);
+    if ((state >= 128) && (state <= 132)) {
+      return state;
+    }
+
+    throw new WSP.CodeError("MM-state-value: invalid state " + state);
+  },
+};
+
+/**
+ * Priority-value = Low | Normal | High
+ * Low = <Octet 128>
+ * Normal = <Octet 129>
+ * High = <Octet 130>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.35
+ */
+let PriorityValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded integer.
+   *
+   * @throws CodeError if decoded value is not in the range 128..130.
+   */
+  decode: function decode(data) {
+    let priority = WSP.Octet.decode(data);
+    if ((priority >= 128) && (priority <= 130)) {
+      return priority;
+    }
+
+    throw new WSP.CodeError("Priority-value: invalid priority " + priority);
+  },
+};
+
+/**
+ * Recommended-Retrieval-Mode-value = Manual
+ * Manual = <Octet 128>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.39
+ */
+let RecommendedRetrievalModeValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded integer.
+   */
+  decode: function decode(data) {
+    return WSP.Octet.decodeEqualTo(data, 128);
+  },
+};
+
+/**
+ * Reply-charging-value = Requested | Requested text only | Accepted |
+ *                        Accepted text only
+ * Requested = <Octet 128>
+ * Requested text only = <Octet 129>
+ * Accepted = <Octet 130>
+ * Accepted text only = <Octet 131>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.43
+ */
+let ReplyChargingValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded integer.
+   *
+   * @throws CodeError if decoded value is not in the range 128..131.
+   */
+  decode: function decode(data) {
+    let value = WSP.Octet.decode(data);
+    if ((value >= 128) && (value <= 131)) {
+      return value;
+    }
+
+    throw new WSP.CodeError("Reply-charging-value: invalid value " + value);
+  },
+};
+
+/**
+ * Retrieve-status-value = Ok | Error-transient-failure |
+ *                         Error-transient-message-not-found |
+ *                         Error-transient-network-problem |
+ *                         Error-permanent-failure |
+ *                         Error-permanent-service-denied |
+ *                         Error-permanent-message-not-found |
+ *                         Error-permanent-content-unsupported
+ * Ok = <Octet 128>
+ * Error-transient-failure = <Octet 192>
+ * Error-transient-message-not-found = <Octet 193>
+ * Error-transient-network-problem = <Octet 194>
+ * Error-permanent-failure = <Octet 224>
+ * Error-permanent-service-denied = <Octet 225>
+ * Error-permanent-message-not-found = <Octet 226>
+ * Error-permanent-content-unsupported = <Octet 227>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.50
+ */
+let RetrieveStatusValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded integer.
+   */
+  decode: function decode(data) {
+    let value = WSP.Octet.decode(data);
+    if ((value == 128)
+        || ((value >= 192) && (value <= 194))
+        || ((value >= 224) && (value <= 227))) {
+      return value;
+    }
+
+    if ((value >= 195) && (value <= 223)) {
+      // The values 195 through 223 are reserved for future use to indicate
+      // other transient failures. An MMS Client MUST react the same to a value
+      // in range 195 to 223 as it does to the value 192
+      // (Error-transient-failure).
+      return MMS_PDU_ERROR_TRANSIENT_FAILURE;
+    }
+
+    // The values 228 through 255 are reserved for future use to indicate
+    // other permanent failures. An MMS Client MUST react the same to a value
+    // in range 228 to 255 as it does to the value 224
+    // (Error-permanent-failure).
+
+    // Any other values SHALL NOT be used. They are reserved for future use.
+    // An MMS Client that receives such a reserved value MUST react the same
+    // as it does to the value 224 (Error-permanent-failure).
+    return MMS_PDU_ERROR_PERMANENT_FAILURE;
+  },
+};
+
+/**
+ * Status-value = Expired | Retrieved | Rejected | Deferred | Unrecognised |
+ *                Indeterminate | Forwarded | Unreachable
+ * Expired = <Octet 128>
+ * Retrieved = <Octet 129>
+ * Rejected = <Octet 130>
+ * Deferred = <Octet 131>
+ * Unrecognised = <Octet 132>
+ * Indeterminate = <Octet 133>
+ * Forwarded = <Octet 134>
+ * Unreachable = <Octet 135>
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.54
+ */
+let StatusValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded integer.
+   *
+   * @throws CodeError if decoded value is not in the range 128..135.
+   */
+  decode: function decode(data) {
+    let status = WSP.Octet.decode(data);
+    if ((status >= 128) && (status <= 135)) {
+      return status;
+    }
+
+    throw new WSP.CodeError("Status-value: invalid status " + status);
+  },
+
+  /**
+   * @param data
+   *        A wrapped object to store encoded raw data.
+   * @param value
+   *        A numeric status value to be encoded.
+   *
+   * @throws CodeError if the value is not in the range 128..135.
+   */
+  encode: function encode(data, value) {
+    if ((value < 128) || (value > 135)) {
+      throw new WSP.CodeError("Status-value: invalid status " + value);
+    }
+
+    WSP.Octet.encode(data, value);
+  },
+};
+
+let PduHelper = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param headers
+   *        An optional object to store parsed header fields. Created
+   *        automatically if undefined.
+   *
+   * @return A boolean value indicating whether it's followed by message body.
+   */
+  parseHeaders: function parseHeaders(data, headers) {
+    if (!headers) {
+      headers = {};
+    }
+
+    let header;
+    while (data.offset < data.array.length) {
+      // There is no `header length` information in MMS PDU. If we just got
+      // something wrong in parsing header fields, we might not be able to
+      // determine the correct header-content boundary.
+      header = HeaderField.decode(data, headers);
+
+      if (header) {
+        headers[header.name] = header.value;
+        if (header.name == "content-type") {
+          // `... if the PDU contains a message body the Content Type MUST be
+          // the last header field, followed by message body.` See
+          // OMA-TS-MMS_ENC-V1_3-20110913-A section 7.
+          break;
+        }
+      }
+    }
+
+    return headers;
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param msg
+   *        A message object to store decoded multipart or octet array content.
+   */
+  parseContent: function parseContent(data, msg) {
+    let contentType = msg.headers["content-type"].media;
+    if ((contentType == "application/vnd.wap.multipart.related")
+        || (contentType == "application/vnd.wap.multipart.mixed")) {
+      msg.parts = WSP.PduHelper.parseMultiPart(data);
+      return;
+    }
+
+    if (data.offset >= data.array.length) {
+      return;
+    }
+
+    msg.content = WSP.Octet.decodeMultiple(data, data.array.length);
+    if (false) {
+      for (let begin = 0; begin < msg.content.length; begin += 20) {
+        debug("content: " + JSON.stringify(msg.content.subarray(begin, begin + 20)));
+      }
+    }
+  },
+
+  /**
+   * Check existences of all mandatory fields of a MMS message. Also sets `type`
+   * and `typeinfo` for convient access.
+   *
+   * @param msg
+   *        A MMS message object.
+   *
+   * @throws FatalCodeError if the PDU type is not supported yet.
+   */
+  checkMandatoryFields: function checkMandatoryFields(msg) {
+    let type = WSP.ensureHeader(msg.headers, "x-mms-message-type");
+    let entry = MMS_PDU_TYPES[type];
+    if (!entry) {
+      throw new WSP.FatalCodeError(
+        "checkMandatoryFields: unsupported message type " + type);
+    }
+
+    entry.mandatoryFields.forEach(function (name) {
+      WSP.ensureHeader(msg.headers, name);
+    });
+
+    // Setup convient alias that referenced frequently.
+    msg.type = type;
+    msg.typeinfo = entry;
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param msg [optional]
+   *        Optional target object for decoding.
+   *
+   * @return A MMS message object or null in case of errors found.
+   */
+  parse: function parse(data, msg) {
+    if (!msg) {
+      msg = {};
+    }
+
+    try {
+      msg.headers = this.parseHeaders(data, msg.headers);
+
+      // Validity checks
+      this.checkMandatoryFields(msg);
+
+      if (msg.typeinfo.hasContent) {
+        this.parseContent(data, msg);
+      }
+    } catch (e) {
+      debug("Failed to parse MMS message, error message: " + e.message);
+      return null;
+    }
+
+    return msg;
+  },
+
+  /**
+   * Convert javascript Array to an nsIInputStream.
+   */
+  convertArrayToInputStream: function convertDataToInputStream(array) {
+    let storageStream = Cc["@mozilla.org/storagestream;1"]
+                        .createInstance(Ci.nsIStorageStream);
+    storageStream.init(4096, array.length, null);
+
+    let boStream = Cc["@mozilla.org/binaryoutputstream;1"]
+                   .createInstance(Ci.nsIBinaryOutputStream);
+    boStream.setOutputStream(storageStream.getOutputStream(0));
+    boStream.writeByteArray(array, array.length)
+    boStream.close();
+
+    return storageStream.newInputStream(0);
+  },
+
+  /**
+   * @param data [optional]
+   *        A wrapped object to store encoded raw data. Created if undefined.
+   * @param headers
+   *        A dictionary object containing multiple name/value mapping.
+   *
+   * @return the passed data parameter or a created one.
+   */
+  encodeHeaders: function encodeHeaders(data, headers) {
+    if (!data) {
+      data = {array: [], offset: 0};
+    }
+
+    function encodeHeader(name) {
+      HeaderField.encode(data, {name: name, value: headers[name]});
+    }
+
+    function encodeHeaderIfExists(name) {
+      // Header value could be zero or null.
+      if (headers[name] !== undefined) {
+        encodeHeader(name);
+      }
+    }
+
+    // `In the encoding of the header fields, the order of the fields is not
+    // significant, except that X-Mms-Message-Type, X-Mms-Transaction-ID (when
+    // present) and X-Mms-MMS-Version MUST be at the beginning of the message
+    // headers, in that order, and if the PDU contains a message body the
+    // Content Type MUST be the last header field, followed by message body.`
+    // ~ OMA-TS-MMS_ENC-V1_3-20110913-A section 7
+    encodeHeader("x-mms-message-type");
+    encodeHeaderIfExists("x-mms-transaction-id");
+    encodeHeaderIfExists("x-mms-mms-version");
+
+    for (let key in headers) {
+      if ((key == "x-mms-message-type")
+          || (key == "x-mms-transaction-id")
+          || (key == "x-mms-mms-version")
+          || (key == "content-type")) {
+        continue;
+      }
+      encodeHeader(key);
+    }
+
+    encodeHeaderIfExists("content-type");
+
+    // Remove extra space consumed during encoding.
+    while (data.array.length > data.offset) {
+      data.array.pop();
+    }
+
+    return data;
+  },
+
+  /**
+   * @param multiStream
+   *        An exsiting nsIMultiplexInputStream.
+   * @param msg
+   *        A MMS message object.
+   *
+   * @return An instance of nsIMultiplexInputStream or null in case of errors.
+   */
+  compose: function compose(multiStream, msg) {
+    if (!multiStream) {
+      multiStream = Cc["@mozilla.org/io/multiplex-input-stream;1"]
+                    .createInstance(Ci.nsIMultiplexInputStream);
+    }
+
+    try {
+      // Validity checks
+      this.checkMandatoryFields(msg);
+
+      let data = this.encodeHeaders(null, msg.headers);
+      debug("Composed PDU Header: " + JSON.stringify(data.array));
+      let headerStream = this.convertArrayToInputStream(data.array);
+      multiStream.appendStream(headerStream);
+
+      return multiStream;
+    } catch (e) {
+      debug("Failed to compose MMS message, error message: " + e.message);
+      return null;
+    }
+  },
+};
+
+const MMS_PDU_TYPES = (function () {
+  let pdus = {};
+  function add(number, hasContent, mandatoryFields) {
+    pdus[number] = {
+      number: number,
+      hasContent: hasContent,
+      mandatoryFields: mandatoryFields,
+    };
+  }
+
+  add(MMS_PDU_TYPE_NOTIFICATION_IND, false, ["x-mms-message-type",
+                                             "x-mms-transaction-id",
+                                             "x-mms-mms-version",
+                                             "x-mms-message-class",
+                                             "x-mms-message-size",
+                                             "x-mms-expiry",
+                                             "x-mms-content-location"]);
+  add(MMS_PDU_TYPE_RETRIEVE_CONF, true, ["x-mms-message-type",
+                                         "x-mms-mms-version",
+                                         "content-type"]);
+  add(MMS_PDU_TYPE_NOTIFYRESP_IND, false, ["x-mms-message-type",
+                                           "x-mms-transaction-id",
+                                           "x-mms-mms-version",
+                                           "x-mms-status"]);
+
+  return pdus;
+})();
+
+/**
+ * Header field names and assigned numbers.
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.4
+ */
+const MMS_HEADER_FIELDS = (function () {
+  let names = {};
+  function add(name, number, coder) {
+    let entry = {
+      name: name,
+      number: number,
+      coder: coder,
+    };
+    names[name] = names[number] = entry;
+  }
+
+  add("bcc",                                     0x01, Address);
+  add("cc",                                      0x02, Address);
+  add("x-mms-content-location",                  0x03, ContentLocationValue);
+  add("content-type",                            0x04, WSP.ContentTypeValue);
+  add("date",                                    0x05, WSP.DateValue);
+  add("x-mms-delivery-report",                   0x06, BooleanValue);
+  //add("x-mms-delivery-time", 0x07);
+  add("x-mms-expiry",                            0x08, ExpiryValue);
+  add("from",                                    0x09, FromValue);
+  add("x-mms-message-class",                     0x0A, MessageClassValue);
+  add("message-id",                              0x0B, WSP.TextString);
+  add("x-mms-message-type",                      0x0C, MessageTypeValue);
+  add("x-mms-mms-version",                       0x0D, WSP.ShortInteger);
+  add("x-mms-message-size",                      0x0E, WSP.LongInteger);
+  add("x-mms-priority",                          0x0F, PriorityValue);
+  add("x-mms-read-report",                       0x10, BooleanValue);
+  add("x-mms-report-allowed",                    0x11, BooleanValue);
+  //add("x-mms-response-status", 0x12);
+  //add("x-mms-response-text", 0x13);
+  //add("x-mms-sender-visibility", 0x14);
+  add("x-mms-status",                            0x15, StatusValue);
+  add("subject",                                 0x16, EncodedStringValue);
+  add("to",                                      0x17, Address);
+  add("x-mms-transaction-id",                    0x18, WSP.TextString);
+  add("x-mms-retrieve-status",                   0x19, RetrieveStatusValue);
+  add("x-mms-retrieve-text",                     0x1A, EncodedStringValue);
+  //add("x-mms-read-status", 0x1B);
+  add("x-mms-reply-charging",                    0x1C, WSP.ReplyChargingValue);
+  add("x-mms-reply-charging-deadline",           0x1D, ExpiryValue);
+  add("x-mms-reply-charging-id",                 0x1E, WSP.TextString);
+  add("x-mms-reply-charging-size",               0x1F, WSP.LongInteger);
+  add("x-mms-previously-sent-by",                0x20, PreviouslySentByValue);
+  add("x-mms-previously-sent-date",              0x21, PreviouslySentDateValue);
+  add("x-mms-store",                             0x22, BooleanValue);
+  add("x-mms-mm-state",                          0x23, MmStateValue);
+  add("x-mms-mm-flags",                          0x24, MmFlagsValue);
+  //add("x-mms-store-status", 0x25);
+  //add("x-mms-store-status-text", 0x26);
+  add("x-mms-stored",                            0x27, BooleanValue);
+  //add("x-mms-attributes", 0x28);
+  add("x-mms-totals",                            0x29, BooleanValue);
+  //add("x-mms-mbox-totals", 0x2A);
+  add("x-mms-quotas",                            0x2B, BooleanValue);
+  //add("x-mms-mbox-quotas", 0x2C);
+  add("x-mms-message-count",                     0x2D, WSP.IntegerValue);
+  //add("content", 0x2E);
+  add("x-mms-start",                             0x2F, WSP.IntegerValue);
+  //add("additional-headers", 0x30);
+  add("x-mms-distribution-indicator",            0x31, BooleanValue);
+  add("x-mms-element-descriptor",                0x32, ElementDescriptorValue);
+  add("x-mms-limit",                             0x33, WSP.IntegerValue);
+  add("x-mms-recommended-retrieval-mode",        0x34, RecommendedRetrievalModeValue);
+  add("x-mms-recommended-retrieval-mode-text",   0x35, EncodedStringValue);
+  //add("x-mms-status-text", 0x36);
+  add("x-mms-applic-id",                         0x37, WSP.TextString);
+  add("x-mms-reply-applic-id",                   0x38, WSP.TextString);
+  add("x-mms-aux-applic-id",                     0x39, WSP.TextString);
+  add("x-mms-content-class",                     0x3A, ContentClassValue);
+  add("x-mms-drm-content",                       0x3B, BooleanValue);
+  add("x-mms-adaptation-allowed",                0x3C, BooleanValue);
+  add("x-mms-replace-id",                        0x3D, WSP.TextString);
+  add("x-mms-cancel-id",                         0x3E, WSP.TextString);
+  add("x-mms-cancel-status",                     0x3F, BooleanValue);
+
+  return names;
+})();
+
+// @see OMA-TS-MMS_ENC-V1_3-20110913-A Table 27: Parameter Name Assignments
+const MMS_WELL_KNOWN_PARAMS = (function () {
+  let params = {};
+
+  function add(name, number, coder) {
+    let entry = {
+      name: name,
+      number: number,
+      coder: coder,
+    };
+    params[name] = params[number] = entry;
+  }
+
+  add("type", 0x02, WSP.ConstrainedEncoding);
+
+  return params;
+})();
+
+let debug;
+if (DEBUG) {
+  debug = function (s) {
+    dump("-$- MmsPduHelper: " + s + "\n");
+  };
+} else {
+  debug = function (s) {};
+}
+
+const EXPORTED_SYMBOLS = ALL_CONST_SYMBOLS.concat([
+  // Utility functions
+  "translatePduErrorToStatus",
+
+  // Decoders
+  "BooleanValue",
+  "Address",
+  "HeaderField",
+  "MmsHeader",
+  "ContentClassValue",
+  "ContentLocationValue",
+  "ElementDescriptorValue",
+  "Parameter",
+  "EncodedStringValue",
+  "ExpiryValue",
+  "FromValue",
+  "MessageClassValue",
+  "ClassIdentifier",
+  "MessageTypeValue",
+  "PriorityValue",
+  "RecommendedRetrievalModeValue",
+  "ReplyChargingValue",
+  "StatusValue",
+
+  // Parser
+  "PduHelper",
+]);
+
new file mode 100644
--- /dev/null
+++ b/dom/mms/src/ril/MmsService.js
@@ -0,0 +1,480 @@
+/* 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";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+const RIL_MMSSERVICE_CONTRACTID = "@mozilla.org/mms/rilmmsservice;1";
+const RIL_MMSSERVICE_CID = Components.ID("{217ddd76-75db-4210-955d-8806cd8d87f9}");
+
+const DEBUG = false;
+
+const kNetworkInterfaceStateChangedTopic = "network-interface-state-changed";
+const kXpcomShutdownObserverTopic        = "xpcom-shutdown";
+
+// File modes for saving MMS attachments.
+const FILE_OPEN_MODE = FileUtils.MODE_CREATE
+                     | FileUtils.MODE_WRONLY
+                     | FileUtils.MODE_TRUNCATE;
+
+// Size of each segment in a nsIStorageStream. Must be a power of two.
+const STORAGE_STREAM_SEGMENT_SIZE = 4096;
+
+// HTTP status codes:
+// @see http://tools.ietf.org/html/rfc2616#page-39
+const HTTP_STATUS_OK = 200;
+
+const CONFIG_SEND_REPORT_NEVER       = 0;
+const CONFIG_SEND_REPORT_DEFAULT_NO  = 1;
+const CONFIG_SEND_REPORT_DEFAULT_YES = 2;
+const CONFIG_SEND_REPORT_ALWAYS      = 3;
+
+XPCOMUtils.defineLazyServiceGetter(this, "gpps",
+                                   "@mozilla.org/network/protocol-proxy-service;1",
+                                   "nsIProtocolProxyService");
+
+XPCOMUtils.defineLazyGetter(this, "MMS", function () {
+  let MMS = {};
+  Cu.import("resource://gre/modules/MmsPduHelper.jsm", MMS);
+  return MMS;
+});
+
+/**
+ * MmsService
+ */
+function MmsService() {
+  Services.obs.addObserver(this, kXpcomShutdownObserverTopic, false);
+  Services.obs.addObserver(this, kNetworkInterfaceStateChangedTopic, false);
+}
+MmsService.prototype = {
+
+  classID:   RIL_MMSSERVICE_CID,
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMmsService,
+                                         Ci.nsIWapPushApplication,
+                                         Ci.nsIObserver,
+                                         Ci.nsIProtocolProxyFilter]),
+
+  /**
+   * Whether or not should we enable X-Mms-Report-Allowed in M-NotifyResp.ind
+   * and M-Acknowledge.ind PDU.
+   */
+  confSendDeliveryReport: CONFIG_SEND_REPORT_DEFAULT_YES,
+
+  proxyInfo: null,
+  MMSC: null,
+
+  /** MMS proxy filter reference count. */
+  proxyFilterRefCount: 0,
+
+  /**
+   * Calculate Whether or not should we enable X-Mms-Report-Allowed.
+   *
+   * @param config
+   *        Current config value.
+   * @param wish
+   *        Sender wish. Could be undefined, false, or true.
+   */
+  getReportAllowed: function getReportAllowed(config, wish) {
+    if ((config == CONFIG_SEND_REPORT_DEFAULT_NO)
+        || (config == CONFIG_SEND_REPORT_DEFAULT_YES)) {
+      if (wish != null) {
+        config += (wish ? 1 : -1);
+      }
+    }
+    return config >= CONFIG_SEND_REPORT_DEFAULT_YES;
+  },
+
+  /**
+   * Acquire referece-counted MMS proxy filter.
+   */
+  acquireProxyFilter: function acquireProxyFilter() {
+    if (!this.proxyFilterRefCount) {
+      debug("Register proxy filter");
+      gpps.registerFilter(this, 0);
+    }
+    this.proxyFilterRefCount++;
+  },
+
+  /**
+   * Release referece-counted MMS proxy filter.
+   */
+  releaseProxyFilter: function releaseProxyFilter() {
+    this.proxyFilterRefCount--;
+    if (this.proxyFilterRefCount <= 0) {
+      this.proxyFilterRefCount = 0;
+
+      debug("Unregister proxy filter");
+      gpps.unregisterFilter(this);
+    }
+  },
+
+  /**
+   * Send MMS request to MMSC.
+   *
+   * @param method
+   *        "GET" or "POST".
+   * @param url
+   *        Target url string.
+   * @param istream [optional]
+   *        An nsIInputStream instance as data source to be sent.
+   * @param callback
+   *        A callback function that takes two arguments: one for http status,
+   *        the other for wrapped PDU data for further parsing.
+   */
+  sendMmsRequest: function sendMmsRequest(method, url, istream, callback) {
+    let that = this;
+    function releaseProxyFilterAndCallback(status, data) {
+      // Always release proxy filter before callback.
+      that.releaseProxyFilter(false);
+      if (callback) {
+        callback(status, data);
+      }
+    }
+
+    this.acquireProxyFilter();
+
+    try {
+      let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+                .createInstance(Ci.nsIXMLHttpRequest);
+
+      // Basic setups
+      xhr.open(method, url, true);
+      xhr.responseType = "arraybuffer";
+      if (istream) {
+        xhr.setRequestHeader("Content-Type", "application/vnd.wap.mms-message");
+        xhr.setRequestHeader("Content-Length", istream.available());
+      } else {
+        xhr.setRequestHeader("Content-Length", 0);
+      }
+
+      // Setup event listeners
+      xhr.onerror = function () {
+        debug("xhr error, response headers: " + xhr.getAllResponseHeaders());
+        releaseProxyFilterAndCallback(xhr.status, null);
+      };
+      xhr.onreadystatechange = function () {
+        if (xhr.readyState != Ci.nsIXMLHttpRequest.DONE) {
+          return;
+        }
+
+        let data = null;
+        switch (xhr.status) {
+          case HTTP_STATUS_OK: {
+            debug("xhr success, response headers: " + xhr.getAllResponseHeaders());
+
+            let array = new Uint8Array(xhr.response);
+            if (false) {
+              for (let begin = 0; begin < array.length; begin += 20) {
+                debug("res: " + JSON.stringify(array.subarray(begin, begin + 20)));
+              }
+            }
+
+            data = {array: array, offset: 0};
+            break;
+          }
+          default: {
+            debug("xhr done, but status = " + xhr.status);
+            break;
+          }
+        }
+
+        releaseProxyFilterAndCallback(xhr.status, data);
+      }
+
+      // Send request
+      xhr.send(istream);
+    } catch (e) {
+      debug("xhr error, can't send: " + e.message);
+      releaseProxyFilterAndCallback(0, null);
+    }
+  },
+
+  /**
+   * Send M-NotifyResp.ind back to MMSC.
+   *
+   * @param tid
+   *        X-Mms-Transaction-ID of the message.
+   * @param status
+   *        X-Mms-Status of the response.
+   * @param ra
+   *        X-Mms-Report-Allowed of the response.
+   *
+   * @see OMA-TS-MMS_ENC-V1_3-20110913-A section 6.2
+   */
+  sendNotificationResponse: function sendNotificationResponse(tid, status, ra) {
+    debug("sendNotificationResponse: tid = " + tid + ", status = " + status
+          + ", reportAllowed = " + ra);
+
+    let headers = {};
+
+    // Mandatory fields
+    headers["x-mms-message-type"] = MMS.MMS_PDU_TYPE_NOTIFYRESP_IND;
+    headers["x-mms-transaction-id"] = tid;
+    headers["x-mms-mms-version"] = MMS.MMS_VERSION;
+    headers["x-mms-status"] = status;
+    // Optional fields
+    headers["x-mms-report-allowed"] = ra;
+
+    let istream = MMS.PduHelper.compose(null, {headers: headers});
+    this.sendMmsRequest("POST", this.MMSC, istream);
+  },
+
+  /**
+   * @param file
+   *        A nsIFile object indicating where to save the data.
+   * @param data
+   *        An array of raw octets.
+   * @param callback
+   *        Callback function when I/O is done.
+   *
+   * @return An nsIRequest representing the copy operation returned by
+   *         NetUtil.asyncCopy().
+   */
+  saveContentToFile: function saveContentToFile(file, data, callback) {
+    // Write to a StorageStream for NetUtil.asyncCopy()
+    let sstream = Cc["@mozilla.org/storagestream;1"]
+                  .createInstance(Ci.nsIStorageStream);
+    sstream.init(STORAGE_STREAM_SEGMENT_SIZE, data.length, null);
+    let bostream = Cc["@mozilla.org/binaryoutputstream;1"]
+                   .createInstance(Ci.nsIBinaryOutputStream);
+    bostream.setOutputStream(sstream.getOutputStream(0));
+    bostream.writeByteArray(data, data.length);
+    bostream.close();
+
+    // Write message body to file
+    let ofstream = FileUtils.openSafeFileOutputStream(file, FILE_OPEN_MODE);
+    return NetUtil.asyncCopy(sstream.newInputStream(0), ofstream, callback);
+  },
+
+  /**
+   * @param msg
+   *        A MMS message object.
+   * @param callback
+   *        A callback function that accepts one argument as retrieved message.
+   */
+  saveMessageContent: function saveMessageContent(msg, callback) {
+    function saveCallback(obj, counter, status) {
+      obj.saved = Components.isSuccessCode(status);
+      debug("saveMessageContent: " + obj.file.path + ", saved: " + obj.saved);
+
+      // The async copy callback may not be invoked in order, so we only
+      // callback after all of them were done.
+      counter.count++;
+      if (counter.count >= counter.max) {
+        if (callback) {
+          callback(msg);
+        }
+      }
+    }
+
+    let tid = msg.headers["x-mms-transaction-id"];
+    if (msg.parts) {
+      let counter = {max: msg.parts.length, count: 0};
+
+      msg.parts.forEach((function (part, index) {
+        part.file = FileUtils.getFile("ProfD", ["mms", tid, index], true);
+        if (!part.content) {
+          saveCallback(part, counter, Cr.NS_ERROR_NOT_AVAILABLE);
+        } else {
+          this.saveContentToFile(part.file, part.content,
+                                 saveCallback.bind(null, part, counter));
+        }
+      }).bind(this));
+    } else if (msg.content) {
+      msg.file = FileUtils.getFile("ProfD", ["mms", tid, "content"], true);
+      this.saveContentToFile(msg.file, msg.content,
+                             saveCallback.bind(null, msg, {max: 1, count: 0}));
+    } else {
+      // Nothing to save here.
+      if (callback) {
+        callback(msg);
+      }
+    }
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param options
+   *        Additional options to be passed to corresponding PDU handler.
+   *
+   * @return true if incoming data parsed successfully and passed to PDU
+   *         handler; false otherwise.
+   */
+  parseStreamAndDispatch: function parseStreamAndDispatch(data, options) {
+    let msg = MMS.PduHelper.parse(data, null);
+    if (!msg) {
+      return false;
+    }
+    debug("parseStreamAndDispatch: msg = " + JSON.stringify(msg));
+
+    switch (msg.type) {
+      case MMS.MMS_PDU_TYPE_NOTIFICATION_IND:
+        this.handleNotificationIndication(msg, options);
+        break;
+      case MMS.MMS_PDU_TYPE_RETRIEVE_CONF:
+        this.handleRetrieveConfirmation(msg, options);
+        break;
+      default:
+        debug("Unsupported X-MMS-Message-Type: " + msg.type);
+        return false;
+    }
+
+    return true;
+  },
+
+  /**
+   * Handle incoming M-Notification.ind PDU.
+   *
+   * @param msg
+   *        The MMS message object.
+   */
+  handleNotificationIndication: function handleNotificationIndication(msg) {
+    function callback(status, retr) {
+      let tid = msg.headers["x-mms-transaction-id"];
+
+      // For X-Mms-Report-Allowed
+      let wish = msg.headers["x-mms-delivery-report"];
+      // `The absence of the field does not indicate any default value.`
+      // So we go checking the same field in retrieved message instead.
+      if ((wish == null) && retr) {
+        wish = retr.headers["x-mms-delivery-report"];
+      }
+      let ra = this.getReportAllowed(this.confSendDeliveryReport, wish);
+
+      this.sendNotificationResponse(tid, status, ra);
+    }
+
+    function retrCallback(error, retr) {
+      callback.call(this, MMS.translatePduErrorToStatus(error), retr);
+    }
+
+    let url = msg.headers["x-mms-content-location"].uri;
+    this.sendMmsRequest("GET", url, null, (function (status, data) {
+      if (!data) {
+        callback.call(this, MMS.MMS_PDU_STATUS_DEFERRED, null);
+      } else if (!this.parseStreamAndDispatch(data, retrCallback.bind(this))) {
+        callback.call(this, MMS.MMS_PDU_STATUS_UNRECOGNISED, null);
+      }
+    }).bind(this));
+  },
+
+  /**
+   * Handle incoming M-Retrieve.conf PDU.
+   *
+   * @param msg
+   *        The MMS message object.
+   * @param callback
+   *        A callback function that accepts one argument as retrieved message.
+   */
+  handleRetrieveConfirmation: function handleRetrieveConfirmation(msg, callback) {
+    function callbackIfValid(status, msg) {
+      if (callback) {
+        callback(status, msg)
+      }
+    }
+
+    // Fix default header field values.
+    if (msg.headers["x-mms-delivery-report"] == null) {
+      msg.headers["x-mms-delivery-report"] = false;
+    }
+
+    let status = msg.headers["x-mms-retrieve-status"];
+    if ((status != null) && (status != MMS.MMS_PDU_ERROR_OK)) {
+      callbackIfValid(status, msg);
+      return;
+    }
+
+    this.saveMessageContent(msg, callbackIfValid.bind(null, MMS.MMS_PDU_ERROR_OK));
+  },
+
+  /**
+   * Update proxyInfo & MMSC from preferences.
+   *
+   * @param enabled
+   *        Enable or disable MMS proxy.
+   */
+  updateProxyInfo: function updateProxyInfo(enabled) {
+    try {
+      if (enabled) {
+        this.MMSC = Services.prefs.getCharPref("ril.data.mmsc");
+        this.proxyInfo = gpps.newProxyInfo("http",
+                                           Services.prefs.getCharPref("ril.data.mmsproxy"),
+                                           Services.prefs.getIntPref("ril.data.mmsport"),
+                                           Ci.nsIProxyInfo.TRANSPARENT_PROXY_RESOLVES_HOST,
+                                           -1, null);
+        debug("updateProxyInfo: "
+              + JSON.stringify({MMSC: this.MMSC, proxyInfo: this.proxyInfo}));
+        return;
+      }
+    } catch (e) {
+      // Failed to refresh proxy info from settings. Fallback to disable.
+    }
+
+    this.MMSC = null;
+    this.proxyInfo = null;
+  },
+
+  // nsIMmsService
+
+  hasSupport: function hasSupport() {
+    return true;
+  },
+
+  // nsIWapPushApplication
+
+  receiveWapPush: function receiveWapPush(array, length, offset, options) {
+    this.parseStreamAndDispatch({array: array, offset: offset});
+  },
+
+  // nsIObserver
+
+  observe: function observe(subject, topic, data) {
+    switch (topic) {
+      case kNetworkInterfaceStateChangedTopic: {
+        let iface = subject.QueryInterface(Ci.nsINetworkInterface);
+        if ((iface.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE)
+            || (iface.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS)) {
+          this.updateProxyInfo(iface.state == Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED);
+        }
+        break;
+      }
+      case kXpcomShutdownObserverTopic: {
+        Services.obs.removeObserver(this, kXpcomShutdownObserverTopic);
+        Services.obs.removeObserver(this, kNetworkInterfaceStateChangedTopic);
+        break;
+      }
+    }
+  },
+
+  // nsIProtocolProxyFilter
+
+  applyFilter: function applyFilter(service, uri, proxyInfo) {
+    if (uri.prePath == this.MMSC) {
+      debug("applyFilter: match " + uri.spec);
+      return this.proxyInfo;
+    }
+
+    return proxyInfo;
+  },
+};
+
+const NSGetFactory = XPCOMUtils.generateNSGetFactory([MmsService]);
+
+let debug;
+if (DEBUG) {
+  debug = function (s) {
+    dump("-@- MmsService: " + s + "\n");
+  };
+} else {
+  debug = function (s) {};
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/mms/src/ril/MmsService.manifest
@@ -0,0 +1,4 @@
+# MmsService.js
+component {217ddd76-75db-4210-955d-8806cd8d87f9} MmsService.js
+contract @mozilla.org/mms/rilmmsservice;1 {217ddd76-75db-4210-955d-8806cd8d87f9}
+category profile-after-change MmsService @mozilla.org/mms/rilmmsservice;1
new file mode 100644
--- /dev/null
+++ b/dom/mms/src/ril/WapPushManager.js
@@ -0,0 +1,92 @@
+/* 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";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+Cu.import("resource://gre/modules/WspPduHelper.jsm");
+
+const DEBUG = false; // set to true to see debug messages
+
+/**
+ * Helpers for WAP PDU processing.
+ */
+let WapPushManager = {
+
+  /**
+   * Parse raw PDU data and deliver to a proper target.
+   *
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param options
+   *        Extra context for decoding.
+   */
+  processMessage: function processMessage(data, options) {
+    try {
+      PduHelper.parse(data, true, options);
+      debug("options: " + JSON.stringify(options));
+    } catch (ex) {
+      debug("Failed to parse sessionless WSP PDU: " + ex.message);
+      return;
+    }
+
+    let appid = options.headers["x-wap-application-id"];
+    if (!appid) {
+      debug("Push message doesn't contains X-Wap-Application-Id.");
+      return;
+    }
+
+    if (appid == "x-wap-application:mms.ua") {
+      let mmsService = Cc["@mozilla.org/mms/rilmmsservice;1"]
+                       .getService(Ci.nsIMmsService);
+      mmsService.QueryInterface(Ci.nsIWapPushApplication)
+                .receiveWapPush(data.array, data.array.length, data.offset, options);
+    } else {
+      debug("No WAP Push application registered for " + appid);
+    }
+  },
+
+  /**
+   * @param array
+   *        A Uint8Array or an octet array representing raw PDU data.
+   * @param length
+   *        Length of the array.
+   * @param offset
+   *        Offset of the array that a raw PDU data begins.
+   * @param options
+   *        WDP bearer information.
+   */
+  receiveWdpPDU: function receiveWdpPDU(array, length, offset, options) {
+    if ((options.bearer == null) || !options.sourceAddress
+        || (options.sourcePort == null) || !array) {
+      debug("Incomplete WDP PDU");
+      return;
+    }
+
+    if (options.destinationPort != WDP_PORT_PUSH) {
+      debug("Not WAP Push port: " + options.destinationPort);
+      return;
+    }
+
+    this.processMessage({array: array, offset: offset}, options);
+  },
+};
+
+let debug;
+if (DEBUG) {
+  debug = function (s) {
+    dump("-*- WapPushManager: " + s + "\n");
+  };
+} else {
+  debug = function (s) {};
+}
+
+const EXPORTED_SYMBOLS = ALL_CONST_SYMBOLS.concat([
+  "WapPushManager",
+]);
+
new file mode 100644
--- /dev/null
+++ b/dom/mms/src/ril/WspPduHelper.jsm
@@ -0,0 +1,2125 @@
+/* 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";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/wap_consts.js");
+
+let DEBUG; // set to true to see debug messages
+
+// Special ASCII characters
+const NUL = 0;
+const CR = 13;
+const LF = 10;
+const SP = 32;
+const HT = 9;
+const DQUOTE = 34;
+const DEL = 127;
+
+// Special ASCII character ranges
+const CTLS = 32;
+const ASCIIS = 128;
+
+/**
+ * Error class for generic encoding/decoding failures.
+ */
+function CodeError(message) {
+  this.name = "CodeError";
+  this.message = message || "Invalid format";
+}
+CodeError.prototype = new Error();
+CodeError.prototype.constructor = CodeError;
+
+/**
+ * Error class for unexpected NUL char at decoding text elements.
+ *
+ * @param message [optional]
+ *        A short description for the error.
+ */
+function NullCharError(message) {
+  this.name = "NullCharError";
+  this.message = message || "Null character found";
+}
+NullCharError.prototype = new CodeError();
+NullCharError.prototype.constructor = NullCharError;
+
+/**
+ * Error class for fatal encoding/decoding failures.
+ *
+ * This error is only raised when expected format isn't met and the parser
+ * context can't do anything more to either skip it or hand over to other
+ * alternative encoding/decoding steps.
+ *
+ * @param message [optional]
+ *        A short description for the error.
+ */
+function FatalCodeError(message) {
+  this.name = "FatalCodeError";
+  this.message = message || "Decoding fails";
+}
+FatalCodeError.prototype = new Error();
+FatalCodeError.prototype.constructor = FatalCodeError;
+
+/**
+ * Error class for undefined well known encoding.
+ *
+ * When a encoded header field/parameter has unknown/unsupported value, we may
+ * never know how to decode the next value. For example, a parameter of
+ * undefined well known encoding may be followed by a Q-value, which is
+ * basically a uintvar. However, there is no way you can distiguish an Q-value
+ * 0.64, encoded as 0x41, from a string begins with 'A', which is also 0x41.
+ * The `skipValue` will try the latter one, which is not expected.
+ *
+ * @param message [optional]
+ *        A short description for the error.
+ */
+function NotWellKnownEncodingError(message) {
+  this.name = "NotWellKnownEncodingError";
+  this.message = message || "Not well known encoding";
+}
+NotWellKnownEncodingError.prototype = new FatalCodeError();
+NotWellKnownEncodingError.prototype.constructor = NotWellKnownEncodingError;
+
+/**
+ * Internal helper function to retrieve the value of a property with its name
+ * specified by `name` inside the object `headers`.
+ *
+ * @param headers
+ *        An object that contains parsed header fields.
+ * @param name
+ *        Header name string to be checked.
+ *
+ * @return Value of specified header field.
+ *
+ * @throws FatalCodeError if headers[name] is undefined.
+ */
+function ensureHeader(headers, name) {
+  let value = headers[name];
+  // Header field might have a null value as NoValue
+  if (value === undefined) {
+    throw new FatalCodeError("ensureHeader: header " + name + " not defined");
+  }
+  return value;
+}
+
+/**
+ * Skip field value.
+ *
+ * The WSP field values are encoded so that the length of the field value can
+ * always be determined, even if the detailed format of a specific field value
+ * is not known. This makes it possible to skip over individual header fields
+ * without interpreting their content. ... the first octet in all the field
+ * values can be interpreted as follows:
+ *
+ *   0 -  30 | This octet is followed by the indicated number (0 - 30) of data
+ *             octets.
+ *        31 | This octet is followed by a unitvar, which indicates the number
+ *             of data octets after it.
+ *  32 - 127 | The value is a text string, terminated by a zero octet (NUL
+ *             character).
+ * 128 - 255 | It is an encoded 7-bit value; this header has no more data.
+ *
+ * @param data
+ *        A wrapped object containing raw PDU data.
+ *
+ * @return Skipped value of several possible types like string, integer, or
+ *         an array of octets.
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.1.2
+ */
+function skipValue(data) {
+  let begin = data.offset;
+  let value = Octet.decode(data);
+  if (value <= 31) {
+    if (value == 31) {
+      value = UintVar.decode(data);
+    }
+
+    if (value) {
+      // `value` can be larger than 30, max length of a multi-octet integer
+      // here. So we must decode it as an array instead.
+      value = Octet.decodeMultiple(data, data.offset + value);
+    } else {
+      value = null;
+    }
+  } else if (value <= 127) {
+    data.offset = begin;
+    value = NullTerminatedTexts.decode(data);
+  } else {
+    value &= 0x7F;
+  }
+
+  return value;
+}
+
+/**
+ * Helper function for decoding multiple alternative forms.
+ *
+ * @param data
+ *        A wrapped object containing raw PDU data.
+ * @param options
+ *        Extra context for decoding.
+ *
+ * @return Decoded value.
+ */
+function decodeAlternatives(data, options) {
+  let begin = data.offset;
+  for (let i = 2; i < arguments.length; i++) {
+    try {
+      return arguments[i].decode(data, options);
+    } catch (e) {
+      // Throw the last exception we get
+      if (i == (arguments.length - 1)) {
+        throw e;
+      }
+
+      data.offset = begin;
+    }
+  }
+}
+
+/**
+ * Helper function for encoding multiple alternative forms.
+ *
+ * @param data
+ *        A wrapped object to store encoded raw data.
+ * @param value
+ *        Object value of arbitrary type to be encoded.
+ * @param options
+ *        Extra context for encoding.
+ */
+function encodeAlternatives(data, value, options) {
+  let begin = data.offset;
+  for (let i = 3; i < arguments.length; i++) {
+    try {
+      arguments[i].encode(data, value, options);
+      return;
+    } catch (e) {
+      // Throw the last exception we get
+      if (i == (arguments.length - 1)) {
+        throw e;
+      }
+
+      data.offset = begin;
+    }
+  }
+}
+
+let Octet = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @throws RangeError if no more data is available.
+   */
+  decode: function decode(data) {
+    if (data.offset >= data.array.length) {
+      throw new RangeError();
+    }
+
+    return data.array[data.offset++];
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param end
+   *        An ending offset indicating the end of octet array to read.
+   *
+   * @return A decoded array object.
+   *
+   * @throws RangeError if no enough data to read.
+   * @throws TypeError if `data` has neither subarray() nor slice() method.
+   */
+  decodeMultiple: function decodeMultiple(data, end) {
+    if ((end < data.offset) || (end > data.array.length)) {
+      throw new RangeError();
+    }
+    if (end == data.offset) {
+      return null;
+    }
+
+    let result;
+    if (data.array.subarray) {
+      result = data.array.subarray(data.offset, end);
+    } else if (data.array.slice) {
+      result = data.array.slice(data.offset, end);
+    } else {
+      throw new TypeError();
+    }
+
+    data.offset = end;
+    return result;
+  },
+
+  /**
+   * Internal octet decoding for specific value.
+   *
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param expected
+   *        Expected octet value.
+   *
+   * @return Expected octet value.
+   *
+   * @throws CodeError if read octet is not equal to expected one.
+   */
+  decodeEqualTo: function decodeEqualTo(data, expected) {
+    if (this.decode(data) != expected) {
+      throw new CodeError("Octet - decodeEqualTo: doesn't match " + expected);
+    }
+
+    return expected;
+  },
+
+  /**
+   * @param data
+   *        A wrapped object to store encoded raw data.
+   * @param octet
+   *        Octet value to be encoded.
+   */
+  encode: function encode(data, octet) {
+    if (data.offset >= data.array.length) {
+      data.array.push(octet);
+      data.offset++;
+    } else {
+      data.array[data.offset++] = octet;
+    }
+  },
+};
+
+/**
+ * TEXT = <any OCTET except CTLs, but including LWS>
+ * CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
+ * LWS = [CRLF] 1*(SP|HT)
+ * CRLF = CR LF
+ * CR = <US-ASCII CR, carriage return (13)>
+ * LF = <US-ASCII LF, linefeed (10)>
+ * SP = <US-ASCII SP, space (32)>
+ * HT = <US-ASCII HT, horizontal-tab(9)>
+ *
+ * @see RFC 2616 clause 2.2 Basic Rules
+ */
+let Text = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded character.
+   *
+   * @throws NullCharError if a NUL character read.
+   * @throws CodeError if a control character read.
+   */
+  decode: function decode(data) {
+    let code = Octet.decode(data);
+    if ((code >= CTLS) && (code != DEL)) {
+      return String.fromCharCode(code);
+    }
+
+    if (code == NUL) {
+      throw new NullCharError();
+    }
+
+    if (code != CR) {
+      throw new CodeError("Text: invalid char code " + code);
+    }
+
+    // "A CRLF is allowed in the definition of TEXT only as part of a header
+    // field continuation. It is expected that the folding LWS will be
+    // replaced with a single SP before interpretation of the TEXT value."
+    // ~ RFC 2616 clause 2.2
+
+    let extra;
+
+    // Rethrow everything as CodeError. We had already a successful read above.
+    try {
+      extra = Octet.decode(data);
+      if (extra != LF) {
+        throw new CodeError("Text: doesn't match LWS sequence");
+      }
+
+      extra = Octet.decode(data);
+      if ((extra != SP) && (extra != HT)) {
+        throw new CodeError("Text: doesn't match LWS sequence");
+      }
+    } catch (e if e instanceof CodeError) {
+      throw e;
+    } catch (e) {
+      throw new CodeError("Text: doesn't match LWS sequence");
+    }
+
+    // Let's eat as many SP|HT as possible.
+    let begin;
+
+    // Do not throw anything here. We had already matched (SP | HT).
+    try {
+      do {
+        begin = data.offset;
+        extra = Octet.decode(data);
+      } while ((extra == SP) || (extra == HT));
+    } catch (e) {}
+
+    data.offset = begin;
+    return " ";
+  },
+
+  /**
+   * @param data
+   *        A wrapped object to store encoded raw data.
+   * @param text
+   *        String text of one character to be encoded.
+   *
+   * @throws CodeError if a control character got.
+   */
+  encode: function encode(data, text) {
+    if (!text) {
+      throw new CodeError("Text: empty string");
+    }
+
+    let code = text.charCodeAt(0);
+    if ((code < CTLS) || (code == DEL) || (code > 255)) {
+      throw new CodeError("Text: invalid char code " + code);
+    }
+    Octet.encode(data, code);
+  },
+};
+
+let NullTerminatedTexts = {
+  /**
+   * Decode internal referenced null terminated text string.
+   *
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded string.
+   */
+  decode: function decode(data) {
+    let str = "";
+    try {
+      // A End-of-string is also a CTL, which should cause a error.
+      while (true) {
+        str += Text.decode(data);
+      }
+    } catch (e if e instanceof NullCharError) {
+      return str;
+    }
+  },
+
+  /**
+   * @param data
+   *        A wrapped object to store encoded raw data.
+   * @param str
+   *        A String to be encoded.
+   */
+  encode: function encode(data, str) {
+    if (str) {
+      for (let i = 0; i < str.length; i++) {
+        Text.encode(data, str.charAt(i));
+      }
+    }
+    Octet.encode(data, 0);
+  },
+};
+
+/**
+ * TOKEN = 1*<any CHAR except CTLs or separators>
+ * CHAR = <any US-ASCII character (octets 0 - 127)>
+ * SEPARATORS = ()<>@,;:\"/[]?={} SP HT
+ *
+ * @see RFC 2616 clause 2.2 Basic Rules
+ */
+let Token = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded character.
+   *
+   * @throws NullCharError if a NUL character read.
+   * @throws CodeError if an invalid character read.
+   */
+  decode: function decode(data) {
+    let code = Octet.decode(data);
+    if ((code < ASCIIS) && (code >= CTLS)) {
+      if ((code == HT) || (code == SP)
+          || (code == 34) || (code == 40) || (code == 41) // ASCII "()
+          || (code == 44) || (code == 47)                 // ASCII ,/
+          || ((code >= 58) && (code <= 64))               // ASCII :;<=>?@
+          || ((code >= 91) && (code <= 93))               // ASCII [\]
+          || (code == 123) || (code == 125)) {            // ASCII {}
+        throw new CodeError("Token: invalid char code " + code);
+      }
+
+      return String.fromCharCode(code);
+    }
+
+    if (code == NUL) {
+      throw new NullCharError();
+    }
+
+    throw new CodeError("Token: invalid char code " + code);
+  },
+
+  /**
+   * @param data
+   *        A wrapped object to store encoded raw data.
+   * @param token
+   *        String text of one character to be encoded.
+   *
+   * @throws CodeError if an invalid character got.
+   */
+  encode: function encode(data, token) {
+    if (!token) {
+      throw new CodeError("Token: empty string");
+    }
+
+    let code = token.charCodeAt(0);
+    if ((code < ASCIIS) && (code >= CTLS)) {
+      if ((code == HT) || (code == SP)
+          || (code == 34) || (code == 40) || (code == 41) // ASCII "()
+          || (code == 44) || (code == 47)                 // ASCII ,/
+          || ((code >= 58) && (code <= 64))               // ASCII :;<=>?@
+          || ((code >= 91) && (code <= 93))               // ASCII [\]
+          || (code == 123) || (code == 125)) {            // ASCII {}
+        // Fallback to throw CodeError
+      } else {
+        Octet.encode(data, token.charCodeAt(0));
+	return;
+      }
+    }
+
+    throw new CodeError("Token: invalid char code " + code);
+  },
+};
+
+/**
+ * uric       = reserved | unreserved | escaped
+ * reserved   = ;/?:@&=+$,
+ * unreserved = alphanum | mark
+ * mark       = -_.!~*'()
+ * escaped    = % hex hex
+ * excluded but used = #%
+ *
+ * Or, in decimal, they are: 33,35-59,61,63-90,95,97-122,126
+ *
+ * @see RFC 2396 Uniform Resource Indentifiers (URI)
+ */
+let URIC = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded character.
+   *
+   * @throws NullCharError if a NUL character read.
+   * @throws CodeError if an invalid character read.
+   */
+  decode: function decode(data) {
+    let code = Octet.decode(data);
+    if (code == NUL) {
+      throw new NullCharError();
+    }
+
+    if ((code <= CTLS) || (code >= ASCIIS) || (code == 34) || (code == 60)
+        || (code == 62) || ((code >= 91) && (code <= 94)) || (code == 96)
+        || ((code >= 123) && (code <= 125)) || (code == 127)) {
+      throw new CodeError("URIC: invalid char code " + code);
+    }
+
+    return String.fromCharCode(code);
+  },
+};
+
+/**
+ * If the first character in the TEXT is in the range of 128-255, a Quote
+ * character must precede it. Otherwise the Quote character must be omitted.
+ * The Quote is not part of the contents.
+ *
+ *   Text-string = [Quote] *TEXT End-of-string
+ *   Quote = <Octet 127>
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.1
+ */
+let TextString = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded string.
+   */
+  decode: function decode(data) {
+    let begin = data.offset;
+    let firstCode = Octet.decode(data);
+    if (firstCode == 127) {
+      // Quote found, check if first char code is larger-equal than 128.
+      begin = data.offset;
+      try {
+        if (Octet.decode(data) < 128) {
+          throw new CodeError("Text-string: illegal quote found.");
+        }
+      } catch (e if e instanceof CodeError) {
+        throw e;
+      } catch (e) {
+        throw new CodeError("Text-string: unexpected error.");
+      }
+    } else if (firstCode >= 128) {
+      throw new CodeError("Text-string: invalid char code " + firstCode);
+    }
+
+    data.offset = begin;
+    return NullTerminatedTexts.decode(data);
+  },
+
+  /**
+   * @param data
+   *        A wrapped object to store encoded raw data.
+   * @param str
+   *        A String to be encoded.
+   */
+  encode: function encode(data, str) {
+    if (!str) {
+      Octet.encode(data, 0);
+      return;
+    }
+
+    let firstCharCode = str.charCodeAt(0);
+    if (firstCharCode >= 128) {
+      Octet.encode(data, 127);
+    }
+
+    NullTerminatedTexts.encode(data, str);
+  },
+};
+
+/**
+ * Token-text = Token End-of-string
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.1
+ */
+let TokenText = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded string.
+   */
+  decode: function decode(data) {
+    let str = "";
+    try {
+      // A End-of-string is also a CTL, which should cause a error.
+      while (true) {
+        str += Token.decode(data);
+      }
+    } catch (e if e instanceof NullCharError) {
+      return str;
+    }
+  },
+
+  /**
+   * @param data
+   *        A wrapped object to store encoded raw data.
+   * @param str
+   *        A String to be encoded.
+   */
+  encode: function encode(data, str) {
+    if (str) {
+      for (let i = 0; i < str.length; i++) {
+        Token.encode(data, str.charAt(i));
+      }
+    }
+    Octet.encode(data, 0);
+  },
+};
+
+/**
+ * The TEXT encodes an RFC2616 Quoted-string with the enclosing
+ * quotation-marks <"> removed.
+ *
+ *   Quoted-string = <Octet 34> *TEXT End-of-string
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.1
+ */
+let QuotedString = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded string.
+   *
+   * @throws CodeError if first octet read is not 0x34.
+   */
+  decode: function decode(data) {
+    let value = Octet.decode(data);
+    if (value != 34) {
+      throw new CodeError("Quoted-string: not quote " + value);
+    }
+
+    return NullTerminatedTexts.decode(data);
+  },
+};
+
+/**
+ * Integers in range 0-127 shall be encoded as a one octet value with the
+ * most significant bit set to one (1xxx xxxx) and with the value in the
+ * remaining least significant bits.
+ *
+ *   Short-integer = OCTET
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.1
+ */
+let ShortInteger = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded integer value.
+   *
+   * @throws CodeError if the octet read is less than 0x80.
+   */
+  decode: function decode(data) {
+    let value = Octet.decode(data);
+    if (!(value & 0x80)) {
+      throw new CodeError("Short-integer: invalid value " + value);
+    }
+
+    return (value & 0x7F);
+  },
+
+  /**
+   * @param data
+   *        A wrapped object to store encoded raw data.
+   * @param value
+   *        A numeric value to be encoded.
+   *
+   * @throws CodeError if the octet read is larger-equal than 0x80.
+   */
+  encode: function encode(data, value) {
+    if (value & 0x80) {
+      throw new CodeError("Short-integer: invalid value " + value);
+    }
+
+    Octet.encode(data, value | 0x80);
+  },
+};
+
+/**
+ * The content octets shall be an unsigned integer value with the most
+ * significant octet encoded first (big-endian representation). The minimum
+ * number of octets must be used to encode the value.
+ *
+ *   Long-integer = Short-length Multi-octet-integer
+ *   Short-length = <Any octet 0-30>
+ *   Multi-octet-integer = 1*30 OCTET
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.1
+ */
+let LongInteger = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param length
+   *        Number of octets to read.
+   *
+   * @return A decoded integer value or an octets array of max 30 elements.
+   */
+  decodeMultiOctetInteger: function decodeMultiOctetInteger(data, length) {
+    if (length < 7) {
+      // Return a integer instead of an array as possible. For a multi-octet
+      // integer, there are only maximum 53 bits for integer in javascript. We
+      // will get an inaccurate one beyond that. We can't neither use bitwise
+      // operation here, for it will be limited in 32 bits.
+      let value = 0;
+      while (length--) {
+        value = value * 256 + Octet.decode(data);
+      }
+      return value;
+    }
+
+    return Octet.decodeMultiple(data, data.offset + length);
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded integer value or an octets array of max 30 elements.
+   *
+   * @throws CodeError if the length read is not in 1..30.
+   */
+  decode: function decode(data) {
+    let length = Octet.decode(data);
+    if ((length < 1) || (length > 30)) {
+      throw new CodeError("Long-integer: invalid length " + length);
+    }
+
+    return this.decodeMultiOctetInteger(data, length);
+  },
+};
+
+/**
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.1
+ */
+let UintVar = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded integer value.
+   */
+  decode: function decode(data) {
+    let value = Octet.decode(data);
+    let result = value & 0x7F;
+    while (value & 0x80) {
+      value = Octet.decode(data);
+      result = result * 128 + (value & 0x7F);
+    }
+
+    return result;
+  },
+};
+
+/**
+ * This encoding is used for token values, which have no well-known binary
+ * encoding, or when the assigned number of the well-known encoding is small
+ * enough to fit into Short-Integer.
+ *
+ *   Constrained-encoding = Extension-Media | Short-integer
+ *   Extension-Media = *TEXT End-of-string
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.1
+ */
+let ConstrainedEncoding = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decode integer value or string.
+   */
+  decode: function decode(data) {
+    return decodeAlternatives(data, null, NullTerminatedTexts, ShortInteger);
+  },
+};
+
+/**
+ * Value-length = Short-length | (Length-quote Length)
+ * Short-length = <Any octet 0-30>
+ * Length-quote = <Octet 31>
+ * Length = Uintvar-integer
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.2
+ */
+let ValueLength = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded integer value.
+   *
+   * @throws CodeError if the first octet read is larger than 31.
+   */
+  decode: function decode(data) {
+    let value = Octet.decode(data);
+    if (value <= 30) {
+      return value;
+    }
+
+    if (value == 31) {
+      return UintVar.decode(data);
+    }
+
+    throw new CodeError("Value-length: invalid value " + value);
+  },
+};
+
+/**
+ * No-value = <Octet 0>
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.3
+ */
+let NoValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Always returns null.
+   */
+  decode: function decode(data) {
+    Octet.decodeEqualTo(data, 0);
+    return null;
+  },
+};
+
+/**
+ * Text-value = No-value | Token-text | Quoted-string
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.3
+ */
+let TextValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded string or null for No-value.
+   */
+  decode: function decode(data) {
+    return decodeAlternatives(data, null, NoValue, TokenText, QuotedString);
+  },
+};
+
+/**
+ * Integer-Value = Short-integer | Long-integer
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.3
+ */
+let IntegerValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded integer value or array of octets.
+   */
+  decode: function decode(data) {
+    return decodeAlternatives(data, null, ShortInteger, LongInteger);
+  },
+};
+
+/**
+ * The encoding of dates shall be done in number of seconds from
+ * 1970-01-01, 00:00:00 GMT.
+ *
+ *   Date-value = Long-integer
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.3
+ */
+let DateValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A Date object.
+   */
+  decode: function decode(data) {
+    let numOrArray = LongInteger.decode(data);
+    let seconds;
+    if (typeof numOrArray == "number") {
+      seconds = numOrArray;
+    } else {
+      seconds = 0;
+      for (let i = 0; i < numOrArray.length; i++) {
+        seconds = seconds * 256 + numOrArray[i];
+      }
+    }
+
+    return new Date(seconds * 1000);
+  },
+};
+
+/**
+ * Delta-seconds-value = Integer-value
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.3
+ */
+let DeltaSecondsValue = IntegerValue;
+
+/**
+ * Quality factor 0 and quality factors with one or two decimal digits are
+ * encoded into 1-100; three digits ones into 101-1099.
+ *
+ *   Q-value = 1*2 OCTET
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.3
+ */
+let QValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded integer value of 1..1099.
+   *
+   * @throws CodeError if decoded UintVar is not in range 1..1099.
+   */
+  decode: function decode(data) {
+    let value = UintVar.decode(data);
+    if (value > 0) {
+      if (value <= 100) {
+        return (value - 1) / 100.0;
+      }
+      if (value <= 1099) {
+        return (value - 100) / 1000.0;
+      }
+    }
+
+    throw new CodeError("Q-value: invalid value " + value);
+  },
+};
+
+/**
+ * The three most significant bits of the Short-integer value are interpreted
+ * to encode a major version number in the range 1-7, and the four least
+ * significant bits contain a minor version number in the range 0-14. If
+ * there is only a major version number, this is encoded by placing the value
+ * 15 in the four least significant bits.
+ *
+ *   Version-value = Short-integer | Text-string
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.3
+ */
+let VersionValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Binary encoded version number.
+   */
+  decode: function decode(data) {
+    let begin = data.offset;
+    let value;
+    try {
+      value = ShortInteger.decode(data);
+      if ((value >= 0x10) && (value < 0x80)) {
+        return value;
+      }
+
+      throw new CodeError("Version-value: invalid value " + value);
+    } catch (e) {}
+
+    data.offset = begin;
+
+    let str = TextString.decode(data);
+    if (!str.match(/^[1-7](\.1?\d)?$/)) {
+      throw new CodeError("Version-value: invalid value " + str);
+    }
+
+    let major = str.charCodeAt(0) - 0x30;
+    let minor = 0x0F;
+    if (str.length > 1) {
+      minor = str.charCodeAt(2) - 0x30;
+      if (str.length > 3) {
+        minor = 10 + (str.charCodeAt(3) - 0x30);
+        if (minor > 14) {
+          throw new CodeError("Version-value: invalid minor " + minor);
+        }
+      }
+    }
+
+    return major << 4 | minor;
+  },
+};
+
+/**
+ * URI value should be encoded per [RFC2616], but service user may use a
+ * different format.
+ *
+ *   Uri-value = Text-string
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.3
+ * @see RFC 2616 clause 2.2 Basic Rules
+ */
+let UriValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded uri string.
+   */
+  decode: function decode(data) {
+    let str = "";
+    try {
+      // A End-of-string is also a CTL, which should cause a error.
+      while (true) {
+        str += URIC.decode(data);
+      }
+    } catch (e if e instanceof NullCharError) {
+      return str;
+    }
+  },
+};
+
+/**
+ * Parameter = Typed-parameter | Untyped-parameter
+ *
+ * For Typed-parameters, the actual expected type of the value is implied by
+ * the well-known parameter. In addition to the expected type, there may be no
+ * value. If the value cannot be encoded using expected type, it shall be
+ * encoded as text.
+ *
+ *   Typed-parameter = Well-known-parameter-token Typed-value
+ *   Well-known-parameter-token = Integer-value
+ *   Typed-value = Compact-value | Text-value
+ *   Compact-value = Integer-value | Date-value | Delta-seconds-value | Q-value
+ *                   | Version-value | Uri-value
+ *
+ * For Untyped-parameters, the type of the value is unknown, but is shall be
+ * encoded as an integer, if that is possible.
+ *
+ *   Untyped-parameter = Token-text Untyped-value
+ *   Untyped-value = Integer-value | Text-value
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.4
+ */
+let Parameter = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded object containing `name` and `value` properties or null
+   *         if something wrong. The `name` property must be a string, but the
+   *         `value` property can be many different types depending on `name`.
+   *
+   * @throws CodeError if decoded IntegerValue is an array.
+   * @throws NotWellKnownEncodingError if decoded well-known parameter number
+   *         is not registered or supported.
+   */
+  decodeTypedParameter: function decodeTypedParameter(data) {
+    let numOrArray = IntegerValue.decode(data);
+    // `decodeIntegerValue` can return a array, which doesn't apply here.
+    if (typeof numOrArray != "number") {
+      throw new CodeError("Typed-parameter: invalid integer type");
+    }
+
+    let number = numOrArray;
+    let param = WSP_WELL_KNOWN_PARAMS[number];
+    if (!param) {
+      throw new NotWellKnownEncodingError(
+        "Typed-parameter: not well known parameter " + number);
+    }
+
+    let begin = data.offset, value;
+    try {
+      // Althought Text-string is not included in BNF of Compact-value, but
+      // some service provider might still pass a less-strict text form and
+      // cause a unexpected CodeError raised. For example, the `start`
+      // parameter expects its value of Text-value, but service provider might
+      // gives "<smil>", which contains illegal characters "<" and ">".
+      value = decodeAlternatives(data, null,
+                                 param.coder, TextValue, TextString);
+    } catch (e) {
+      data.offset = begin;
+
+      // Skip current parameter.
+      value = skipValue(data);
+      debug("Skip malformed typed parameter: "
+            + JSON.stringify({name: param.name, value: value}));
+
+      return null;
+    }
+
+    return {
+      name: param.name,
+      value: value,
+    };
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded object containing `name` and `value` properties or null
+   *         if something wrong. The `name` property must be a string, but the
+   *         `value` property can be many different types depending on `name`.
+   */
+  decodeUntypedParameter: function decodeUntypedParameter(data) {
+    let name = TokenText.decode(data);
+
+    let begin = data.offset, value;
+    try {
+      value = decodeAlternatives(data, null, IntegerValue, TextValue);
+    } catch (e) {
+      data.offset = begin;
+
+      // Skip current parameter.
+      value = skipValue(data);
+      debug("Skip malformed untyped parameter: "
+            + JSON.stringify({name: name, value: value}));
+
+      return null;
+    }
+
+    return {
+      name: name.toLowerCase(),
+      value: value,
+    };
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded object containing `name` and `value` properties or null
+   *         if something wrong. The `name` property must be a string, but the
+   *         `value` property can be many different types depending on `name`.
+   */
+  decode: function decode(data) {
+    let begin = data.offset;
+    try {
+      return this.decodeTypedParameter(data);
+    } catch (e) {
+      data.offset = begin;
+      return this.decodeUntypedParameter(data);
+    }
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param end
+   *        Ending offset of following parameters.
+   *
+   * @return An array of decoded objects.
+   */
+  decodeMultiple: function decodeMultiple(data, end) {
+    let params = null, param;
+
+    while (data.offset < end) {
+      try {
+        param = this.decode(data);
+      } catch (e) {
+        break;
+      }
+      if (param) {
+        if (!params) {
+          params = {};
+        }
+        params[param.name] = param.value;
+      }
+    }
+
+    return params;
+  },
+};
+
+/**
+ * Header = Message-header | Shift-sequence
+ * Message-header = Well-known-header | Application-header
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.6
+ */
+let Header = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded object containing `name` and `value` properties or null
+   *         in case of a failed parsing. The `name` property must be a string,
+   *         but the `value` property can be many different types depending on
+   *         `name`.
+   */
+  decodeMessageHeader: function decodeMessageHeader(data) {
+    return decodeAlternatives(data, null, WellKnownHeader, ApplicationHeader);
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded object containing `name` and `value` properties or null
+   *         in case of a failed parsing. The `name` property must be a string,
+   *         but the `value` property can be many different types depending on
+   *         `name`.
+   */
+  decode: function decode(data) {
+    // TODO: support header code page shift-sequence
+    return this.decodeMessageHeader(data);
+  },
+};
+
+/**
+ * Well-known-header = Well-known-field-name Wap-value
+ * Well-known-field-name = Short-integer
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.6
+ */
+let WellKnownHeader = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded object containing `name` and `value` properties or null
+   *         in case of a failed parsing. The `name` property must be a string,
+   *         but the `value` property can be many different types depending on
+   *         `name`.
+   *
+   * @throws NotWellKnownEncodingError if decoded well-known header field
+   *         number is not registered or supported.
+   */
+  decode: function decode(data) {
+    let index = ShortInteger.decode(data);
+
+    let entry = WSP_HEADER_FIELDS[index];
+    if (!entry) {
+      throw new NotWellKnownEncodingError(
+        "Well-known-header: not well known header " + index);
+    }
+
+    let begin = data.offset, value;
+    try {
+      value = decodeAlternatives(data, null, entry.coder, TextValue);
+    } catch (e) {
+      data.offset = begin;
+
+      value = skipValue(data);
+      debug("Skip malformed well known header(" + index + "): "
+            + JSON.stringify({name: entry.name, value: value}));
+
+      return null;
+    }
+
+    return {
+      name: entry.name,
+      value: value,
+    };
+  },
+};
+
+/**
+ * Application-header = Token-text Application-specific-value
+ * Application-specific-value = Text-string
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.6
+ */
+let ApplicationHeader = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded object containing `name` and `value` properties or null
+   *         in case of a failed parsing. The `name` property must be a string,
+   *         but the `value` property can be many different types depending on
+   *         `name`.
+   */
+  decode: function decode(data) {
+    let name = TokenText.decode(data);
+
+    let begin = data.offset, value;
+    try {
+      value = TextString.decode(data);
+    } catch (e) {
+      data.offset = begin;
+
+      value = skipValue(data);
+      debug("Skip malformed application header: "
+            + JSON.stringify({name: name, value: value}));
+
+      return null;
+    }
+
+    return {
+      name: name.toLowerCase(),
+      value: value,
+    };
+  },
+
+  /**
+   * @param data
+   *        A wrapped object to store encoded raw data.
+   * @param header
+   *        An object containing two attributes: a string-typed `name` and a
+   *        `value` of arbitrary type.
+   *
+   * @throws CodeError if got an empty header name.
+   */
+  encode: function encode(data, header) {
+    if (!header.name) {
+      throw new CodeError("Application-header: empty header name");
+    }
+
+    TokenText.encode(data, header.name);
+    TextString.encode(data, header.value);
+  },
+};
+
+/**
+ * Field-name = Token-text | Well-known-field-name
+ * Well-known-field-name = Short-integer
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.6
+ */
+let FieldName = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A field name string.
+   *
+   * @throws NotWellKnownEncodingError if decoded well-known header field
+   *         number is not registered or supported.
+   */
+  decode: function decode(data) {
+    let begin = data.offset;
+    try {
+      return TokenText.decode(data).toLowerCase();
+    } catch (e) {}
+
+    data.offset = begin;
+
+    let number = ShortInteger.decode(data);
+    let entry = WSP_HEADER_FIELDS[number];
+    if (!entry) {
+      throw new NotWellKnownEncodingError(
+        "Field-name: not well known encoding " + number);
+    }
+
+    return entry.name;
+  },
+};
+
+/**
+ * Accept-charset-value = Constrained-charset | Accept-charset-general-form
+ * Constrained-charset = Any-charset | Constrained-encoding
+ * Any-charset = <Octet 128>
+ * Accept-charset-general-form = Value-length (Well-known-charset | Token-text) [Q-value]
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.8
+ */
+let AcceptCharsetValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A object with a property `charset` of string "*".
+   */
+  decodeAnyCharset: function decodeAnyCharset(data) {
+    Octet.decodeEqualTo(data, 128);
+    return {charset: "*"};
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A object with a string property `charset` and a optional integer
+   *         property `q`.
+   *
+   * @throws NotWellKnownEncodingError if decoded well-known charset number is
+   *         not registered or supported.
+   */
+  decodeConstrainedCharset: function decodeConstrainedCharset(data) {
+    let begin = data.offset;
+    try {
+      return this.decodeAnyCharset(data);
+    } catch (e) {}
+
+    data.offset = begin;
+
+    let numOrStr = ConstrainedEncoding.decode(data);
+    if (typeof numOrStr == "string") {
+      return {charset: numOrStr};
+    }
+
+    let charset = numOrStr;
+    let entry = WSP_WELL_KNOWN_CHARSETS[charset];
+    if (!entry) {
+      throw new NotWellKnownEncodingError(
+        "Constrained-charset: not well known charset: " + charset);
+    }
+
+    return {charset: entry.name};
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A object with a string property `charset` and a optional integer
+   *         property `q`.
+   */
+  decodeAcceptCharsetGeneralForm: function decodeAcceptCharsetGeneralForm(data) {
+    let length = ValueLength.decode(data);
+
+    let begin = data.offset;
+    let end = begin + length;
+
+    let result;
+    try {
+      result = WellKnownCharset.decode(data);
+    } catch (e) {
+      data.offset = begin;
+
+      result = {charset: TokenText.decode(data)};
+      if (data.offset < end) {
+        result.q = QValue.decode(data);
+      }
+    }
+
+    if (data.offset != end) {
+      data.offset = end;
+    }
+
+    return result;
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A object with a string property `charset` and a optional integer
+   *         property `q`.
+   */
+  decode: function decode(data) {
+    let begin = data.offset;
+    try {
+      return this.decodeConstrainedCharset(data);
+    } catch (e) {
+      data.offset = begin;
+      return this.decodeAcceptCharsetGeneralForm(data);
+    }
+  },
+};
+
+/**
+ * Well-known-charset = Any-charset | Integer-value
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.8
+ */
+let WellKnownCharset = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A object with a string property `charset`.
+   *
+   * @throws CodeError if decoded charset number is an array.
+   * @throws NotWellKnownEncodingError if decoded well-known charset number
+   *         is not registered or supported.
+   */
+  decode: function decode(data) {
+    let begin = data.offset;
+
+    try {
+      return AcceptCharsetValue.decodeAnyCharset(data);
+    } catch (e) {}
+
+    data.offset = begin;
+
+    // `IntegerValue.decode` can return a array, which doesn't apply here.
+    let numOrArray = IntegerValue.decode(data);
+    if (typeof numOrArray != "number") {
+      throw new CodeError("Well-known-charset: invalid integer type");
+    }
+
+    let charset = numOrArray;
+    let entry = WSP_WELL_KNOWN_CHARSETS[charset];
+    if (!entry) {
+      throw new NotWellKnownEncodingError(
+        "Well-known-charset: not well known charset " + charset);
+    }
+
+    return {charset: entry.name};
+  },
+};
+
+/**
+ * The short form of the Content-type-value MUST only be used when the
+ * well-known media is in the range of 0-127 or a text string. In all other
+ * cases the general form MUST be used.
+ *
+ *   Content-type-value = Constrained-media | Content-general-form
+ *   Constrained-media = Constrained-encoding
+ *   Content-general-form = Value-length Media-type
+ *   Media-type = Media *(Parameter)
+ *   Media = Well-known-media | Extension-Media
+ *   Well-known-media = Integer-value
+ *   Extension-Media = *TEXT End-of-string
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.24
+ */
+let ContentTypeValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded object containing `media` and `params` properties or
+   *         null in case of a failed parsing. The `media` property must be a
+   *         string, and the `params` property is always null.
+   *
+   * @throws NotWellKnownEncodingError if decoded well-known content type number
+   *         is not registered or supported.
+   */
+  decodeConstrainedMedia: function decodeConstrainedMedia(data) {
+    let numOrStr = ConstrainedEncoding.decode(data);
+    if (typeof numOrStr == "string") {
+      return {
+        media: numOrStr.toLowerCase(),
+        params: null,
+      };
+    }
+
+    let number = numOrStr;
+    let entry = WSP_WELL_KNOWN_CONTENT_TYPES[number];
+    if (!entry) {
+      throw new NotWellKnownEncodingError(
+        "Constrained-media: not well known media " + number);
+    }
+
+    return {
+      media: entry.type,
+      params: null,
+    };
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decode string.
+   *
+   * @throws CodeError if decoded content type number is an array.
+   * @throws NotWellKnownEncodingError if decoded well-known content type
+   *         number is not registered or supported.
+   */
+  decodeMedia: function decodeMedia(data) {
+    let begin = data.offset, number;
+    try {
+      number = IntegerValue.decode(data);
+    } catch (e) {
+      data.offset = begin;
+      return NullTerminatedTexts.decode(data).toLowerCase();
+    }
+
+    // `decodeIntegerValue` can return a array, which doesn't apply here.
+    if (typeof number != "number") {
+      throw new CodeError("Media: invalid integer type");
+    }
+
+    let entry = WSP_WELL_KNOWN_CONTENT_TYPES[number];
+    if (!entry) {
+      throw new NotWellKnownEncodingError("Media: not well known media " + number);
+    }
+
+    return entry.type;
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param end
+   *        Ending offset of the Media-type value.
+   *
+   * @return A decoded object containing `media` and `params` properties or
+   *         null in case of a failed parsing. The `media` property must be a
+   *         string, and the `params` property is a hash map from a string to
+   *         an value of unspecified type.
+   */
+  decodeMediaType: function decodeMediaType(data, end) {
+    let media = this.decodeMedia(data);
+    let params = Parameter.decodeMultiple(data, end);
+
+    return {
+      media: media,
+      params: params,
+    };
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded object containing `media` and `params` properties or
+   *         null in case of a failed parsing. The `media` property must be a
+   *         string, and the `params` property is null or a hash map from a
+   *         string to an value of unspecified type.
+   */
+  decodeContentGeneralForm: function decodeContentGeneralForm(data) {
+    let length = ValueLength.decode(data);
+    let end = data.offset + length;
+
+    let value = this.decodeMediaType(data, end);
+
+    if (data.offset != end) {
+      data.offset = end;
+    }
+
+    return value;
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded object containing `media` and `params` properties or
+   *         null in case of a failed parsing. The `media` property must be a
+   *         string, and the `params` property is null or a hash map from a
+   *         string to an value of unspecified type.
+   */
+  decode: function decode(data) {
+    let begin = data.offset;
+
+    try {
+      return this.decodeConstrainedMedia(data);
+    } catch (e) {
+      data.offset = begin;
+      return this.decodeContentGeneralForm(data);
+    }
+  },
+};
+
+/**
+ * Application-id-value = Uri-value | App-assigned-code
+ * App-assigned-code = Integer-value
+ *
+ * @see WAP-230-WSP-20010705-a clause 8.4.2.54
+ */
+let ApplicationIdValue = {
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return Decoded string value.
+   *
+   * @throws CodeError if decoded application id number is an array.
+   * @throws NotWellKnownEncodingError if decoded well-known application id
+   *         number is not registered or supported.
+   */
+  decode: function decode(data) {
+    let begin = data.offset;
+    try {
+      return UriValue.decode(data);
+    } catch (e) {}
+
+    data.offset = begin;
+
+    // `decodeIntegerValue` can return a array, which doesn't apply here.
+    let numOrArray = IntegerValue.decode(data);
+    if (typeof numOrArray != "number") {
+      throw new CodeError("Application-id-value: invalid integer type");
+    }
+
+    let id = numOrArray;
+    let entry = OMNA_PUSH_APPLICATION_IDS[id];
+    if (!entry) {
+      throw new NotWellKnownEncodingError(
+        "Application-id-value: not well known id: " + id);
+    }
+
+    return entry.urn;
+  },
+};
+
+let PduHelper = {
+  /**
+   * Parse multiple header fields with end mark.
+   *
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param end
+   *        An ending offset indicating the end of headers.
+   * @param headers [optional]
+   *        An optional object to store parsed header fields. Created
+   *        automatically if undefined.
+   *
+   * @return A object containing decoded header fields as its attributes.
+   */
+  parseHeaders: function parseHeaders(data, end, headers) {
+    if (!headers) {
+      headers = {};
+    }
+
+    let header;
+    while (data.offset < end) {
+      try {
+        header = Header.decode(data);
+      } catch (e) {
+        break;
+      }
+      if (header) {
+        headers[header.name] = header.value;
+      }
+    }
+
+    if (data.offset != end) {
+      debug("Parser expects ending in " + end + ", but in " + data.offset);
+      // Explicitly seek to end in case of skipped header fields.
+      data.offset = end;
+    }
+
+    return headers;
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param msg
+   *        Message object to be populated with decoded header fields.
+   *
+   * @see WAP-230-WSP-20010705-a clause 8.2.4
+   */
+  parsePushHeaders: function parsePushHeaders(data, msg) {
+    if (!msg.headers) {
+      msg.headers = {};
+    }
+
+    let headersLen = UintVar.decode(data);
+    let headersEnd = data.offset + headersLen;
+
+    let contentType = ContentTypeValue.decode(data);
+    msg.headers["content-type"] = contentType;
+
+    msg.headers = this.parseHeaders(data, headersEnd, msg.headers);
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return An array of objects representing multipart entries or null in case
+   *         of errors found.
+   *
+   * @see WAP-230-WSP-20010705-a section 8.5
+   */
+  parseMultiPart: function parseMultiPart(data) {
+    let nEntries = UintVar.decode(data);
+    if (!nEntries) {
+      return null;
+    }
+
+    let parts = new Array(nEntries);
+    for (let i = 0; i < nEntries; i++) {
+      // Length of the ContentType and Headers fields combined.
+      let headersLen = UintVar.decode(data);
+      // Length of the Data field
+      let contentLen = UintVar.decode(data);
+
+      let headersEnd = data.offset + headersLen;
+      let contentEnd = headersEnd + contentLen;
+
+      try {
+        let headers = {};
+
+        let contentType = ContentTypeValue.decode(data);
+        headers["content-type"] = contentType;
+        headers["content-length"] = contentLen;
+
+        headers = this.parseHeaders(data, headersEnd, headers);
+
+        let content = Octet.decodeMultiple(data, contentEnd);
+
+        parts[i] = {
+          index: i,
+          headers: headers,
+          content: content,
+        };
+      } catch (e) {
+        debug("Failed to parse multipart entry, message: " + e.message);
+        // Placeholder to keep original index of following entries.
+        parts[i] = null;
+      }
+
+      if (data.offset != contentEnd) {
+        // Seek to entry boundary for next entry.
+        data.offset = contentEnd;
+      }
+    }
+
+    return parts;
+  },
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   * @param isSessionless
+   *        Whether or not the PDU contains a session less WSP PDU.
+   * @param msg [optional]
+   *        Optional pre-defined PDU object.
+   *
+   * @return Parsed WSP PDU object or null in case of errors found.
+   */
+  parse: function parse(data, isSessionless, msg) {
+    if (!msg) {
+      msg = {
+        type: null,
+      };
+    }
+
+    try {
+      if (isSessionless) {
+        // "The `transactionId` is used to associate requests with replies in
+        // the connectionless session service." ~ WAP-230-WSP-20010705-a 8.2.1
+        msg.transactionId = Octet.decode(data);
+      }
+
+      msg.type = Octet.decode(data);
+      switch (msg.type) {
+        case WSP_PDU_TYPE_PUSH:
+          this.parsePushHeaders(data, msg);
+          break;
+      }
+    } catch (e) {
+      debug("Parse error. Message: " + e.message);
+      msg = null;
+    }
+
+    return msg;
+  },
+};
+
+// WSP Header Field Name Assignments
+// Note: Items commented out are either deprecated or not implemented.
+//       Deprecated items should only be supported for backward compatibility
+//       purpose.
+// @see WAP-230-WSP-20010705-a Appendix A. Assigned Numbers.
+const WSP_HEADER_FIELDS = (function () {
+  let names = {};
+  function add(name, number, coder) {
+    let entry = {
+      name: name,
+      number: number,
+      coder: coder,
+    };
+    names[name] = names[number] = entry;
+  }
+
+  //add("accept",               0x00);
+  //add("accept-charset",       0x01); Deprecated
+  //add("accept-encoding",      0x02); Deprecated
+  //add("accept-language",      0x03);
+  //add("accept-ranges",        0x04);
+  add("age",                    0x05, DeltaSecondsValue);
+  //add("allow",                0x06);
+  //add("authorization",        0x07);
+  //add("cache-control",        0x08); Deprecated
+  //add("connection",           0x09);
+  //add("content-base",         0x0A); Deprecated
+  //add("content-encoding",     0x0B);
+  //add("content-language",     0x0C);
+  add("content-length",         0x0D, IntegerValue);
+  add("content-location",       0x0E, UriValue);
+  //add("content-md5",          0x0F);
+  //add("content-range",        0x10); Deprecated
+  add("content-type",           0x11, ContentTypeValue);
+  add("date",                   0x12, DateValue);
+  add("etag",                   0x13, TextString);
+  add("expires",                0x14, DateValue);
+  add("from",                   0x15, TextString);
+  add("host",                   0x16, TextString);
+  add("if-modified-since",      0x17, DateValue);
+  add("if-match",               0x18, TextString);
+  add("if-none-match",          0x19, TextString);
+  //add("if-range",             0x1A);
+  add("if-unmodified-since",    0x1B, DateValue);
+  add("location",               0x1C, UriValue);
+  add("last-modified",          0x1D, DateValue);
+  add("max-forwards",           0x1E, IntegerValue);
+  //add("pragma",               0x1F);
+  //add("proxy-authenticate",   0x20);
+  //add("proxy-authentication", 0x21);
+  //add("public",               0x22);
+  //add("range",                0x23);
+  add("referer",                0x24, UriValue);
+  //add("retry-after",          0x25);
+  add("server",                 0x26, TextString);
+  //add("transfer-encoding",    0x27);
+  add("upgrade",                0x28, TextString);
+  add("user-agent",             0x29, TextString);
+  //add("vary",                 0x2A);
+  add("via",                    0x2B, TextString);
+  //add("warning",              0x2C);
+  //add("www-authenticate",     0x2D);
+  //add("content-disposition",  0x2E); Deprecated
+  add("x-wap-application-id",   0x2F, ApplicationIdValue);
+  add("x-wap-content-uri",      0x30, UriValue);
+  add("x-wap-initiator-uri",    0x31, UriValue);
+  //add("accept-application",   0x32);
+  add("bearer-indication",      0x33, IntegerValue);
+  add("push-flag",              0x34, ShortInteger);
+  add("profile",                0x35, UriValue);
+  //add("profile-diff",         0x36);
+  //add("profile-warning",      0x37); Deprecated
+  //add("expect",               0x38);
+  //add("te",                   0x39);
+  //add("trailer",              0x3A);
+  add("accept-charset",         0x3B, AcceptCharsetValue);
+  //add("accept-encoding",      0x3C);
+  //add("cache-control",        0x3D); Deprecated
+  //add("content-range",        0x3E);
+  add("x-wap-tod",              0x3F, DateValue);
+  add("content-id",             0x40, QuotedString);
+  //add("set-cookie",           0x41);
+  //add("cookie",               0x42);
+  //add("encoding-version",     0x43);
+  //add("profile-warning",      0x44);
+  //add("content-disposition",  0x45);
+  //add("x-wap-security",       0x46);
+  //add("cache-control",        0x47);
+
+  return names;
+})();
+
+// WSP Content Type Assignments
+// @see http://www.wapforum.org/wina
+const WSP_WELL_KNOWN_CONTENT_TYPES = (function () {
+  let types = {};
+
+  function add(type, number) {
+    let entry = {
+      type: type,
+      number: number,
+    };
+    types[type] = types[number] = entry;
+  }
+
+  // Well Known Values
+  add("application/vnd.wap.multipart.related", 0x33);
+  add("application/vnd.wap.mms-message", 0x3E);
+
+  return types;
+})();
+
+// WSP Well-Known Parameter Assignments
+// Note: Items commented out are either deprecated or not implemented.
+//       Deprecated items should not be used.
+// @see WAP-230-WSP-20010705-a Appendix A. Assigned Numbers.
+const WSP_WELL_KNOWN_PARAMS = (function () {
+  let params = {};
+
+  function add(name, number, coder) {
+    let entry = {
+      name: name,
+      number: number,
+      coder: coder,
+    };
+    params[name] = params[number] = entry;
+  }
+
+  add("q",                 0x00, QValue);
+  add("charset",           0x01, WellKnownCharset);
+  add("level",             0x02, VersionValue);
+  add("type",              0x03, IntegerValue);
+  add("name",              0x05, TextValue); // Deprecated, but used in some carriers, eg. Hinet.
+  //add("filename",        0x06); Deprecated
+  add("differences",       0x07, FieldName);
+  add("padding",           0x08, ShortInteger);
+  add("type",              0x09, ConstrainedEncoding);
+  add("start",             0x0A, TextValue); // Deprecated, but used in some carriers, eg. T-Mobile.
+  //add("start-info",      0x0B); Deprecated
+  //add("comment",         0x0C); Deprecated
+  //add("domain",          0x0D); Deprecated
+  add("max-age",           0x0E, DeltaSecondsValue);
+  //add("path",            0x0F); Deprecated
+  add("secure",            0x10, NoValue);
+  add("sec",               0x11, ShortInteger);
+  add("mac",               0x12, TextValue);
+  add("creation-date",     0x13, DateValue);
+  add("modification-date", 0x14, DateValue);
+  add("read-date",         0x15, DateValue);
+  add("size",              0x16, IntegerValue);
+  add("name",              0x17, TextValue);
+  add("filename",          0x18, TextValue);
+  add("start",             0x19, TextValue);
+  add("start-info",        0x1A, TextValue);
+  add("comment",           0x1B, TextValue);
+  add("domain",            0x1C, TextValue);
+  add("path",              0x1D, TextValue);
+
+  return params;
+})();
+
+// WSP Character Set Assignments
+// @see WAP-230-WSP-20010705-a Appendix A. Assigned Numbers.
+// @see http://www.iana.org/assignments/character-sets
+const WSP_WELL_KNOWN_CHARSETS = (function () {
+  let charsets = {};
+
+  function add(name, number, converter) {
+    let entry = {
+      name: name,
+      number: number,
+      converter: converter,
+    };
+
+    charsets[name] = charsets[number] = entry;
+  }
+
+  add("ansi_x3.4-1968",     3, null);
+  add("iso_8859-1:1987",    4, "ISO-8859-1");
+  add("utf-8",            106, "UTF-8");
+  add("windows-1252",    2252, "windows-1252");
+
+  return charsets;
+})();
+
+// OMNA PUSH Application ID
+// @see http://www.openmobilealliance.org/tech/omna/omna-push-app-id.aspx
+const OMNA_PUSH_APPLICATION_IDS = (function () {
+  let ids = {};
+
+  function add(urn, number) {
+    let entry = {
+      urn: urn,
+      number: number,
+    };
+
+    ids[urn] = ids[number] = entry;
+  }
+
+  add("x-wap-application:mms.ua", 0x04);
+
+  return ids;
+})();
+
+let debug;
+if (DEBUG) {
+  debug = function (s) {
+    dump("-@- WspPduHelper: " + s + "\n");
+  };
+} else {
+  debug = function (s) {};
+}
+
+const EXPORTED_SYMBOLS = ALL_CONST_SYMBOLS.concat([
+  // Constant values
+  "WSP_HEADER_FIELDS",
+  "WSP_WELL_KNOWN_CONTENT_TYPES",
+  "WSP_WELL_KNOWN_PARAMS",
+  "WSP_WELL_KNOWN_CHARSETS",
+  "OMNA_PUSH_APPLICATION_IDS",
+
+  // Error classes
+  "CodeError",
+  "FatalCodeError",
+  "NotWellKnownEncodingError",
+
+  // Utility functions
+  "ensureHeader",
+  "skipValue",
+  "decodeAlternatives",
+  "encodeAlternatives",
+
+  // Decoders
+  "Octet",
+  "Text",
+  "NullTerminatedTexts",
+  "Token",
+  "URIC",
+  "TextString",
+  "TokenText",
+  "QuotedString",
+  "ShortInteger",
+  "LongInteger",
+  "UintVar",
+  "ConstrainedEncoding",
+  "ValueLength",
+  "NoValue",
+  "TextValue",
+  "IntegerValue",
+  "DateValue",
+  "DeltaSecondsValue",
+  "QValue",
+  "VersionValue",
+  "UriValue",
+  "Parameter",
+  "Header",
+  "WellKnownHeader",
+  "ApplicationHeader",
+  "FieldName",
+  "AcceptCharsetValue",
+  "WellKnownCharset",
+  "ContentTypeValue",
+  "ApplicationIdValue",
+
+  // Parser
+  "PduHelper",
+]);
+
new file mode 100644
--- /dev/null
+++ b/dom/mms/src/ril/mms_consts.js
@@ -0,0 +1,62 @@
+/* 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/. */
+
+// Encoded X-Mms-Message-Type values
+// @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.30
+const MMS_PDU_TYPE_SEND_REQ = 128;
+const MMS_PDU_TYPE_SEND_CONF = 129;
+const MMS_PDU_TYPE_NOTIFICATION_IND = 130;
+const MMS_PDU_TYPE_NOTIFYRESP_IND = 131;
+const MMS_PDU_TYPE_RETRIEVE_CONF = 132;
+const MMS_PDU_TYPE_ACKNOWLEDGE_IND = 133;
+const MMS_PDU_TYPE_DELIVERY_IND = 134;
+const MMS_PDU_TYPE_READ_REC_IND = 135;
+const MMS_PDU_TYPE_READ_ORIG_IND = 136;
+const MMS_PDU_TYPE_FORWARD_REQ = 137;
+const MMS_PDU_TYPE_FORWARD_CONF = 138;
+const MMS_PDU_TYPE_MBOX_STORE_REQ = 139;
+const MMS_PDU_TYPE_MBOX_STORE_CONF = 140;
+const MMS_PDU_TYPE_MBOX_VIEW_REQ = 141;
+const MMS_PDU_TYPE_MBOX_VIEW_CONF = 142;
+const MMS_PDU_TYPE_MBOX_UPLOAD_REQ = 143;
+const MMS_PDU_TYPE_MBOX_UPLOAD_CONF = 144;
+const MMS_PDU_TYPE_MBOX_DELETE_REQ = 145;
+const MMS_PDU_TYPE_MBOX_DELETE_CONF = 146;
+const MMS_PDU_TYPE_MBOX_DESCR = 147;
+const MMS_PDU_TYPE_DELETE_REQ = 148;
+const MMS_PDU_TYPE_DELETE_CONF = 149;
+const MMS_PDU_TYPE_CANCEL_REQ = 150;
+const MMS_PDU_TYPE_CANCEL_CONF = 151;
+
+// MMS version 1.3
+// @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.34
+const MMS_VERSION = (0x01 << 4) | 0x03;
+
+// X-Mms-Retrieve-Status values
+// @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.50
+const MMS_PDU_ERROR_OK                            = 128;
+const MMS_PDU_ERROR_TRANSIENT_FAILURE             = 192;
+const MMS_PDU_ERROR_TRANSIENT_MESSAGE_NOT_FOUND   = 193;
+const MMS_PDU_ERROR_TRANSIENT_NETWORK_PROBLEM     = 194;
+const MMS_PDU_ERROR_PERMANENT_FAILURE             = 224;
+const MMS_PDU_ERROR_PERMANENT_SERVICE_DENIED      = 225;
+const MMS_PDU_ERROR_PERMANENT_MESSAGE_NOT_FOUND   = 226;
+const MMS_PDU_ERROR_PERMANENT_CONTENT_UNSUPPORTED = 227;
+
+// X-Mms-Status values
+// @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.54
+const MMS_PDU_STATUS_EXPIRED       = 128;
+const MMS_PDU_STATUS_RETRIEVED     = 129;
+const MMS_PDU_STATUS_REJECTED      = 130;
+const MMS_PDU_STATUS_DEFERRED      = 131;
+const MMS_PDU_STATUS_UNRECOGNISED  = 132;
+const MMS_PDU_STATUS_INDETERMINATE = 133;
+const MMS_PDU_STATUS_FORWARDED     = 134;
+const MMS_PDU_STATUS_UNREACHABLE   = 135;
+
+const ALL_CONST_SYMBOLS = Object.keys(this);
+
+// Allow this file to be imported via Components.utils.import().
+const EXPORTED_SYMBOLS = ALL_CONST_SYMBOLS;
+
new file mode 100644
--- /dev/null
+++ b/dom/mms/src/ril/wap_consts.js
@@ -0,0 +1,22 @@
+/* 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";
+
+// WSP PDU Type Assignments
+// @see WAP-230-WSP-20010705-a Appendix A. Assigned Numbers.
+const WSP_PDU_TYPE_PUSH = 0x06;
+
+// Registered WDP Port Numbers
+// @see WAP-259-WDP-20010614-a Appendix B. Port Number Definitions.
+const WDP_PORT_PUSH = 2948;
+
+// Bearer Type Assignments
+// @see WAP-259-WDP-20010614-a Appendix C. Network Bearer Table.
+const WDP_BEARER_GSM_SMS_GSM_MSISDN = 0x03;
+
+const ALL_CONST_SYMBOLS = Object.keys(this);
+
+const EXPORTED_SYMBOLS = ALL_CONST_SYMBOLS;
+
new file mode 100644
--- /dev/null
+++ b/dom/mms/tests/header_helpers.js
@@ -0,0 +1,164 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+
+let subscriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
+                        .getService(Ci.mozIJSSubScriptLoader);
+
+/**
+ * Test whether specified function throws exception with expected
+ * result.
+ *
+ * @param func
+ *        Function to be tested.
+ * @param exception
+ *        Expected class name of thrown exception. Use null for no throws.
+ * @param stack
+ *        Optional stack object to be printed. null for Components#stack#caller.
+ */
+function do_check_throws(func, result, stack)
+{
+  if (!stack)
+    stack = Components.stack.caller;
+
+  try {
+    func();
+  } catch (ex) {
+    if (ex.name == result) {
+      return;
+    }
+    do_throw("expected result " + result + ", caught " + ex, stack);
+  }
+
+  if (result) {
+    do_throw("expected result " + result + ", none thrown", stack);
+  }
+}
+
+/**
+ * Internal test function for comparing results.
+ *
+ * @param func
+ *        A function under test. It should accept an arguement and return the
+ *        result.
+ * @param data
+ *        Input data for `func`.
+ * @param expect
+ *        Expected result.
+ */
+function wsp_test_func(func, data, expect) {
+  let result_str = JSON.stringify(func(data));
+  let expect_str = JSON.stringify(expect);
+  if (result_str !== expect_str) {
+    do_throw("expect decoded value: '" + expect_str + "', got '" + result_str + "'");
+  }
+}
+
+/**
+ * Test customized WSP PDU decoding.
+ *
+ * @param func
+ *        Decoding func under test. It should return a decoded value if invoked.
+ * @param input
+ *        Array of octets as test data.
+ * @param expect
+ *        Expected decoded value, use null if expecting errors instead.
+ * @param exception
+ *        Expected class name of thrown exception. Use null for no throws.
+ */
+function wsp_decode_test_ex(func, input, expect, exception) {
+  let data = {array: input, offset: 0};
+  do_check_throws(wsp_test_func.bind(null, func, data, expect), exception);
+}
+
+/**
+ * Test default WSP PDU decoding.
+ *
+ * @param target
+ *        Target decoding object, ie. TextValue.
+ * @param input
+ *        Array of octets as test data.
+ * @param expect
+ *        Expected decoded value, use null if expecting errors instead.
+ * @param exception
+ *        Expected class name of thrown exception. Use null for no throws.
+ */
+function wsp_decode_test(target, input, expect, exception) {
+  let func = function decode_func(data) {
+    return target.decode(data);
+  };
+
+  wsp_decode_test_ex(func, input, expect, exception);
+}
+
+/**
+ * Test customized WSP PDU encoding.
+ *
+ * @param func
+ *        Encoding func under test. It should return an encoded octet array if
+ *        invoked.
+ * @param input
+ *        An object to be encoded.
+ * @param expect
+ *        Expected encoded octet array, use null if expecting errors instead.
+ * @param exception
+ *        Expected class name of thrown exception. Use null for no throws.
+ */
+function wsp_encode_test_ex(func, input, expect, exception) {
+  let data = {array: [], offset: 0};
+  do_check_throws(wsp_test_func.bind(null, func.bind(null, data), input,
+                                     expect), exception);
+}
+
+/**
+ * Test default WSP PDU encoding.
+ *
+ * @param target
+ *        Target decoding object, ie. TextValue.
+ * @param input
+ *        An object to be encoded.
+ * @param expect
+ *        Expected encoded octet array, use null if expecting errors instead.
+ * @param exception
+ *        Expected class name of thrown exception. Use null for no throws.
+ */
+function wsp_encode_test(target, input, expect, exception) {
+  let func = function encode_func(data, input) {
+    target.encode(data, input);
+
+    // Remove extra space consumed during encoding.
+    while (data.array.length > data.offset) {
+      data.array.pop();
+    }
+
+    return data.array;
+  }
+
+  wsp_encode_test_ex(func, input, expect, exception);
+}
+
+/**
+ * @param str
+ *        A string.
+ * @param noAppendNull
+ *        True to omit terminating NUL octet. Default false.
+ *
+ * @return A number array of char codes of characters in `str`.
+ */
+function strToCharCodeArray(str, noAppendNull) {
+  let result = [];
+
+  for (let i = 0; i < str.length; i++) {
+    result.push(str.charCodeAt(i));
+  }
+  if (!noAppendNull) {
+    result.push(0);
+  }
+
+  return result;
+}
+
new file mode 100644
--- /dev/null
+++ b/dom/mms/tests/test_mms_pdu_helper.js
@@ -0,0 +1,552 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let MMS = {};
+subscriptLoader.loadSubScript("resource://gre/modules/MmsPduHelper.jsm", MMS);
+MMS.debug = do_print;
+
+function run_test() {
+  run_next_test();
+}
+
+//
+// Test target: BooleanValue
+//
+
+//// BooleanValue.decode ////
+
+add_test(function test_BooleanValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if (i == 128) {
+      wsp_decode_test(MMS.BooleanValue, [128], true);
+    } else if (i == 129) {
+      wsp_decode_test(MMS.BooleanValue, [129], false);
+    } else {
+      wsp_decode_test(MMS.BooleanValue, [i], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//// BooleanValue.encode ////
+
+add_test(function test_BooleanValue_encode() {
+  wsp_encode_test(MMS.BooleanValue, true, [128]);
+  wsp_encode_test(MMS.BooleanValue, false, [129]);
+
+  run_next_test();
+});
+
+//
+// Test target: Address
+//
+
+//// Address.decode ////
+
+add_test(function test_Address_decode() {
+  // Test for PLMN address
+  wsp_decode_test(MMS.Address, strToCharCodeArray("+123.456-789/TYPE=PLMN"),
+                      {address: "+123.456-789", type: "PLMN"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("123456789/TYPE=PLMN"),
+                      {address: "123456789", type: "PLMN"});
+  // Test for IPv4
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1.2.3.4/TYPE=IPv4"),
+                      {address: "1.2.3.4", type: "IPv4"});
+  // Test for IPv6
+  wsp_decode_test(MMS.Address,
+    strToCharCodeArray("1111:AAAA:bbbb:CdEf:1ABC:2cde:3Def:0000/TYPE=IPv6"),
+    {address: "1111:AAAA:bbbb:CdEf:1ABC:2cde:3Def:0000", type: "IPv6"}
+  );
+  // Test for other device-address
+  wsp_decode_test(MMS.Address, strToCharCodeArray("+H-e.l%l_o/TYPE=W0r1d_"),
+                      {address: "+H-e.l%l_o", type: "W0r1d_"});
+  // Test for num-shortcode
+  wsp_decode_test(MMS.Address, strToCharCodeArray("+123"),
+                      {address: "+123", type: "num"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("*123"),
+                      {address: "*123", type: "num"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("#123"),
+                      {address: "#123", type: "num"});
+  // Test for alphanum-shortcode
+  wsp_decode_test(MMS.Address, strToCharCodeArray("H0wD0Y0uTurnTh1s0n"),
+                      {address: "H0wD0Y0uTurnTh1s0n", type: "alphanum"});
+  // Test for other unknown typed sequence
+  wsp_decode_test(MMS.Address, strToCharCodeArray("Joe User <joe@user.org>"),
+                      {address: "Joe User <joe@user.org>", type: "unknown"});
+
+  run_next_test();
+});
+
+//
+// Test target: HeaderField
+//
+
+//// HeaderField.decode ////
+
+add_test(function test_HeaderField_decode() {
+  wsp_decode_test(MMS.HeaderField, [65, 0, 66, 0], {name: "a", value: "B"});
+  wsp_decode_test(MMS.HeaderField, [0x80 | 0x27, 128],
+                      {name: "x-mms-stored", value: true});
+
+  run_next_test();
+});
+
+//// HeaderField.encode ////
+
+add_test(function test_HeaderField_encode() {
+  // Test for MmsHeader
+  wsp_encode_test(MMS.HeaderField, {name: "X-Mms-Message-Type",
+                                    value: MMS.MMS_PDU_TYPE_SEND_REQ},
+                  [0x80 | 0x0C, MMS.MMS_PDU_TYPE_SEND_REQ]);
+  // Test for ApplicationHeader
+  wsp_encode_test(MMS.HeaderField, {name: "a", value: "B"}, [97, 0, 66, 0]);
+
+  run_next_test();
+});
+
+//
+// Test target: MmsHeader
+//
+
+//// MmsHeader.decode ////
+
+add_test(function test_MmsHeader_decode() {
+  wsp_decode_test(MMS.MmsHeader, [0x80 | 0x00], null, "NotWellKnownEncodingError");
+  wsp_decode_test(MMS.MmsHeader, [0x80 | 0x27, 128],
+                      {name: "x-mms-stored", value: true});
+  wsp_decode_test(MMS.MmsHeader, [0x80 | 0x27, 255], null);
+
+  run_next_test();
+});
+
+//// MmsHeader.encode ////
+
+add_test(function test_MmsHeader_encode() {
+  // Test for empty header name:
+  wsp_encode_test(MMS.MmsHeader, {name: undefined, value: null}, null, "CodeError");
+  wsp_encode_test(MMS.MmsHeader, {name: null, value: null}, null, "CodeError");
+  wsp_encode_test(MMS.MmsHeader, {name: "", value: null}, null, "CodeError");
+  // Test for non-well-known header name:
+  wsp_encode_test(MMS.MmsHeader, {name: "X-No-Such-Field", value: null},
+                  null, "NotWellKnownEncodingError");
+  // Test for normal header
+  wsp_encode_test(MMS.MmsHeader, {name: "X-Mms-Message-Type",
+                                  value: MMS.MMS_PDU_TYPE_SEND_REQ},
+                  [0x80 | 0x0C, MMS.MMS_PDU_TYPE_SEND_REQ]);
+
+  run_next_test();
+});
+
+//
+// Test target: ContentClassValue
+//
+
+//// ContentClassValue.decode ////
+
+add_test(function test_ContentClassValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 135)) {
+      wsp_decode_test(MMS.ContentClassValue, [i], i);
+    } else {
+      wsp_decode_test(MMS.ContentClassValue, [i], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: ContentLocationValue
+//
+
+//// ContentLocationValue.decode ////
+
+add_test(function test_ContentLocationValue_decode() {
+  // Test for MMS_PDU_TYPE_MBOX_DELETE_CONF & MMS_PDU_TYPE_DELETE_CONF
+  wsp_decode_test_ex(function (data) {
+      data.array[0] = data.array.length - 1;
+
+      let options = {};
+      options["x-mms-message-type"] = /*MMS.MMS_PDU_TYPE_MBOX_DELETE_CONF*/146;
+      return MMS.ContentLocationValue.decode(data, options);
+    }, [0, 0x80 | 0x00].concat(strToCharCodeArray("http://no.such.com/path")),
+    {statusCount: 0, uri: "http://no.such.com/path"}
+  );
+  wsp_decode_test_ex(function (data) {
+      data.array[0] = data.array.length - 1;
+
+      let options = {};
+      options["x-mms-message-type"] = /*MMS.MMS_PDU_TYPE_DELETE_CONF*/149;
+      return MMS.ContentLocationValue.decode(data, options);
+    }, [0, 0x80 | 0x00].concat(strToCharCodeArray("http://no.such.com/path")),
+    {statusCount: 0, uri: "http://no.such.com/path"}
+  );
+
+  run_next_test();
+});
+
+//
+// Test target: ElementDescriptorValue
+//
+
+//// ElementDescriptorValue.decode ////
+
+add_test(function test_ElementDescriptorValue_decode() {
+  wsp_decode_test(MMS.ElementDescriptorValue, [2, 97, 0], {contentReference: "a"});
+  wsp_decode_test(MMS.ElementDescriptorValue, [4, 97, 0, 0x80 | 0x02, 0x80],
+                      {contentReference: "a", params: {type: 0}});
+
+  run_next_test();
+});
+
+//
+// Test target: Parameter
+//
+
+//// Parameter.decodeParameterName ////
+
+add_test(function test_Parameter_decodeParameterName() {
+  wsp_decode_test_ex(function (data) {
+      return MMS.Parameter.decodeParameterName(data);
+    }, [0x80 | 0x02], "type"
+  );
+  wsp_decode_test_ex(function (data) {
+      return MMS.Parameter.decodeParameterName(data);
+    }, strToCharCodeArray("type"), "type"
+  );
+
+  run_next_test();
+});
+
+//// Parameter.decode ////
+
+add_test(function test_Parameter_decode() {
+  wsp_decode_test(MMS.Parameter, [0x80 | 0x02, 0x80 | 0x00], {name: "type", value: 0});
+
+  run_next_test();
+});
+
+//// Parameter.decodeMultiple ////
+
+add_test(function test_Parameter_decodeMultiple() {
+  // FIXME: The following test case falls because Parameter-value decoding of
+  //        "type" parameters utilies WSP.ConstrainedEncoding, which in turn
+  //        utilies WSP.TextString, and TextString is not matual exclusive to
+  //        each other.
+  //wsp_decode_test_ex(function (data) {
+  //    return MMS.Parameter.decodeMultiple(data, data.array.length);
+  //  }, [0x80 | 0x02, 0x80 | 0x00].concat(strToCharCodeArray("good")).concat([0x80 | 0x01]),
+  //  {type: 0, good: 1}
+  //);
+
+  run_next_test();
+});
+
+//
+// Test target: EncodedStringValue
+//
+
+//// EncodedStringValue.decode ////
+
+add_test(function test_EncodedStringValue_decode() {
+  // Test for normal TextString
+  wsp_decode_test(MMS.EncodedStringValue, strToCharCodeArray("Hello"), "Hello");
+  // Test for non-well-known charset
+  wsp_decode_test(MMS.EncodedStringValue, [1, 0x80], null, "NotWellKnownEncodingError");
+  // Test for utf-8
+  let (entry = MMS.WSP.WSP_WELL_KNOWN_CHARSETS["utf-8"]) {
+    // "Mozilla" in full width.
+    let str = "\uff2d\uff4f\uff5a\uff49\uff4c\uff4c\uff41";
+
+    let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+               .createInstance(Ci.nsIScriptableUnicodeConverter);
+    conv.charset = entry.converter;
+
+    let raw;
+    try {
+      let raw = conv.convertToByteArray(str).concat([0]);
+      if (raw[0] >= 128) {
+        wsp_decode_test(MMS.EncodedStringValue,
+                            [raw.length + 2, 0x80 | entry.number, 127].concat(raw), str);
+      } else {
+        wsp_decode_test(MMS.EncodedStringValue,
+                            [raw.length + 1, 0x80 | entry.number].concat(raw), str);
+      }
+    } catch (e) {
+      do_print("Can't convert test string to byte array with " + entry.converter);
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: ExpiryValue
+//
+
+//// ExpiryValue.decode ////
+
+add_test(function test_ExpiryValue_decode() {
+  // Test for Absolute-token Date-value
+  wsp_decode_test(MMS.ExpiryValue, [3, 128, 1, 0x80], new Date(0x80 * 1000));
+  // Test for Relative-token Delta-seconds-value
+  wsp_decode_test(MMS.ExpiryValue, [3, 129, 0x80], 0);
+
+  run_next_test();
+});
+
+//
+// Test target: PreviouslySentByValue
+//
+
+//// PreviouslySentByValue.decode ////
+
+add_test(function test_PreviouslySentByValue_decode() {
+  wsp_decode_test(MMS.PreviouslySentByValue, [3, 0x80 | 0x03, 65, 0],
+                      {forwardedCount: 3, originator: {address: "A",
+                                                       type: "alphanum"}});
+
+  run_next_test();
+});
+
+//
+// Test target: PreviouslySentDateValue
+//
+
+//// PreviouslySentDateValue.decode ////
+
+add_test(function test_PreviouslySentDateValue_decode() {
+  wsp_decode_test(MMS.PreviouslySentDateValue, [3, 0x80 | 0x03, 1, 4],
+                      {forwardedCount: 3, timestamp: new Date(4 * 1000)});
+
+  run_next_test();
+});
+
+//
+// Test target: FromValue
+//
+
+//// FromValue.decode ////
+
+add_test(function test_FromValue_decode() {
+  // Test for Insert-address-token:
+  wsp_decode_test(MMS.FromValue, [1, 129], null);
+  // Test for Address-present-token:
+  let (addr = strToCharCodeArray("+123/TYPE=PLMN")) {
+    wsp_decode_test(MMS.FromValue, [addr.length + 2, 128].concat(addr),
+                        {address: "+123", type: "PLMN"});
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: MessageClassValue
+//
+
+//// MessageClassValue.decode ////
+
+add_test(function test_MessageClassValue_decode() {
+  wsp_decode_test(MMS.MessageClassValue, [65, 0], "A");
+  wsp_decode_test(MMS.MessageClassValue, [128], "personal");
+
+  run_next_test();
+});
+
+//
+// Test target: ClassIdentifier
+//
+
+//// ClassIdentifier.decode ////
+
+add_test(function test_ClassIdentifier_decode() {
+  let (IDs = ["personal", "advertisement", "informational", "auto"]) {
+    for (let i = 0; i < 256; i++) {
+      if ((i >= 128) && (i <= 131)) {
+        wsp_decode_test(MMS.ClassIdentifier, [i], IDs[i - 128]);
+      } else {
+        wsp_decode_test(MMS.ClassIdentifier, [i], null, "CodeError");
+      }
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: MessageTypeValue
+//
+
+//// MessageTypeValue.decode ////
+
+add_test(function test_MessageTypeValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 151)) {
+      wsp_decode_test(MMS.MessageTypeValue, [i], i);
+    } else {
+      wsp_decode_test(MMS.MessageTypeValue, [i], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//// MessageTypeValue.encode ////
+
+add_test(function test_MessageTypeValue_encode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 151)) {
+      wsp_encode_test(MMS.MessageTypeValue, i, [i]);
+    } else {
+      wsp_encode_test(MMS.MessageTypeValue, i, null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: MmFlagsValue
+//
+
+//// MmFlagsValue.decode ////
+
+add_test(function test_MmFlagsValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 130)) {
+      wsp_decode_test(MMS.MmFlagsValue, [3, i, 65, 0], {type: i, text: "A"});
+    } else {
+      wsp_decode_test(MMS.MmFlagsValue, [3, i, 65, 0], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: MmStateValue
+//
+
+//// MmStateValue.decode ////
+
+add_test(function test_MmStateValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 132)) {
+      wsp_decode_test(MMS.MmStateValue, [i, 0], i);
+    } else {
+      wsp_decode_test(MMS.MmStateValue, [i, 0], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: PriorityValue
+//
+
+//// PriorityValue.decode ////
+
+add_test(function test_PriorityValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 130)) {
+      wsp_decode_test(MMS.PriorityValue, [i], i);
+    } else {
+      wsp_decode_test(MMS.PriorityValue, [i], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: RecommendedRetrievalModeValue
+//
+
+//// RecommendedRetrievalModeValue.decode ////
+
+add_test(function test_RecommendedRetrievalModeValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if (i == 128) {
+      wsp_decode_test(MMS.RecommendedRetrievalModeValue, [i], i);
+    } else {
+      wsp_decode_test(MMS.RecommendedRetrievalModeValue, [i], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: ReplyChargingValue
+//
+
+//// ReplyChargingValue.decode ////
+
+add_test(function test_ReplyChargingValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 131)) {
+      wsp_decode_test(MMS.ReplyChargingValue, [i], i);
+    } else {
+      wsp_decode_test(MMS.ReplyChargingValue, [i], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: RetrieveStatusValue
+//
+
+//// RetrieveStatusValue.decode ////
+
+add_test(function test_RetrieveStatusValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i == 128)
+        || ((i >= 192) && (i <= 194))
+        || ((i >= 224) && (i <= 227))) {
+      wsp_decode_test(MMS.RetrieveStatusValue, [i], i);
+    } else if ((i >= 195) && (i <= 223)) {
+      wsp_decode_test(MMS.RetrieveStatusValue, [i], 192);
+    } else {
+      wsp_decode_test(MMS.RetrieveStatusValue, [i], 224);
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: StatusValue
+//
+
+//// StatusValue.decode ////
+
+add_test(function test_StatusValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 135)) {
+      wsp_decode_test(MMS.StatusValue, [i], i);
+    } else {
+      wsp_decode_test(MMS.StatusValue, [i], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//// StatusValue.encode ////
+
+add_test(function test_StatusValue_encode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 135)) {
+      wsp_encode_test(MMS.StatusValue, i, [i]);
+    } else {
+      wsp_encode_test(MMS.StatusValue, i, null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
new file mode 100644
--- /dev/null
+++ b/dom/mms/tests/test_wsp_pdu_helper.js
@@ -0,0 +1,918 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+let WSP = {};
+subscriptLoader.loadSubScript("resource://gre/modules/WspPduHelper.jsm", WSP);
+WSP.debug = do_print;
+
+function run_test() {
+  run_next_test();
+}
+
+//
+// Test target: ensureHeader
+//
+
+add_test(function test_ensureHeader() {
+  do_check_throws(function () {
+      WSP.ensureHeader({}, "no-such-property");
+    }, "FatalCodeError"
+  );
+
+  run_next_test();
+});
+
+//
+// Test target: skipValue()
+//
+
+add_test(function test_skipValue() {
+  // Test for zero-valued first octet:
+  wsp_decode_test_ex(function (data) {
+      return WSP.skipValue(data);
+    }, [0], null
+  );
+  // Test first octet < 31
+  wsp_decode_test_ex(function (data) {
+      return WSP.skipValue(data);
+    }, [1, 2], [2]
+  );
+  // Test first octet = 31
+  wsp_decode_test_ex(function (data) {
+      return WSP.skipValue(data);
+    }, [31, 0], null
+  );
+  wsp_decode_test_ex(function (data) {
+      return WSP.skipValue(data);
+    }, [31, 1, 2], [2]
+  );
+  // Test first octet <= 127
+  wsp_decode_test_ex(function (data) {
+      return WSP.skipValue(data);
+    }, strToCharCodeArray("Hello world!"), "Hello world!"
+  );
+  // Test first octet >= 128
+  wsp_decode_test_ex(function (data) {
+      return WSP.skipValue(data);
+    }, [0x80 | 0x01], 0x01
+  );
+
+  run_next_test();
+});
+
+//
+// Test target: Octet
+//
+
+//// Octet.decode ////
+
+add_test(function test_Octet_decode() {
+  wsp_decode_test(WSP.Octet, [1], 1);
+  wsp_decode_test(WSP.Octet, [], null, "RangeError");
+
+  run_next_test();
+});
+
+//// Octet.decodeMultiple ////
+
+add_test(function test_Octet_decodeMultiple() {
+  wsp_decode_test_ex(function (data) {
+      return WSP.Octet.decodeMultiple(data, 3);
+    }, [0, 1, 2], [0, 1, 2], null
+  );
+  wsp_decode_test_ex(function (data) {
+      return WSP.Octet.decodeMultiple(data, 3);
+    }, new Uint8Array([0, 1, 2]), new Uint8Array([0, 1, 2]), null
+  );
+  wsp_decode_test_ex(function (data) {
+      return WSP.Octet.decodeMultiple(data, 4);
+    }, [0, 1, 2], null, "RangeError"
+  );
+
+  run_next_test();
+});
+
+//// Octet.decodeEqualTo ////
+
+add_test(function test_Octet_decodeEqualTo() {
+  wsp_decode_test_ex(function (data) {
+      return WSP.Octet.decodeEqualTo(data, 1);
+    }, [1], 1, null
+  );
+  wsp_decode_test_ex(function (data) {
+      return WSP.Octet.decodeEqualTo(data, 2);
+    }, [1], null, "CodeError"
+  );
+  wsp_decode_test_ex(function (data) {
+      return WSP.Octet.decodeEqualTo(data, 2);
+    }, [], null, "RangeError"
+  );
+
+  run_next_test();
+});
+
+//// Octet.encode ////
+
+add_test(function test_Octet_encode() {
+  for (let i = 0; i < 256; i++) {
+    wsp_encode_test(WSP.Octet, i, [i]);
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: Text
+//
+
+//// Text.decode ////
+
+add_test(function test_Text_decode() {
+  for (let i = 0; i < 256; i++) {
+    if (i == 0) {
+      wsp_decode_test(WSP.Text, [0], null, "NullCharError");
+    } else if ((i < WSP.CTLS) || (i == WSP.DEL)) {
+      wsp_decode_test(WSP.Text, [i], null, "CodeError");
+    } else {
+      wsp_decode_test(WSP.Text, [i], String.fromCharCode(i));
+    }
+  }
+  // Test \r\n(SP|HT)* sequence:
+  wsp_decode_test(WSP.Text, strToCharCodeArray("\r\n \t \t \t", true), " ");
+  wsp_decode_test(WSP.Text, strToCharCodeArray("\r\n \t \t \t"), " ");
+  wsp_decode_test(WSP.Text, strToCharCodeArray("\r\n \t \t \tA"), " ");
+
+  run_next_test();
+});
+
+//// Text.encode ////
+
+add_test(function test_Text_encode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i < WSP.CTLS) || (i == WSP.DEL)) {
+      wsp_encode_test(WSP.Text, String.fromCharCode(i), null, "CodeError");
+    } else {
+      wsp_encode_test(WSP.Text, String.fromCharCode(i), [i]);
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: NullTerminatedTexts
+//
+
+//// NullTerminatedTexts.decode ////
+
+add_test(function test_NullTerminatedTexts_decode() {
+  // Test incompleted string:
+  wsp_decode_test(WSP.NullTerminatedTexts, strToCharCodeArray(" ", true), null, "RangeError");
+  // Test control char:
+  wsp_decode_test(WSP.NullTerminatedTexts, strToCharCodeArray(" \n"), null, "CodeError");
+  // Test normal string:
+  wsp_decode_test(WSP.NullTerminatedTexts, strToCharCodeArray(""), "");
+  wsp_decode_test(WSP.NullTerminatedTexts, strToCharCodeArray("oops"), "oops");
+  // Test \r\n(SP|HT)* sequence:
+  wsp_decode_test(WSP.NullTerminatedTexts, strToCharCodeArray("A\r\n \t \t \tB"), "A B");
+
+  run_next_test();
+});
+
+//// NullTerminatedTexts.encode ////
+
+add_test(function test_NullTerminatedTexts_encode() {
+  wsp_encode_test(WSP.NullTerminatedTexts, "", [0]);
+  wsp_encode_test(WSP.NullTerminatedTexts, "Hello, World!",
+                  strToCharCodeArray("Hello, World!"));
+
+  run_next_test();
+});
+
+//
+// Test target: Token
+//
+
+let TOKEN_SEPS = "()<>@,;:\\\"/[]?={} \t";
+
+//// Token.decode ////
+
+add_test(function test_Token_decode() {
+  for (let i = 0; i < 256; i++) {
+    if (i == 0) {
+      wsp_decode_test(WSP.Token, [i], null, "NullCharError");
+    } else if ((i < WSP.CTLS) || (i >= WSP.ASCIIS)
+        || (TOKEN_SEPS.indexOf(String.fromCharCode(i)) >= 0)) {
+      wsp_decode_test(WSP.Token, [i], null, "CodeError");
+    } else {
+      wsp_decode_test(WSP.Token, [i], String.fromCharCode(i));
+    }
+  }
+
+  run_next_test();
+});
+
+//// Token.encode ////
+
+add_test(function test_Token_encode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i < WSP.CTLS) || (i >= WSP.ASCIIS)
+        || (TOKEN_SEPS.indexOf(String.fromCharCode(i)) >= 0)) {
+      wsp_encode_test(WSP.Token, String.fromCharCode(i), null, "CodeError");
+    } else {
+      wsp_encode_test(WSP.Token, String.fromCharCode(i), [i]);
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: URIC
+//
+
+//// URIC.decode ////
+
+add_test(function test_URIC_decode() {
+  let uric = "!#$%&'()*+,-./0123456789:;=?@ABCDEFGHIJKLMN"
+             + "OPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~";
+  for (let i = 0; i < 256; i++) {
+    if (i == 0) {
+      wsp_decode_test(WSP.URIC, [i], null, "NullCharError");
+    } else if (uric.indexOf(String.fromCharCode(i)) >= 0) {
+      wsp_decode_test(WSP.URIC, [i], String.fromCharCode(i));
+    } else {
+      wsp_decode_test(WSP.URIC, [i], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: TextString
+//
+
+//// TextString.decode ////
+
+add_test(function test_TextString_decode() {
+  // Test quoted string
+  wsp_decode_test(WSP.TextString, [127, 128, 0], String.fromCharCode(128));
+  // Test illegal quoted string
+  wsp_decode_test(WSP.TextString, [127, 32, 0], null, "CodeError");
+  // Test illegal unquoted string
+  wsp_decode_test(WSP.TextString, [128, 0], null, "CodeError");
+  // Test normal string
+  wsp_decode_test(WSP.TextString, [32, 0], " ");
+
+  run_next_test();
+});
+
+//// TextString.encode ////
+
+add_test(function test_TextString_encode() {
+  // Test quoted string
+  wsp_encode_test(WSP.TextString, String.fromCharCode(128), [127, 128, 0]);
+  // Test normal string
+  wsp_encode_test(WSP.TextString, "Mozilla", strToCharCodeArray("Mozilla"));
+
+  run_next_test();
+});
+
+//
+// Test target: TokenText
+//
+
+//// TokenText.decode ////
+
+add_test(function test_TokenText_decode() {
+  wsp_decode_test(WSP.TokenText, [65], null, "RangeError");
+  wsp_decode_test(WSP.TokenText, [0], "");
+  wsp_decode_test(WSP.TokenText, [65, 0], "A");
+
+  run_next_test();
+});
+
+//// TokenText.encode ////
+
+add_test(function test_TokenText_encode() {
+  wsp_encode_test(WSP.TokenText, "B2G", strToCharCodeArray("B2G"));
+
+  run_next_test();
+});
+
+//
+// Test target: QuotedString
+//
+
+//// QuotedString.decode ////
+
+add_test(function test_QuotedString_decode() {
+  // Test non-quoted string
+  wsp_decode_test(WSP.QuotedString, [32, 0], null, "CodeError");
+  // Test incompleted string
+  wsp_decode_test(WSP.QuotedString, [34, 32], null, "RangeError");
+  wsp_decode_test(WSP.QuotedString, [34, 32, 0], " ");
+
+  run_next_test();
+});
+
+//
+// Test target: ShortInteger
+//
+
+//// ShortInteger.decode ////
+
+add_test(function test_ShortInteger_decode() {
+  for (let i = 0; i < 256; i++) {
+    if (i & 0x80) {
+      wsp_decode_test(WSP.ShortInteger, [i], i & 0x7F);
+    } else {
+      wsp_decode_test(WSP.ShortInteger, [i], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//// ShortInteger.encode ////
+
+add_test(function test_ShortInteger_encode() {
+  for (let i = 0; i < 256; i++) {
+    if (i & 0x80) {
+      wsp_encode_test(WSP.ShortInteger, i, null, "CodeError");
+    } else {
+      wsp_encode_test(WSP.ShortInteger, i, [0x80 | i]);
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: LongInteger
+//
+
+//// LongInteger.decode ////
+
+function LongInteger_testcases(target) {
+  // Test LongInteger of zero octet
+  wsp_decode_test(target, [0, 0], null, "CodeError");
+  wsp_decode_test(target, [1, 0x80], 0x80);
+  wsp_decode_test(target, [2, 0x80, 2], 0x8002);
+  wsp_decode_test(target, [3, 0x80, 2, 3], 0x800203);
+  wsp_decode_test(target, [4, 0x80, 2, 3, 4], 0x80020304);
+  wsp_decode_test(target, [5, 0x80, 2, 3, 4, 5], 0x8002030405);
+  wsp_decode_test(target, [6, 0x80, 2, 3, 4, 5, 6], 0x800203040506);
+  // Test LongInteger of more than 6 octets
+  wsp_decode_test(target, [7, 0x80, 2, 3, 4, 5, 6, 7], [0x80, 2, 3, 4, 5, 6, 7]);
+  // Test LongInteger of more than 30 octets
+  wsp_decode_test(target, [31], null, "CodeError");
+}
+add_test(function test_LongInteger_decode() {
+  LongInteger_testcases(WSP.LongInteger);
+
+  run_next_test();
+});
+
+//
+// Test target: UintVar
+//
+
+//// UintVar.decode ////
+
+add_test(function test_UintVar_decode() {
+  wsp_decode_test(WSP.UintVar, [0x80], null, "RangeError");
+  // Test up to max 53 bits integer
+  wsp_decode_test(WSP.UintVar, [0x7F], 0x7F);
+  wsp_decode_test(WSP.UintVar, [0xFF, 0x7F], 0x3FFF);
+  wsp_decode_test(WSP.UintVar, [0xFF, 0xFF, 0x7F], 0x1FFFFF);
+  wsp_decode_test(WSP.UintVar, [0xFF, 0xFF, 0xFF, 0x7F], 0xFFFFFFF);
+  wsp_decode_test(WSP.UintVar, [0xFF, 0xFF, 0xFF, 0xFF, 0x7F], 0x7FFFFFFFF);
+  wsp_decode_test(WSP.UintVar, [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F], 0x3FFFFFFFFFF);
+  wsp_decode_test(WSP.UintVar, [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F], 0x1FFFFFFFFFFFF);
+  wsp_decode_test(WSP.UintVar, [0x8F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x7F], 0x1FFFFFFFFFFFFF);
+  wsp_decode_test(WSP.UintVar, [0x01, 0x02], 1);
+  wsp_decode_test(WSP.UintVar, [0x80, 0x01, 0x02], 1);
+  wsp_decode_test(WSP.UintVar, [0x80, 0x80, 0x80, 0x01, 0x2], 1);
+
+  run_next_test();
+});
+
+//
+// Test target: ConstrainedEncoding (decodeAlternatives)
+//
+
+//// ConstrainedEncoding.decode ////
+
+add_test(function test_ConstrainedEncoding_decode() {
+  wsp_decode_test(WSP.ConstrainedEncoding, [0x80], 0);
+  wsp_decode_test(WSP.ConstrainedEncoding, [32, 0], " ");
+
+  run_next_test();
+});
+
+//
+// Test target: ValueLength
+//
+
+//// ValueLength.decode ////
+
+add_test(function test_ValueLength_decode() {
+  for (let i = 0; i < 256; i++) {
+    if (i < 31) {
+      wsp_decode_test(WSP.ValueLength, [i, 0x8F, 0x7F], i);
+    } else if (i == 31) {
+      wsp_decode_test(WSP.ValueLength, [i, 0x8F, 0x7F], 0x7FF);
+    } else {
+      wsp_decode_test(WSP.ValueLength, [i, 0x8F, 0x7F], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: NoValue
+//
+
+//// NoValue.decode ////
+
+add_test(function test_NoValue_decode() {
+  wsp_decode_test(WSP.NoValue, [0], null);
+  for (let i = 1; i < 256; i++) {
+    wsp_decode_test(WSP.NoValue, [i], null, "CodeError");
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: TextValue
+//
+
+//// TextValue.decode ////
+
+add_test(function test_TextValue_decode() {
+  wsp_decode_test(WSP.TextValue, [0], null);
+  wsp_decode_test(WSP.TextValue, [65, 0], "A");
+  wsp_decode_test(WSP.TextValue, [32, 0], null, "CodeError");
+  wsp_decode_test(WSP.TextValue, [34, 32, 0], " ");
+
+  run_next_test();
+});
+
+//
+// Test target: IntegerValue
+//
+
+//// IntegerValue.decode ////
+
+add_test(function test_IntegerValue_decode() {
+  for (let i = 128; i < 256; i++) {
+    wsp_decode_test(WSP.IntegerValue, [i], i & 0x7F);
+  }
+
+  LongInteger_testcases(WSP.IntegerValue);
+
+  run_next_test();
+});
+
+//
+// Test target: DateValue
+//
+
+//// DateValue.decode ////
+
+add_test(function test_DateValue_decode() {
+  wsp_decode_test(WSP.DateValue, [0, 0], null, "CodeError");
+  wsp_decode_test(WSP.DateValue, [1, 0x80], new Date(0x80 * 1000));
+  wsp_decode_test(WSP.DateValue, [31], null, "CodeError");
+
+  run_next_test();
+});
+
+//
+// Test target: DeltaSecondsValue
+//
+  // DeltaSecondsValue is only an alias of IntegerValue.
+
+//
+// Test target: QValue
+//
+
+//// QValue.decode ////
+
+add_test(function test_QValue_decode() {
+  wsp_decode_test(WSP.QValue, [0], null, "CodeError");
+  wsp_decode_test(WSP.QValue, [1], 0);
+  wsp_decode_test(WSP.QValue, [100], 0.99);
+  wsp_decode_test(WSP.QValue, [101], 0.001);
+  wsp_decode_test(WSP.QValue, [0x88, 0x4B], 0.999);
+  wsp_decode_test(WSP.QValue, [0x88, 0x4C], null, "CodeError");
+
+  run_next_test();
+});
+
+//
+// Test target: VersionValue
+//
+
+//// VersionValue.decode ////
+
+add_test(function test_VersionValue_decode() {
+  for (let major = 1; major < 8; major++) {
+    let version = (major << 4) | 0x0F;
+    wsp_decode_test(WSP.VersionValue, [0x80 | version], version);
+    wsp_decode_test(WSP.VersionValue, [major + 0x30, 0], version);
+
+    for (let minor = 0; minor < 15; minor++) {
+      version = (major << 4) | minor;
+      wsp_decode_test(WSP.VersionValue, [0x80 | version], version);
+      if (minor >= 10) {
+        wsp_decode_test(WSP.VersionValue, [major + 0x30, 0x2E, 0x31, (minor - 10) + 0x30, 0], version);
+      } else {
+        wsp_decode_test(WSP.VersionValue, [major + 0x30, 0x2E, minor + 0x30, 0], version);
+      }
+    }
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: UriValue
+//
+
+//// UriValue.decode ////
+
+add_test(function test_UriValue_decode() {
+  wsp_decode_test(WSP.UriValue, [97], null, "RangeError");
+  wsp_decode_test(WSP.UriValue, [0], "");
+  wsp_decode_test(WSP.UriValue, [65, 0], "A");
+
+  run_next_test();
+});
+
+//
+// Test target: Parameter
+//
+
+//// Parameter.decodeTypedParameter ////
+
+add_test(function test_Parameter_decodeTypedParameter() {
+  // Test for array-typed return value from IntegerValue
+  wsp_decode_test_ex(function (data) {
+      return WSP.Parameter.decodeTypedParameter(data);
+    }, [7, 0, 0, 0, 0, 0, 0, 0], null, "CodeError"
+  );
+  // Test for number-typed return value from IntegerValue
+  wsp_decode_test_ex(function (data) {
+      return WSP.Parameter.decodeTypedParameter(data);
+    }, [1, 0, 0], {name: "q", value: null}
+  );
+  // Test for NotWellKnownEncodingError
+  wsp_decode_test_ex(function (data) {
+      return WSP.Parameter.decodeTypedParameter(data);
+    }, [1, 0xFF], null, "NotWellKnownEncodingError"
+  );
+  // Test for parameter specific decoder
+  wsp_decode_test_ex(function (data) {
+      return WSP.Parameter.decodeTypedParameter(data);
+    }, [1, 0, 100], {name: "q", value: 0.99}
+  );
+  // Test for TextValue
+  wsp_decode_test_ex(function (data) {
+      return WSP.Parameter.decodeTypedParameter(data);
+    }, [1, 0x10, 48, 46, 57, 57, 0], {name: "secure", value: "0.99"}
+  );
+  // Test for TextString
+  wsp_decode_test_ex(function (data) {
+      return WSP.Parameter.decodeTypedParameter(data);
+    }, [1, 0x19, 60, 115, 109, 105, 108, 62, 0], {name: "start", value: "<smil>"}
+  );
+  // Test for skipValue
+  wsp_decode_test_ex(function (data) {
+      return WSP.Parameter.decodeTypedParameter(data);
+    }, [1, 0x19, 128], null
+  );
+
+  run_next_test();
+});
+
+//// Parameter.decodeUntypedParameter ////
+
+add_test(function test_Parameter_decodeUntypedParameter() {
+  wsp_decode_test_ex(function (data) {
+      return WSP.Parameter.decodeUntypedParameter(data);
+    }, [1], null, "CodeError"
+  );
+  wsp_decode_test_ex(function (data) {
+      return WSP.Parameter.decodeUntypedParameter(data);
+    }, [65, 0, 0], {name: "a", value: null}
+  );
+  // Test for IntegerValue
+  wsp_decode_test_ex(function (data) {
+      return WSP.Parameter.decodeUntypedParameter(data);
+    }, [65, 0, 1, 0], {name: "a", value: 0}
+  );
+  // Test for TextValue
+  wsp_decode_test_ex(function (data) {
+      return WSP.Parameter.decodeUntypedParameter(data);
+    }, [65, 0, 66, 0], {name: "a", value: "B"}
+  );
+
+  run_next_test();
+});
+
+//// Parameter.decode ////
+
+add_test(function test_Parameter_decode() {
+  wsp_decode_test_ex(function (data) {
+      return WSP.Parameter.decode(data);
+    }, [1, 0x19, 60, 115, 109, 105, 108, 62, 0], {name: "start", value: "<smil>"}
+  );
+  wsp_decode_test_ex(function (data) {
+      return WSP.Parameter.decode(data);
+    }, [65, 0, 66, 0], {name: "a", value: "B"}
+  );
+
+  run_next_test();
+});
+
+//// Parameter.decodeMultiple ////
+
+add_test(function test_Parameter_decodeMultiple() {
+  wsp_decode_test_ex(function (data) {
+      return WSP.Parameter.decodeMultiple(data, 13);
+    }, [1, 0x19, 60, 115, 109, 105, 108, 62, 0, 65, 0, 66, 0], {start: "<smil>", a: "B"}
+  );
+
+  run_next_test();
+});
+
+//
+// Test target: Header
+//
+
+//// Header.decode ////
+
+add_test(function test_Header_decode() {
+  wsp_decode_test(WSP.Header, [0x34 | 0x80, 0x80], {name: "push-flag", value: 0});
+  wsp_decode_test(WSP.Header, [65, 0, 66, 0], {name: "a", value: "B"});
+
+  run_next_test();
+});
+
+//
+// Test target: WellKnownHeader
+//
+
+//// WellKnownHeader.decode ////
+
+add_test(function test_WellKnownHeader_decode() {
+  wsp_decode_test(WSP.WellKnownHeader, [0xFF], null, "NotWellKnownEncodingError");
+  let (entry = WSP.WSP_HEADER_FIELDS["push-flag"]) {
+    // Test for Short-Integer
+    wsp_decode_test(WSP.WellKnownHeader, [entry.number | 0x80, 0x80],
+                        {name: entry.name, value: 0});
+    // Test for NoValue
+    wsp_decode_test(WSP.WellKnownHeader, [entry.number | 0x80, 0],
+                        {name: entry.name, value: null});
+    // Test for TokenText
+    wsp_decode_test(WSP.WellKnownHeader, [entry.number | 0x80, 65, 0],
+                        {name: entry.name, value: "A"});
+    // Test for QuotedString
+    wsp_decode_test(WSP.WellKnownHeader, [entry.number | 0x80, 34, 128, 0],
+                        {name: entry.name, value: String.fromCharCode(128)});
+    // Test for skipValue
+    wsp_decode_test(WSP.WellKnownHeader, [entry.number | 0x80, 2, 0, 0], null);
+  }
+
+  run_next_test();
+});
+
+//
+// Test target: ApplicationHeader
+//
+
+//// ApplicationHeader.decode ////
+
+add_test(function test_ApplicationHeader_decode() {
+  wsp_decode_test(WSP.ApplicationHeader, [5, 0, 66, 0], null, "CodeError");
+  wsp_decode_test(WSP.ApplicationHeader, [65, 0, 66, 0], {name: "a", value: "B"});
+  // Test for skipValue
+  wsp_decode_test(WSP.ApplicationHeader, [65, 0, 2, 0, 0], null);
+
+  run_next_test();
+});
+
+//// ApplicationHeader.encode ////
+
+add_test(function test_ApplicationHeader_encode() {
+  // Test invalid header name string:
+  wsp_encode_test(WSP.ApplicationHeader, {name: undefined, value: "asdf"}, null, "CodeError");
+  wsp_encode_test(WSP.ApplicationHeader, {name: null, value: "asdf"}, null, "CodeError");
+  wsp_encode_test(WSP.ApplicationHeader, {name: "", value: "asdf"}, null, "CodeError");
+  wsp_encode_test(WSP.ApplicationHeader, {name: "a b", value: "asdf"}, null, "CodeError");
+  // Test value string:
+  wsp_encode_test(WSP.ApplicationHeader, {name: "asdf", value: undefined},
+                  strToCharCodeArray("asdf").concat([0]));
+  wsp_encode_test(WSP.ApplicationHeader, {name: "asdf", value: null},
+                  strToCharCodeArray("asdf").concat([0]));
+  wsp_encode_test(WSP.ApplicationHeader, {name: "asdf", value: ""},
+                  strToCharCodeArray("asdf").concat([0]));
+  wsp_encode_test(WSP.ApplicationHeader, {name: "asdf", value: "fdsa"},
+                  strToCharCodeArray("asdf").concat(strToCharCodeArray("fdsa")));
+
+  run_next_test();
+});
+
+//
+// Test target: FieldName
+//
+
+//// FieldName.decode ////
+
+add_test(function test_FieldName_decode() {
+  wsp_decode_test(WSP.FieldName, [0], "");
+  wsp_decode_test(WSP.FieldName, [65, 0], "a");
+  wsp_decode_test(WSP.FieldName, [97, 0], "a");
+  let (entry = WSP.WSP_HEADER_FIELDS["content-length"]) {
+    wsp_decode_test(WSP.FieldName, [entry.number | 0x80], entry.name);
+  }
+  wsp_decode_test(WSP.FieldName, [0xFF], null, "NotWellKnownEncodingError");
+
+  run_next_test();
+});
+
+//
+// Test target: AcceptCharsetValue
+//
+
+//// AcceptCharsetValue.decode ////
+
+add_test(function test_AcceptCharsetValue_decode() {
+  wsp_decode_test(WSP.AcceptCharsetValue, [0xFF], null, "CodeError");
+  // Test for Any-Charset
+  wsp_decode_test(WSP.AcceptCharsetValue, [128], {charset: "*"});
+  // Test for Constrained-Charset
+  wsp_decode_test(WSP.AcceptCharsetValue, [65, 0], {charset: "A"});
+  let (entry = WSP.WSP_WELL_KNOWN_CHARSETS["utf-8"]) {
+    wsp_decode_test(WSP.AcceptCharsetValue, [entry.number | 0x80], {charset: entry.name});
+  }
+  // Test for Accept-Charset-General-Form
+  wsp_decode_test(WSP.AcceptCharsetValue, [1, 128], {charset: "*"});
+  let (entry = WSP.WSP_WELL_KNOWN_CHARSETS["utf-8"]) {
+    wsp_decode_test(WSP.AcceptCharsetValue, [2, 1, entry.number], {charset: entry.name});
+    wsp_decode_test(WSP.AcceptCharsetValue, [1, entry.number | 0x80], {charset: entry.name});
+  }
+  wsp_decode_test(WSP.AcceptCharsetValue, [3, 65, 0, 100], {charset: "A", q: 0.99});
+
+  run_next_test();
+});
+
+//
+// Test target: WellKnownCharset
+//
+
+//// WellKnownCharset.decode ////
+
+add_test(function test_WellKnownCharset_decode() {
+  wsp_decode_test(WSP.WellKnownCharset, [0xFF], null, "NotWellKnownEncodingError");
+  // Test for Any-Charset
+  wsp_decode_test(WSP.WellKnownCharset, [128], {charset: "*"});
+  // Test for number-typed return value from IntegerValue
+  wsp_decode_test(WSP.WellKnownCharset, [1, 3], {charset: "ansi_x3.4-1968"});
+  // Test for array-typed return value from IntegerValue
+  wsp_decode_test(WSP.WellKnownCharset, [7, 0, 0, 0, 0, 0, 0, 0, 3], null, "CodeError");
+
+  run_next_test();
+});
+
+//
+// Test target: ContentTypeValue
+//
+
+//// ContentTypeValue.decodeConstrainedMedia ////
+
+add_test(function test_ContentTypeValue_decodeConstrainedMedia() {
+  // Test for string-typed return value from ConstrainedEncoding
+  wsp_decode_test_ex(function (data) {
+      return WSP.ContentTypeValue.decodeConstrainedMedia(data);
+    }, [65, 0], {media: "a", params: null}
+  );
+  // Test for number-typed return value from ConstrainedEncoding
+  wsp_decode_test_ex(function (data) {
+      return WSP.ContentTypeValue.decodeConstrainedMedia(data);
+    }, [0x33 | 0x80], {media: "application/vnd.wap.multipart.related", params: null}
+  );
+  // Test for NotWellKnownEncodingError
+  wsp_decode_test_ex(function (data) {
+      return WSP.ContentTypeValue.decodeConstrainedMedia(data);
+    }, [0x80], null, "NotWellKnownEncodingError"
+  );
+
+  run_next_test();
+});
+
+//// ContentTypeValue.decodeMedia ////
+
+add_test(function test_ContentTypeValue_decodeMedia() {
+  // Test for NullTerminatedTexts
+  wsp_decode_test_ex(function (data) {
+      return WSP.ContentTypeValue.decodeMedia(data);
+    }, [65, 0], "a"
+  );
+  // Test for IntegerValue
+  wsp_decode_test_ex(function (data) {
+      return WSP.ContentTypeValue.decodeMedia(data);
+    }, [0x3E | 0x80], "application/vnd.wap.mms-message"
+  );
+  wsp_decode_test_ex(function (data) {
+      return WSP.ContentTypeValue.decodeMedia(data);
+    }, [0x80], null, "NotWellKnownEncodingError"
+  );
+
+  run_next_test();
+});
+
+//// ContentTypeValue.decodeMediaType ////
+
+add_test(function test_ContentTypeValue_decodeMediaType() {
+  wsp_decode_test_ex(function (data) {
+      return WSP.ContentTypeValue.decodeMediaType(data, 1);
+    }, [0x3E | 0x80],
+    {media: "application/vnd.wap.mms-message", params: null}
+  );
+  wsp_decode_test_ex(function (data) {
+      return WSP.ContentTypeValue.decodeMediaType(data, 14);
+    }, [0x3E | 0x80, 1, 0x19, 60, 115, 109, 105, 108, 62, 0, 65, 0, 66, 0],
+    {media: "application/vnd.wap.mms-message", params: {start: "<smil>", a: "B"}}
+  );
+
+  run_next_test();
+});
+
+//// ContentTypeValue.decodeContentGeneralForm ////
+
+add_test(function test_ContentTypeValue_decodeContentGeneralForm() {
+  wsp_decode_test_ex(function (data) {
+      return WSP.ContentTypeValue.decodeContentGeneralForm(data);
+    }, [14, 0x3E | 0x80, 1, 0x19, 60, 115, 109, 105, 108, 62, 0, 65, 0, 66, 0],
+    {media: "application/vnd.wap.mms-message", params: {start: "<smil>", a: "B"}}
+  );
+
+  run_next_test();
+});
+
+//// ContentTypeValue.decode ////
+
+add_test(function test_ContentTypeValue_decode() {
+  wsp_decode_test(WSP.ContentTypeValue,
+    [14, 0x3E | 0x80, 1, 0x19, 60, 115, 109, 105, 108, 62, 0, 65, 0, 66, 0],
+    {media: "application/vnd.wap.mms-message", params: {start: "<smil>", a: "B"}}
+  );
+
+  wsp_decode_test(WSP.ContentTypeValue, [0x33 | 0x80],
+    {media: "application/vnd.wap.multipart.related", params: null}
+  );
+
+  run_next_test();
+});
+
+//
+// Test target: ApplicationIdValue
+//
+
+//// ApplicationIdValue.decode ////
+
+add_test(function test_ApplicationIdValue_decode() {
+  wsp_decode_test(WSP.ApplicationIdValue, [0], "");
+  wsp_decode_test(WSP.ApplicationIdValue, [65, 0], "A");
+  wsp_decode_test(WSP.ApplicationIdValue, [97, 0], "a");
+  let (entry = WSP.OMNA_PUSH_APPLICATION_IDS["x-wap-application:mms.ua"]) {
+    wsp_decode_test(WSP.ApplicationIdValue, [entry.number | 0x80], entry.urn);
+    wsp_decode_test(WSP.ApplicationIdValue, [1, entry.number], entry.urn);
+  }
+  wsp_decode_test(WSP.ApplicationIdValue, [0xFF], null, "NotWellKnownEncodingError");
+
+  run_next_test();
+});
+
+//
+// Test target: PduHelper
+//
+
+//// PduHelper.parseHeaders ////
+
+add_test(function test_PduHelper_parseHeaders() {
+  wsp_decode_test_ex(function (data) {
+      return WSP.PduHelper.parseHeaders(data, data.array.length);
+    }, [0x80 | 0x05, 2, 0x23, 0x28, 0x80 | 0x2F, 0x80 | 0x04],
+    {"age": 9000, "x-wap-application-id": "x-wap-application:mms.ua"}
+  );
+
+  run_next_test();
+});
+
new file mode 100644
--- /dev/null
+++ b/dom/mms/tests/xpcshell.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+head = header_helpers.js
+tail =
+
+[test_wsp_pdu_helper.js]
+[test_mms_pdu_helper.js]
--- a/dom/system/gonk/GonkGPSGeolocationProvider.cpp
+++ b/dom/system/gonk/GonkGPSGeolocationProvider.cpp
@@ -32,16 +32,40 @@ LocationCallback(GpsLocation* location)
                                                         location->accuracy,
                                                         location->accuracy,
                                                         location->bearing,
                                                         location->speed,
                                                         location->timestamp);
   callback->Update(somewhere);
 }
 
+static void
+StatusCallback(GpsStatus* status)
+{}
+
+static void
+SvStatusCallback(GpsSvStatus* sv_info)
+{}
+
+static void
+NmeaCallback(GpsUtcTime timestamp, const char* nmea, int length)
+{}
+
+static void
+SetCapabilitiesCallback(uint32_t capabilities)
+{}
+
+static void
+AcquireWakelockCallback()
+{}
+
+static void
+ReleaseWakelockCallback()
+{}
+
 typedef void *(*pthread_func)(void *);
 
 /** Callback for creating a thread that can call into the JS codes.
  */
 static pthread_t
 CreateThreadCallback(const char* name, void (*start)(void *), void* arg)
 {
   pthread_t thread;
@@ -52,26 +76,33 @@ CreateThreadCallback(const char* name, v
   /* Unfortunately pthread_create and the callback disagreed on what
    * start function should return.
    */
   pthread_create(&thread, &attr, reinterpret_cast<pthread_func>(start), arg);
 
   return thread;
 }
 
+static void
+RequestUtcTimeCallback()
+{}
+
 static GpsCallbacks gCallbacks = {
   sizeof(GpsCallbacks),
   LocationCallback,
-  NULL, /* StatusCallback */
-  NULL, /* SvStatusCallback */
-  NULL, /* NmeaCallback */
-  NULL, /* SetCapabilitiesCallback */
-  NULL, /* AcquireWakelockCallback */
-  NULL, /* ReleaseWakelockCallback */
+  StatusCallback,
+  SvStatusCallback,
+  NmeaCallback,
+  SetCapabilitiesCallback,
+  AcquireWakelockCallback,
+  ReleaseWakelockCallback,
   CreateThreadCallback,
+#ifdef GPS_CAPABILITY_ON_DEMAND_TIME
+  RequestUtcTimeCallback,
+#endif
 };
 
 GonkGPSGeolocationProvider::GonkGPSGeolocationProvider()
   : mStarted(false)
   , mGpsInterface(nsnull)
 {
 }
 
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -65,16 +65,22 @@ XPCOMUtils.defineLazyServiceGetter(this,
 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
                                    "@mozilla.org/parentprocessmessagemanager;1",
                                    "nsIFrameMessageManager");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
                                    "@mozilla.org/settingsService;1",
                                    "nsISettingsService");
 
+XPCOMUtils.defineLazyGetter(this, "WAP", function () {
+  let WAP = {};
+  Cu.import("resource://gre/modules/WapPushManager.js", WAP);
+  return WAP;
+});
+
 function convertRILCallState(state) {
   switch (state) {
     case RIL.CALL_STATE_ACTIVE:
       return nsIRadioInterfaceLayer.CALL_STATE_CONNECTED;
     case RIL.CALL_STATE_HOLDING:
       return nsIRadioInterfaceLayer.CALL_STATE_HELD;
     case RIL.CALL_STATE_DIALING:
       return nsIRadioInterfaceLayer.CALL_STATE_DIALING;
@@ -168,17 +174,19 @@ function RadioInterfaceLayer() {
 
   for each (let msgname in RIL_IPC_MSG_NAMES) {
     ppmm.addMessageListener(msgname, this);
   }
   Services.obs.addObserver(this, "xpcom-shutdown", false);
   Services.obs.addObserver(this, kMozSettingsChangedObserverTopic, false);
 
   this._sentSmsEnvelopes = {};
+
   this.portAddressedSmsApps = {};
+  this.portAddressedSmsApps[WAP.WDP_PORT_PUSH] = this.handleSmsWdpPortPush.bind(this);
 }
 RadioInterfaceLayer.prototype = {
 
   classID:   RADIOINTERFACELAYER_CID,
   classInfo: XPCOMUtils.generateCI({classID: RADIOINTERFACELAYER_CID,
                                     classDescription: "RadioInterfaceLayer",
                                     interfaces: [Ci.nsIWorkerHolder,
                                                  Ci.nsIRadioInterfaceLayer]}),
@@ -578,20 +586,49 @@ RadioInterfaceLayer.prototype = {
 
   /**
    * Handle call error.
    */
   handleCallError: function handleCallError(message) {
     ppmm.sendAsyncMessage("RIL:CallError", message);   
   },
 
+  /**
+   * Handle WDP port push PDU. Constructor WDP bearer information and deliver
+   * to WapPushManager.
+   *
+   * @param message
+   *        A SMS message.
+   */
+  handleSmsWdpPortPush: function handleSmsWdpPortPush(message) {
+    if (message.encoding != RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
+      debug("Got port addressed SMS but not encoded in 8-bit alphabet. Drop!");
+      return;
+    }
+
+    let options = {
+      bearer: WAP.WDP_BEARER_GSM_SMS_GSM_MSISDN,
+      sourceAddress: message.sender,
+      sourcePort: message.header.originatorPort,
+      destinationAddress: this.radioState.icc.MSISDN,
+      destinationPort: message.header.destinationPort,
+    };
+    WAP.WapPushManager.receiveWdpPDU(message.fullData, message.fullData.length,
+                                     0, options);
+  },
+
   portAddressedSmsApps: null,
   handleSmsReceived: function handleSmsReceived(message) {
     debug("handleSmsReceived: " + JSON.stringify(message));
 
+    // FIXME: Bug 737202 - Typed arrays become normal arrays when sent to/from workers
+    if (message.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
+      message.fullData = new Uint8Array(message.fullData);
+    }
+
     // Dispatch to registered handler if application port addressing is
     // available. Note that the destination port can possibly be zero when
     // representing a UDP/TCP port.
     if (message.header && message.header.destinationPort != null) {
       let handler = this.portAddressedSmsApps[message.header.destinationPort];
       if (handler) {
         handler(message);
       }
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -94,17 +94,17 @@ frontend::CompileScript(JSContext *cx, J
     JS_ASSERT_IF(callerFrame, compileAndGo);
     JS_ASSERT_IF(staticLevel != 0, callerFrame);
 
     Parser parser(cx, principals, originPrincipals, chars, length, filename, lineno, version,
                   callerFrame, /* foldConstants = */ true, compileAndGo);
     if (!parser.init())
         return NULL;
 
-    SharedContext sc(cx, /* inFunction = */ false);
+    SharedContext sc(cx, scopeChain, /* fun = */ NULL, /* funbox = */ NULL);
 
     TreeContext tc(&parser, &sc);
     if (!tc.init())
         return NULL;
 
     BytecodeEmitter bce(&parser, &sc, lineno, noScriptRval, needScriptGlobal);
     if (!bce.init())
         return NULL;
@@ -113,24 +113,23 @@ frontend::CompileScript(JSContext *cx, J
     JSObject *globalObj = scopeChain && scopeChain == &scopeChain->global()
                           ? &scopeChain->global()
                           : NULL;
 
     JS_ASSERT_IF(globalObj, globalObj->isNative());
     JS_ASSERT_IF(globalObj, JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(globalObj->getClass()));
 
     GlobalScope globalScope(cx, globalObj);
-    bce.sc->setScopeChain(scopeChain);
     bce.globalScope = &globalScope;
-    if (!SetStaticLevel(bce.sc, staticLevel))
+    if (!SetStaticLevel(&sc, staticLevel))
         return NULL;
 
     /* If this is a direct call to eval, inherit the caller's strictness.  */
     if (callerFrame && callerFrame->isScriptFrame() && callerFrame->script()->strictModeCode)
-        bce.sc->setInStrictMode();
+        sc.setInStrictMode();
 
 #ifdef DEBUG
     bool savedCallerFun;
     savedCallerFun = false;
 #endif
     if (compileAndGo) {
         if (source) {
             /*
@@ -160,20 +159,18 @@ frontend::CompileScript(JSContext *cx, J
 #endif
         }
     }
 
     /*
      * Inline this->statements to emit as we go to save AST space. We must
      * generate our script-body blockid since we aren't calling Statements.
      */
-    uint32_t bodyid;
-    if (!GenerateBlockId(bce.sc, bodyid))
+    if (!GenerateBlockId(&sc, sc.bodyid))
         return NULL;
-    bce.sc->bodyid = bodyid;
 
     ParseNode *pn;
 #if JS_HAS_XML_SUPPORT
     pn = NULL;
     bool onlyXML;
     onlyXML = true;
 #endif
 
@@ -196,17 +193,17 @@ frontend::CompileScript(JSContext *cx, J
         if (inDirectivePrologue && !parser.recognizeDirectivePrologue(pn, &inDirectivePrologue))
             return NULL;
 
         if (!FoldConstants(cx, pn, bce.parser))
             return NULL;
 
         if (!AnalyzeFunctions(bce.parser))
             return NULL;
-        bce.sc->functionList = NULL;
+        tc.functionList = NULL;
 
         if (!EmitTree(cx, &bce, pn))
             return NULL;
 
 #if JS_HAS_XML_SUPPORT
         if (!pn->isKind(PNK_SEMI) || !pn->pn_kid || !pn->pn_kid->isXMLItem())
             onlyXML = false;
 #endif
@@ -261,28 +258,28 @@ frontend::CompileFunctionBody(JSContext 
                               Bindings *bindings, const jschar *chars, size_t length,
                               const char *filename, unsigned lineno, JSVersion version)
 {
     Parser parser(cx, principals, originPrincipals, chars, length, filename, lineno, version,
                   /* cfp = */ NULL, /* foldConstants = */ true, /* compileAndGo = */ false);
     if (!parser.init())
         return false;
 
-    SharedContext funsc(cx, /* inFunction = */ true);
+    JS_ASSERT(fun);
+    SharedContext funsc(cx, /* scopeChain = */ NULL, fun, /* funbox = */ NULL);
 
     TreeContext funtc(&parser, &funsc);
     if (!funtc.init())
         return NULL;
 
     BytecodeEmitter funbce(&parser, &funsc, lineno,
                            /* noScriptRval = */ false, /* needsScriptGlobal = */ false);
     if (!funbce.init())
         return false;
 
-    funsc.setFunction(fun);
     funsc.bindings.transfer(cx, bindings);
     fun->setArgCount(funsc.bindings.numArgs());
     if (!GenerateBlockId(&funsc, funsc.bodyid))
         return false;
 
     /* FIXME: make Function format the source for a function definition. */
     ParseNode *fn = FunctionNode::create(PNK_NAME, &parser);
     if (fn) {
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -78,17 +78,18 @@ BytecodeEmitter::BytecodeEmitter(Parser 
     constMap(sc->context),
     constList(sc->context),
     globalScope(NULL),
     closedArgs(sc->context),
     closedVars(sc->context),
     typesetCount(0),
     noScriptRval(noScriptRval),
     needScriptGlobal(needScriptGlobal),
-    hasSingletons(false)
+    hasSingletons(false),
+    inForInit(false)
 {
     memset(&prolog, 0, sizeof prolog);
     memset(&main, 0, sizeof main);
     current = &main;
     firstLine = prolog.currentLine = main.currentLine = lineno;
 }
 
 bool
@@ -655,17 +656,17 @@ LookupCompileTimeConstant(JSContext *cx,
 {
     /*
      * Chase down the bce stack, but only until we reach the outermost bce.
      * This enables propagating consts from top-level into switch cases in a
      * function compiled along with the top-level script.
      */
     constp->setMagic(JS_NO_CONSTANT);
     do {
-        if (bce->sc->inFunction || bce->parser->compileAndGo) {
+        if (bce->sc->inFunction() || bce->parser->compileAndGo) {
             /* XXX this will need revising if 'const' becomes block-scoped. */
             StmtInfo *stmt = LexicalLookup(bce->sc, atom, NULL);
             if (stmt)
                 return JS_TRUE;
 
             if (BytecodeEmitter::ConstMap::Ptr p = bce->constMap.lookup(atom)) {
                 JS_ASSERT(!p->value.isMagic(JS_NO_CONSTANT));
                 *constp = p->value;
@@ -674,17 +675,17 @@ LookupCompileTimeConstant(JSContext *cx,
 
             /*
              * Try looking in the variable object for a direct property that
              * is readonly and permanent.  We know such a property can't be
              * shadowed by another property on obj's prototype chain, or a
              * with object or catch variable; nor can prop's value be changed,
              * nor can prop be deleted.
              */
-            if (bce->sc->inFunction) {
+            if (bce->sc->inFunction()) {
                 if (bce->sc->bindings.hasBinding(cx, atom))
                     break;
             } else {
                 JS_ASSERT(bce->parser->compileAndGo);
                 JSObject *obj = bce->sc->scopeChain();
 
                 const Shape *shape = obj->nativeLookup(cx, AtomToId(atom));
                 if (shape) {
@@ -992,17 +993,17 @@ BytecodeEmitter::noteClosedArg(ParseNode
  * js::frontend::CompileScript; see comments there.
  *
  * The function returns -1 on failures.
  */
 static int
 AdjustBlockSlot(JSContext *cx, BytecodeEmitter *bce, int slot)
 {
     JS_ASSERT((unsigned) slot < bce->maxStackDepth);
-    if (bce->sc->inFunction) {
+    if (bce->sc->inFunction()) {
         slot += bce->sc->bindings.numVars();
         if ((unsigned) slot >= SLOTNO_LIMIT) {
             ReportCompileErrorNumber(cx, bce->tokenStream(), NULL, JSREPORT_ERROR,
                                      JSMSG_TOO_MANY_LOCALS);
             slot = -1;
         }
     }
     return slot;
@@ -1215,17 +1216,17 @@ BindNameToSlot(JSContext *cx, BytecodeEm
         StackFrame *caller = bce->parser->callerFrame;
         if (caller) {
             JS_ASSERT(bce->parser->compileAndGo);
 
             /*
              * Don't generate upvars on the left side of a for loop. See
              * bug 470758.
              */
-            if (bce->sc->inForInit)
+            if (bce->inForInit)
                 return JS_TRUE;
 
             JS_ASSERT(caller->isScriptFrame());
 
             /*
              * If this is an eval in the global scope, then unbound variables
              * must be globals, so try to use GNAME ops.
              */
@@ -1559,28 +1560,31 @@ CheckSideEffects(JSContext *cx, Bytecode
     return ok;
 }
 
 bool
 BytecodeEmitter::needsImplicitThis()
 {
     if (!parser->compileAndGo)
         return true;
-    if (!sc->inFunction) {
+
+    if (sc->inFunction()) {
+        for (const FunctionBox *funbox = this->sc->funbox(); funbox; funbox = funbox->parent) {
+            if (funbox->inWith)
+                return true;
+        }
+    } else {
         JSObject *scope = sc->scopeChain();
         while (scope) {
             if (scope->isWith())
                 return true;
             scope = scope->enclosingScope();
         }
     }
-    for (const FunctionBox *funbox = this->sc->funbox; funbox; funbox = funbox->parent) {
-        if (funbox->inWith)
-            return true;
-    }
+
     for (StmtInfo *stmt = sc->topStmt; stmt; stmt = stmt->down) {
         if (stmt->type == STMT_WITH)
             return true;
     }
     return false;
 }
 
 static JSBool
@@ -1645,21 +1649,21 @@ static bool
 EmitXMLName(JSContext *cx, ParseNode *pn, JSOp op, BytecodeEmitter *bce)
 {
     JS_ASSERT(!bce->sc->inStrictMode());
     JS_ASSERT(pn->isKind(PNK_XMLUNARY));
     JS_ASSERT(pn->isOp(JSOP_XMLNAME));
     JS_ASSERT(op == JSOP_XMLNAME || op == JSOP_CALLXMLNAME);
 
     ParseNode *pn2 = pn->pn_kid;
-    bool oldInForInit = bce->sc->inForInit;
-    bce->sc->inForInit = false;
+    bool oldInForInit = bce->inForInit;
+    bce->inForInit = false;
     if (!EmitTree(cx, bce, pn2))
         return false;
-    bce->sc->inForInit = oldInForInit;
+    bce->inForInit = oldInForInit;
     if (NewSrcNote2(cx, bce, SRC_PCBASE, bce->offset() - pn2->pn_offset) < 0)
         return false;
 
     if (Emit1(cx, bce, op) < 0)
         return false;
 
     return true;
 }
@@ -2625,27 +2629,27 @@ MaybeEmitVarDecl(JSContext *cx, Bytecode
     if (!pn->pn_cookie.isFree()) {
         atomIndex = pn->pn_cookie.slot();
     } else {
         if (!bce->makeAtomIndex(pn->pn_atom, &atomIndex))
             return false;
     }
 
     if (JOF_OPTYPE(pn->getOp()) == JOF_ATOM &&
-        (!bce->sc->inFunction || bce->sc->funIsHeavyweight()))
+        (!bce->sc->inFunction() || bce->sc->funIsHeavyweight()))
     {
         bce->switchToProlog();
         if (!UpdateLineNumberNotes(cx, bce, pn->pn_pos.begin.lineno))
             return false;
         if (!EmitIndexOp(cx, prologOp, atomIndex, bce))
             return false;
         bce->switchToMain();
     }
 
-    if (bce->sc->inFunction &&
+    if (bce->sc->inFunction() &&
         JOF_OPTYPE(pn->getOp()) == JOF_LOCAL &&
         !pn->isLet() &&
         bce->shouldNoteClosedName(pn))
     {
         if (!bce->noteClosedVar(pn))
             return false;
     }
 
@@ -3338,21 +3342,21 @@ EmitVariables(JSContext *cx, BytecodeEmi
                     return false;
             }
             if (pn->isOp(JSOP_DEFCONST) &&
                 !DefineCompileTimeConstant(cx, bce, pn2->pn_atom, pn3))
             {
                 return JS_FALSE;
             }
 
-            bool oldInForInit = bce->sc->inForInit;
-            bce->sc->inForInit = false;
+            bool oldInForInit = bce->inForInit;
+            bce->inForInit = false;
             if (!EmitTree(cx, bce, pn3))
                 return JS_FALSE;
-            bce->sc->inForInit = oldInForInit;
+            bce->inForInit = oldInForInit;
         } else if (letNotes) {
             /* JSOP_ENTERLETx expects at least 1 slot to have been pushed. */
             if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
                 return JS_FALSE;
         }
 
         /* If we are not initializing, nothing to pop. */
         if (emitOption != InitializeVars) {
@@ -4397,17 +4401,17 @@ EmitLexicalScope(JSContext *cx, Bytecode
      * shouldn't be so complicated; try to find a simpler condition.
      */
     ptrdiff_t noteIndex = -1;
     if (pn->expr()->getKind() != PNK_FOR &&
         pn->expr()->getKind() != PNK_CATCH &&
         (stmtInfo.down
          ? stmtInfo.down->type == STMT_BLOCK &&
            (!stmtInfo.down->down || stmtInfo.down->down->type != STMT_FOR_IN_LOOP)
-         : !bce->sc->inFunction))
+         : !bce->sc->inFunction()))
     {
         /* There must be no source note already output for the next op. */
         JS_ASSERT(bce->noteCount() == 0 ||
                   bce->lastNoteOffset() != bce->offset() ||
                   !GettableNoteForNextOp(bce));
         noteIndex = NewSrcNote2(cx, bce, SRC_BRACE, 0);
         if (noteIndex < 0)
             return false;
@@ -4491,20 +4495,20 @@ EmitForIn(JSContext *cx, BytecodeEmitter
      * If the left part is 'var x', emit code to define x if necessary
      * using a prolog opcode, but do not emit a pop. If the left part was
      * originally 'var x = i', the parser will have rewritten it; see
      * Parser::forStatement. 'for (let x = i in o)' is mercifully banned.
      */
     if (pn1) {
         ParseNode *decl = letDecl ? pn1->pn_expr : pn1;
         JS_ASSERT(decl->isKind(PNK_VAR) || decl->isKind(PNK_LET));
-        bce->sc->inForInit = true;
+        bce->inForInit = true;
         if (!EmitVariables(cx, bce, decl, DefineVars))
             return false;
-        bce->sc->inForInit = false;
+        bce->inForInit = false;
     }
 
     /* Compile the object expression to the right of 'in'. */
     if (!EmitTree(cx, bce, forHead->pn_kid3))
         return JS_FALSE;
 
     /*
      * Emit a bytecode to convert top of stack value to the iterator
@@ -4634,17 +4638,17 @@ EmitNormalFor(JSContext *cx, BytecodeEmi
 
     /* C-style for (init; cond; update) ... loop. */
     JSOp op = JSOP_POP;
     ParseNode *pn3 = forHead->pn_kid1;
     if (!pn3) {
         /* No initializer: emit an annotated nop for the decompiler. */
         op = JSOP_NOP;
     } else {
-        bce->sc->inForInit = true;
+        bce->inForInit = true;
 #if JS_HAS_DESTRUCTURING
         if (pn3->isKind(PNK_ASSIGN)) {
             JS_ASSERT(pn3->isOp(JSOP_NOP));
             if (!MaybeEmitGroupAssignment(cx, bce, op, pn3, GroupIsNotDecl, &op))
                 return false;
         }
 #endif
         if (op == JSOP_POP) {
@@ -4657,17 +4661,17 @@ EmitNormalFor(JSContext *cx, BytecodeEmi
                  * not need to emit a pop below, so switch to a nop,
                  * just for the decompiler.
                  */
                 JS_ASSERT(pn3->isArity(PN_LIST) || pn3->isArity(PN_BINARY));
                 if (pn3->pn_xflags & PNX_GROUPINIT)
                     op = JSOP_NOP;
             }
         }
-        bce->sc->inForInit = false;
+        bce->inForInit = false;
     }
 
     /*
      * NB: the SRC_FOR note has offsetBias 1 (JSOP_{NOP,POP}_LENGTH).
      * Use tmp to hold the biased srcnote "top" offset, which differs
      * from the top local variable by the length of the JSOP_GOTO
      * emitted in between tmp and top if this loop has a condition.
      */
@@ -4787,49 +4791,47 @@ EmitFunc(JSContext *cx, BytecodeEmitter 
     JS_ASSERT(fun->isInterpreted());
     if (fun->script()) {
         /*
          * This second pass is needed to emit JSOP_NOP with a source note
          * for the already-emitted function definition prolog opcode. See
          * comments in EmitStatementList.
          */
         JS_ASSERT(pn->isOp(JSOP_NOP));
-        JS_ASSERT(bce->sc->inFunction);
+        JS_ASSERT(bce->sc->inFunction());
         return EmitFunctionDefNop(cx, bce, pn->pn_index);
     }
 
     JS_ASSERT_IF(pn->pn_funbox->funIsHeavyweight(),
                  fun->kind() == JSFUN_INTERPRETED);
 
     {
-        SharedContext sc(cx, /* inFunction = */ true);
+        FunctionBox *funbox = pn->pn_funbox;
+        SharedContext sc(cx, /* scopeChain = */ NULL, fun, funbox);
         BytecodeEmitter bce2(bce->parser, &sc, pn->pn_pos.begin.lineno,
                              /* noScriptRval = */ false, /* needsScriptGlobal = */ false);
         if (!bce2.init())
             return false;
 
-        FunctionBox *funbox = pn->pn_funbox;
-        bce2.sc->cxFlags = funbox->cxFlags;
+        sc.cxFlags = funbox->cxFlags;
         if (bce->sc->funMightAliasLocals())
-            bce2.sc->setFunMightAliasLocals();  // inherit funMightAliasLocals from parent
-
-        bce2.sc->bindings.transfer(cx, &funbox->bindings);
-        bce2.sc->setFunction(fun);
-        bce2.sc->funbox = funbox;
+            sc.setFunMightAliasLocals();  // inherit funMightAliasLocals from parent
+
+        sc.bindings.transfer(cx, &funbox->bindings);
         bce2.parent = bce;
         bce2.globalScope = bce->globalScope;
 
         /*
          * js::frontend::SetStaticLevel limited static nesting depth to fit in
          * 16 bits and to reserve the all-ones value, thereby reserving the
          * magic FREE_UPVAR_COOKIE value. Note the bce2.staticLevel assignment
          * below.
          */
         JS_ASSERT(bce->sc->staticLevel < JS_BITMASK(16) - 1);
-        bce2.sc->staticLevel = bce->sc->staticLevel + 1;
+        sc.staticLevel = bce->sc->staticLevel + 1;
 
         /* We measured the max scope depth when we parsed the function. */
         if (!EmitFunctionScript(cx, &bce2, pn->pn_body))
             return false;
     }
 
     /* Make the function object a literal in the outer script's pool. */
     unsigned index = bce->objectList.index(pn->pn_funbox);
@@ -4846,17 +4848,17 @@ EmitFunc(JSContext *cx, BytecodeEmitter 
      * For a script we emit the code as we parse. Thus the bytecode for
      * top-level functions should go in the prolog to predefine their
      * names in the variable object before the already-generated main code
      * is executed. This extra work for top-level scripts is not necessary
      * when we emit the code for a function. It is fully parsed prior to
      * invocation of the emitter and calls to EmitTree for function
      * definitions can be scheduled before generating the rest of code.
      */
-    if (!bce->sc->inFunction) {
+    if (!bce->sc->inFunction()) {
         JS_ASSERT(!bce->sc->topStmt);
         JS_ASSERT(pn->pn_cookie.isFree());
         if (pn->pn_cookie.isFree()) {
             bce->switchToProlog();
             if (!EmitFunctionOp(cx, JSOP_DEFFUN, index, bce))
                 return false;
             if (!UpdateLineNumberNotes(cx, bce, pn->pn_pos.begin.lineno))
                 return false;
@@ -5132,17 +5134,17 @@ EmitStatement(JSContext *cx, BytecodeEmi
      * expression statement as the script's result, despite the fact
      * that it appears useless to the compiler.
      *
      * API users may also set the JSOPTION_NO_SCRIPT_RVAL option when
      * calling JS_Compile* to suppress JSOP_POPV.
      */
     bool wantval = false;
     JSBool useful = JS_FALSE;
-    if (bce->sc->inFunction) {
+    if (bce->sc->inFunction()) {
         JS_ASSERT(!bce->noScriptRval);
     } else {
         useful = wantval = !bce->noScriptRval;
     }
 
     /* Don't eliminate expressions with side effects. */
     if (!useful) {
         if (!CheckSideEffects(cx, bce, pn2, &useful))
@@ -5321,23 +5323,23 @@ EmitCallOrNew(JSContext *cx, BytecodeEmi
     /* Remember start of callable-object bytecode for decompilation hint. */
     ptrdiff_t off = top;
 
     /*
      * Emit code for each argument in order, then emit the JSOP_*CALL or
      * JSOP_NEW bytecode with a two-byte immediate telling how many args
      * were pushed on the operand stack.
      */
-    bool oldInForInit = bce->sc->inForInit;
-    bce->sc->inForInit = false;
+    bool oldInForInit = bce->inForInit;
+    bce->inForInit = false;
     for (ParseNode *pn3 = pn2->pn_next; pn3; pn3 = pn3->pn_next) {
         if (!EmitTree(cx, bce, pn3))
             return false;
     }
-    bce->sc->inForInit = oldInForInit;
+    bce->inForInit = oldInForInit;
     if (NewSrcNote2(cx, bce, SRC_PCBASE, bce->offset() - off) < 0)
         return false;
 
     uint32_t argc = pn->pn_count - 1;
     if (Emit3(cx, bce, pn->getOp(), ARGC_HI(argc), ARGC_LO(argc)) < 0)
         return false;
     CheckTypeSet(cx, bce, pn->getOp());
     if (pn->isOp(JSOP_EVAL))
@@ -5807,30 +5809,30 @@ EmitUnary(JSContext *cx, BytecodeEmitter
     /* Unary op, including unary +/-. */
     JSOp op = pn->getOp();
     ParseNode *pn2 = pn->pn_kid;
 
     JS_ASSERT(op != JSOP_XMLNAME);
     if (op == JSOP_TYPEOF && !pn2->isKind(PNK_NAME))
         op = JSOP_TYPEOFEXPR;
 
-    bool oldInForInit = bce->sc->inForInit;
-    bce->sc->inForInit = false;
+    bool oldInForInit = bce->inForInit;
+    bce->inForInit = false;
     if (!EmitTree(cx, bce, pn2))
         return false;
 
-    bce->sc->inForInit = oldInForInit;
+    bce->inForInit = oldInForInit;
     return Emit1(cx, bce, op) >= 0;
 }
 
 static bool
 EmitDefaults(JSContext *cx, BytecodeEmitter *bce, ParseNode *pn)
 {
     JS_ASSERT(pn->isKind(PNK_ARGSBODY));
-    uint16_t ndefaults = bce->sc->funbox->ndefaults;
+    uint16_t ndefaults = bce->sc->funbox()->ndefaults;
     JSFunction *fun = bce->sc->fun();
     unsigned nformal = fun->nargs - fun->hasRest();
     EMIT_UINT16_IMM_OP(JSOP_ACTUALSFILLED, nformal - ndefaults);
     ptrdiff_t top = bce->offset();
     size_t tableSize = (size_t)(JUMP_OFFSET_LEN * (3 + ndefaults));
     if (EmitN(cx, bce, JSOP_TABLESWITCH, tableSize) < 0)
         return false;
     jsbytecode *pc = bce->code(top + JUMP_OFFSET_LEN);
@@ -6069,17 +6071,17 @@ frontend::EmitTree(JSContext *cx, Byteco
         break;
 
       case PNK_RETURN:
         ok = EmitReturn(cx, bce, pn);
         break;
 
 #if JS_HAS_GENERATORS
       case PNK_YIELD:
-        JS_ASSERT(bce->sc->inFunction);
+        JS_ASSERT(bce->sc->inFunction());
         if (pn->pn_kid) {
             if (!EmitTree(cx, bce, pn->pn_kid))
                 return JS_FALSE;
         } else {
             if (Emit1(cx, bce, JSOP_UNDEFINED) < 0)
                 return JS_FALSE;
         }
         if (pn->pn_hidden && NewSrcNote(cx, bce, SRC_HIDDEN) < 0)
@@ -6214,46 +6216,46 @@ frontend::EmitTree(JSContext *cx, Byteco
                 break;
             }
 
             /*
              * Binary :: has a right operand that brackets arbitrary code,
              * possibly including a let (a = b) ... expression.  We must clear
              * inForInit to avoid mis-compiling such beasts.
              */
-            bool oldInForInit = bce->sc->inForInit;
-            bce->sc->inForInit = false;
+            bool oldInForInit = bce->inForInit;
+            bce->inForInit = false;
 #endif
 
             /* Binary operators that evaluate both operands unconditionally. */
             if (!EmitTree(cx, bce, pn->pn_left))
                 return JS_FALSE;
             if (!EmitTree(cx, bce, pn->pn_right))
                 return JS_FALSE;
 #if JS_HAS_XML_SUPPORT
-            bce->sc->inForInit = oldInForInit;
+            bce->inForInit = oldInForInit;
 #endif
             if (Emit1(cx, bce, pn->getOp()) < 0)
                 return JS_FALSE;
         }
         break;
 
 #if JS_HAS_XML_SUPPORT
       case PNK_XMLUNARY:
         if (pn->getOp() == JSOP_XMLNAME) {
             if (!EmitXMLName(cx, pn, JSOP_XMLNAME, bce))
                 return false;
         } else {
             JSOp op = pn->getOp();
             JS_ASSERT(op == JSOP_BINDXMLNAME || op == JSOP_SETXMLNAME);
-            bool oldInForInit = bce->sc->inForInit;
-            bce->sc->inForInit = false;
+            bool oldInForInit = bce->inForInit;
+            bce->inForInit = false;
             if (!EmitTree(cx, bce, pn->pn_kid))
                 return false;
-            bce->sc->inForInit = oldInForInit;
+            bce->inForInit = oldInForInit;
             if (Emit1(cx, bce, op) < 0)
                 return false;
         }
         break;
 #endif
 
       case PNK_THROW:
 #if JS_HAS_XML_SUPPORT
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -122,16 +122,18 @@ struct BytecodeEmitter
 
     /* These two should only be true if sc->inFunction() is false. */
     const bool      noScriptRval:1;     /* The caller is JS_Compile*Script*. */
     const bool      needScriptGlobal:1; /* API caller does not want result value
                                            from global script. */
 
     bool            hasSingletons:1;    /* script contains singleton initializer JSOP_OBJECT */
 
+    bool            inForInit:1;        /* emitting init expr of for; exclude 'in' */
+
     BytecodeEmitter(Parser *parser, SharedContext *sc, unsigned lineno,
                     bool noScriptRval, bool needScriptGlobal);
     bool init();
 
     /*
      * Note that BytecodeEmitters are magic: they own the arena "top-of-stack"
      * space above their tempMark points. This means that you cannot alloc from
      * tempLifoAlloc and save the pointer beyond the next BytecodeEmitter
@@ -158,17 +160,17 @@ struct BytecodeEmitter
         if (!atomIndices->add(p, atom, index))
             return false;
 
         *indexp = index;
         return true;
     }
 
     bool checkSingletonContext() {
-        if (!parser->compileAndGo || sc->inFunction)
+        if (!parser->compileAndGo || sc->inFunction())
             return false;
         for (StmtInfo *stmt = sc->topStmt; stmt; stmt = stmt->down) {
             if (STMT_IS_LOOP(stmt))
                 return false;
         }
         hasSingletons = true;
         return true;
     }
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -168,36 +168,36 @@ Parser::newObjectBox(JSObject *obj)
     traceListHead = objbox;
 
     return objbox;
 }
 
 FunctionBox::FunctionBox(ObjectBox* traceListHead, JSObject *obj, ParseNode *fn, TreeContext *tc)
   : ObjectBox(traceListHead, obj),
     node(fn),
-    siblings(tc->sc->functionList),
+    siblings(tc->functionList),
     kids(NULL),
-    parent(tc->sc->funbox),
+    parent(tc->sc->inFunction() ? tc->sc->funbox() : NULL),
     bindings(tc->sc->context),
     level(tc->sc->staticLevel),
     ndefaults(0),
     queued(false),
     inLoop(false),
     inWith(!!tc->innermostWith),
     inGenexpLambda(false),
     cxFlags(tc->sc->context)     // the cxFlags are set in LeaveFunction
 {
     isFunctionBox = true;
     for (StmtInfo *stmt = tc->sc->topStmt; stmt; stmt = stmt->down) {
         if (STMT_IS_LOOP(stmt)) {
             inLoop = true;
             break;
         }
     }
-    if (!tc->sc->inFunction) {
+    if (!tc->sc->inFunction()) {
         JSObject *scope = tc->sc->scopeChain();
         while (scope) {
             if (scope->isWith())
                 inWith = true;
             scope = scope->enclosingScope();
         }
     }
 }
@@ -216,17 +216,17 @@ Parser::newFunctionBox(JSObject *obj, Pa
      * function.
      */
     FunctionBox *funbox = context->tempLifoAlloc().new_<FunctionBox>(traceListHead, obj, fn, tc);
     if (!funbox) {
         js_ReportOutOfMemory(context);
         return NULL;
     }
 
-    traceListHead = tc->sc->functionList = funbox;
+    traceListHead = tc->functionList = funbox;
 
     return funbox;
 }
 
 void
 Parser::trace(JSTracer *trc)
 {
     ObjectBox *objbox = traceListHead;
@@ -262,21 +262,20 @@ Parser::parse(JSObject *chain)
     /*
      * Protect atoms from being collected by a GC activation, which might
      * - nest on this thread due to out of memory (the so-called "last ditch"
      *   GC attempted within js_NewGCThing), or
      * - run for any reason on another thread if this thread is suspended on
      *   an object lock before it finishes generating bytecode into a script
      *   protected from the GC by a root or a stack frame reference.
      */
-    SharedContext globalsc(context, /* inFunction = */ false);
+    SharedContext globalsc(context, chain, /* fun = */ NULL, /* funbox = */ NULL);
     TreeContext globaltc(this, &globalsc);
     if (!globaltc.init())
         return NULL;
-    globalsc.setScopeChain(chain);
     if (!GenerateBlockId(&globalsc, globalsc.bodyid))
         return NULL;
 
     ParseNode *pn = statements();
     if (pn) {
         if (!tokenStream.matchToken(TOK_EOF)) {
             reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_SYNTAX_ERROR);
             pn = NULL;
@@ -427,17 +426,17 @@ ReportBadReturn(JSContext *cx, Parser *p
         errnum = anonerrnum;
     }
     return ReportCompileErrorNumber(cx, TS(parser), pn, flags, errnum, name.ptr());
 }
 
 static JSBool
 CheckFinalReturn(JSContext *cx, Parser *parser, ParseNode *pn)
 {
-    JS_ASSERT(parser->tc->sc->inFunction);
+    JS_ASSERT(parser->tc->sc->inFunction());
     return HasFinalReturn(pn) == ENDS_IN_RETURN ||
            ReportBadReturn(cx, parser, pn, JSREPORT_WARNING | JSREPORT_STRICT,
                            JSMSG_NO_RETURN_VALUE, JSMSG_ANON_NO_RETURN_VALUE);
 }
 
 /*
  * Check that it is permitted to assign to lhs.  Strict mode code may not
  * assign to 'eval' or 'arguments'.
@@ -500,17 +499,17 @@ ReportBadParameter(JSContext *cx, Parser
  * strict mode reserved keywords, and must not be 'eval' or 'arguments'.  We
  * must perform these checks here, and not eagerly during parsing, because a
  * function's body may turn on strict mode for the function head.
  */
 static bool
 CheckStrictParameters(JSContext *cx, Parser *parser)
 {
     SharedContext *sc = parser->tc->sc;
-    JS_ASSERT(sc->inFunction);
+    JS_ASSERT(sc->inFunction());
 
     if (!sc->needStrictChecks() || sc->bindings.numArgs() == 0)
         return true;
 
     JSAtom *argumentsAtom = cx->runtime->atomState.argumentsAtom;
     JSAtom *evalAtom = cx->runtime->atomState.evalAtom;
 
     /* name => whether we've warned about the name already */
@@ -569,17 +568,17 @@ BindLocalVariable(JSContext *cx, SharedC
     pn->pn_cookie.set(sc->staticLevel, index);
     pn->pn_dflags |= PND_BOUND;
     return true;
 }
 
 ParseNode *
 Parser::functionBody(FunctionBodyType type)
 {
-    JS_ASSERT(tc->sc->inFunction);
+    JS_ASSERT(tc->sc->inFunction());
 
     StmtInfo stmtInfo(context);
     PushStatement(tc->sc, &stmtInfo, STMT_BLOCK, -1);
     stmtInfo.flags = SIF_BODY_BLOCK;
 
     JS_ASSERT(!tc->hasReturnExpr && !tc->hasReturnVoid);
 
     ParseNode *pn;
@@ -729,17 +728,17 @@ Parser::functionBody(FunctionBodyType ty
     }
 
     return pn;
 }
 
 bool
 Parser::checkForArgumentsAndRest()
 {
-    JS_ASSERT(!tc->sc->inFunction);
+    JS_ASSERT(!tc->sc->inFunction());
     if (callerFrame && callerFrame->isFunctionFrame() && callerFrame->fun()->hasRest()) {
         PropertyName *arguments = context->runtime->atomState.argumentsAtom;
         for (AtomDefnRange r = tc->lexdeps->all(); !r.empty(); r.popFront()) {
             if (r.front().key() == arguments) {
                 reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_ARGUMENTS_AND_REST);
                 return false;
             }
         }
@@ -987,17 +986,17 @@ struct BindData {
     }
 };
 
 #if JS_HAS_DESTRUCTURING
 static JSBool
 BindDestructuringArg(JSContext *cx, BindData *data, JSAtom *atom, Parser *parser)
 {
     TreeContext *tc = parser->tc;
-    JS_ASSERT(tc->sc->inFunction);
+    JS_ASSERT(tc->sc->inFunction());
 
     /*
      * NB: Check tc->decls rather than tc->sc->bindings, because destructuring
      *     bindings aren't added to tc->sc->bindings until after all arguments have
      *     been parsed.
      */
     if (tc->decls.lookupFirst(atom)) {
         ReportCompileErrorNumber(cx, TS(parser), NULL, JSREPORT_ERROR,
@@ -1043,17 +1042,17 @@ Parser::newFunction(TreeContext *tc, JSA
      * function's parent slot to tc->sc->scopeChain. If the global context is a
      * compile-and-go one, we leave the pre-set parent intact; otherwise we
      * clear parent and proto.
      */
     while (tc->parent)
         tc = tc->parent;
 
     RootedObject parent(context);
-    parent = tc->sc->inFunction ? NULL : tc->sc->scopeChain();
+    parent = tc->sc->inFunction() ? NULL : tc->sc->scopeChain();
 
     RootedFunction fun(context);
     fun = js_NewFunction(context, NULL, NULL, 0,
                          JSFUN_INTERPRETED | (kind == Expression ? JSFUN_LAMBDA : 0),
                          parent, atom);
     if (fun && !compileAndGo) {
         if (!fun->clearParent(context))
             return NULL;
@@ -1075,41 +1074,27 @@ MatchOrInsertSemicolon(JSContext *cx, To
         ts->getToken(TSF_OPERAND);
         ReportCompileErrorNumber(cx, ts, NULL, JSREPORT_ERROR, JSMSG_SEMI_BEFORE_STMNT);
         return JS_FALSE;
     }
     (void) ts->matchToken(TOK_SEMI);
     return JS_TRUE;
 }
 
-static FunctionBox *
-EnterFunction(ParseNode *fn, Parser *parser, JSAtom *funAtom = NULL,
-              FunctionSyntaxKind kind = Expression)
+static bool
+EnterFunction(SharedContext *outersc, SharedContext *funsc)
 {
-    TreeContext *funtc = parser->tc;
-    TreeContext *outertc = funtc->parent;
-    JSFunction *fun = parser->newFunction(outertc, funAtom, kind);
-    if (!fun)
-        return NULL;
-
-    /* Create box for fun->object early to protect against last-ditch GC. */
-    FunctionBox *funbox = parser->newFunctionBox(fun, fn, outertc);
-    if (!funbox)
-        return NULL;
-
-    /* Initialize non-default members of funtc->sc. */
-    funtc->sc->blockidGen = outertc->sc->blockidGen;
-    if (!GenerateBlockId(funtc->sc, funtc->sc->bodyid))
-        return NULL;
-    funtc->sc->setFunction(fun);
-    funtc->sc->funbox = funbox;
-    if (!SetStaticLevel(funtc->sc, outertc->sc->staticLevel + 1))
-        return NULL;
-
-    return funbox;
+    /* Initialize non-default members of funsc. */
+    funsc->blockidGen = outersc->blockidGen;
+    if (!GenerateBlockId(funsc, funsc->bodyid))
+        return false;
+    if (!SetStaticLevel(funsc, outersc->staticLevel + 1))
+        return false;
+
+    return true;
 }
 
 static bool
 DeoptimizeUsesWithin(Definition *dn, const TokenPos &pos)
 {
     unsigned ndeoptimized = 0;
 
     for (ParseNode *pnu = dn->dn_uses; pnu; pnu = pnu->pn_link) {
@@ -1288,17 +1273,17 @@ Parser::functionArguments(ParseNode **li
 
     hasRest = false;
 
     ParseNode *argsbody = ListNode::create(PNK_ARGSBODY, this);
     if (!argsbody)
         return false;
     argsbody->setOp(JSOP_NOP);
     argsbody->makeEmpty();
-    tc->sc->funbox->node->pn_body = argsbody;
+    tc->sc->funbox()->node->pn_body = argsbody;
 
     if (!tokenStream.matchToken(TOK_RP)) {
         bool hasDefaults = false;
 #if JS_HAS_DESTRUCTURING
         JSAtom *duplicatedArg = NULL;
         bool destructuringArg = false;
         ParseNode *list = NULL;
 #endif
@@ -1413,32 +1398,32 @@ Parser::functionArguments(ParseNode **li
                     if (destructuringArg)
                         goto report_dup_and_destructuring;
                 }
 #endif
 
                 uint16_t slot;
                 if (!tc->sc->bindings.addArgument(context, name, &slot))
                     return false;
-                if (!DefineArg(tc->sc->funbox->node, name, slot, this))
+                if (!DefineArg(tc->sc->funbox()->node, name, slot, this))
                     return false;
 
                 if (tokenStream.matchToken(TOK_ASSIGN)) {
                     if (hasRest) {
                         reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_REST_WITH_DEFAULT);
                         return false;
                     }
                     hasDefaults = true;
                     ParseNode *def_expr = assignExprWithoutYield(JSMSG_YIELD_IN_DEFAULT);
                     if (!def_expr)
                         return false;
-                    ParseNode *arg = tc->sc->funbox->node->pn_body->last();
+                    ParseNode *arg = tc->sc->funbox()->node->pn_body->last();
                     arg->pn_dflags |= PND_DEFAULT;
                     arg->pn_expr = def_expr;
-                    tc->sc->funbox->ndefaults++;
+                    tc->sc->funbox()->ndefaults++;
                 } else if (!hasRest && hasDefaults) {
                     reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_NONDEFAULT_FORMAL_AFTER_DEFAULT);
                     return false;
                 }
 
                 break;
               }
 
@@ -1547,17 +1532,17 @@ Parser::functionDef(HandlePropertyName f
         /*
          * A function directly inside another's body needs only a local
          * variable to bind its name to its value, and not an activation object
          * property (it might also need the activation property, if the outer
          * function contains with statements, e.g., but the stack slot wins
          * when BytecodeEmitter.cpp's BindNameToSlot can optimize a JSOP_NAME
          * into a JSOP_GETLOCAL bytecode).
          */
-        if (bodyLevel && tc->sc->inFunction) {
+        if (bodyLevel && tc->sc->inFunction()) {
             /*
              * Define a local in the outer function so that BindNameToSlot
              * can properly optimize accesses. Note that we need a local
              * variable, not an argument, for the function statement. Thus
              * we add a variable even if a parameter with the given name
              * already exists.
              */
             unsigned index;
@@ -1576,31 +1561,37 @@ Parser::functionDef(HandlePropertyName f
 
               default:;
             }
         }
     }
 
     TreeContext *outertc = tc;
 
+    RootedFunction fun(context, newFunction(outertc, funName, kind));
+    if (!fun)
+        return NULL;
+
+    /* Create box for fun->object early to protect against last-ditch GC. */
+    FunctionBox *funbox = newFunctionBox(fun, pn, outertc);
+    if (!funbox)
+        return NULL;
+
     /* Initialize early for possible flags mutation via destructuringExpr. */
-    SharedContext funsc(context, /* inFunction = */ true);
+    SharedContext funsc(context, /* scopeChain = */ NULL, fun, funbox);
     TreeContext funtc(this, &funsc);
     if (!funtc.init())
         return NULL;
 
-    FunctionBox *funbox = EnterFunction(pn, this, funName, kind);
-    if (!funbox)
+    if (!EnterFunction(outertc->sc, &funsc))
         return NULL;
 
     if (outertc->sc->inStrictMode())
         funsc.setInStrictMode();    // inherit strict mode from parent
 
-    RootedFunction fun(context, funbox->function());
-
     /* Now parse formal argument list and compute fun->nargs. */
     ParseNode *prelude = NULL;
     bool hasRest;
     if (!functionArguments(&prelude, hasRest))
         return NULL;
 
     fun->setArgCount(funsc.bindings.numArgs());
     if (funbox->ndefaults)
@@ -1752,24 +1743,24 @@ Parser::functionDef(HandlePropertyName f
                 if (!outertc->funcStmts || !outertc->funcStmts->init())
                     return NULL;
             }
             if (!outertc->funcStmts->put(funName))
                 return NULL;
         }
     }
 
-    funbox->kids = funsc.functionList;
+    funbox->kids = funtc.functionList;
 
     pn->pn_funbox = funbox;
     pn->setOp(op);
     pn->pn_body->append(body);
     pn->pn_body->pn_pos = body->pn_pos;
 
-    JS_ASSERT_IF(!outertc->sc->inFunction && bodyLevel && kind == Statement,
+    JS_ASSERT_IF(!outertc->sc->inFunction() && bodyLevel && kind == Statement,
                  pn->pn_cookie.isFree());
 
     pn->pn_blockid = outertc->sc->blockid();
 
     if (!LeaveFunction(pn, this, funName, kind))
         return NULL;
 
     return pn;
@@ -2118,17 +2109,17 @@ OuterLet(SharedContext *sc, StmtInfo *st
             return true;
     }
     return false;
 }
 
 static bool
 BindFunctionLocal(JSContext *cx, BindData *data, MultiDeclRange &mdl, SharedContext *sc)
 {
-    JS_ASSERT(sc->inFunction);
+    JS_ASSERT(sc->inFunction());
 
     ParseNode *pn = data->pn;
     JSAtom *name = pn->pn_atom;
 
     BindingKind kind = sc->bindings.lookup(cx, name, NULL);
     if (kind == NONE) {
         /*
          * Property not found in current variable scope: we have not seen this
@@ -2141,17 +2132,17 @@ BindFunctionLocal(JSContext *cx, BindDat
 
         if (!BindLocalVariable(cx, sc, pn, kind))
             return false;
         pn->setOp(JSOP_GETLOCAL);
         return true;
     }
 
     if (kind == ARGUMENT) {
-        JS_ASSERT(sc->inFunction);
+        JS_ASSERT(sc->inFunction());
         JS_ASSERT(!mdl.empty() && mdl.front()->kind() == Definition::ARG);
     } else {
         JS_ASSERT(kind == VARIABLE || kind == CONSTANT);
     }
 
     return true;
 }
 
@@ -2294,17 +2285,17 @@ BindVarOrConst(JSContext *cx, BindData *
             return JS_FALSE;
         pn->setDefn(true);
         pn->pn_dflags &= ~PND_PLACEHOLDER;
     }
 
     if (data->op == JSOP_DEFCONST)
         pn->pn_dflags |= PND_CONST;
 
-    if (tc->sc->inFunction)
+    if (tc->sc->inFunction())
         return BindFunctionLocal(cx, data, mdl, tc->sc);
 
     return true;
 }
 
 static bool
 MakeSetCall(JSContext *cx, ParseNode *pn, Parser *parser, unsigned msg)
 {
@@ -2350,17 +2341,17 @@ NoteLValue(JSContext *cx, ParseNode *pn,
     /*
      * An enclosing function's name is an immutable binding in ES5, so
      * assignments to them must do nothing or throw a TypeError depending on
      * code strictness. Outside strict mode, we optimize away assignment to
      * the function name. For assignment to function name to fail in strict
      * mode, we must have a binding for it in the scope chain; we ensure this
      * happens by making such functions heavyweight.
      */
-    if (sc->inFunction && pn->pn_atom == sc->fun()->atom)
+    if (sc->inFunction() && pn->pn_atom == sc->fun()->atom)
         sc->setFunIsHeavyweight();
 }
 
 static bool
 NoteNameUse(ParseNode *pn, Parser *parser)
 {
     PropertyName *name = pn->pn_atom->asPropertyName();
     StmtInfo *stmt = LexicalLookup(parser->tc->sc, name, NULL);
@@ -2686,17 +2677,17 @@ Parser::destructuringExpr(BindData *data
 }
 
 #endif /* JS_HAS_DESTRUCTURING */
 
 ParseNode *
 Parser::returnOrYield(bool useAssignExpr)
 {
     TokenKind tt = tokenStream.currentToken().type;
-    if (!tc->sc->inFunction) {
+    if (!tc->sc->inFunction()) {
         reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_RETURN_OR_YIELD,
                           (tt == TOK_RETURN) ? js_return_str : js_yield_str);
         return NULL;
     }
 
     ParseNode *pn = UnaryNode::create((tt == TOK_RETURN) ? PNK_RETURN : PNK_YIELD, this);
     if (!pn)
         return NULL;
@@ -3155,17 +3146,17 @@ Parser::forStatement()
              * production; if it is set, then the 'in' keyword will not be
              * recognized as an operator, leaving it available to be parsed as
              * part of a for/in loop.
              *
              * A side effect of this restriction is that (unparenthesized)
              * expressions involving an 'in' operator are illegal in the init
              * clause of an ordinary for loop.
              */
-            tc->sc->inForInit = true;
+            tc->inForInit = true;
             if (tt == TOK_VAR || tt == TOK_CONST) {
                 forDecl = true;
                 tokenStream.consumeKnownToken(tt);
                 pn1 = variables(tt == TOK_VAR ? PNK_VAR : PNK_CONST);
             }
 #if JS_HAS_BLOCK_SCOPE
             else if (tt == TOK_LET) {
                 (void) tokenStream.getToken();
@@ -3178,17 +3169,17 @@ Parser::forStatement()
                         return NULL;
                     pn1 = variables(PNK_LET, blockObj, DontHoistVars);
                 }
             }
 #endif
             else {
                 pn1 = expr();
             }
-            tc->sc->inForInit = false;
+            tc->inForInit = false;
             if (!pn1)
                 return NULL;
         }
     }
 
     JS_ASSERT_IF(forDecl, pn1->isArity(PN_LIST));
     JS_ASSERT(!!blockObj == (forDecl && pn1->isOp(JSOP_NOP)));
 
@@ -3196,17 +3187,17 @@ Parser::forStatement()
 
     /* If non-null, the parent that should be returned instead of forHead. */
     ParseNode *forParent = NULL;
 
     /*
      * We can be sure that it's a for/in loop if there's still an 'in'
      * keyword here, even if JavaScript recognizes 'in' as an operator,
      * as we've excluded 'in' from being parsed in RelExpr by setting
-     * tc->sc->inForInit.
+     * tc->inForInit.
      */
     ParseNode *forHead;        /* initialized by both branches. */
     StmtInfo letStmt(context); /* used if blockObj != NULL. */
     ParseNode *pn2, *pn3;      /* forHead->pn_kid1 and pn_kid2. */
     bool forOf;
     if (pn1 && matchInOrOf(&forOf)) {
         /*
          * Parse the rest of the for/in or for/of head.
@@ -4259,17 +4250,17 @@ Parser::variables(ParseNodeKind kind, St
             pn2 = primaryExpr(tt, JS_FALSE);
             tc->inDeclDestructuring = false;
             if (!pn2)
                 return NULL;
 
             if (!CheckDestructuring(context, &data, pn2, this))
                 return NULL;
             bool ignored;
-            if (tc->sc->inForInit && matchInOrOf(&ignored)) {
+            if (tc->inForInit && matchInOrOf(&ignored)) {
                 tokenStream.ungetToken();
                 pn->append(pn2);
                 continue;
             }
 
             MUST_MATCH_TOKEN(TOK_ASSIGN, JSMSG_BAD_DESTRUCT_DECL);
             JS_ASSERT(tokenStream.currentToken().t_op == JSOP_NOP);
 
@@ -4466,34 +4457,34 @@ RelationalTokenToParseNodeKind(const Tok
 }
 
 BEGIN_EXPR_PARSER(relExpr1)
 {
     /*
      * Uses of the in operator in shiftExprs are always unambiguous,
      * so unset the flag that prohibits recognizing it.
      */
-    bool oldInForInit = tc->sc->inForInit;
-    tc->sc->inForInit = false;
+    bool oldInForInit = tc->inForInit;
+    tc->inForInit = false;
 
     ParseNode *pn = shiftExpr1i();
     while (pn &&
            (tokenStream.isCurrentTokenRelational() ||
             /*
              * Recognize the 'in' token as an operator only if we're not
              * currently in the init expr of a for loop.
              */
             (oldInForInit == 0 && tokenStream.isCurrentTokenType(TOK_IN)) ||
             tokenStream.isCurrentTokenType(TOK_INSTANCEOF))) {
         ParseNodeKind kind = RelationalTokenToParseNodeKind(tokenStream.currentToken());
         JSOp op = tokenStream.currentToken().t_op;
         pn = ParseNode::newBinaryOrAppend(kind, op, pn, shiftExpr1n(), this);
     }
     /* Restore previous state of inForInit flag. */
-    tc->sc->inForInit |= oldInForInit;
+    tc->inForInit |= oldInForInit;
 
     return pn;
 }
 END_EXPR_PARSER(relExpr1)
 
 inline ParseNodeKind
 EqualityTokenToParseNodeKind(const Token &token)
 {
@@ -4577,20 +4568,20 @@ Parser::condExpr1()
     if (!condition || !tokenStream.isCurrentTokenType(TOK_HOOK))
         return condition;
 
     /*
      * Always accept the 'in' operator in the middle clause of a ternary,
      * where it's unambiguous, even if we might be parsing the init of a
      * for statement.
      */
-    bool oldInForInit = tc->sc->inForInit;
-    tc->sc->inForInit = false;
+    bool oldInForInit = tc->inForInit;
+    tc->inForInit = false;
     ParseNode *thenExpr = assignExpr();
-    tc->sc->inForInit = oldInForInit;
+    tc->inForInit = oldInForInit;
     if (!thenExpr)
         return NULL;
 
     MUST_MATCH_TOKEN(TOK_COLON, JSMSG_COLON_IN_COND);
 
     ParseNode *elseExpr = assignExpr();
     if (!elseExpr)
         return NULL;
@@ -5002,17 +4993,17 @@ GenexpGuard::checkValidBody(ParseNode *p
  * generator expression.
  */
 bool
 GenexpGuard::maybeNoteGenerator(ParseNode *pn)
 {
     TreeContext *tc = parser->tc;
     if (tc->yieldCount > 0) {
         tc->sc->setFunIsGenerator();
-        if (!tc->sc->inFunction) {
+        if (!tc->sc->inFunction()) {
             parser->reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_RETURN_OR_YIELD,
                                       js_yield_str);
             return false;
         }
         if (tc->hasReturnExpr) {
             /* At the time we saw the yield, we might not have set funIsGenerator yet. */
             ReportBadReturn(tc->sc->context, parser, pn, JSREPORT_ERROR,
                             JSMSG_BAD_GENERATOR_RETURN,
@@ -5107,19 +5098,19 @@ CompExprTransplanter::transplant(ParseNo
          * Recall that funbox->level is the static level of the code containing
          * the definition or expression of the function and not the static level
          * of the function's body.
          */
         FunctionBox *funbox = pn->pn_funbox;
 
         funbox->level = tc->sc->staticLevel + funcLevel;
         if (++funcLevel == 1 && genexp) {
-            FunctionBox *parent = tc->sc->funbox;
-
-            FunctionBox **funboxp = &tc->parent->sc->functionList;
+            FunctionBox *parent = tc->sc->funbox();
+
+            FunctionBox **funboxp = &tc->parent->functionList;
             while (*funboxp != funbox)
                 funboxp = &(*funboxp)->siblings;
             *funboxp = funbox->siblings;
 
             funbox->parent = parent;
             funbox->siblings = parent->kids;
             parent->kids = funbox;
             funbox->level = tc->sc->staticLevel;
@@ -5494,23 +5485,32 @@ Parser::generatorExpr(ParseNode *kid)
     if (!genfn)
         return NULL;
     genfn->setOp(JSOP_LAMBDA);
     JS_ASSERT(!genfn->pn_body);
     genfn->pn_dflags = 0;
 
     {
         TreeContext *outertc = tc;
-        SharedContext gensc(context, /* inFunction = */ true);
+
+        RootedFunction fun(context, newFunction(outertc, /* atom = */ NULL, Expression));
+        if (!fun)
+            return NULL;
+
+        /* Create box for fun->object early to protect against last-ditch GC. */
+        FunctionBox *funbox = newFunctionBox(fun, genfn, outertc);
+        if (!funbox)
+            return NULL;
+
+        SharedContext gensc(context, /* scopeChain = */ NULL, fun, funbox);
         TreeContext gentc(this, &gensc);
         if (!gentc.init())
             return NULL;
 
-        FunctionBox *funbox = EnterFunction(genfn, this);
-        if (!funbox)
+        if (!EnterFunction(outertc->sc, &gensc))
             return NULL;
 
         /*
          * We assume conservatively that any deoptimization flags in tc->sc
          * come from the kid. So we propagate these flags into genfn. For code
          * simplicity we also do not detect if the flags were only set in the
          * kid and could be removed from tc->sc.
          */
@@ -5869,20 +5869,20 @@ Parser::memberExpr(JSBool allowCallSynta
 ParseNode *
 Parser::bracketedExpr()
 {
     /*
      * Always accept the 'in' operator in a parenthesized expression,
      * where it's unambiguous, even if we might be parsing the init of a
      * for statement.
      */
-    bool oldInForInit = tc->sc->inForInit;
-    tc->sc->inForInit = false;
+    bool oldInForInit = tc->inForInit;
+    tc->inForInit = false;
     ParseNode *pn = expr();
-    tc->sc->inForInit = oldInForInit;
+    tc->inForInit = oldInForInit;
     return pn;
 }
 
 #if JS_HAS_XML_SUPPORT
 
 ParseNode *
 Parser::endBracketedExpr()
 {
@@ -6472,22 +6472,21 @@ Parser::xmlElementOrListRoot(JSBool allo
 ParseNode *
 Parser::parseXMLText(JSObject *chain, bool allowList)
 {
     /*
      * Push a compiler frame if we have no frames, or if the top frame is a
      * lightweight function activation, or if its scope chain doesn't match
      * the one passed to us.
      */
-    SharedContext xmlsc(context, /* inFunction = */ false);
+    SharedContext xmlsc(context, chain, /* fun = */ NULL, /* funbox = */ NULL);
     TreeContext xmltc(this, &xmlsc);
     if (!xmltc.init())
         return NULL;
     JS_ASSERT(!xmlsc.inStrictMode());
-    xmlsc.setScopeChain(chain);
 
     /* Set XML-only mode to turn off special treatment of {expr} in XML. */
     tokenStream.setXMLOnlyMode();
     TokenKind tt = tokenStream.getToken(TSF_OPERAND);
 
     ParseNode *pn;
     if (tt != TOK_XMLSTAGO) {
         reportErrorNumber(NULL, JSREPORT_ERROR, JSMSG_BAD_XML_MARKUP);
--- a/js/src/frontend/SemanticAnalysis.cpp
+++ b/js/src/frontend/SemanticAnalysis.cpp
@@ -155,20 +155,21 @@ MarkExtensibleScopeDescendants(JSContext
     }
 
     return true;
 }
 
 bool
 frontend::AnalyzeFunctions(Parser *parser)
 {
-    SharedContext *sc = parser->tc->sc;
-    if (!sc->functionList)
+    TreeContext *tc = parser->tc;
+    SharedContext *sc = tc->sc;
+    if (!tc->functionList)
         return true;
-    if (!MarkExtensibleScopeDescendants(sc->context, sc->functionList, false))
+    if (!MarkExtensibleScopeDescendants(sc->context, tc->functionList, false))
         return false;
     bool isDirectEval = !!parser->callerFrame;
     bool isHeavyweight = false;
-    SetFunctionKinds(sc->functionList, &isHeavyweight, sc->inFunction, isDirectEval);
+    SetFunctionKinds(tc->functionList, &isHeavyweight, sc->inFunction(), isDirectEval);
     if (isHeavyweight)
         sc->setFunIsHeavyweight();
     return true;
 }
--- a/js/src/frontend/TreeContext-inl.h
+++ b/js/src/frontend/TreeContext-inl.h
@@ -11,34 +11,33 @@
 #include "frontend/Parser.h"
 #include "frontend/TreeContext.h"
 
 #include "frontend/ParseMaps-inl.h"
 
 namespace js {
 
 inline
-SharedContext::SharedContext(JSContext *cx, bool inFunction)
+SharedContext::SharedContext(JSContext *cx, JSObject *scopeChain, JSFunction *fun,
+                             FunctionBox *funbox)
   : context(cx),
     bodyid(0),
     blockidGen(0),
     topStmt(NULL),
     topScopeStmt(NULL),
     blockChain(cx),
-    fun_(cx),
-    scopeChain_(cx),
+    fun_(cx, fun),
+    funbox_(funbox),
+    scopeChain_(cx, scopeChain),
     staticLevel(0),
-    funbox(NULL),
-    functionList(NULL),
     bindings(cx),
     bindingsRoot(cx, &bindings),
-    inFunction(inFunction),
-    inForInit(false),
     cxFlags(cx)
 {
+    JS_ASSERT((fun && !scopeChain_) || (!fun && !funbox));
 }
 
 inline unsigned
 SharedContext::blockid()
 {
     return topStmt ? topStmt->blockid : bodyid;
 }
 
@@ -65,23 +64,25 @@ SharedContext::argumentsLocalSlot() cons
 inline
 TreeContext::TreeContext(Parser *prs, SharedContext *sc)
   : sc(sc),
     parenDepth(0),
     yieldCount(0),
     blockNode(NULL),
     decls(prs->context),
     yieldNode(NULL),
+    functionList(NULL),
     parserTC(&prs->tc),
     lexdeps(prs->context),
     parent(prs->tc),
     innermostWith(NULL),
     funcStmts(NULL),
     hasReturnExpr(false),
     hasReturnVoid(false),
+    inForInit(false),
     inDeclDestructuring(false)
 {
     prs->tc = this;
 }
 
 inline bool
 TreeContext::init()
 {
--- a/js/src/frontend/TreeContext.cpp
+++ b/js/src/frontend/TreeContext.cpp
@@ -38,18 +38,17 @@ frontend::SetStaticLevel(SharedContext *
     sc->staticLevel = staticLevel;
     return true;
 }
 
 bool
 frontend::GenerateBlockId(SharedContext *sc, uint32_t &blockid)
 {
     if (sc->blockidGen == JS_BIT(20)) {
-        JS_ReportErrorNumber(sc->context, js_GetErrorMessage, NULL,
-                             JSMSG_NEED_DIET, "program");
+        JS_ReportErrorNumber(sc->context, js_GetErrorMessage, NULL, JSMSG_NEED_DIET, "program");
         return false;
     }
     blockid = sc->blockidGen++;
     return true;
 }
 
 void
 frontend::PushStatement(SharedContext *sc, StmtInfo *stmt, StmtType type, ptrdiff_t top)
--- a/js/src/frontend/TreeContext.h
+++ b/js/src/frontend/TreeContext.h
@@ -56,20 +56,16 @@ class ContextFlags {
     // binding access since it does not go through the normal name lookup
     // mechanism. This is debatable and could be changed (although care must be
     // taken not to turn off the whole 'arguments' optimization). To answer the
     // more general "is this argument aliased" question, script->needsArgsObj
     // should be tested (see JSScript::argIsAlised).
     //
     bool            bindingsAccessedDynamically:1;
 
-    // The |fun*| flags are only relevant if |inFunction| is true.  Due to
-    // sloppiness, however, some are set in cases where |inFunction| is
-    // false.
-
     // The function needs Call object per call.
     bool            funIsHeavyweight:1;
 
     // We parsed a yield statement in the function.
     bool            funIsGenerator:1;
 
     // The function or a function that encloses it may define new local names
     // at runtime through means other than calling eval.
@@ -140,77 +136,71 @@ struct SharedContext {
     StmtInfo        *topStmt;       /* top of statement info stack */
     StmtInfo        *topScopeStmt;  /* top lexical scope statement */
     Rooted<StaticBlockObject *> blockChain;
                                     /* compile time block scope chain (NB: one
                                        deeper than the topScopeStmt/downScope
                                        chain when in head of let block/expr) */
 
   private:
-    RootedFunction  fun_;           /* function to store argument and variable
-                                       names when inFunction is set */
-    RootedObject    scopeChain_;    /* scope chain object for the script */
+    const RootedFunction fun_;      /* function to store argument and variable
+                                       names when it's a function's context */
+    FunctionBox *const funbox_;     /* null or box for function we're compiling
+                                       if inFunction() is true and not in
+                                       js::frontend::CompileFunctionBody */
+
+    const RootedObject scopeChain_; /* scope chain object for the script */
 
   public:
     unsigned        staticLevel;    /* static compilation unit nesting level */
 
-    FunctionBox     *funbox;        /* null or box for function we're compiling
-                                       if inFunction is set and not in
-                                       js::frontend::CompileFunctionBody */
-    FunctionBox     *functionList;
-
     Bindings        bindings;       /* bindings in this code, including
                                        arguments if we're compiling a function */
     Bindings::AutoRooter bindingsRoot; /* root for stack allocated bindings. */
 
-    const bool      inFunction:1;   /* parsing/emitting inside function body */
-
-    bool            inForInit:1;    /* parsing/emitting init expr of for; exclude 'in' */
-
     ContextFlags    cxFlags;
 
-    inline SharedContext(JSContext *cx, bool inFunction);
+    // If it's function code, fun must be non-NULL and scopeChain must be NULL.
+    // If it's global code, fun and funbox must be NULL.
+    inline SharedContext(JSContext *cx, JSObject *scopeChain, JSFunction *fun, FunctionBox *funbox);
+
+    // In theory, |fun*| flags are only relevant if |inFunction()| is true.
+    // However, we get and set in some cases where |inFunction()| is false,
+    // which is why |INFUNC| doesn't appear in all of the fun* and setFun*
+    // functions below.
+#define INFUNC JS_ASSERT(inFunction())
 
-    bool inStrictMode()                const { return cxFlags.inStrictMode; }
-    bool bindingsAccessedDynamically() const { return cxFlags.bindingsAccessedDynamically; }
-    bool funIsHeavyweight()            const { return cxFlags.funIsHeavyweight; }
-    bool funIsGenerator()              const { return cxFlags.funIsGenerator; }
-    bool funMightAliasLocals()         const { return cxFlags.funMightAliasLocals; }
-    bool funHasExtensibleScope()       const { return cxFlags.funHasExtensibleScope; }
-    bool funArgumentsHasLocalBinding() const { return cxFlags.funArgumentsHasLocalBinding; }
-    bool funDefinitelyNeedsArgsObj()   const { return cxFlags.funDefinitelyNeedsArgsObj; }
+    bool inStrictMode()                const {         return cxFlags.inStrictMode; }
+    bool bindingsAccessedDynamically() const {         return cxFlags.bindingsAccessedDynamically; }
+    bool funIsHeavyweight()            const { INFUNC; return cxFlags.funIsHeavyweight; }
+    bool funIsGenerator()              const { INFUNC; return cxFlags.funIsGenerator; }
+    bool funMightAliasLocals()         const {         return cxFlags.funMightAliasLocals; }
+    bool funHasExtensibleScope()       const {         return cxFlags.funHasExtensibleScope; }
+    bool funArgumentsHasLocalBinding() const { INFUNC; return cxFlags.funArgumentsHasLocalBinding; }
+    bool funDefinitelyNeedsArgsObj()   const { INFUNC; return cxFlags.funDefinitelyNeedsArgsObj; }
 
-    void setInStrictMode()                  { cxFlags.inStrictMode                = true; }
-    void setBindingsAccessedDynamically()   { cxFlags.bindingsAccessedDynamically = true; }
-    void setFunIsHeavyweight()              { cxFlags.funIsHeavyweight            = true; }
-    void setFunIsGenerator()                { cxFlags.funIsGenerator              = true; }
-    void setFunMightAliasLocals()           { cxFlags.funMightAliasLocals         = true; }
-    void setFunHasExtensibleScope()         { cxFlags.funHasExtensibleScope       = true; }
-    void setFunArgumentsHasLocalBinding()   { cxFlags.funArgumentsHasLocalBinding = true; }
+    void setInStrictMode()                  {         cxFlags.inStrictMode                = true; }
+    void setBindingsAccessedDynamically()   {         cxFlags.bindingsAccessedDynamically = true; }
+    void setFunIsHeavyweight()              {         cxFlags.funIsHeavyweight            = true; }
+    void setFunIsGenerator()                { INFUNC; cxFlags.funIsGenerator              = true; }
+    void setFunMightAliasLocals()           {         cxFlags.funMightAliasLocals         = true; }
+    void setFunHasExtensibleScope()         {         cxFlags.funHasExtensibleScope       = true; }
+    void setFunArgumentsHasLocalBinding()   { INFUNC; cxFlags.funArgumentsHasLocalBinding = true; }
     void setFunDefinitelyNeedsArgsObj()     { JS_ASSERT(cxFlags.funArgumentsHasLocalBinding);
-                                              cxFlags.funDefinitelyNeedsArgsObj   = true; }
+                                              INFUNC; cxFlags.funDefinitelyNeedsArgsObj   = true; }
+
+#undef INFUNC
 
     unsigned argumentsLocalSlot() const;
 
-    JSFunction *fun() const {
-        JS_ASSERT(inFunction);
-        return fun_;
-    }
-    void setFunction(JSFunction *fun) {
-        JS_ASSERT(inFunction);
-        fun_ = fun;
-    }
-    JSObject *scopeChain() const {
-        JS_ASSERT(!inFunction);
-        return scopeChain_;
-    }
-    void setScopeChain(JSObject *scopeChain) {
-        JS_ASSERT(!inFunction);
-        scopeChain_ = scopeChain;
-    }
+    bool inFunction() const { return !!fun_; }
+
+    JSFunction *fun()      const { JS_ASSERT(inFunction());  return fun_; }
+    FunctionBox *funbox()  const { JS_ASSERT(inFunction());  return funbox_; }
+    JSObject *scopeChain() const { JS_ASSERT(!inFunction()); return scopeChain_; }
 
     unsigned blockid();
 
     // True if we are at the topmost level of a entire script or function body.
     // For example, while parsing this code we would encounter f1 and f2 at
     // body level, but we would not encounter f3 or f4 at body level:
     //
     //   function f1() { function f2() { } }
@@ -234,16 +224,17 @@ struct TreeContext {                /* t
     uint32_t        yieldCount;     /* number of |yield| tokens encountered at
                                        non-zero depth in current paren tree */
     ParseNode       *blockNode;     /* parse node for a block with let declarations
                                        (block with its own lexical scope)  */
     AtomDecls       decls;          /* function, const, and var declarations */
     ParseNode       *yieldNode;     /* parse node for a yield expression that might
                                        be an error if we turn out to be inside a
                                        generator expression */
+    FunctionBox     *functionList;
 
   private:
     TreeContext     **parserTC;     /* this points to the Parser's active tc
                                        and holds either |this| or one of
                                        |this|'s descendents */
 
   public:
     OwnedAtomDefnMapPtr lexdeps;    /* unresolved lexical name dependencies */
@@ -258,16 +249,18 @@ struct TreeContext {                /* t
 
     /*
      * Flags that are set for a short time during parsing to indicate context
      * or the presence of a code feature.
      */
     bool            hasReturnExpr:1; /* function has 'return <expr>;' */
     bool            hasReturnVoid:1; /* function has 'return;' */
 
+    bool            inForInit:1;    /* parsing init expr of for; exclude 'in' */
+
     // Set when parsing a declaration-like destructuring pattern.  This flag
     // causes PrimaryExpr to create PN_NAME parse nodes for variable references
     // which are not hooked into any definition's use chain, added to any tree
     // context's AtomList, etc. etc.  CheckDestructuring will do that work
     // later.
     //
     // The comments atop CheckDestructuring explain the distinction between
     // assignment-like and declaration-like destructuring patterns, and why
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -1241,17 +1241,17 @@ JSScript::NewScriptFromEmitter(JSContext
         return NULL;
 
     bce->sc->bindings.makeImmutable();
 
     JS_ASSERT(script->mainOffset == 0);
     script->mainOffset = prologLength;
     PodCopy<jsbytecode>(script->code, bce->prologBase(), prologLength);
     PodCopy<jsbytecode>(script->main(), bce->base(), mainLength);
-    nfixed = bce->sc->inFunction ? bce->sc->bindings.numVars() : 0;
+    nfixed = bce->sc->inFunction() ? bce->sc->bindings.numVars() : 0;
     JS_ASSERT(nfixed < SLOTNO_LIMIT);
     script->nfixed = uint16_t(nfixed);
     InitAtomMap(cx, bce->atomIndices.getMap(), script->atoms);
 
     filename = bce->parser->tokenStream.getFilename();
     if (filename) {
         script->filename = SaveScriptFilename(cx, filename);
         if (!script->filename)
@@ -1304,39 +1304,36 @@ JSScript::NewScriptFromEmitter(JSContext
     }
     script->bindingsAccessedDynamically = bce->sc->bindingsAccessedDynamically();
     script->hasSingletons = bce->hasSingletons;
 #ifdef JS_METHODJIT
     if (cx->compartment->debugMode())
         script->debugMode = true;
 #endif
 
-    if (bce->sc->inFunction) {
+    if (bce->sc->inFunction()) {
         if (bce->sc->funArgumentsHasLocalBinding()) {
             // This must precede the script->bindings.transfer() call below.
             script->setArgumentsHasLocalBinding(bce->sc->argumentsLocalSlot());
             if (bce->sc->funDefinitelyNeedsArgsObj())
                 script->setNeedsArgsObj(true);
         } else {
             JS_ASSERT(!bce->sc->funDefinitelyNeedsArgsObj());
         }
-    } else {
-        JS_ASSERT(!bce->sc->funArgumentsHasLocalBinding());
-        JS_ASSERT(!bce->sc->funDefinitelyNeedsArgsObj());
     }
 
     if (nClosedArgs)
         PodCopy<uint32_t>(script->closedArgs()->vector, &bce->closedArgs[0], nClosedArgs);
     if (nClosedVars)
         PodCopy<uint32_t>(script->closedVars()->vector, &bce->closedVars[0], nClosedVars);
 
     script->bindings.transfer(cx, &bce->sc->bindings);
 
     fun = NULL;
-    if (bce->sc->inFunction) {
+    if (bce->sc->inFunction()) {
         JS_ASSERT(!bce->noScriptRval);
         JS_ASSERT(!bce->needScriptGlobal);
 
         script->isGenerator = bce->sc->funIsGenerator();
 
         /*
          * We initialize fun->script() to be the script constructed above
          * so that the debugger has a valid fun->script().
@@ -1357,20 +1354,16 @@ JSScript::NewScriptFromEmitter(JSContext
 
         if (!script->typeSetFunction(cx, fun, singleton))
             return NULL;
 
         fun->setScript(script);
         script->globalObject = fun->getParent() ? &fun->getParent()->global() : NULL;
 
     } else {
-        // It'd be nice to JS_ASSERT(!bce->sc->funIsHeavyweight()) here, but
-        // Parser.cpp is sloppy and sometimes applies it to non-functions.
-        JS_ASSERT(!bce->sc->funIsGenerator());
-
         /*
          * Initialize script->object, if necessary, so that the debugger has a
          * valid holder object.
          */
         if (bce->needScriptGlobal)
             script->globalObject = GetCurrentGlobal(cx);
 
         script->noScriptRval = bce->noScriptRval;
--- a/js/src/jswrapper.h
+++ b/js/src/jswrapper.h
@@ -233,17 +233,17 @@ UnwrapObject(JSObject *obj, bool stopAtO
 
 // Given a JSObject, returns that object stripped of wrappers. At each stage,
 // the security wrapper has the opportunity to veto the unwrap. Since checked
 // code should never be unwrapping outer window wrappers, we always stop at
 // outer windows.
 JS_FRIEND_API(JSObject *)
 UnwrapObjectChecked(JSContext *cx, JSObject *obj);
 
-bool
+JS_FRIEND_API(bool)
 IsCrossCompartmentWrapper(const JSObject *obj);
 
 void
 NukeCrossCompartmentWrapper(JSObject *wrapper);
 
 } /* namespace js */
 
 #endif
--- a/js/src/v8/base.js
+++ b/js/src/v8/base.js
@@ -1,9 +1,9 @@
-// Copyright 2008 the V8 project authors. All rights reserved.
+// Copyright 2012 the V8 project authors. All rights reserved.
 // Redistribution and use in source and binary forms, with or without
 // modification, are permitted provided that the following conditions are
 // met:
 //
 //     * Redistributions of source code must retain the above copyright
 //       notice, this list of conditions and the following disclaimer.
 //     * Redistributions in binary form must reproduce the above
 //       copyright notice, this list of conditions and the following
@@ -73,17 +73,17 @@ function BenchmarkSuite(name, reference,
 
 // Keep track of all declared benchmark suites.
 BenchmarkSuite.suites = [];
 
 
 // Scores are not comparable across versions. Bump the version if
 // you're making changes that will affect that scores, e.g. if you add
 // a new benchmark or change an existing one.
-BenchmarkSuite.version = '6';
+BenchmarkSuite.version = '7';
 
 
 // To make the benchmark results predictable, we replace Math.random
 // with a 100% deterministic alternative.
 Math.random = (function() {
   var seed = 49734321;
   return function() {
     // Robert Jenkins' 32 bit integer hash function.
--- a/js/src/v8/crypto.js
+++ b/js/src/v8/crypto.js
@@ -1401,17 +1401,17 @@ function rng_seed_int(x) {
   rng_pool[rng_pptr++] ^= (x >> 8) & 255;
   rng_pool[rng_pptr++] ^= (x >> 16) & 255;
   rng_pool[rng_pptr++] ^= (x >> 24) & 255;
   if(rng_pptr >= rng_psize) rng_pptr -= rng_psize;
 }
 
 // Mix in the current time (w/milliseconds) into the pool
 function rng_seed_time() {
-  // Use pre-computed date to avoid making the benchmark 
+  // Use pre-computed date to avoid making the benchmark
   // results dependent on the current date.
   rng_seed_int(1122926989487);
 }
 
 // Initialize the pool with junk if needed.
 if(rng_pool == null) {
   rng_pool = new Array();
   rng_pptr = 0;
--- a/js/src/v8/earley-boyer.js
+++ b/js/src/v8/earley-boyer.js
@@ -129,17 +129,17 @@ function sc_rempropBang(sym, key) {
     var ht = sc_properties[sym];
     if (ht)
 	delete ht[key];
 }
 
 /*** META ((export #t)) */
 function sc_any2String(o) {
     return jsstring2string(sc_toDisplayString(o));
-}    
+}
 
 /*** META ((export #t)
            (peephole (infix 2 2 "==="))
            (type bool))
 */
 function sc_isEqv(o1, o2) {
     return (o1 === o2);
 }
@@ -918,17 +918,17 @@ function sc_append() {
 function sc_dualAppendBang(l1, l2) {
     if (l1 === null) return l2;
     if (l2 === null) return l1;
     var tmp = l1;
     while (tmp.cdr !== null) tmp=tmp.cdr;
     tmp.cdr = l2;
     return l1;
 }
-    
+
 /*** META ((export #t)) */
 function sc_appendBang() {
     var res = null;
     for (var i = 0; i < arguments.length; i++)
 	res = sc_dualAppendBang(res, arguments[i]);
     return res;
 }
 
@@ -1158,17 +1158,17 @@ sc_Char.readable2char = {
     "sub": "\032",
     "esc": "\033",
     "fs": "\034",
     "gs": "\035",