Merge last PGO-green changeset of mozilla-inbound to mozilla-central
authorEd Morley <emorley@mozilla.com>
Thu, 26 Apr 2012 13:46:02 +0100
changeset 92574 811b1ce5f4b2ff575c1c94c1e2370d844cacf183
parent 92573 cc5254f9825fe2a1edc3817575b1ea36ab294013 (current diff)
parent 92479 4e3152b3c7b0db72a314aba767392fd5a0ca6a94 (diff)
child 92575 c6d998a2cc8c63a277f602e250077568ad78f427
push id8756
push userphilringnalda@gmail.com
push dateFri, 27 Apr 2012 04:42:37 +0000
treeherdermozilla-inbound@a2f61f95b2c3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone15.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge last PGO-green changeset of mozilla-inbound to mozilla-central
content/html/content/src/nsHTMLTextAreaElement.cpp
js/src/jsfriendapi.h
js/src/jswrapper.cpp
--- a/b2g/config/tooltool-manifests/releng.manifest
+++ b/b2g/config/tooltool-manifests/releng.manifest
@@ -1,14 +1,14 @@
 [
 {
-    "size": 195, 
-    "digest": "85369693c2362131515014cd4547cd0824225b03e1e52a352a84012e6e8586fa46ad619181167d220c463761865719a1747a83aeee76a5fcc5ab7859c14ef24a", 
-    "algorithm": "sha512", 
-    "filename": "setup.sh"
+"size": 195, 
+"digest": "ff960e3cb9e07865bde6fdb0d4127a11205d4cb70ee527e30ccc38f6370a9d750eb5da8993cb3c5de28b45836a3a20b8c25c4adcc68b1945a59dbe1c8b28cc23", 
+"algorithm": "sha512", 
+"filename": "setup.sh"
 }, 
 {
-    "size": 121344236, 
-    "digest": "0bf5cceced8add6142c1c7522890c39554b94848e3f5eb9b92de2a8c6f6af3c0e8ab69ddf7bae39eaab2bcb637233ff02de07b47a1850767d2810a46fb31bf65", 
-    "algorithm": "sha512", 
-    "filename": "gonk-toolchain-0.tar.bz2"
+"size": 121135598, 
+"digest": "e182eb95105f186ec81546c373752c3af5b8fbc520fd14d37fcb1bb40efac76b96befe868b5a5a17f967bd04390122b7911d167dda1516225709cbd3a00e2c78", 
+"algorithm": "sha512", 
+"filename": "gonk-toolchain-1.tar.bz2"
 }
 ]
--- a/browser/confvars.sh
+++ b/browser/confvars.sh
@@ -43,16 +43,20 @@ 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.
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -477,17 +477,17 @@ function FindProxyForURL(url, host)
 user_pref("network.proxy.type", 2);
 user_pref("network.proxy.autoconfig_url", "%(pacURL)s");
 
 user_pref("camino.use_system_proxy_settings", false); // Camino-only, harmless to others
 """ % {"pacURL": pacURL}
       prefs.append(part)
 
     for v in extraPrefs:
-      thispref = v.split("=")
+      thispref = v.split("=", 1)
       if len(thispref) < 2:
         print "Error: syntax error in --setpref=" + v
         sys.exit(1)
       part = 'user_pref("%s", %s);\n' % (thispref[0], thispref[1])
       prefs.append(part)
 
     # write the preferences
     prefsFile = open(profileDir + "/" + "user.js", "a")
--- a/configure.in
+++ b/configure.in
@@ -4600,28 +4600,26 @@ BUILD_CTYPES=1
 MOZ_USE_NATIVE_POPUP_WINDOWS=
 MOZ_ANDROID_HISTORY=
 MOZ_WEBSMS_BACKEND=
 MOZ_GRAPHITE=1
 
 case "${target}" in
 *darwin*)
     ACCESSIBILITY=
-    MOZ_WEBAPP_RUNTIME=1
     ;;
 *)
     ACCESSIBILITY=1
     ;;
 esac
 
 case "$target_os" in
     mingw*)
         NS_ENABLE_TSF=1
         AC_DEFINE(NS_ENABLE_TSF)
-        MOZ_WEBAPP_RUNTIME=1
         ;;
 esac
 
 case "${target}" in
     *-android*|*-linuxandroid*)
         if test "$CPU_ARCH" = "arm" ; then
           USE_ARM_KUSER=1
         fi
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -206,17 +206,16 @@ static NS_DEFINE_CID(kXTFServiceCID, NS_
 #include "nsIObjectLoadingContent.h"
 #include "nsCCUncollectableMarker.h"
 #include "mozilla/Base64.h"
 #include "mozilla/Preferences.h"
 #include "nsDOMMutationObserver.h"
 
 #include "nsWrapperCacheInlines.h"
 #include "nsIDOMDocumentType.h"
-#include "nsIDOMWindowUtils.h"
 #include "nsCharSeparatedTokenizer.h"
 
 extern "C" int MOZ_XMLTranslateEntity(const char* ptr, const char* end,
                                       const char** next, PRUnichar* result);
 extern "C" int MOZ_XMLCheckQName(const char* ptr, const char* end,
                                  int ns_aware, const char** colon);
 
 using namespace mozilla::dom;
@@ -4895,23 +4894,16 @@ nsContentUtils::GetViewportInfo(nsIDocum
     }
   }
 
   if (aDocument->IsXUL()) {
     ret.autoScale = false;
     return ret;
   }
 
-  nsIDOMWindow* window = aDocument->GetWindow();
-  nsCOMPtr<nsIDOMWindowUtils> windowUtils(do_GetInterface(window));
-
-  if (!windowUtils) {
-    return ret;
-  }
-
   nsAutoString handheldFriendly;
   aDocument->GetHeaderData(nsGkAtoms::handheldFriendly, handheldFriendly);
 
   if (handheldFriendly.EqualsLiteral("true")) {
     return ret;
   }
 
   PRInt32 errorCode;
--- a/content/base/src/nsNodeInfo.cpp
+++ b/content/base/src/nsNodeInfo.cpp
@@ -61,18 +61,17 @@
 #include "nsGkAtoms.h"
 
 using namespace mozilla;
 
 static const size_t kNodeInfoPoolSizes[] = {
   sizeof(nsNodeInfo)
 };
 
-static const PRInt32 kNodeInfoPoolInitialSize = 
-  (NS_SIZE_IN_HEAP(sizeof(nsNodeInfo))) * 64;
+static const PRInt32 kNodeInfoPoolInitialSize = sizeof(nsNodeInfo) * 64;
 
 // static
 nsFixedSizeAllocator* nsNodeInfo::sNodeInfoPool = nsnull;
 
 // static
 nsNodeInfo*
 nsNodeInfo::Create(nsIAtom *aName, nsIAtom *aPrefix, PRInt32 aNamespaceID,
                    PRUint16 aNodeType, nsIAtom *aExtraName,
--- a/content/base/src/nsObjectLoadingContent.cpp
+++ b/content/base/src/nsObjectLoadingContent.cpp
@@ -796,17 +796,18 @@ nsObjectLoadingContent::OnStartRequest(n
     chan->SetContentType(channelType);
   }
 
   // We want to use the channel type unless one of the following is true:
   //
   // 1) The channel type is application/octet-stream and we have a
   //    type hint and the type hint is not a document type.
   // 2) Our type hint is a type that we support with a plugin.
-  if ((channelType.EqualsASCII(APPLICATION_OCTET_STREAM) && 
+  if (((channelType.EqualsASCII(APPLICATION_OCTET_STREAM) ||
+        channelType.EqualsASCII(BINARY_OCTET_STREAM)) && 
        !mContentType.IsEmpty() &&
        GetTypeOfContent(mContentType) != eType_Document) ||
       // Need to check IsPluginEnabledForType() in addition to GetTypeOfContent()
       // because otherwise the default plug-in's catch-all behavior would
       // confuse things.
       (NS_SUCCEEDED(IsPluginEnabledForType(mContentType)) && 
        GetTypeOfContent(mContentType) == eType_Plugin)) {
     // Set the type we'll use for dispatch on the channel.  Otherwise we could
@@ -822,17 +823,18 @@ nsObjectLoadingContent::OnStartRequest(n
   }
 
   nsCOMPtr<nsIURI> uri;
   chan->GetURI(getter_AddRefs(uri));
   if (!uri) {
     return NS_ERROR_FAILURE;
   }
 
-  if (mContentType.EqualsASCII(APPLICATION_OCTET_STREAM)) {
+  if (mContentType.EqualsASCII(APPLICATION_OCTET_STREAM) ||
+      mContentType.EqualsASCII(BINARY_OCTET_STREAM)) {
     nsCAutoString extType;
     if (IsPluginEnabledByExtension(uri, extType)) {
       mContentType = extType;
       chan->SetContentType(extType);
     }
   }
 
   // Now find out what type the content is
--- a/content/canvas/src/nsCanvasRenderingContext2D.cpp
+++ b/content/canvas/src/nsCanvasRenderingContext2D.cpp
@@ -2579,19 +2579,20 @@ nsCanvasRenderingContext2D::SetFont(cons
     gfxFontStyle style(fontStyle->mFont.style,
                        fontStyle->mFont.weight,
                        fontStyle->mFont.stretch,
                        NSAppUnitsToFloatPixels(fontSize, float(aupcp)),
                        language,
                        fontStyle->mFont.sizeAdjust,
                        fontStyle->mFont.systemFont,
                        printerFont,
-                       fontStyle->mFont.featureSettings,
                        fontStyle->mFont.languageOverride);
 
+    fontStyle->mFont.AddFontFeaturesToStyle(&style);
+
     CurrentState().fontGroup =
         gfxPlatform::GetPlatform()->CreateFontGroup(fontStyle->mFont.name,
                                                     &style,
                                                     presShell->GetPresContext()->GetUserFontSet());
     NS_ASSERTION(CurrentState().fontGroup, "Could not get font group");
 
     // The font getter is required to be reserialized based on what we
     // parsed (including having line-height removed).  (Older drafts of
--- a/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
+++ b/content/canvas/src/nsCanvasRenderingContext2DAzure.cpp
@@ -2792,19 +2792,20 @@ nsCanvasRenderingContext2DAzure::SetFont
   gfxFontStyle style(fontStyle->mFont.style,
                       fontStyle->mFont.weight,
                       fontStyle->mFont.stretch,
                       NSAppUnitsToFloatPixels(fontSize, float(aupcp)),
                       language,
                       fontStyle->mFont.sizeAdjust,
                       fontStyle->mFont.systemFont,
                       printerFont,
-                      fontStyle->mFont.featureSettings,
                       fontStyle->mFont.languageOverride);
 
+  fontStyle->mFont.AddFontFeaturesToStyle(&style);
+
   CurrentState().fontGroup =
       gfxPlatform::GetPlatform()->CreateFontGroup(fontStyle->mFont.name,
                                                   &style,
                                                   presShell->GetPresContext()->GetUserFontSet());
   NS_ASSERTION(CurrentState().fontGroup, "Could not get font group");
 
   // The font getter is required to be reserialized based on what we
   // parsed (including having line-height removed).  (Older drafts of
--- a/content/events/src/nsEventDispatcher.cpp
+++ b/content/events/src/nsEventDispatcher.cpp
@@ -418,17 +418,17 @@ class ChainItemPool {
 public:
   ChainItemPool() {
     if (!sEtciPool) {
       sEtciPool = new nsFixedSizeAllocator();
       if (sEtciPool) {
         static const size_t kBucketSizes[] = { sizeof(nsEventTargetChainItem) };
         static const PRInt32 kNumBuckets = sizeof(kBucketSizes) / sizeof(size_t);
         static const PRInt32 kInitialPoolSize =
-          NS_SIZE_IN_HEAP(sizeof(nsEventTargetChainItem)) * NS_CHAIN_POOL_SIZE;
+          sizeof(nsEventTargetChainItem) * NS_CHAIN_POOL_SIZE;
         nsresult rv = sEtciPool->Init("EventTargetChainItem Pool", kBucketSizes,
                                       kNumBuckets, kInitialPoolSize);
         if (NS_FAILED(rv)) {
           delete sEtciPool;
           sEtciPool = nsnull;
         }
       }
     }
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -568,18 +568,17 @@ nsHTMLInputElement::nsHTMLInputElement(a
   , mInInternalActivate(false)
   , mCheckedIsToggled(false)
   , mIndeterminate(false)
   , mInhibitRestoration(aFromParser & FROM_PARSER_FRAGMENT)
   , mCanShowValidUI(true)
   , mCanShowInvalidUI(true)
 {
   mInputData.mState = new nsTextEditorState(this);
-  NS_ADDREF(mInputData.mState);
-  
+
   if (!gUploadLastDir)
     nsHTMLInputElement::InitUploadLastDir();
 
   // Set up our default state.  By default we're enabled (since we're
   // a control type that can be disabled but not actually disabled
   // right now), optional, and valid.  We are NOT readwrite by default
   // until someone calls UpdateEditableState on us, apparently!  Also
   // by default we don't have to show validity UI and so forth.
@@ -600,17 +599,18 @@ nsHTMLInputElement::~nsHTMLInputElement(
 void
 nsHTMLInputElement::FreeData()
 {
   if (!IsSingleLineTextControl(false)) {
     nsMemory::Free(mInputData.mValue);
     mInputData.mValue = nsnull;
   } else {
     UnbindFromFrame(nsnull);
-    NS_IF_RELEASE(mInputData.mState);
+    delete mInputData.mState;
+    mInputData.mState = nsnull;
   }
 }
 
 nsTextEditorState*
 nsHTMLInputElement::GetEditorState() const
 {
   if (!IsSingleLineTextControl(false)) {
     return nsnull;
@@ -625,30 +625,33 @@ nsHTMLInputElement::GetEditorState() con
 
 // nsISupports
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsHTMLInputElement)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsHTMLInputElement,
                                                   nsGenericHTMLFormElement)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mControllers)
   if (tmp->IsSingleLineTextControl(false)) {
-    NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_MEMBER(mInputData.mState, nsTextEditorState)
+    tmp->mInputData.mState->Traverse(cb);
   }
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMARRAY(mFiles)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mFileList)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsHTMLInputElement,
                                                   nsGenericHTMLFormElement)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mControllers)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMARRAY(mFiles)
   if (tmp->mFileList) {
     tmp->mFileList->Disconnect();
     tmp->mFileList = nsnull;
   }
+  if (tmp->IsSingleLineTextControl(false)) {
+    tmp->mInputData.mState->Unlink();
+  }
   //XXX should unlink more?
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
                                                               
 NS_IMPL_ADDREF_INHERITED(nsHTMLInputElement, nsGenericElement) 
 NS_IMPL_RELEASE_INHERITED(nsHTMLInputElement, nsGenericElement) 
 
 
 DOMCI_NODE_DATA(HTMLInputElement, nsHTMLInputElement)
@@ -2391,17 +2394,16 @@ nsHTMLInputElement::HandleTypeChange(PRU
 
   // Only single line text inputs have a text editor state.
   bool isNewTypeSingleLine = IsSingleLineTextControl(false, aNewType);
   bool isCurrentTypeSingleLine = IsSingleLineTextControl(false, mType);
 
   if (isNewTypeSingleLine && !isCurrentTypeSingleLine) {
     FreeData();
     mInputData.mState = new nsTextEditorState(this);
-    NS_ADDREF(mInputData.mState);
   } else if (isCurrentTypeSingleLine && !isNewTypeSingleLine) {
     FreeData();
   }
 
   mType = aNewType;
 
   if (!mParserCreating) {
     /**
--- a/content/html/content/src/nsHTMLTextAreaElement.cpp
+++ b/content/html/content/src/nsHTMLTextAreaElement.cpp
@@ -256,17 +256,17 @@ protected:
   /** Whether our disabled state has changed from the default **/
   bool                     mDisabledChanged;
   /** Whether we should make :-moz-ui-invalid apply on the element. **/
   bool                     mCanShowInvalidUI;
   /** Whether we should make :-moz-ui-valid apply on the element. **/
   bool                     mCanShowValidUI;
 
   /** The state of the text editor (selection controller and the editor) **/
-  nsRefPtr<nsTextEditorState> mState;
+  nsTextEditorState mState;
 
   NS_IMETHOD SelectAll(nsPresContext* aPresContext);
   /**
    * Get the value, whether it is from the content or the frame.
    * @param aValue the value [out]
    * @param aIgnoreWrap whether to ignore the wrap attribute when getting the
    *        value.  If this is true, linebreaks will not be inserted even if
    *        wrap=hard.
@@ -330,17 +330,17 @@ nsHTMLTextAreaElement::nsHTMLTextAreaEle
   : nsGenericHTMLFormElement(aNodeInfo),
     mValueChanged(false),
     mHandlingSelect(false),
     mDoneAddingChildren(!aFromParser),
     mInhibitStateRestoration(!!(aFromParser & FROM_PARSER_FRAGMENT)),
     mDisabledChanged(false),
     mCanShowInvalidUI(true),
     mCanShowValidUI(true),
-    mState(new nsTextEditorState(this))
+    mState(this)
 {
   AddMutationObserver(this);
 
   // Set up our default state.  By default we're enabled (since we're
   // a control type that can be disabled but not actually disabled
   // right now), optional, and valid.  We are NOT readwrite by default
   // until someone calls UpdateEditableState on us, apparently!  Also
   // by default we don't have to show validity UI and so forth.
@@ -349,21 +349,22 @@ nsHTMLTextAreaElement::nsHTMLTextAreaEle
                     NS_EVENT_STATE_VALID);
 }
 
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsHTMLTextAreaElement)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsHTMLTextAreaElement,
                                                 nsGenericHTMLFormElement)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mControllers)
+  tmp->mState.Unlink();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsHTMLTextAreaElement,
                                                   nsGenericHTMLFormElement)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mControllers)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_MEMBER(mState, nsTextEditorState)
+  tmp->mState.Traverse(cb);
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_ADDREF_INHERITED(nsHTMLTextAreaElement, nsGenericElement) 
 NS_IMPL_RELEASE_INHERITED(nsHTMLTextAreaElement, nsGenericElement) 
 
 
 DOMCI_NODE_DATA(HTMLTextAreaElement, nsHTMLTextAreaElement)
 
@@ -495,97 +496,97 @@ nsHTMLTextAreaElement::GetValue(nsAStrin
 {
   GetValueInternal(aValue, true);
   return NS_OK;
 }
 
 void
 nsHTMLTextAreaElement::GetValueInternal(nsAString& aValue, bool aIgnoreWrap) const
 {
-  mState->GetValue(aValue, aIgnoreWrap);
+  mState.GetValue(aValue, aIgnoreWrap);
 }
 
 NS_IMETHODIMP_(nsIEditor*)
 nsHTMLTextAreaElement::GetTextEditor()
 {
-  return mState->GetEditor();
+  return mState.GetEditor();
 }
 
 NS_IMETHODIMP_(nsISelectionController*)
 nsHTMLTextAreaElement::GetSelectionController()
 {
-  return mState->GetSelectionController();
+  return mState.GetSelectionController();
 }
 
 NS_IMETHODIMP_(nsFrameSelection*)
 nsHTMLTextAreaElement::GetConstFrameSelection()
 {
-  return mState->GetConstFrameSelection();
+  return mState.GetConstFrameSelection();
 }
 
 NS_IMETHODIMP
 nsHTMLTextAreaElement::BindToFrame(nsTextControlFrame* aFrame)
 {
-  return mState->BindToFrame(aFrame);
+  return mState.BindToFrame(aFrame);
 }
 
 NS_IMETHODIMP_(void)
 nsHTMLTextAreaElement::UnbindFromFrame(nsTextControlFrame* aFrame)
 {
   if (aFrame) {
-    mState->UnbindFromFrame(aFrame);
+    mState.UnbindFromFrame(aFrame);
   }
 }
 
 NS_IMETHODIMP
 nsHTMLTextAreaElement::CreateEditor()
 {
-  return mState->PrepareEditor();
+  return mState.PrepareEditor();
 }
 
 NS_IMETHODIMP_(nsIContent*)
 nsHTMLTextAreaElement::GetRootEditorNode()
 {
-  return mState->GetRootNode();
+  return mState.GetRootNode();
 }
 
 NS_IMETHODIMP_(nsIContent*)
 nsHTMLTextAreaElement::CreatePlaceholderNode()
 {
-  NS_ENSURE_SUCCESS(mState->CreatePlaceholderNode(), nsnull);
-  return mState->GetPlaceholderNode();
+  NS_ENSURE_SUCCESS(mState.CreatePlaceholderNode(), nsnull);
+  return mState.GetPlaceholderNode();
 }
 
 NS_IMETHODIMP_(nsIContent*)
 nsHTMLTextAreaElement::GetPlaceholderNode()
 {
-  return mState->GetPlaceholderNode();
+  return mState.GetPlaceholderNode();
 }
 
 NS_IMETHODIMP_(void)
 nsHTMLTextAreaElement::UpdatePlaceholderText(bool aNotify)
 {
-  mState->UpdatePlaceholderText(aNotify);
+  mState.UpdatePlaceholderText(aNotify);
 }
 
 NS_IMETHODIMP_(void)
 nsHTMLTextAreaElement::SetPlaceholderClass(bool aVisible, bool aNotify)
 {
-  mState->SetPlaceholderClass(aVisible, aNotify);
+  mState.SetPlaceholderClass(aVisible, aNotify);
 }
 
 nsresult
 nsHTMLTextAreaElement::SetValueInternal(const nsAString& aValue,
                                         bool aUserInput)
 {
   // Need to set the value changed flag here, so that
   // nsTextControlFrame::UpdateValueDisplay retrieves the correct value
   // if needed.
   SetValueChanged(true);
-  mState->SetValue(aValue, aUserInput);
+  mState.SetValue(aValue, aUserInput);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP 
 nsHTMLTextAreaElement::SetValue(const nsAString& aValue)
 {
   return SetValueInternal(aValue, false);
@@ -602,18 +603,18 @@ nsHTMLTextAreaElement::SetUserInput(cons
 }
 
 NS_IMETHODIMP
 nsHTMLTextAreaElement::SetValueChanged(bool aValueChanged)
 {
   bool previousValue = mValueChanged;
 
   mValueChanged = aValueChanged;
-  if (!aValueChanged && !mState->IsEmpty()) {
-    mState->EmptyValue();
+  if (!aValueChanged && !mState.IsEmpty()) {
+    mState.EmptyValue();
   }
 
   if (mValueChanged != previousValue) {
     UpdateState(true);
   }
 
   return NS_OK;
 }
@@ -845,28 +846,28 @@ nsHTMLTextAreaElement::GetTextLength(PRI
 NS_IMETHODIMP
 nsHTMLTextAreaElement::GetSelectionStart(PRInt32 *aSelectionStart)
 {
   NS_ENSURE_ARG_POINTER(aSelectionStart);
 
   PRInt32 selEnd;
   nsresult rv = GetSelectionRange(aSelectionStart, &selEnd);
 
-  if (NS_FAILED(rv) && mState->IsSelectionCached()) {
-    *aSelectionStart = mState->GetSelectionProperties().mStart;
+  if (NS_FAILED(rv) && mState.IsSelectionCached()) {
+    *aSelectionStart = mState.GetSelectionProperties().mStart;
     return NS_OK;
   }
   return rv;
 }
 
 NS_IMETHODIMP
 nsHTMLTextAreaElement::SetSelectionStart(PRInt32 aSelectionStart)
 {
-  if (mState->IsSelectionCached()) {
-    mState->GetSelectionProperties().mStart = aSelectionStart;
+  if (mState.IsSelectionCached()) {
+    mState.GetSelectionProperties().mStart = aSelectionStart;
     return NS_OK;
   }
 
   nsAutoString direction;
   nsresult rv = GetSelectionDirection(direction);
   NS_ENSURE_SUCCESS(rv, rv);
   PRInt32 start, end;
   rv = GetSelectionRange(&start, &end);
@@ -881,28 +882,28 @@ nsHTMLTextAreaElement::SetSelectionStart
 NS_IMETHODIMP
 nsHTMLTextAreaElement::GetSelectionEnd(PRInt32 *aSelectionEnd)
 {
   NS_ENSURE_ARG_POINTER(aSelectionEnd);
 
   PRInt32 selStart;
   nsresult rv = GetSelectionRange(&selStart, aSelectionEnd);
 
-  if (NS_FAILED(rv) && mState->IsSelectionCached()) {
-    *aSelectionEnd = mState->GetSelectionProperties().mEnd;
+  if (NS_FAILED(rv) && mState.IsSelectionCached()) {
+    *aSelectionEnd = mState.GetSelectionProperties().mEnd;
     return NS_OK;
   }
   return rv;
 }
 
 NS_IMETHODIMP
 nsHTMLTextAreaElement::SetSelectionEnd(PRInt32 aSelectionEnd)
 {
-  if (mState->IsSelectionCached()) {
-    mState->GetSelectionProperties().mEnd = aSelectionEnd;
+  if (mState.IsSelectionCached()) {
+    mState.GetSelectionProperties().mEnd = aSelectionEnd;
     return NS_OK;
   }
 
   nsAutoString direction;
   nsresult rv = GetSelectionDirection(direction);
   NS_ENSURE_SUCCESS(rv, rv);
   PRInt32 start, end;
   rv = GetSelectionRange(&start, &end);
@@ -957,35 +958,35 @@ nsHTMLTextAreaElement::GetSelectionDirec
       rv = textControlFrame->GetSelectionRange(nsnull, nsnull, &dir);
       if (NS_SUCCEEDED(rv)) {
         DirectionToName(dir, aDirection);
       }
     }
   }
 
   if (NS_FAILED(rv)) {
-    if (mState->IsSelectionCached()) {
-      DirectionToName(mState->GetSelectionProperties().mDirection, aDirection);
+    if (mState.IsSelectionCached()) {
+      DirectionToName(mState.GetSelectionProperties().mDirection, aDirection);
       return NS_OK;
     }
   }
 
   return rv;
 }
 
 NS_IMETHODIMP
 nsHTMLTextAreaElement::SetSelectionDirection(const nsAString& aDirection) {
-  if (mState->IsSelectionCached()) {
+  if (mState.IsSelectionCached()) {
     nsITextControlFrame::SelectionDirection dir = nsITextControlFrame::eNone;
     if (aDirection.EqualsLiteral("forward")) {
       dir = nsITextControlFrame::eForward;
     } else if (aDirection.EqualsLiteral("backward")) {
       dir = nsITextControlFrame::eBackward;
     }
-    mState->GetSelectionProperties().mDirection = dir;
+    mState.GetSelectionProperties().mDirection = dir;
     return NS_OK;
   }
 
   PRInt32 start, end;
   nsresult rv = GetSelectionRange(&start, &end);
   if (NS_SUCCEEDED(rv)) {
     rv = SetSelectionRange(start, end, aDirection);
   }
@@ -1290,17 +1291,17 @@ nsHTMLTextAreaElement::AfterSetAttr(PRIn
         UpdateBarredFromConstraintValidation();
       }
     } else if (aName == nsGkAtoms::maxlength) {
       UpdateTooLongValidityState();
     }
 
     if (aName == nsGkAtoms::readonly) {
       UpdateEditableState(aNotify);
-      mState->UpdateEditableState(aNotify);
+      mState.UpdateEditableState(aNotify);
     }
     UpdateState(aNotify);
   }
 
   return nsGenericHTMLFormElement::AfterSetAttr(aNameSpaceID, aName, aValue,
                                                 aNotify);
 }
 
@@ -1522,30 +1523,30 @@ nsHTMLTextAreaElement::ValueChanged() co
 {
   return mValueChanged;
 }
 
 NS_IMETHODIMP_(void)
 nsHTMLTextAreaElement::GetTextEditorValue(nsAString& aValue,
                                           bool aIgnoreWrap) const
 {
-  mState->GetValue(aValue, aIgnoreWrap);
+  mState.GetValue(aValue, aIgnoreWrap);
 }
 
 NS_IMETHODIMP_(void)
 nsHTMLTextAreaElement::SetTextEditorValue(const nsAString& aValue,
                                           bool aUserInput)
 {
-  mState->SetValue(aValue, aUserInput);
+  mState.SetValue(aValue, aUserInput);
 }
 
 NS_IMETHODIMP_(void)
 nsHTMLTextAreaElement::InitializeKeyboardEventListeners()
 {
-  mState->InitializeKeyboardEventListeners();
+  mState.InitializeKeyboardEventListeners();
 }
 
 NS_IMETHODIMP_(void)
 nsHTMLTextAreaElement::OnValueChanged(bool aNotify)
 {
   // Update the validity state
   bool validBefore = IsValid();
   UpdateTooLongValidityState();
@@ -1556,17 +1557,17 @@ nsHTMLTextAreaElement::OnValueChanged(bo
        && !nsContentUtils::IsFocusedContent((nsIContent*)(this)))) {
     UpdateState(aNotify);
   }
 }
 
 NS_IMETHODIMP_(bool)
 nsHTMLTextAreaElement::HasCachedSelection()
 {
-  return mState->IsSelectionCached();
+  return mState.IsSelectionCached();
 }
 
 void
 nsHTMLTextAreaElement::FieldSetDisabledChanged(bool aNotify)
 {
   UpdateValueMissingValidityState();
   UpdateBarredFromConstraintValidation();
 
--- a/content/html/content/src/nsTextEditorState.cpp
+++ b/content/html/content/src/nsTextEditorState.cpp
@@ -965,32 +965,34 @@ nsTextEditorState::Clear()
   } else {
     // If we have a bound frame around, UnbindFromFrame will call DestroyEditor
     // for us.
     DestroyEditor();
   }
   NS_IF_RELEASE(mTextListener);
 }
 
-NS_IMPL_CYCLE_COLLECTION_CLASS(nsTextEditorState)
-NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsTextEditorState, AddRef)
-NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsTextEditorState, Release)
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_NATIVE(nsTextEditorState)
+void nsTextEditorState::Unlink()
+{
+  nsTextEditorState* tmp = this;
   tmp->Clear();
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mSelCon)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mEditor)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mRootNode)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mPlaceholderDiv)
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NATIVE_BEGIN(nsTextEditorState)
+}
+
+void nsTextEditorState::Traverse(nsCycleCollectionTraversalCallback& cb)
+{
+  nsTextEditorState* tmp = this;
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR_AMBIGUOUS(mSelCon, nsISelectionController)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mEditor)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mRootNode)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mPlaceholderDiv)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+}
 
 nsFrameSelection*
 nsTextEditorState::GetConstFrameSelection() {
   if (mSelCon)
     return mSelCon->GetConstFrameSelection();
   return nsnull;
 }
 
--- a/content/html/content/src/nsTextEditorState.h
+++ b/content/html/content/src/nsTextEditorState.h
@@ -149,18 +149,18 @@ class nsITextControlElement;
 
 class RestoreSelectionState;
 
 class nsTextEditorState {
 public:
   explicit nsTextEditorState(nsITextControlElement* aOwningElement);
   ~nsTextEditorState();
 
-  NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(nsTextEditorState)
-  NS_INLINE_DECL_REFCOUNTING(nsTextEditorState)
+  void Traverse(nsCycleCollectionTraversalCallback& cb);
+  void Unlink();
 
   nsIEditor* GetEditor();
   nsISelectionController* GetSelectionController() const;
   nsFrameSelection* GetConstFrameSelection();
   nsresult BindToFrame(nsTextControlFrame* aFrame);
   void UnbindFromFrame(nsTextControlFrame* aFrame);
   nsresult PrepareEditor(const nsAString *aValue = nsnull);
   void InitializeKeyboardEventListeners();
--- a/content/media/nsMediaDecoder.cpp
+++ b/content/media/nsMediaDecoder.cpp
@@ -217,28 +217,28 @@ bool nsMediaDecoder::CanPlayThrough()
          stats.mDownloadPosition > stats.mPlaybackPosition + readAheadMargin;
 }
 
 namespace mozilla {
 
 MediaMemoryReporter* MediaMemoryReporter::sUniqueInstance;
 
 NS_MEMORY_REPORTER_IMPLEMENT(MediaDecodedVideoMemory,
-                             "explicit/media/decoded-video",
-                             KIND_HEAP,
-                             UNITS_BYTES,
-                             MediaMemoryReporter::GetDecodedVideoMemory,
-                             "Memory used by decoded video frames.")
+  "explicit/media/decoded-video",
+  KIND_HEAP,
+  UNITS_BYTES,
+  MediaMemoryReporter::GetDecodedVideoMemory,
+  "Memory used by decoded video frames.")
 
 NS_MEMORY_REPORTER_IMPLEMENT(MediaDecodedAudioMemory,
-                             "explicit/media/decoded-audio",
-                             KIND_HEAP,
-                             UNITS_BYTES,
-                             MediaMemoryReporter::GetDecodedAudioMemory,
-                             "Memory used by decoded audio chunks.")
+  "explicit/media/decoded-audio",
+  KIND_HEAP,
+  UNITS_BYTES,
+  MediaMemoryReporter::GetDecodedAudioMemory,
+  "Memory used by decoded audio chunks.")
 
 MediaMemoryReporter::MediaMemoryReporter()
   : mMediaDecodedVideoMemory(new NS_MEMORY_REPORTER_NAME(MediaDecodedVideoMemory))
   , mMediaDecodedAudioMemory(new NS_MEMORY_REPORTER_NAME(MediaDecodedAudioMemory))
 {
   NS_RegisterMemoryReporter(mMediaDecodedVideoMemory);
   NS_RegisterMemoryReporter(mMediaDecodedAudioMemory);
 }
--- a/content/xbl/src/nsXBLPrototypeBinding.cpp
+++ b/content/xbl/src/nsXBLPrototypeBinding.cpp
@@ -288,19 +288,19 @@ nsFixedSizeAllocator* nsXBLPrototypeBind
 
 static const PRInt32 kNumElements = 128;
 
 static const size_t kAttrBucketSizes[] = {
   sizeof(nsXBLAttributeEntry)
 };
 
 static const PRInt32 kAttrNumBuckets = sizeof(kAttrBucketSizes)/sizeof(size_t);
-static const PRInt32 kAttrInitialSize = (NS_SIZE_IN_HEAP(sizeof(nsXBLAttributeEntry))) * kNumElements;
+static const PRInt32 kAttrInitialSize = sizeof(nsXBLAttributeEntry) * kNumElements;
 
-static const PRInt32 kInsInitialSize = (NS_SIZE_IN_HEAP(sizeof(nsXBLInsertionPointEntry))) * kNumElements;
+static const PRInt32 kInsInitialSize = sizeof(nsXBLInsertionPointEntry) * kNumElements;
 
 // Implementation /////////////////////////////////////////////////////////////////
 
 // Constructors/Destructors
 nsXBLPrototypeBinding::nsXBLPrototypeBinding()
 : mImplementation(nsnull),
   mBaseBinding(nsnull),
   mInheritStyle(true), 
--- a/content/xbl/src/nsXBLService.cpp
+++ b/content/xbl/src/nsXBLService.cpp
@@ -222,17 +222,17 @@ private:
 };
 
 static const size_t kBucketSizes[] = {
   sizeof(nsXBLBindingRequest)
 };
 
 static const PRInt32 kNumBuckets = sizeof(kBucketSizes)/sizeof(size_t);
 static const PRInt32 kNumElements = 64;
-static const PRInt32 kInitialSize = (NS_SIZE_IN_HEAP(sizeof(nsXBLBindingRequest))) * kNumElements;
+static const PRInt32 kInitialSize = sizeof(nsXBLBindingRequest) * kNumElements;
 
 nsIXBLService* nsXBLBindingRequest::gXBLService = nsnull;
 int nsXBLBindingRequest::gRefCnt = 0;
 
 // nsXBLStreamListener, a helper class used for 
 // asynchronous parsing of URLs
 /* Header file */
 class nsXBLStreamListener : public nsIStreamListener, public nsIDOMEventListener
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -1242,19 +1242,19 @@ def getArgumentConversionTemplate(type, 
         if type.nullable():
             nullBehavior = "eNull"
             undefinedBehavior = "eNull"
         else:
             nullBehavior = "eStringify"
             undefinedBehavior = "eStringify"
 
         return (
-            "  xpc_qsDOMString ${name}(cx, ${argVal}, ${argPtr},\n"
-            "                       xpc_qsDOMString::%s,\n"
-            "                       xpc_qsDOMString::%s);\n"
+            "  const xpc_qsDOMString ${name}(cx, ${argVal}, ${argPtr},\n"
+            "                             xpc_qsDOMString::%s,\n"
+            "                             xpc_qsDOMString::%s);\n"
             "  if (!${name}.IsValid()) {\n"
             "    return false;\n"
             "  }\n" % (nullBehavior, undefinedBehavior))
 
     if type.isEnum():
         if type.nullable():
             raise TypeError("We don't support nullable enumerated arguments "
                             "yet")
@@ -1463,24 +1463,27 @@ def getWrapTemplateForTypeImpl(type, res
     ${jsvalRef} = JSVAL_NULL;
     return true;
   }""" % result) if type.nullable() else ""
         if descriptor.castable and not type.unroll().inner.isExternal():
             wrappingCode += """
   if (WrapNewBindingObject(cx, obj, %s, ${jsvalPtr})) {
     return true;
   }""" % result
-            if descriptor.workers:
-                # Worker bindings can only fail to wrap as a new-binding object
-                # if they already threw an exception
+            # We don't support prefable stuff in workers.
+            assert(not descriptor.prefable or not descriptor.workers)
+            if not descriptor.prefable:
+                # Non-prefable bindings can only fail to wrap as a new-binding object
+                # if they already threw an exception.  Same thing for
+                # non-prefable bindings.
                 wrappingCode += """
   MOZ_ASSERT(JS_IsExceptionPending(cx));
   return false;"""
             else:
-                # Try old-style wrapping for non-worker bindings
+                # Try old-style wrapping for bindings which might be preffed off.
                 wrappingCode += """
   return HandleNewBindingWrappingFailure(cx, obj, %s, ${jsvalPtr});""" % result
         else:
             if descriptor.notflattened:
                 getIID = "&NS_GET_IID(%s), " % descriptor.nativeType
             else:
                 getIID = ""
             wrappingCode += """
--- a/dom/bindings/Nullable.h
+++ b/dom/bindings/Nullable.h
@@ -43,17 +43,17 @@ public:
     mIsNull = false;
     return mValue;
   }
 
   void SetNull() {
     mIsNull = true;
   }
 
-  T Value() const {
+  const T& Value() const {
     MOZ_ASSERT(!mIsNull);
     return mValue;
   }
 
   bool IsNull() const {
     return mIsNull;
   }
 };
--- a/dom/contacts/ContactManager.js
+++ b/dom/contacts/ContactManager.js
@@ -329,18 +329,16 @@ ContactManager.prototype = {
     if (!Services.prefs.getBoolPref("dom.mozContacts.enabled"))
       return null;
 
     this.initHelper(aWindow, ["Contacts:Find:Return:OK", "Contacts:Find:Return:KO",
                      "Contacts:Clear:Return:OK", "Contacts:Clear:Return:KO",
                      "Contact:Save:Return:OK", "Contact:Save:Return:KO",
                      "Contact:Remove:Return:OK", "Contact:Remove:Return:KO"]);
 
-    Services.obs.addObserver(this, "inner-window-destroyed", false);
-
     let principal = aWindow.document.nodePrincipal;
     let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
 
     let perm = principal == secMan.getSystemPrincipal() ? 
                  Ci.nsIPermissionManager.ALLOW_ACTION : 
                  Services.perms.testExactPermission(principal.URI, "webcontacts-manage");
  
     //only pages with perm set can use the contacts
--- a/dom/contacts/fallback/ContactDB.jsm
+++ b/dom/contacts/fallback/ContactDB.jsm
@@ -148,37 +148,20 @@ ContactDB.prototype = {
       let store = txn.objectStore(STORE_NAME);
 
       txn.oncomplete = function (event) {
         debug("Transaction complete. Returning to callback.");
         successCb(txn.result);
       };
 
       txn.onabort = function (event) {
-        debug("Caught error on transaction" + event.target.error.name);
-        switch(event.target.error.name) {
-          case "AbortError":
-          case "ConstraintError":
-          case "DataError":
-          case "SyntaxError":
-          case "InvalidStateError":
-          case "NotFoundError":
-          case "QuotaExceededError":
-          case "ReadOnlyError":
-          case "TimeoutError":
-          case "TransactionInactiveError":
-          case "VersionError":
-          case "UnknownError":
-            failureCb("UnknownError");
-            break;
-          default:
-            debug("Unknown error", event.target.error.name);
-            failureCb("UnknownError");
-            break;
-        }
+        debug("Caught error on transaction");
+        // FIXXMEE: this will work in the future. Bug 748630
+        // failureCb(event.target.error.name);
+        failureCb("UnknownError");
       };
       callback(txn, store);
     }, failureCb);
   },
 
   makeImport: function makeImport(aContact) {
     let contact = {};
     contact.properties = {
--- a/dom/contacts/fallback/ContactService.jsm
+++ b/dom/contacts/fallback/ContactService.jsm
@@ -56,16 +56,17 @@ let DOMContactManager = {
     this._messages.forEach((function(msgName) {
       ppmm.removeMessageListener(msgName, this);
     }).bind(this));
     Services.obs.removeObserver(this, "profile-before-change");
     ppmm = null;
     this._messages = null;
     if (this._db)
       this._db.close();
+    this._db = null;
   },
 
   receiveMessage: function(aMessage) {
     function sortfunction(a, b){
       let x, y;
       if (a.properties[msg.findOptions.sortBy])
         x = a.properties[msg.findOptions.sortBy][0].toLowerCase();
       if (b.properties[msg.findOptions.sortBy])
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -376,24 +376,26 @@ ContentChild::RecvPMemoryReportRequestCo
       nsCOMPtr<nsIMemoryReporter> r;
       e->GetNext(getter_AddRefs(r));
 
       nsCString path;
       PRInt32 kind;
       PRInt32 units;
       PRInt64 amount;
       nsCString desc;
-      r->GetPath(path);
-      r->GetKind(&kind);
-      r->GetUnits(&units);
-      r->GetAmount(&amount);
-      r->GetDescription(desc);
 
-      MemoryReport memreport(process, path, kind, units, amount, desc);
-      reports.AppendElement(memreport);
+      if (NS_SUCCEEDED(r->GetPath(path)) &&
+          NS_SUCCEEDED(r->GetKind(&kind)) &&
+          NS_SUCCEEDED(r->GetUnits(&units)) &&
+          NS_SUCCEEDED(r->GetAmount(&amount)) &&
+          NS_SUCCEEDED(r->GetDescription(desc)))
+      {
+        MemoryReport memreport(process, path, kind, units, amount, desc);
+        reports.AppendElement(memreport);
+      }
     }
 
     // Then do the memory multi-reporters, by calling CollectReports on each
     // one, whereupon the callback will turn each measurement into a
     // MemoryReport.
     mgr->EnumerateMultiReporters(getter_AddRefs(e));
     nsRefPtr<MemoryReportsWrapper> wrappedReports =
         new MemoryReportsWrapper(&reports);
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -508,17 +508,17 @@ RadioInterfaceLayer.prototype = {
 
   portAddressedSmsApps: null,
   handleSmsReceived: function handleSmsReceived(message) {
     debug("handleSmsReceived: " + JSON.stringify(message));
 
     // 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.destinationPort != null) {
+    if (message.header && message.header.destinationPort != null) {
       let handler = this.portAddressedSmsApps[message.header.destinationPort];
       if (handler) {
         handler(message);
       }
       return;
     }
 
     if (message.encoding == RIL.PDU_DCS_MSG_CODING_8BITS_ALPHABET) {
--- a/extensions/spellcheck/hunspell/src/mozHunspell.cpp
+++ b/extensions/spellcheck/hunspell/src/mozHunspell.cpp
@@ -107,22 +107,22 @@ void HunspellReportMemoryAllocation(void
 void HunspellReportMemoryDeallocation(void* ptr) {
   gHunspellAllocatedSize -= HunspellMallocSizeOfForCounterDec(ptr);
 }
 static PRInt64 HunspellGetCurrentAllocatedSize() {
   return gHunspellAllocatedSize;
 }
 
 NS_MEMORY_REPORTER_IMPLEMENT(Hunspell,
-    "explicit/spell-check",
-    KIND_HEAP,
-    UNITS_BYTES,
-    HunspellGetCurrentAllocatedSize,
-    "Memory used by the Hunspell spell checking engine.  This number accounts "
-    "for the memory in use by Hunspell's internal data structures."
+  "explicit/spell-check",
+  KIND_HEAP,
+  UNITS_BYTES,
+  HunspellGetCurrentAllocatedSize,
+  "Memory used by the Hunspell spell checking engine.  This number accounts "
+  "for the memory in use by Hunspell's internal data structures."
 )
 
 nsresult
 mozHunspell::Init()
 {
   if (!mDictionaries.Init())
     return NS_ERROR_OUT_OF_MEMORY;
 
--- a/gfx/src/nsFont.cpp
+++ b/gfx/src/nsFont.cpp
@@ -34,78 +34,71 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsFont.h"
 #include "nsString.h"
 #include "nsUnicharUtils.h"
 #include "nsCRT.h"
+#include "gfxFont.h"
 
 nsFont::nsFont(const char* aName, PRUint8 aStyle, PRUint8 aVariant,
                PRUint16 aWeight, PRInt16 aStretch, PRUint8 aDecoration,
                nscoord aSize, float aSizeAdjust,
-               const nsString* aFeatureSettings,
                const nsString* aLanguageOverride)
 {
   NS_ASSERTION(aName && IsASCII(nsDependentCString(aName)),
                "Must only pass ASCII names here");
   name.AssignASCII(aName);
   style = aStyle;
   systemFont = false;
   variant = aVariant;
   weight = aWeight;
   stretch = aStretch;
   decorations = aDecoration;
   size = aSize;
   sizeAdjust = aSizeAdjust;
-  if (aFeatureSettings) {
-    featureSettings = *aFeatureSettings;
-  }
   if (aLanguageOverride) {
     languageOverride = *aLanguageOverride;
   }
 }
 
 nsFont::nsFont(const nsString& aName, PRUint8 aStyle, PRUint8 aVariant,
                PRUint16 aWeight, PRInt16 aStretch, PRUint8 aDecoration,
                nscoord aSize, float aSizeAdjust,
-               const nsString* aFeatureSettings,
                const nsString* aLanguageOverride)
   : name(aName)
 {
   style = aStyle;
   systemFont = false;
   variant = aVariant;
   weight = aWeight;
   stretch = aStretch;
   decorations = aDecoration;
   size = aSize;
   sizeAdjust = aSizeAdjust;
-  if (aFeatureSettings) {
-    featureSettings = *aFeatureSettings;
-  }
   if (aLanguageOverride) {
     languageOverride = *aLanguageOverride;
   }
 }
 
 nsFont::nsFont(const nsFont& aOther)
   : name(aOther.name)
 {
   style = aOther.style;
   systemFont = aOther.systemFont;
   variant = aOther.variant;
   weight = aOther.weight;
   stretch = aOther.stretch;
   decorations = aOther.decorations;
   size = aOther.size;
   sizeAdjust = aOther.sizeAdjust;
-  featureSettings = aOther.featureSettings;
   languageOverride = aOther.languageOverride;
+  fontFeatureSettings = aOther.fontFeatureSettings;
 }
 
 nsFont::nsFont()
 {
 }
 
 nsFont::~nsFont()
 {
@@ -115,18 +108,18 @@ bool nsFont::BaseEquals(const nsFont& aO
 {
   if ((style == aOther.style) &&
       (systemFont == aOther.systemFont) &&
       (weight == aOther.weight) &&
       (stretch == aOther.stretch) &&
       (size == aOther.size) &&
       (sizeAdjust == aOther.sizeAdjust) &&
       name.Equals(aOther.name, nsCaseInsensitiveStringComparator()) &&
-      (featureSettings == aOther.featureSettings) &&
-      (languageOverride == aOther.languageOverride)) {
+      (languageOverride == aOther.languageOverride) &&
+      (fontFeatureSettings == aOther.fontFeatureSettings)) {
     return true;
   }
   return false;
 }
 
 bool nsFont::Equals(const nsFont& aOther) const
 {
   if (BaseEquals(aOther) &&
@@ -143,21 +136,28 @@ nsFont& nsFont::operator=(const nsFont& 
   style = aOther.style;
   systemFont = aOther.systemFont;
   variant = aOther.variant;
   weight = aOther.weight;
   stretch = aOther.stretch;
   decorations = aOther.decorations;
   size = aOther.size;
   sizeAdjust = aOther.sizeAdjust;
-  featureSettings = aOther.featureSettings;
   languageOverride = aOther.languageOverride;
+  fontFeatureSettings = aOther.fontFeatureSettings;
   return *this;
 }
 
+void
+nsFont::AddFontFeaturesToStyle(gfxFontStyle *aStyle) const
+{
+  // simple copy for now, font-variant implementation will expand
+  aStyle->featureSettings.AppendElements(fontFeatureSettings);
+}
+
 static bool IsGenericFontFamily(const nsString& aFamily)
 {
   PRUint8 generic;
   nsFont::GetGenericID(aFamily, &generic);
   return generic != kGenericFont_NONE;
 }
 
 const PRUnichar kNullCh       = PRUnichar('\0');
--- a/gfx/src/nsFont.h
+++ b/gfx/src/nsFont.h
@@ -37,16 +37,17 @@
 
 #ifndef nsFont_h___
 #define nsFont_h___
 
 #include "gfxCore.h"
 #include "nsCoord.h"
 #include "nsStringGlue.h"
 #include "gfxFontConstants.h"
+#include "gfxFontFeatures.h"
 
 // XXX we need a method to enumerate all of the possible fonts on the
 // system across family, weight, style, size, etc. But not here!
 
 // Enumerator callback function. Return false to stop
 typedef bool (*nsFontFamilyEnumFunc)(const nsString& aFamily, bool aGeneric, void *aData);
 
 // IDs for generic fonts
@@ -58,16 +59,18 @@ const PRUint8 kGenericFont_moz_variable 
 const PRUint8 kGenericFont_moz_fixed    = 0x01; // our special "use the user's fixed font"
 // CSS
 const PRUint8 kGenericFont_serif        = 0x02;
 const PRUint8 kGenericFont_sans_serif   = 0x04;
 const PRUint8 kGenericFont_monospace    = 0x08;
 const PRUint8 kGenericFont_cursive      = 0x10;
 const PRUint8 kGenericFont_fantasy      = 0x20;
 
+class gfxFontStyle;
+
 // Font structure.
 struct NS_GFX nsFont {
   // The family name of the font
   nsString name;
 
   // The style of font (normal, italic, oblique; see gfxFontConstants.h)
   PRUint8 style;
 
@@ -94,35 +97,33 @@ struct NS_GFX nsFont {
 
   // The aspect-value (ie., the ratio actualsize:actualxheight) that any
   // actual physical font created from this font structure must have when
   // rendering or measuring a string. A value of 0 means no adjustment
   // needs to be done.
   float sizeAdjust;
 
   // Font features from CSS font-feature-settings
-  nsString featureSettings;
+  nsTArray<gfxFontFeature> fontFeatureSettings;
 
   // Language system tag, to override document language;
   // this is an OpenType "language system" tag represented as a 32-bit integer
   // (see http://www.microsoft.com/typography/otspec/languagetags.htm).
   nsString languageOverride;
 
   // Initialize the font struct with an ASCII name
   nsFont(const char* aName, PRUint8 aStyle, PRUint8 aVariant,
          PRUint16 aWeight, PRInt16 aStretch, PRUint8 aDecoration,
          nscoord aSize, float aSizeAdjust=0.0f,
-         const nsString* aFeatureSettings = nsnull,
          const nsString* aLanguageOverride = nsnull);
 
   // Initialize the font struct with a (potentially) unicode name
   nsFont(const nsString& aName, PRUint8 aStyle, PRUint8 aVariant,
          PRUint16 aWeight, PRInt16 aStretch, PRUint8 aDecoration,
          nscoord aSize, float aSizeAdjust=0.0f,
-         const nsString* aFeatureSettings = nsnull,
          const nsString* aLanguageOverride = nsnull);
 
   // Make a copy of the given font
   nsFont(const nsFont& aFont);
 
   nsFont();
   ~nsFont();
 
@@ -131,16 +132,19 @@ struct NS_GFX nsFont {
   }
 
   bool Equals(const nsFont& aOther) const ;
   // Compare ignoring differences in 'variant' and 'decoration'
   bool BaseEquals(const nsFont& aOther) const;
 
   nsFont& operator=(const nsFont& aOther);
 
+  // Add featureSettings into style
+  void AddFontFeaturesToStyle(gfxFontStyle *aStyle) const;
+
   // Utility method to interpret name string
   // enumerates all families specified by this font only
   // returns true if completed, false if stopped
   // enclosing quotes will be removed, and whitespace compressed (as needed)
   bool EnumerateFamilies(nsFontFamilyEnumFunc aFunc, void* aData) const;
   void GetFirstFamily(nsString& aFamily) const;
 
   // Utility method to return the ID of a generic font
--- a/gfx/src/nsFontMetrics.cpp
+++ b/gfx/src/nsFontMetrics.cpp
@@ -129,19 +129,20 @@ nsFontMetrics::Init(const nsFont& aFont,
     gfxFontStyle style(aFont.style,
                        aFont.weight,
                        aFont.stretch,
                        gfxFloat(aFont.size) / mP2A,
                        aLanguage,
                        aFont.sizeAdjust,
                        aFont.systemFont,
                        mDeviceContext->IsPrinterSurface(),
-                       aFont.featureSettings,
                        aFont.languageOverride);
 
+    aFont.AddFontFeaturesToStyle(&style);
+
     mFontGroup = gfxPlatform::GetPlatform()->
         CreateFontGroup(aFont.name, &style, aUserFontSet);
     if (mFontGroup->FontListLength() < 1)
         return NS_ERROR_UNEXPECTED;
 
     return NS_OK;
 }
 
--- a/gfx/thebes/Makefile.in
+++ b/gfx/thebes/Makefile.in
@@ -53,16 +53,17 @@ EXPORTS	= \
 	gfxBlur.h \
 	gfxCachedTempSurface.h \
 	gfxColor.h \
 	gfxContext.h \
 	gfxDrawable.h \
 	gfxFailure.h \
 	gfxFont.h \
 	gfxFontConstants.h \
+	gfxFontFeatures.h \
 	gfxFontUtils.h \
 	gfxFontTest.h \
 	gfxImageSurface.h \
 	gfxLineSegment.h \
 	gfxMatrix.h \
 	gfxPath.h \
 	gfxPattern.h \
 	gfxPlatform.h \
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -1345,16 +1345,58 @@ gfxFontCache::SizeOfExcludingThis(nsMall
 void
 gfxFontCache::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf,
                                   FontCacheSizes*   aSizes) const
 {
     aSizes->mFontInstances += aMallocSizeOf(this);
     SizeOfExcludingThis(aMallocSizeOf, aSizes);
 }
 
+/* static */ bool
+gfxFontShaper::MergeFontFeatures(
+    const nsTArray<gfxFontFeature>& aStyleRuleFeatures,
+    const nsTArray<gfxFontFeature>& aFontFeatures,
+    bool aDisableLigatures,
+    nsDataHashtable<nsUint32HashKey,PRUint32>& aMergedFeatures)
+{
+    // bail immediately if nothing to do
+    if (aStyleRuleFeatures.IsEmpty() &&
+        aFontFeatures.IsEmpty() &&
+        !aDisableLigatures) {
+        return false;
+    }
+
+    aMergedFeatures.Init();
+
+    // Ligature features are enabled by default in the generic shaper,
+    // so we explicitly turn them off if necessary (for letter-spacing)
+    if (aDisableLigatures) {
+        aMergedFeatures.Put(HB_TAG('l','i','g','a'), 0);
+        aMergedFeatures.Put(HB_TAG('c','l','i','g'), 0);
+    }
+
+    // add feature values from font
+    PRUint32 i, count;
+
+    count = aFontFeatures.Length();
+    for (i = 0; i < count; i++) {
+        const gfxFontFeature& feature = aFontFeatures.ElementAt(i);
+        aMergedFeatures.Put(feature.mTag, feature.mValue);
+    }
+
+    // add feature values from style rules
+    count = aStyleRuleFeatures.Length();
+    for (i = 0; i < count; i++) {
+        const gfxFontFeature& feature = aStyleRuleFeatures.ElementAt(i);
+        aMergedFeatures.Put(feature.mTag, feature.mValue);
+    }
+
+    return aMergedFeatures.Count() != 0;
+}
+
 void
 gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, bool aOtherIsOnLeft)
 {
     mAscent = NS_MAX(mAscent, aOther.mAscent);
     mDescent = NS_MAX(mDescent, aOther.mDescent);
     if (aOtherIsOnLeft) {
         mBoundingBox =
             (mBoundingBox + gfxPoint(aOther.mAdvanceWidth, 0)).Union(aOther.mBoundingBox);
@@ -3958,67 +4000,16 @@ gfxFontGroup::Shutdown()
     NS_IF_RELEASE(gLangService);
 }
 
 nsILanguageAtomService* gfxFontGroup::gLangService = nsnull;
 
 
 #define DEFAULT_PIXEL_FONT_SIZE 16.0f
 
-/*static*/ void
-gfxFontStyle::ParseFontFeatureSettings(const nsString& aFeatureString,
-                                       nsTArray<gfxFontFeature>& aFeatures)
-{
-  aFeatures.Clear();
-  PRUint32 offset = 0;
-  while (offset < aFeatureString.Length()) {
-    // skip whitespace
-    while (offset < aFeatureString.Length() &&
-           nsCRT::IsAsciiSpace(aFeatureString[offset])) {
-      ++offset;
-    }
-    PRInt32 limit = aFeatureString.FindChar(',', offset);
-    if (limit < 0) {
-      limit = aFeatureString.Length();
-    }
-    // check that we have enough text for a 4-char tag,
-    // the '=' sign, and at least one digit
-    if (offset + 6 <= PRUint32(limit) &&
-      aFeatureString[offset+4] == '=') {
-      gfxFontFeature setting;
-      setting.mTag =
-        ((aFeatureString[offset] & 0xff) << 24) +
-        ((aFeatureString[offset+1] & 0xff) << 16) +
-        ((aFeatureString[offset+2] & 0xff) << 8) +
-         (aFeatureString[offset+3] & 0xff);
-      nsString valString;
-      aFeatureString.Mid(valString, offset+5, limit-offset-5);
-      PRInt32 rv;
-      setting.mValue = valString.ToInteger(&rv);
-      if (rv == NS_OK) {
-        PRUint32 i;
-        // could optimize this based on the fact that the features array
-        // is sorted, but it's unlikely to be more than a few entries
-        for (i = 0; i < aFeatures.Length(); i++) {
-          if (aFeatures[i].mTag == setting.mTag) {
-            aFeatures[i].mValue = setting.mValue;
-            break;
-          }
-        }
-        if (i == aFeatures.Length()) {
-          // we keep the features array sorted so that we can
-          // use nsTArray<>::Equals() to compare feature lists
-          aFeatures.InsertElementSorted(setting);
-        }
-      }
-    }
-    offset = limit + 1;
-  }
-}
-
 /*static*/ PRUint32
 gfxFontStyle::ParseFontLanguageOverride(const nsString& aLangTag)
 {
   if (!aLangTag.Length() || aLangTag.Length() > 4) {
     return NO_FONT_LANGUAGE_OVERRIDE;
   }
   PRUint32 index, result = 0;
   for (index = 0; index < aLangTag.Length(); ++index) {
@@ -4043,27 +4034,24 @@ gfxFontStyle::gfxFontStyle() :
     style(NS_FONT_STYLE_NORMAL)
 {
 }
 
 gfxFontStyle::gfxFontStyle(PRUint8 aStyle, PRUint16 aWeight, PRInt16 aStretch,
                            gfxFloat aSize, nsIAtom *aLanguage,
                            float aSizeAdjust, bool aSystemFont,
                            bool aPrinterFont,
-                           const nsString& aFeatureSettings,
                            const nsString& aLanguageOverride):
     language(aLanguage),
     size(aSize), sizeAdjust(aSizeAdjust),
     languageOverride(ParseFontLanguageOverride(aLanguageOverride)),
     weight(aWeight), stretch(aStretch),
     systemFont(aSystemFont), printerFont(aPrinterFont),
     style(aStyle)
 {
-    ParseFontFeatureSettings(aFeatureSettings, featureSettings);
-
     if (weight > 900)
         weight = 900;
     if (weight < 100)
         weight = 100;
 
     if (size >= FONT_MAX_SIZE) {
         size = FONT_MAX_SIZE;
         sizeAdjust = 0.0;
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -55,16 +55,17 @@
 #include "nsExpirationTracker.h"
 #include "gfxFontConstants.h"
 #include "gfxPlatform.h"
 #include "nsIAtom.h"
 #include "nsISupportsImpl.h"
 #include "gfxPattern.h"
 #include "mozilla/HashFunctions.h"
 #include "nsIMemoryReporter.h"
+#include "gfxFontFeatures.h"
 
 typedef struct _cairo_scaled_font cairo_scaled_font_t;
 
 #ifdef DEBUG
 #include <stdio.h>
 #endif
 
 class gfxContext;
@@ -79,45 +80,24 @@ class gfxShapedWord;
 class nsILanguageAtomService;
 
 typedef struct _hb_blob_t hb_blob_t;
 
 #define FONT_MAX_SIZE                  2000.0
 
 #define NO_FONT_LANGUAGE_OVERRIDE      0
 
-// An OpenType feature tag and value pair
-struct THEBES_API gfxFontFeature {
-    PRUint32 mTag; // see http://www.microsoft.com/typography/otspec/featuretags.htm
-    PRUint32 mValue; // 0 = off, 1 = on, larger values may be used as parameters
-                     // to features that select among multiple alternatives
-};
-
 struct FontListSizes;
 
-inline bool
-operator<(const gfxFontFeature& a, const gfxFontFeature& b)
-{
-    return (a.mTag < b.mTag) || ((a.mTag == b.mTag) && (a.mValue < b.mValue));
-}
-
-inline bool
-operator==(const gfxFontFeature& a, const gfxFontFeature& b)
-{
-    return (a.mTag == b.mTag) && (a.mValue == b.mValue);
-}
-
-
 struct THEBES_API gfxFontStyle {
     gfxFontStyle();
     gfxFontStyle(PRUint8 aStyle, PRUint16 aWeight, PRInt16 aStretch,
                  gfxFloat aSize, nsIAtom *aLanguage,
                  float aSizeAdjust, bool aSystemFont,
                  bool aPrinterFont,
-                 const nsString& aFeatureSettings,
                  const nsString& aLanguageOverride);
     gfxFontStyle(const gfxFontStyle& aStyle);
 
     // the language (may be an internal langGroup code rather than an actual
     // language code) specified in the document or element's lang property,
     // or inferred from the charset
     nsRefPtr<nsIAtom> language;
 
@@ -1168,16 +1148,23 @@ public:
     virtual ~gfxFontShaper() { }
 
     virtual bool ShapeWord(gfxContext *aContext,
                            gfxShapedWord *aShapedWord,
                            const PRUnichar *aText) = 0;
 
     gfxFont *GetFont() const { return mFont; }
 
+    // returns true if features exist in output, false otherwise
+    static bool
+    MergeFontFeatures(const nsTArray<gfxFontFeature>& aStyleRuleFeatures,
+                      const nsTArray<gfxFontFeature>& aFontFeatures,
+                      bool aDisableLigatures,
+                      nsDataHashtable<nsUint32HashKey,PRUint32>& aMergedFeatures);
+
 protected:
     // the font this shaper is working with
     gfxFont * mFont;
 };
 
 /* a SPECIFIC single font family */
 class THEBES_API gfxFont {
 public:
new file mode 100644
--- /dev/null
+++ b/gfx/thebes/gfxFontFeatures.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GFX_FONT_FEATURES_H
+#define GFX_FONT_FEATURES_H
+
+#include "prtypes.h"
+
+// An OpenType feature tag and value pair
+struct gfxFontFeature {
+    PRUint32 mTag; // see http://www.microsoft.com/typography/otspec/featuretags.htm
+    PRUint32 mValue; // 0 = off, 1 = on, larger values may be used as parameters
+                     // to features that select among multiple alternatives
+};
+
+inline bool
+operator<(const gfxFontFeature& a, const gfxFontFeature& b)
+{
+    return (a.mTag < b.mTag) || ((a.mTag == b.mTag) && (a.mValue < b.mValue));
+}
+
+inline bool
+operator==(const gfxFontFeature& a, const gfxFontFeature& b)
+{
+    return (a.mTag == b.mTag) && (a.mValue == b.mValue);
+}
+
+#endif
--- a/gfx/thebes/gfxGraphiteShaper.cpp
+++ b/gfx/thebes/gfxGraphiteShaper.cpp
@@ -149,16 +149,33 @@ MakeGraphiteLangTag(PRUint32 aTag)
     PRUint32 mask = 0x000000FF;
     while ((grLangTag & mask) == ' ') {
         grLangTag &= ~mask;
         mask <<= 8;
     }
     return grLangTag;
 }
 
+struct GrFontFeatures {
+    gr_face        *mFace;
+    gr_feature_val *mFeatures;
+};
+
+static PLDHashOperator
+AddFeature(const PRUint32& aTag, PRUint32& aValue, void *aUserArg)
+{
+    GrFontFeatures *f = static_cast<GrFontFeatures*>(aUserArg);
+
+    const gr_feature_ref* fref = gr_face_find_fref(f->mFace, aTag);
+    if (fref) {
+        gr_fref_set_feature_value(fref, aValue, f->mFeatures);
+    }
+    return PL_DHASH_NEXT;
+}
+
 bool
 gfxGraphiteShaper::ShapeWord(gfxContext      *aContext,
                              gfxShapedWord   *aShapedWord,
                              const PRUnichar *aText)
 {
     // some font back-ends require this in order to get proper hinted metrics
     if (!mFont->SetupCairoFont(aContext)) {
         return false;
@@ -192,42 +209,31 @@ gfxGraphiteShaper::ShapeWord(gfxContext 
         grLang = MakeGraphiteLangTag(entry->mLanguageOverride);
     } else {
         nsCAutoString langString;
         style->language->ToUTF8String(langString);
         grLang = GetGraphiteTagForLang(langString);
     }
     gr_feature_val *grFeatures = gr_face_featureval_for_lang(mGrFace, grLang);
 
-    if (aShapedWord->DisableLigatures()) {
-        const gr_feature_ref* fref =
-            gr_face_find_fref(mGrFace, TRUETYPE_TAG('l','i','g','a'));
-        if (fref) {
-            gr_fref_set_feature_value(fref, 0, grFeatures);
-        }
-    }
+    nsDataHashtable<nsUint32HashKey,PRUint32> mergedFeatures;
 
-    const nsTArray<gfxFontFeature> *features = &style->featureSettings;
-    if (features->IsEmpty()) {
-        features = &entry->mFeatureSettings;
-    }
-    for (PRUint32 i = 0; i < features->Length(); ++i) {
-        const gr_feature_ref* fref =
-            gr_face_find_fref(mGrFace, (*features)[i].mTag);
-        if (fref) {
-            gr_fref_set_feature_value(fref, (*features)[i].mValue, grFeatures);
-        }
+    if (MergeFontFeatures(style->featureSettings, entry->mFeatureSettings,
+                          aShapedWord->DisableLigatures(), mergedFeatures)) {
+        // enumerate result and insert into Graphite feature list
+        GrFontFeatures f = {mGrFace, grFeatures};
+        mergedFeatures.Enumerate(AddFeature, &f);
     }
 
     gr_segment *seg = gr_make_seg(mGrFont, mGrFace, 0, grFeatures,
                                   gr_utf16, aText, aShapedWord->Length(),
                                   aShapedWord->IsRightToLeft());
-    if (features) {
-        gr_featureval_destroy(grFeatures);
-    }
+
+    gr_featureval_destroy(grFeatures);
+
     if (!seg) {
         return false;
     }
 
     nsresult rv = SetGlyphsFromSegment(aShapedWord, seg);
 
     gr_seg_destroy(seg);
 
--- a/gfx/thebes/gfxHarfBuzzShaper.cpp
+++ b/gfx/thebes/gfxHarfBuzzShaper.cpp
@@ -837,16 +837,28 @@ HBUnicodeDecompose(hb_unicode_funcs_t *u
                    hb_codepoint_t      ab,
                    hb_codepoint_t     *a,
                    hb_codepoint_t     *b,
                    void               *user_data)
 {
     return nsUnicodeNormalizer::DecomposeNonRecursively(ab, a, b);
 }
 
+static PLDHashOperator
+AddFeature(const PRUint32& aTag, PRUint32& aValue, void *aUserArg)
+{
+    nsTArray<hb_feature_t>* features = static_cast<nsTArray<hb_feature_t>*> (aUserArg);
+
+    hb_feature_t feat = { 0, 0, 0, UINT_MAX };
+    feat.tag = aTag;
+    feat.value = aValue;
+    features->AppendElement(feat);
+    return PL_DHASH_NEXT;
+}
+
 /*
  * gfxFontShaper override to initialize the text run using HarfBuzz
  */
 
 static hb_font_funcs_t * sHBFontFuncs = nsnull;
 static hb_unicode_funcs_t * sHBUnicodeFuncs = nsnull;
 
 bool
@@ -968,45 +980,26 @@ gfxHarfBuzzShaper::ShapeWord(gfxContext 
     hb_font_t *font = hb_font_create(mHBFace);
     hb_font_set_funcs(font, sHBFontFuncs, &fcd, nsnull);
     hb_font_set_ppem(font, mFont->GetAdjustedSize(), mFont->GetAdjustedSize());
     PRUint32 scale = FloatToFixed(mFont->GetAdjustedSize()); // 16.16 fixed-point
     hb_font_set_scale(font, scale, scale);
 
     nsAutoTArray<hb_feature_t,20> features;
 
-    // Ligature features are enabled by default in the generic shaper,
-    // so we explicitly turn them off if necessary (for letter-spacing)
-    if (aShapedWord->DisableLigatures()) {
-        hb_feature_t ligaOff = { HB_TAG('l','i','g','a'), 0, 0, UINT_MAX };
-        hb_feature_t cligOff = { HB_TAG('c','l','i','g'), 0, 0, UINT_MAX };
-        features.AppendElement(ligaOff);
-        features.AppendElement(cligOff);
-    }
-
-    // css features need to be merged with the existing ones, if any
     gfxFontEntry *entry = mFont->GetFontEntry();
     const gfxFontStyle *style = mFont->GetStyle();
-    const nsTArray<gfxFontFeature> *cssFeatures = &style->featureSettings;
-    if (cssFeatures->IsEmpty()) {
-        cssFeatures = &entry->mFeatureSettings;
-    }
-    for (PRUint32 i = 0; i < cssFeatures->Length(); ++i) {
-        PRUint32 j;
-        for (j = 0; j < features.Length(); ++j) {
-            if (cssFeatures->ElementAt(i).mTag == features[j].tag) {
-                features[j].value = cssFeatures->ElementAt(i).mValue;
-                break;
-            }
-        }
-        if (j == features.Length()) {
-            const gfxFontFeature& f = cssFeatures->ElementAt(i);
-            hb_feature_t hbf = { f.mTag, f.mValue, 0, UINT_MAX };
-            features.AppendElement(hbf);
-        }
+
+    nsDataHashtable<nsUint32HashKey,PRUint32> mergedFeatures;
+
+    if (MergeFontFeatures(style->featureSettings,
+                      mFont->GetFontEntry()->mFeatureSettings,
+                      aShapedWord->DisableLigatures(), mergedFeatures)) {
+        // enumerate result and insert into hb_feature array
+        mergedFeatures.Enumerate(AddFeature, &features);
     }
 
     bool isRightToLeft = aShapedWord->IsRightToLeft();
     hb_buffer_t *buffer = hb_buffer_create();
     hb_buffer_set_unicode_funcs(buffer, sHBUnicodeFuncs);
     hb_buffer_set_direction(buffer, isRightToLeft ? HB_DIRECTION_RTL :
                                                     HB_DIRECTION_LTR);
     // For unresolved "common" or "inherited" runs, default to Latin for now.
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -112,17 +112,17 @@ gfxUserFontSet::~gfxUserFontSet()
 }
 
 gfxFontEntry*
 gfxUserFontSet::AddFontFace(const nsAString& aFamilyName,
                             const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                             PRUint32 aWeight,
                             PRUint32 aStretch,
                             PRUint32 aItalicStyle,
-                            const nsString& aFeatureSettings,
+                            const nsTArray<gfxFontFeature>& aFeatureSettings,
                             const nsString& aLanguageOverride,
                             gfxSparseBitSet *aUnicodeRanges)
 {
     gfxProxyFontEntry *proxyEntry = nsnull;
 
     nsAutoString key(aFamilyName);
     ToLowerCase(key);
 
@@ -135,25 +135,22 @@ gfxUserFontSet::AddFontFace(const nsAStr
 
     gfxMixedFontFamily *family = mFontFamilies.GetWeak(key, &found);
     if (!family) {
         family = new gfxMixedFontFamily(aFamilyName);
         mFontFamilies.Put(key, family);
     }
 
     // construct a new face and add it into the family
-    nsTArray<gfxFontFeature> featureSettings;
-    gfxFontStyle::ParseFontFeatureSettings(aFeatureSettings,
-                                           featureSettings);
     PRUint32 languageOverride =
         gfxFontStyle::ParseFontLanguageOverride(aLanguageOverride);
     proxyEntry =
         new gfxProxyFontEntry(aFontFaceSrcList, family, aWeight, aStretch,
                               aItalicStyle,
-                              featureSettings,
+                              aFeatureSettings,
                               languageOverride,
                               aUnicodeRanges);
     family->AddFontEntry(proxyEntry);
 #ifdef PR_LOGGING
     if (LOG_ENABLED()) {
         LOG(("userfonts (%p) added (%s) with style: %s weight: %d stretch: %d",
              this, NS_ConvertUTF16toUTF8(aFamilyName).get(),
              (aItalicStyle & NS_FONT_STYLE_ITALIC ? "italic" :
--- a/gfx/thebes/gfxUserFontSet.h
+++ b/gfx/thebes/gfxUserFontSet.h
@@ -209,17 +209,17 @@ public:
     // weight, stretch - 0 == unknown, [1, 9] otherwise
     // italic style = constants in gfxFontConstants.h, e.g. NS_FONT_STYLE_NORMAL
     // TODO: support for unicode ranges not yet implemented
     gfxFontEntry *AddFontFace(const nsAString& aFamilyName,
                               const nsTArray<gfxFontFaceSrc>& aFontFaceSrcList,
                               PRUint32 aWeight,
                               PRUint32 aStretch,
                               PRUint32 aItalicStyle,
-                              const nsString& aFeatureSettings,
+                              const nsTArray<gfxFontFeature>& aFeatureSettings,
                               const nsString& aLanguageOverride,
                               gfxSparseBitSet *aUnicodeRanges = nsnull);
 
     // add in a font face for which we have the gfxFontEntry already
     void AddFontFace(const nsAString& aFamilyName, gfxFontEntry* aFontEntry);
 
     // Whether there is a face with this family name
     bool HasFamily(const nsAString& aFamilyName) const
--- a/ipc/glue/SharedMemory.cpp
+++ b/ipc/glue/SharedMemory.cpp
@@ -48,28 +48,29 @@ namespace mozilla {
 namespace ipc {
 
 static PRInt64 gShmemAllocated;
 static PRInt64 gShmemMapped;
 static PRInt64 GetShmemAllocated() { return gShmemAllocated; }
 static PRInt64 GetShmemMapped() { return gShmemMapped; }
 
 NS_MEMORY_REPORTER_IMPLEMENT(ShmemAllocated,
-    "shmem-allocated",
-    KIND_OTHER,
-    UNITS_BYTES,
-    GetShmemAllocated,
-    "Memory shared with other processes that is accessible (but not "
-    "necessarily mapped).")
+  "shmem-allocated",
+  KIND_OTHER,
+  UNITS_BYTES,
+  GetShmemAllocated,
+  "Memory shared with other processes that is accessible (but not "
+  "necessarily mapped).")
+
 NS_MEMORY_REPORTER_IMPLEMENT(ShmemMapped,
-    "shmem-mapped",
-    KIND_OTHER,
-    UNITS_BYTES,
-    GetShmemMapped,
-    "Memory shared with other processes that is mapped into the address space.")
+  "shmem-mapped",
+  KIND_OTHER,
+  UNITS_BYTES,
+  GetShmemMapped,
+  "Memory shared with other processes that is mapped into the address space.")
 
 SharedMemory::SharedMemory()
   : mAllocSize(0)
   , mMappedSize(0)
 {
   // NB: SharedMemory is main-thread-only at the moment, but that may
   // change soon
   static bool registered;
--- a/js/public/Utility.h
+++ b/js/public/Utility.h
@@ -842,17 +842,17 @@ namespace js {
  */
 template<typename T>
 class MoveRef {
   public:
     typedef T Referent;
     explicit MoveRef(T &t) : pointer(&t) { }
     T &operator*()  const { return *pointer; }
     T *operator->() const { return  pointer; }
-#ifdef __GXX_EXPERIMENTAL_CXX0X__
+#if defined(__GXX_EXPERIMENTAL_CXX0X__) && defined(__clang__)
     /*
      * If MoveRef is used in a rvalue position (which is expected), we can
      * end up in a situation where, without this ifdef, we would try to pass
      * a T& to a move constructor, which fails. It is not clear if the compiler
      * should instead use the copy constructor, but for now this lets us build
      * with clang. See bug 689066 and llvm.org/pr11003 for the details.
      * Note: We can probably remove MoveRef completely once we are comfortable
      * using c++11.
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/testBug748212.js
@@ -0,0 +1,1 @@
+"".match(wrap(evalcx("/x/",newGlobal('new-compartment'))));
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -1069,16 +1069,24 @@ JS_GetTypedArrayByteOffset(JSObject *obj
  * be known that it would pass such a test: it is a typed array or a wrapper of
  * a typed array, and the unwrapping will succeed. If cx is NULL, then DEBUG
  * builds may be unable to assert when unwrapping should be disallowed.
  */
 extern JS_FRIEND_API(uint32_t)
 JS_GetTypedArrayByteLength(JSObject *obj, JSContext *cx);
 
 /*
+ * Check whether obj supports JS_ArrayBufferView* APIs. Note that this may
+ * return false if a security wrapper is encountered that denies the
+ * unwrapping.
+ */
+extern JS_FRIEND_API(JSBool)
+JS_IsArrayBufferViewObject(JSObject *obj, JSContext *cx);
+
+/*
  * More generic name for JS_GetTypedArrayByteLength to cover DataViews as well
  */
 extern JS_FRIEND_API(uint32_t)
 JS_GetArrayBufferViewByteLength(JSObject *obj, JSContext *cx);
 
 /*
  * Return a pointer to the start of the data referenced by a typed array. The
  * data is still owned by the typed array, and should not be modified on
--- a/js/src/jsprobes.cpp
+++ b/js/src/jsprobes.cpp
@@ -212,43 +212,43 @@ Probes::JITWatcher::CollectNativeRegions
     // All of the stub code comes immediately after the main code
     for (NativeRegion *iter = regions.begin(); iter != regions.end(); ++iter)
         iter->stubOffset += outerFrame->mainCodeEnd;
 
     return true;
 }
 
 void
-Probes::registerMJITCode(JSContext *cx, js::mjit::JITScript *jscr,
+Probes::registerMJITCode(JSContext *cx, js::mjit::JITChunk *chunk,
                          js::mjit::JSActiveFrame *outerFrame,
                          js::mjit::JSActiveFrame **inlineFrames,
                          void *mainCodeAddress, size_t mainCodeSize,
                          void *stubCodeAddress, size_t stubCodeSize)
 {
     for (JITWatcher **p = jitWatchers.begin(); p != jitWatchers.end(); ++p)
-        (*p)->registerMJITCode(cx, jscr, outerFrame,
+        (*p)->registerMJITCode(cx, chunk, outerFrame,
                                inlineFrames,
                                mainCodeAddress, mainCodeSize,
                                stubCodeAddress, stubCodeSize);
 }
 
 void
-Probes::discardMJITCode(FreeOp *fop, mjit::JITScript *jscr, JSScript *script, void* address)
+Probes::discardMJITCode(FreeOp *fop, mjit::JITScript *jscr, mjit::JITChunk *chunk, void* address)
 {
     for (JITWatcher **p = jitWatchers.begin(); p != jitWatchers.end(); ++p)
-        (*p)->discardMJITCode(fop, jscr, script, address);
+        (*p)->discardMJITCode(fop, jscr, chunk, address);
 }
 
 void
 Probes::registerICCode(JSContext *cx,
-                       mjit::JITScript *jscr, JSScript *script, jsbytecode* pc,
+                       mjit::JITChunk *chunk, JSScript *script, jsbytecode* pc,
                        void *start, size_t size)
 {
     for (JITWatcher **p = jitWatchers.begin(); p != jitWatchers.end(); ++p)
-        (*p)->registerICCode(cx, jscr, script, pc, start, size);
+        (*p)->registerICCode(cx, chunk, script, pc, start, size);
 }
 #endif
 
 /* ICs are unregistered in a batch */
 void
 Probes::discardExecutableRegion(void *start, size_t size)
 {
     for (JITWatcher **p = jitWatchers.begin(); p != jitWatchers.end(); ++p)
--- a/js/src/jsprobes.h
+++ b/js/src/jsprobes.h
@@ -253,27 +253,27 @@ public:
 
 #ifdef JS_METHODJIT
     static bool CollectNativeRegions(RegionVector &regions,
                                      JSRuntime *rt,
                                      mjit::JITChunk *jit,
                                      mjit::JSActiveFrame *outerFrame,
                                      mjit::JSActiveFrame **inlineFrames);
 
-    virtual void registerMJITCode(JSContext *cx, js::mjit::JITScript *jscr,
+    virtual void registerMJITCode(JSContext *cx, js::mjit::JITChunk *chunk,
                                   mjit::JSActiveFrame *outerFrame,
                                   mjit::JSActiveFrame **inlineFrames,
                                   void *mainCodeAddress, size_t mainCodeSize,
                                   void *stubCodeAddress, size_t stubCodeSize) = 0;
 
-    virtual void discardMJITCode(FreeOp *fop, mjit::JITScript *jscr, JSScript *script,
+    virtual void discardMJITCode(FreeOp *fop, mjit::JITScript *jscr, mjit::JITChunk *chunk,
                                  void* address) = 0;
 
     virtual void registerICCode(JSContext *cx,
-                                js::mjit::JITScript *jscr, JSScript *script, jsbytecode* pc,
+                                js::mjit::JITChunk *chunk, JSScript *script, jsbytecode* pc,
                                 void *start, size_t size) = 0;
 #endif
 
     virtual void discardExecutableRegion(void *start, size_t size) = 0;
 };
 
 /*
  * Register a JITWatcher subclass to be informed of JIT code
@@ -301,34 +301,34 @@ removeAllJITWatchers(JSRuntime *rt);
 JITReportGranularity
 JITGranularityRequested();
 
 #ifdef JS_METHODJIT
 /*
  * New method JIT code has been created
  */
 void
-registerMJITCode(JSContext *cx, js::mjit::JITScript *jscr,
+registerMJITCode(JSContext *cx, js::mjit::JITChunk *chunk,
                  mjit::JSActiveFrame *outerFrame,
                  mjit::JSActiveFrame **inlineFrames,
                  void *mainCodeAddress, size_t mainCodeSize,
                  void *stubCodeAddress, size_t stubCodeSize);
 
 /*
  * Method JIT code is about to be discarded
  */
 void
-discardMJITCode(FreeOp *fop, mjit::JITScript *jscr, JSScript *script, void* address);
+discardMJITCode(FreeOp *fop, mjit::JITScript *jscr, mjit::JITChunk *chunk, void* address);
 
 /*
- * IC code has been allocated within the given JITScript
+ * IC code has been allocated within the given JITChunk
  */
 void
 registerICCode(JSContext *cx,
-               mjit::JITScript *jscr, JSScript *script, jsbytecode* pc,
+               mjit::JITChunk *chunk, JSScript *script, jsbytecode* pc,
                void *start, size_t size);
 #endif /* JS_METHODJIT */
 
 /*
  * A whole region of code has been deallocated, containing any number of ICs.
  * (ICs are unregistered in a batch, so individual ICs are not registered.)
  */
 void
--- a/js/src/jstypedarray.cpp
+++ b/js/src/jstypedarray.cpp
@@ -2537,16 +2537,23 @@ JS_IsArrayBufferObject(JSObject *obj, JS
 
 JS_FRIEND_API(JSBool)
 JS_IsTypedArrayObject(JSObject *obj, JSContext *cx)
 {
     obj = UnwrapObject(obj);
     return obj->isTypedArray();
 }
 
+JS_FRIEND_API(JSBool)
+JS_IsArrayBufferViewObject(JSObject *obj, JSContext *cx)
+{
+    obj = UnwrapObject(obj);
+    return obj->isTypedArray();
+}
+
 JS_FRIEND_API(uint32_t)
 JS_GetArrayBufferByteLength(JSObject *obj, JSContext *cx)
 {
     obj = UnwrapObject(obj);
     return obj->asArrayBuffer().byteLength();
 }
 
 JS_FRIEND_API(uint8_t *)
--- a/js/src/jstypedarray.h
+++ b/js/src/jstypedarray.h
@@ -52,17 +52,19 @@ namespace js {
 /*
  * ArrayBufferObject
  *
  * This class holds the underlying raw buffer that the various ArrayBufferView
  * subclasses (DataView and the TypedArrays) access. It can be created
  * explicitly and passed to an ArrayBufferView subclass, or can be created
  * implicitly by constructing a TypedArray with a size.
  */
-struct ArrayBufferObject : public JSObject {
+class ArrayBufferObject : public JSObject
+{
+  public:
     static Class protoClass;
     static JSPropertySpec jsprops[];
     static JSFunctionSpec jsfuncs[];
 
     static JSBool prop_getByteLength(JSContext *cx, JSObject *obj, jsid id, Value *vp);
 
     static JSBool fun_slice(JSContext *cx, unsigned argc, Value *vp);
 
--- a/js/src/jswrapper.cpp
+++ b/js/src/jswrapper.cpp
@@ -352,17 +352,17 @@ Wrapper::fun_toString(JSContext *cx, JSO
     JSString *str = ProxyHandler::fun_toString(cx, wrapper, indent);
     leave(cx, wrapper);
     return str;
 }
 
 bool
 Wrapper::regexp_toShared(JSContext *cx, JSObject *wrapper, RegExpGuard *g)
 {
-    return wrappedObject(wrapper)->asRegExp().getShared(cx, g);
+    return RegExpToShared(cx, *wrappedObject(wrapper), g);
 }
 
 bool
 Wrapper::defaultValue(JSContext *cx, JSObject *wrapper, JSType hint, Value *vp)
 {
     *vp = ObjectValue(*wrappedObject(wrapper));
     if (hint == JSTYPE_VOID)
         return ToPrimitive(cx, vp);
--- a/js/src/methodjit/BaseCompiler.h
+++ b/js/src/methodjit/BaseCompiler.h
@@ -166,17 +166,17 @@ class LinkerHelper : public JSC::LinkBuf
         }
         m_size = masm.size();   // must come after call to executableAllocAndCopy()!
         return pool;
     }
 
     JSC::CodeLocationLabel finalize(VMFrame &f) {
         masm.finalize(*this);
         JSC::CodeLocationLabel label = finalizeCodeAddendum();
-        Probes::registerICCode(f.cx, f.jit(), f.script(), f.pc(),
+        Probes::registerICCode(f.cx, f.chunk(), f.script(), f.pc(),
                                label.executableAddress(), masm.size());
         return label;
     }
 
     void maybeLink(MaybeJump jump, JSC::CodeLocationLabel label) {
         if (!jump.isSet())
             return;
         link(jump.get(), label);
--- a/js/src/methodjit/Compiler.cpp
+++ b/js/src/methodjit/Compiler.cpp
@@ -1769,17 +1769,17 @@ mjit::Compiler::finishThisUp()
 
     /* Patch all outgoing calls. */
     masm.finalize(fullCode, inlineDoubles);
     stubcc.masm.finalize(stubCode, oolDoubles);
 
     JSC::ExecutableAllocator::makeExecutable(result, masm.size() + stubcc.size());
     JSC::ExecutableAllocator::cacheFlush(result, masm.size() + stubcc.size());
 
-    Probes::registerMJITCode(cx, jit,
+    Probes::registerMJITCode(cx, chunk,
                              a,
                              (JSActiveFrame**) inlineFrames.begin(),
                              result, masm.size(),
                              result + masm.size(), stubcc.size());
 
     outerChunkRef().chunk = chunk;
 
     /* Patch all incoming and outgoing cross-chunk jumps. */
--- a/js/src/methodjit/MethodJIT.cpp
+++ b/js/src/methodjit/MethodJIT.cpp
@@ -1323,17 +1323,17 @@ JITScript::destroy(FreeOp *fop)
 }
 
 void
 JITScript::destroyChunk(FreeOp *fop, unsigned chunkIndex, bool resetUses)
 {
     ChunkDescriptor &desc = chunkDescriptor(chunkIndex);
 
     if (desc.chunk) {
-        Probes::discardMJITCode(fop, this, script, desc.chunk->code.m_code.executableAddress());
+        Probes::discardMJITCode(fop, this, desc.chunk, desc.chunk->code.m_code.executableAddress());
         fop->delete_(desc.chunk);
         desc.chunk = NULL;
 
         CrossChunkEdge *edges = this->edges();
         for (unsigned i = 0; i < nedges; i++) {
             CrossChunkEdge &edge = edges[i];
             if (edge.source >= desc.begin && edge.source < desc.end) {
                 edge.sourceJump1 = edge.sourceJump2 = NULL;
@@ -1479,16 +1479,18 @@ JITScript::findCodeChunk(void *addr)
 }
 
 jsbytecode *
 JITScript::nativeToPC(void *returnAddress, CallSite **pinline)
 {
     JITChunk *chunk = findCodeChunk(returnAddress);
     JS_ASSERT(chunk);
 
+    JS_ASSERT(chunk->isValidCode(returnAddress));
+
     size_t low = 0;
     size_t high = chunk->nCallICs;
     js::mjit::ic::CallICInfo *callICs_ = chunk->callICs();
     while (high > low + 1) {
         /* Could overflow here on a script with 2 billion calls. Oh well. */
         size_t mid = (high + low) / 2;
         void *entry = callICs_[mid].funGuard.executableAddress();
 
--- a/js/src/tests/jstests.py
+++ b/js/src/tests/jstests.py
@@ -142,17 +142,17 @@ if __name__ == '__main__':
         if OPTIONS.xul_info_src is None:
             xul_info = manifest.XULInfo.create(JS)
         else:
             xul_abi, xul_os, xul_debug = OPTIONS.xul_info_src.split(r':')
             xul_debug = xul_debug.lower() is 'true'
             xul_info = manifest.XULInfo(xul_abi, xul_os, xul_debug)
         xul_tester = manifest.XULInfoTester(xul_info, JS)
 
-    test_dir = os.path.dirname(os.path.realpath(__file__))
+    test_dir = os.path.dirname(os.path.abspath(__file__))
     test_list = manifest.load(test_dir, xul_tester)
     skipped_list = []
 
     if OPTIONS.make_manifests:
         manifest.make_manifests(OPTIONS.make_manifests, test_list)
         if JS is None:
             sys.exit()
 
--- a/js/src/vm/ObjectImpl.h
+++ b/js/src/vm/ObjectImpl.h
@@ -570,17 +570,17 @@ ElementsHeader::asArrayBufferElements()
  * pointing to the beginning of that array (the end of this structure).
  * See below for usage of this structure.
  */
 class ArrayBufferObject;
 class ObjectElements
 {
     friend struct ::JSObject;
     friend class ObjectImpl;
-    friend struct js::ArrayBufferObject;
+    friend class ArrayBufferObject;
 
     /* Number of allocated slots. */
     uint32_t capacity;
 
     /*
      * Number of initialized elements. This is <= the capacity, and for arrays
      * is <= the length. Memory for elements above the initialized length is
      * uninitialized, but values between the initialized length and the proper
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -1263,29 +1263,27 @@ GetJSUserCompartmentCount()
 
 // Nb: js-system-compartment-count + js-user-compartment-count could be
 // different to the number of compartments reported by
 // JSMemoryMultiReporter if a garbage collection occurred
 // between them being consulted.  We could move these reporters into
 // XPConnectJSCompartmentCount to avoid that problem, but then we couldn't
 // easily report them via telemetry, so we live with the small risk of
 // inconsistencies.
-NS_MEMORY_REPORTER_IMPLEMENT(
-    XPConnectJSSystemCompartmentCount,
+NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSSystemCompartmentCount,
     "js-compartments-system",
     KIND_OTHER,
     nsIMemoryReporter::UNITS_COUNT,
     GetJSSystemCompartmentCount,
     "The number of JavaScript compartments for system code.  The sum of this "
     "and 'js-compartments-user' might not match the number of compartments "
     "listed under 'js' if a garbage collection occurs at an inopportune time, "
     "but such cases should be rare.")
 
-NS_MEMORY_REPORTER_IMPLEMENT(
-    XPConnectJSUserCompartmentCount,
+NS_MEMORY_REPORTER_IMPLEMENT(XPConnectJSUserCompartmentCount,
     "js-compartments-user",
     KIND_OTHER,
     nsIMemoryReporter::UNITS_COUNT,
     GetJSUserCompartmentCount,
     "The number of JavaScript compartments for user code.  The sum of this "
     "and 'js-compartments-system' might not match the number of compartments "
     "listed under 'js' if a garbage collection occurs at an inopportune time, "
     "but such cases should be rare.")
--- a/js/xpconnect/src/XPCQuickStubs.h
+++ b/js/xpconnect/src/XPCQuickStubs.h
@@ -257,30 +257,42 @@ public:
     typedef T implementation_type;
 
     ~xpc_qsBasicString()
     {
         if (mValid)
             Ptr()->~implementation_type();
     }
 
-    JSBool IsValid() { return mValid; }
+    JSBool IsValid() const { return mValid; }
 
     implementation_type *Ptr()
     {
         MOZ_ASSERT(mValid);
         return reinterpret_cast<implementation_type *>(mBuf);
     }
 
+    const implementation_type *Ptr() const
+    {
+        MOZ_ASSERT(mValid);
+        return reinterpret_cast<const implementation_type *>(mBuf);
+    }
+
     operator interface_type &()
     {
         MOZ_ASSERT(mValid);
         return *Ptr();
     }
 
+    operator const interface_type &() const
+    {
+        MOZ_ASSERT(mValid);
+        return *Ptr();
+    }
+
     /* Enum that defines how JS |null| and |undefined| should be treated.  See
      * the WebIDL specification.  eStringify means convert to the string "null"
      * or "undefined" respectively, via the standard JS ToString() operation;
      * eEmpty means convert to the string ""; eNull means convert to an empty
      * string with the void bit set.
      *
      * Per webidl the default behavior of an unannotated interface is
      * eStringify, but our de-facto behavior has been eNull for |null| and
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-parsing/invalid-font-face-descriptor-1-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<style>
+  body { color: green! important; }
+</style>
+There should be no red
new file mode 100644
--- /dev/null
+++ b/layout/reftests/css-parsing/invalid-font-face-descriptor-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<style>
+  body { color: red; }
+  @font-face {
+    src: url(bbb),
+  }
+
+  body { color: green! important; }
+</style>
+There should be no red
--- a/layout/reftests/css-parsing/reftest.list
+++ b/layout/reftests/css-parsing/reftest.list
@@ -1,6 +1,7 @@
 == at-rule-013.html at-rule-013-ref.html
 == invalid-url-handling.xhtml invalid-url-handling-ref.xhtml
 == pseudo-elements-1.html pseudo-elements-1-ref.html
 == invalid-attr-1.html invalid-attr-1-ref.html
 == at-rule-error-handling-import-1.html at-rule-error-handling-ref.html
 == at-rule-error-handling-media-1.html at-rule-error-handling-ref.html
+== invalid-font-face-descriptor-1.html invalid-font-face-descriptor-1-ref.html
--- a/layout/reftests/font-features/font-features-hlig-2.html
+++ b/layout/reftests/font-features/font-features-hlig-2.html
@@ -1,15 +1,15 @@
 <html>
 <head>
 <style type="text/css">
 @font-face {
   font-family: libertine;
   src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
-  -moz-font-feature-settings: "hlig=1";
+  -moz-font-feature-settings: "hlig";
 }
 body {
   font-family: libertine, sans-serif;
   font-size: 400%;
   line-height: 2em;
 }
 </style>
 </head>
--- a/layout/reftests/font-features/font-features-hlig-3.html
+++ b/layout/reftests/font-features/font-features-hlig-3.html
@@ -1,20 +1,20 @@
 <html>
 <head>
 <style type="text/css">
 @font-face {
   font-family: libertine;
   src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
-  -moz-font-feature-settings: "hlig=1";
+  -moz-font-feature-settings: "hlig";
 }
 body {
   font-family: libertine, sans-serif;
   font-size: 400%;
   line-height: 2em;
-  -moz-font-feature-settings: "hlig=0,liga=0";
+  -moz-font-feature-settings: "hlig" off, "liga" off;
 }
 </style>
 </head>
 <body lang="en">
 fastest firefox
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-features-hlig-4.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<style type="text/css">
+@font-face {
+  font-family: libertine;
+  src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
+  -moz-font-feature-settings: "hlig" off;
+}
+body {
+  font-family: libertine, sans-serif;
+  font-size: 400%;
+  line-height: 2em;
+  -moz-font-feature-settings: "hlig" on;
+}
+</style>
+</head>
+<body lang="en">
+fastest firefox
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-features-hlig-5.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<style type="text/css">
+@font-face {
+  font-family: libertine;
+  src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
+  -moz-font-feature-settings: "hlig";
+}
+body {
+  font-family: libertine, sans-serif;
+  font-size: 400%;
+  line-height: 2em;
+  -moz-font-feature-settings: "hlig" off;
+}
+</style>
+</head>
+<body lang="en">
+fastest firefox
+</body>
+</html>
--- a/layout/reftests/font-features/font-features-hlig.html
+++ b/layout/reftests/font-features/font-features-hlig.html
@@ -4,16 +4,16 @@
 @font-face {
   font-family: libertine;
   src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
 }
 body {
   font-family: libertine, sans-serif;
   font-size: 400%;
   line-height: 2em;
-  -moz-font-feature-settings: "hlig=1";
+  -moz-font-feature-settings: "hlig" on;
 }
 </style>
 </head>
 <body lang="en">
 fastest firefox
 </body>
 </html>
--- a/layout/reftests/font-features/font-features-noliga.html
+++ b/layout/reftests/font-features/font-features-noliga.html
@@ -4,16 +4,16 @@
 @font-face {
   font-family: libertine;
   src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
 }
 body {
   font-family: libertine, sans-serif;
   font-size: 400%;
   line-height: 2em;
-  -moz-font-feature-settings: "liga=0";
+  -moz-font-feature-settings: "liga" 0;
 }
 </style>
 </head>
 <body lang="en">
 fastest firefox
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-features-oldsyntax-1.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<style type="text/css">
+@font-face {
+  font-family: libertine;
+  src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
+}
+body {
+  font-family: libertine, sans-serif;
+  font-size: 400%;
+  line-height: 2em;
+  -moz-font-feature-settings: "hlig=1";
+  -moz-font-feature-settings: "hlig" off;
+}
+</style>
+</head>
+<body lang="en">
+fastest firefox
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-features-oldsyntax-2.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<style type="text/css">
+@font-face {
+  font-family: libertine;
+  src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
+}
+body {
+  font-family: libertine, sans-serif;
+  font-size: 400%;
+  line-height: 2em;
+  -moz-font-feature-settings: "hlig" off;
+  -moz-font-feature-settings: "hlig=1";
+}
+</style>
+</head>
+<body lang="en">
+fastest firefox
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-features-oldsyntax-3.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<style type="text/css">
+@font-face {
+  font-family: libertine;
+  src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
+}
+body {
+  font-family: libertine, sans-serif;
+  font-size: 400%;
+  line-height: 2em;
+  -moz-font-feature-settings: "hlig=0";
+  -moz-font-feature-settings: "hlig";
+}
+</style>
+</head>
+<body lang="en">
+fastest firefox
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-features/font-features-oldsyntax-4.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<style type="text/css">
+@font-face {
+  font-family: libertine;
+  src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
+}
+body {
+  font-family: libertine, sans-serif;
+  font-size: 400%;
+  line-height: 2em;
+  -moz-font-feature-settings: "hlig";
+  -moz-font-feature-settings: "hlig=0";
+}
+</style>
+</head>
+<body lang="en">
+fastest firefox
+</body>
+</html>
--- a/layout/reftests/font-features/font-features-order-1.html
+++ b/layout/reftests/font-features/font-features-order-1.html
@@ -5,16 +5,16 @@
 @font-face {
   font-family: libertine;
   src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
 }
 body {
   font-family: libertine, sans-serif;
   font-size: 400%;
   line-height: 2em;
-  -moz-font-feature-settings: "liga=0,liga=1";
+  -moz-font-feature-settings: "liga" 0, "liga" 1;
 }
 </style>
 </head>
 <body lang="en">
 fastest firefox
 </body>
 </html>
--- a/layout/reftests/font-features/font-features-order-2.html
+++ b/layout/reftests/font-features/font-features-order-2.html
@@ -5,16 +5,16 @@
 @font-face {
   font-family: libertine;
   src: url(../fonts/LinLibertine_Re-4.7.5.woff) format("woff");
 }
 body {
   font-family: libertine, sans-serif;
   font-size: 400%;
   line-height: 2em;
-  -moz-font-feature-settings: "liga=1,liga=0";
+  -moz-font-feature-settings: "liga" 1, "liga" 0;
 }
 </style>
 </head>
 <body lang="en">
 fastest firefox
 </body>
 </html>
--- a/layout/reftests/font-features/reftest.list
+++ b/layout/reftests/font-features/reftest.list
@@ -23,20 +23,28 @@ fails-if(d2d) HTTP(..) == font-features-
 HTTP(..) == font-features-turkish.html font-features-noliga.html
 
 # The following should pass even if feature support isn't available,
 # because both testcase and reference will have the default rendering,
 # though they're not really meaningful unless the tests above passed already.
 
 # compare feature specified within @font-face to same feature in style rule
 HTTP(..) == font-features-hlig-2.html font-features-hlig.html
+HTTP(..) == font-features-hlig-4.html font-features-hlig.html
+HTTP(..) != font-features-hlig-5.html font-features-hlig.html
 
 # check that feature in style rule overrides @font-face
 HTTP(..) == font-features-hlig-3.html font-features-noliga.html
 
+# make sure old syntax usage never interferes with new syntax usage
+HTTP(..) == font-features-oldsyntax-1.html font-features-ref.html
+HTTP(..) == font-features-oldsyntax-2.html font-features-ref.html
+HTTP(..) == font-features-oldsyntax-3.html font-features-hlig.html
+HTTP(..) == font-features-oldsyntax-4.html font-features-hlig.html
+
 # compare -moz-font-language-override rendering to lang-tagged rendering
 HTTP(..) == font-features-turkish-override-1.html font-features-turkish.html
 HTTP(..) == font-features-turkish-override-2.html font-features-turkish.html
 
 # check use of -moz-font-language-override to override explicit lang tag
 HTTP(..) == font-features-turkish-override-3.html font-features-ref.html
 HTTP(..) == font-features-turkish-override-4.html font-features-ref.html
 HTTP(..) == font-features-turkish-override-5.html font-features-turkish.html
--- a/layout/reftests/text/graphite-05-feat.html
+++ b/layout/reftests/text/graphite-05-feat.html
@@ -5,17 +5,17 @@
 @font-face {
   font-family: test;
   src: url(../fonts/graphite/grtest-langfeat.ttf);
 }
 
 body {
   margin: 20px;
   font: 100px test;
-  -moz-font-feature-settings: "TST1=1,FTP2=1";
+  -moz-font-feature-settings: "TST1", "FTP2";
 }
 p { margin: 0; padding: 0; }
 </style>
 </head>
 <body>
 <p>FAIL</p>
 </body>
 </html>
--- a/layout/reftests/text/graphite-05-lang.html
+++ b/layout/reftests/text/graphite-05-lang.html
@@ -5,17 +5,17 @@
 @font-face {
   font-family: test;
   src: url(../fonts/graphite/grtest-langfeat.ttf);
 }
 
 body {
   margin: 20px;
   font: 100px test;
-  -moz-font-feature-settings: "TST1=1";
+  -moz-font-feature-settings: "TST1";
 }
 p { margin: 0; padding: 0; }
 </style>
 </head>
 <body>
 <p lang="fr">FAIL</p>
 </body>
 </html>
--- a/layout/reftests/text/graphite-05-multipass.html
+++ b/layout/reftests/text/graphite-05-multipass.html
@@ -5,17 +5,17 @@
 @font-face {
   font-family: test;
   src: url(../fonts/graphite/grtest-multipass.ttf);
 }
 
 body {
   margin: 20px;
   font: 100px test;
-  -moz-font-feature-settings: "TST1=1";
+  -moz-font-feature-settings: "TST1";
 }
 p { margin: 0; padding: 0; }
 </style>
 </head>
 <body>
 <p>FAIL</p>
 </body>
 </html>
--- a/layout/reftests/text/graphite-05-ot-only.html
+++ b/layout/reftests/text/graphite-05-ot-only.html
@@ -5,17 +5,17 @@
 @font-face {
   font-family: test;
   src: url(../fonts/graphite/grtest-ot-only.ttf);
 }
 
 body {
   margin: 20px;
   font: 100px test;
-  -moz-font-feature-settings: "TST1=1";
+  -moz-font-feature-settings: "TST1";
 }
 p { margin: 0; padding: 0; }
 </style>
 </head>
 <body>
 <p>FAIL</p>
 </body>
 </html>
--- a/layout/reftests/text/graphite-05-simple.html
+++ b/layout/reftests/text/graphite-05-simple.html
@@ -5,17 +5,17 @@
 @font-face {
   font-family: test;
   src: url(../fonts/graphite/grtest-simple.ttf);
 }
 
 body {
   margin: 20px;
   font: 100px test;
-  -moz-font-feature-settings: "TST1=1";
+  -moz-font-feature-settings: "TST1";
 }
 p { margin: 0; padding: 0; }
 </style>
 </head>
 <body>
 <p>FAIL</p>
 </body>
 </html>
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -522,16 +522,17 @@ protected:
   bool ParseColumns();
   bool ParseContent();
   bool ParseCounterData(nsCSSProperty aPropID);
   bool ParseCursor();
   bool ParseFont();
   bool ParseFontWeight(nsCSSValue& aValue);
   bool ParseOneFamily(nsAString& aValue);
   bool ParseFamily(nsCSSValue& aValue);
+  bool ParseFontFeatureSettings(nsCSSValue& aValue);
   bool ParseFontSrc(nsCSSValue& aValue);
   bool ParseFontSrcFormat(InfallibleTArray<nsCSSValue>& values);
   bool ParseFontRanges(nsCSSValue& aValue);
   bool ParseListStyle();
   bool ParseMargin();
   bool ParseMarks(nsCSSValue& aValue);
   bool ParseMozTransform();
   bool ParseOutline();
@@ -5674,16 +5675,18 @@ CSSParserImpl::ParseSingleValueProperty(
     NS_ABORT_IF_FALSE(false, "not a single value property");
     return false;
   }
 
   if (nsCSSProps::PropHasFlags(aPropID, CSS_PROPERTY_VALUE_PARSER_FUNCTION)) {
     switch (aPropID) {
       case eCSSProperty_font_family:
         return ParseFamily(aValue);
+      case eCSSProperty_font_feature_settings:
+        return ParseFontFeatureSettings(aValue);
       case eCSSProperty_font_weight:
         return ParseFontWeight(aValue);
       case eCSSProperty_marks:
         return ParseMarks(aValue);
       case eCSSProperty_text_decoration_line:
         return ParseTextDecorationLine(aValue);
       case eCSSProperty_text_overflow:
         return ParseTextOverflow(aValue);
@@ -5792,16 +5795,18 @@ CSSParserImpl::ParseFontDescriptorValue(
     // These two are unique to @font-face and have their own special grammar.
   case eCSSFontDesc_Src:
     return ParseFontSrc(aValue);
 
   case eCSSFontDesc_UnicodeRange:
     return ParseFontRanges(aValue);
 
   case eCSSFontDesc_FontFeatureSettings:
+    return ParseFontFeatureSettings(aValue);
+
   case eCSSFontDesc_FontLanguageOverride:
     return ParseVariant(aValue, VARIANT_NORMAL | VARIANT_STRING, nsnull);
 
   case eCSSFontDesc_UNKNOWN:
   case eCSSFontDesc_COUNT:
     NS_NOTREACHED("bad nsCSSFontDesc code");
   }
   // explicitly do NOT have a default case to let the compiler
@@ -8212,16 +8217,18 @@ CSSParserImpl::ParseFontSrc(nsCSSValue& 
 
       font.EnumerateFamilies(ExtractFirstFamily, (void*) &dat);
       if (!dat.mGood)
         return false;
 
       cur.SetStringValue(dat.mFamilyName, eCSSUnit_Local_Font);
       values.AppendElement(cur);
     } else {
+      // We don't know what to do with this token; unget it and error out
+      UngetToken();
       return false;
     }
 
     if (!ExpectSymbol(',', true))
       break;
   }
 
   if (values.Length() == 0)
@@ -8314,16 +8321,96 @@ CSSParserImpl::ParseFontRanges(nsCSSValu
     = nsCSSValue::Array::Create(ranges.Length());
 
   for (PRUint32 i = 0; i < ranges.Length(); i++)
     srcVals->Item(i).SetIntValue(ranges[i], eCSSUnit_Integer);
   aValue.SetArrayValue(srcVals, eCSSUnit_Array);
   return true;
 }
 
+// font-feature-settings: normal | <feature-tag-value> [, <feature-tag-value>]*
+// <feature-tag-value> = <string> [ <integer> | on | off ]?
+
+// minimum - "tagx", "tagy", "tagz"
+// edge error case - "tagx" on 1, "tagx" "tagy", "tagx" -1, "tagx" big
+
+// pair value is always x = string, y = int
+
+// font feature tags must be four ASCII characters
+#define FEATURE_TAG_LENGTH   4
+
+static bool
+ValidFontFeatureTag(const nsString& aTag)
+{
+  if (aTag.Length() != FEATURE_TAG_LENGTH) {
+    return false;
+  }
+  PRUint32 i;
+  for (i = 0; i < FEATURE_TAG_LENGTH; i++) {
+    PRUint32 ch = aTag[i];
+    if (ch < 0x20 || ch > 0x7e) {
+      return false;
+    }
+  }
+  return true;
+}
+
+bool
+CSSParserImpl::ParseFontFeatureSettings(nsCSSValue& aValue)
+{
+  if (ParseVariant(aValue, VARIANT_INHERIT | VARIANT_NORMAL, nsnull)) {
+    return true;
+  }
+
+  nsCSSValuePairList *cur = aValue.SetPairListValue();
+  for (;;) {
+    // feature tag
+    if (!GetToken(true)) {
+      return false;
+    }
+
+    if (mToken.mType != eCSSToken_String ||
+        !ValidFontFeatureTag(mToken.mIdent)) {
+      UngetToken();
+      return false;
+    }
+    cur->mXValue.SetStringValue(mToken.mIdent, eCSSUnit_String);
+
+    if (!GetToken(true)) {
+      cur->mYValue.SetIntValue(1, eCSSUnit_Integer);
+      break;
+    }
+
+    // optional value or on/off keyword
+    if (mToken.mType == eCSSToken_Number && mToken.mIntegerValid &&
+        mToken.mInteger >= 0) {
+      cur->mYValue.SetIntValue(mToken.mInteger, eCSSUnit_Integer);
+    } else if (mToken.mType == eCSSToken_Ident &&
+               mToken.mIdent.LowerCaseEqualsLiteral("on")) {
+      cur->mYValue.SetIntValue(1, eCSSUnit_Integer);
+    } else if (mToken.mType == eCSSToken_Ident &&
+               mToken.mIdent.LowerCaseEqualsLiteral("off")) {
+      cur->mYValue.SetIntValue(0, eCSSUnit_Integer);
+    } else {
+      // something other than value/on/off, set default value
+      cur->mYValue.SetIntValue(1, eCSSUnit_Integer);
+      UngetToken();
+    }
+
+    if (!ExpectSymbol(',', true)) {
+      break;
+    }
+
+    cur->mNext = new nsCSSValuePairList;
+    cur = cur->mNext;
+  }
+
+  return true;
+}
+
 bool
 CSSParserImpl::ParseListStyle()
 {
   // 'list-style' can accept 'none' for two different subproperties,
   // 'list-style-type' and 'list-style-position'.  In order to accept
   // 'none' as the value of either but still allow another value for
   // either, we need to ensure that the first 'none' we find gets
   // allocated to a dummy property instead.
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -1465,18 +1465,19 @@ CSS_PROP_FONT(
     nsnull,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
 CSS_PROP_FONT(
     -moz-font-feature-settings,
     font_feature_settings,
     CSS_PROP_DOMPROP_PREFIXED(FontFeatureSettings),
     CSS_PROPERTY_PARSE_VALUE |
+        CSS_PROPERTY_VALUE_PARSER_FUNCTION |
         CSS_PROPERTY_APPLIES_TO_FIRST_LETTER_AND_FIRST_LINE,
-    VARIANT_NORMAL | VARIANT_INHERIT | VARIANT_STRING,
+    0,
     nsnull,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
 CSS_PROP_FONT(
     -moz-font-language-override,
     font_language_override,
     CSS_PROP_DOMPROP_PREFIXED(FontLanguageOverride),
     CSS_PROPERTY_PARSE_VALUE |
--- a/layout/style/nsCSSRules.cpp
+++ b/layout/style/nsCSSRules.cpp
@@ -53,16 +53,17 @@
 #include "nsCSSStyleSheet.h"
 
 #include "nsCOMPtr.h"
 #include "nsIDOMCSSStyleSheet.h"
 #include "nsIMediaList.h"
 #include "nsICSSRuleList.h"
 #include "nsIDocument.h"
 #include "nsPresContext.h"
+#include "nsRuleNode.h"
 
 #include "nsContentUtils.h"
 #include "nsStyleConsts.h"
 #include "nsDOMError.h"
 #include "nsStyleUtil.h"
 #include "mozilla/css/Declaration.h"
 #include "nsCSSParser.h"
 #include "nsPrintfCString.h"
@@ -1444,17 +1445,17 @@ nsCSSFontFaceStyleDecl::GetPropertyValue
     val.AppendToString(eCSSProperty_font_weight, aResult);
     return NS_OK;
 
   case eCSSFontDesc_Stretch:
     val.AppendToString(eCSSProperty_font_stretch, aResult);
     return NS_OK;
 
   case eCSSFontDesc_FontFeatureSettings:
-    val.AppendToString(eCSSProperty_font_feature_settings, aResult);
+    nsStyleUtil::AppendFontFeatureSettings(val, aResult);
     return NS_OK;
 
   case eCSSFontDesc_FontLanguageOverride:
     val.AppendToString(eCSSProperty_font_language_override, aResult);
     return NS_OK;
 
   case eCSSFontDesc_Src:
     AppendSerializedFontSrc(val, aResult);
--- a/layout/style/nsCSSValue.cpp
+++ b/layout/style/nsCSSValue.cpp
@@ -1032,17 +1032,24 @@ nsCSSValue::AppendToString(nsCSSProperty
     GetPairValue().AppendToString(aProperty, aResult);
   } else if (eCSSUnit_Triplet == unit) {
     GetTripletValue().AppendToString(aProperty, aResult);
   } else if (eCSSUnit_Rect == unit) {
     GetRectValue().AppendToString(aProperty, aResult);
   } else if (eCSSUnit_List == unit || eCSSUnit_ListDep == unit) {
     GetListValue()->AppendToString(aProperty, aResult);
   } else if (eCSSUnit_PairList == unit || eCSSUnit_PairListDep == unit) {
-    GetPairListValue()->AppendToString(aProperty, aResult);
+    switch (aProperty) {
+      case eCSSProperty_font_feature_settings:
+        nsStyleUtil::AppendFontFeatureSettings(*this, aResult);
+        break;
+      default:
+        GetPairListValue()->AppendToString(aProperty, aResult);
+        break;
+    }
   }
 
   switch (unit) {
     case eCSSUnit_Null:         break;
     case eCSSUnit_Auto:         aResult.AppendLiteral("auto");     break;
     case eCSSUnit_Inherit:      aResult.AppendLiteral("inherit");  break;
     case eCSSUnit_Initial:      aResult.AppendLiteral("-moz-initial"); break;
     case eCSSUnit_None:         aResult.AppendLiteral("none");     break;
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -1319,22 +1319,23 @@ nsComputedDOMStyle::DoGetFontVariant()
 }
 
 nsIDOMCSSValue*
 nsComputedDOMStyle::DoGetMozFontFeatureSettings()
 {
   nsROCSSPrimitiveValue* val = GetROCSSPrimitiveValue();
 
   const nsStyleFont* font = GetStyleFont();
-  if (font->mFont.featureSettings.IsEmpty()) {
+  if (font->mFont.fontFeatureSettings.IsEmpty()) {
     val->SetIdent(eCSSKeyword_normal);
   } else {
-    nsString str;
-    nsStyleUtil::AppendEscapedCSSString(font->mFont.featureSettings, str);
-    val->SetString(str);
+    nsAutoString result;
+    nsStyleUtil::AppendFontFeatureSettings(font->mFont.fontFeatureSettings,
+                                           result);
+    val->SetString(result);
   }
   return val;
 }
 
 nsIDOMCSSValue*
 nsComputedDOMStyle::DoGetMozFontLanguageOverride()
 {
   nsROCSSPrimitiveValue* val = GetROCSSPrimitiveValue();
--- a/layout/style/nsFontFaceLoader.cpp
+++ b/layout/style/nsFontFaceLoader.cpp
@@ -520,17 +520,17 @@ nsUserFontSet::InsertRule(nsCSSFontFaceR
     }
   }
 
   // this is a new rule:
 
   PRUint32 weight = NS_STYLE_FONT_WEIGHT_NORMAL;
   PRUint32 stretch = NS_STYLE_FONT_STRETCH_NORMAL;
   PRUint32 italicStyle = NS_STYLE_FONT_STYLE_NORMAL;
-  nsString featureSettings, languageOverride;
+  nsString languageOverride;
 
   // set up weight
   aRule->GetDesc(eCSSFontDesc_Weight, val);
   unit = val.GetUnit();
   if (unit == eCSSUnit_Integer || unit == eCSSUnit_Enumerated) {
     weight = val.GetIntValue();
   } else if (unit == eCSSUnit_Normal) {
     weight = NS_STYLE_FONT_WEIGHT_NORMAL;
@@ -559,22 +559,23 @@ nsUserFontSet::InsertRule(nsCSSFontFaceR
   } else if (unit == eCSSUnit_Normal) {
     italicStyle = NS_STYLE_FONT_STYLE_NORMAL;
   } else {
     NS_ASSERTION(unit == eCSSUnit_Null,
                  "@font-face style has unexpected unit");
   }
 
   // set up font features
+  nsTArray<gfxFontFeature> featureSettings;
   aRule->GetDesc(eCSSFontDesc_FontFeatureSettings, val);
   unit = val.GetUnit();
   if (unit == eCSSUnit_Normal) {
-    // empty feature string
-  } else if (unit == eCSSUnit_String) {
-    val.GetStringValue(featureSettings);
+    // empty list of features
+  } else if (unit == eCSSUnit_PairList || unit == eCSSUnit_PairListDep) {
+    nsRuleNode::ComputeFontFeatures(val.GetPairListValue(), featureSettings);
   } else {
     NS_ASSERTION(unit == eCSSUnit_Null,
                  "@font-face font-feature-settings has unexpected unit");
   }
 
   // set up font language override
   aRule->GetDesc(eCSSFontDesc_FontLanguageOverride, val);
   unit = val.GetUnit();
--- a/layout/style/nsLayoutStylesheetCache.cpp
+++ b/layout/style/nsLayoutStylesheetCache.cpp
@@ -54,21 +54,21 @@ NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(Lay
 static PRInt64
 GetStylesheetCacheSize()
 {
   return nsLayoutStylesheetCache::SizeOfIncludingThis(
            LayoutStyleSheetCacheMallocSizeOf);
 }
 
 NS_MEMORY_REPORTER_IMPLEMENT(Sheets,
-                             "explicit/layout/style-sheet-cache",
-                             KIND_HEAP,
-                             nsIMemoryReporter::UNITS_BYTES,
-                             GetStylesheetCacheSize,
-                             "Memory used for some built-in style sheets.")
+  "explicit/layout/style-sheet-cache",
+  KIND_HEAP,
+  nsIMemoryReporter::UNITS_BYTES,
+  GetStylesheetCacheSize,
+  "Memory used for some built-in style sheets.")
 
 NS_IMPL_ISUPPORTS1(nsLayoutStylesheetCache, nsIObserver)
 
 nsresult
 nsLayoutStylesheetCache::Observe(nsISupports* aSubject,
                             const char* aTopic,
                             const PRUnichar* aData)
 {
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -2898,26 +2898,44 @@ nsRuleNode::SetFont(nsPresContext* aPres
   }
   else if (eCSSUnit_Initial == scriptLevelValue->GetUnit()) {
     aFont->mScriptLevel = 0;
   }
 
   // font-feature-settings
   const nsCSSValue* featureSettingsValue =
     aRuleData->ValueForFontFeatureSettings();
-  if (eCSSUnit_Inherit == featureSettingsValue->GetUnit()) {
+
+  switch (featureSettingsValue->GetUnit()) {
+  case eCSSUnit_Null:
+    break;
+
+  case eCSSUnit_Normal:
+  case eCSSUnit_Initial:
+    aFont->mFont.fontFeatureSettings.Clear();
+    break;
+
+  case eCSSUnit_Inherit:
     aCanStoreInRuleTree = false;
-    aFont->mFont.featureSettings = aParentFont->mFont.featureSettings;
-  } else if (eCSSUnit_Normal == featureSettingsValue->GetUnit() ||
-             eCSSUnit_Initial == featureSettingsValue->GetUnit()) {
-    aFont->mFont.featureSettings.Truncate();
-  } else if (eCSSUnit_System_Font == featureSettingsValue->GetUnit()) {
-    aFont->mFont.featureSettings = systemFont.featureSettings;
-  } else if (eCSSUnit_String == featureSettingsValue->GetUnit()) {
-    featureSettingsValue->GetStringValue(aFont->mFont.featureSettings);
+    aFont->mFont.fontFeatureSettings = aParentFont->mFont.fontFeatureSettings;
+    break;
+
+  case eCSSUnit_System_Font:
+    aFont->mFont.fontFeatureSettings = systemFont.fontFeatureSettings;
+    break;
+
+  case eCSSUnit_PairList:
+  case eCSSUnit_PairListDep:
+    ComputeFontFeatures(featureSettingsValue->GetPairListValue(),
+                        aFont->mFont.fontFeatureSettings);
+    break;
+
+  default:
+    NS_ABORT_IF_FALSE(false, "unexpected value unit");
+    break;
   }
 
   // font-language-override
   const nsCSSValue* languageOverrideValue =
     aRuleData->ValueForFontLanguageOverride();
   if (eCSSUnit_Inherit == languageOverrideValue->GetUnit()) {
     aCanStoreInRuleTree = false;
     aFont->mFont.languageOverride = aParentFont->mFont.languageOverride;
@@ -2981,16 +2999,46 @@ nsRuleNode::SetFont(nsPresContext* aPres
   if (eCSSUnit_System_Font == sizeAdjustValue->GetUnit()) {
     aFont->mFont.sizeAdjust = systemFont.sizeAdjust;
   } else
     SetFactor(*sizeAdjustValue, aFont->mFont.sizeAdjust,
               aCanStoreInRuleTree, aParentFont->mFont.sizeAdjust, 0.0f,
               SETFCT_NONE);
 }
 
+/* static */ void
+nsRuleNode::ComputeFontFeatures(const nsCSSValuePairList *aFeaturesList,
+                                nsTArray<gfxFontFeature>& aFeatureSettings)
+{
+  aFeatureSettings.Clear();
+  for (const nsCSSValuePairList* p = aFeaturesList; p; p = p->mNext) {
+    gfxFontFeature feat = {0, 0};
+
+    NS_ABORT_IF_FALSE(aFeaturesList->mXValue.GetUnit() == eCSSUnit_String,
+                      "unexpected value unit");
+
+    // tag is a 4-byte ASCII sequence
+    nsAutoString tag;
+    p->mXValue.GetStringValue(tag);
+    if (tag.Length() != 4) {
+      continue;
+    }
+    // parsing validates that these are ASCII chars
+    // tags are always big-endian
+    feat.mTag = (tag[0] << 24) | (tag[1] << 16) | (tag[2] << 8)  | tag[3];
+
+    // value
+    NS_ASSERTION(p->mYValue.GetUnit() == eCSSUnit_Integer,
+                 "should have found an integer unit");
+    feat.mValue = p->mYValue.GetIntValue();
+
+    aFeatureSettings.AppendElement(feat);
+  }
+}
+
 // This should die (bug 380915).
 //
 // SetGenericFont:
 //  - backtrack to an ancestor with the same generic font name (possibly
 //    up to the root where default values come from the presentation context)
 //  - re-apply cascading rules from there without caching intermediate values
 /* static */ void
 nsRuleNode::SetGenericFont(nsPresContext* aPresContext,
--- a/layout/style/nsRuleNode.h
+++ b/layout/style/nsRuleNode.h
@@ -55,16 +55,17 @@ struct PLDHashTable;
 struct nsRuleData;
 class nsIStyleRule;
 struct nsCSSValueList;
 
 class nsCSSValue;
 struct nsCSSRect;
 
 class nsStyleCoord;
+class nsCSSValuePairList;
 
 template <nsStyleStructID MinIndex, nsStyleStructID Count>
 class FixedStyleStructArray
 {
 private:
   void* mArray[Count];
 public:
   void*& operator[](nsStyleStructID aIndex) {
@@ -742,11 +743,14 @@ public:
   bool TreeHasCachedData() const {
     NS_ASSERTION(IsRoot(), "should only be called on root of rule tree");
     return HaveChildren() || mStyleData.mInheritedData || mStyleData.mResetData;
   }
 
   bool NodeHasCachedData(const nsStyleStructID aSID) {
     return !!mStyleData.GetStyleData(aSID);
   }
+
+  static void ComputeFontFeatures(const nsCSSValuePairList *aFeaturesList,
+                                  nsTArray<gfxFontFeature>& aFeatureSettings);
 };
 
 #endif
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -228,17 +228,17 @@ nsChangeHint nsStyleFont::CalcFontDiffer
 {
   if ((aFont1.size == aFont2.size) && 
       (aFont1.sizeAdjust == aFont2.sizeAdjust) && 
       (aFont1.style == aFont2.style) &&
       (aFont1.variant == aFont2.variant) &&
       (aFont1.weight == aFont2.weight) &&
       (aFont1.stretch == aFont2.stretch) &&
       (aFont1.name == aFont2.name) &&
-      (aFont1.featureSettings == aFont2.featureSettings) &&
+      (aFont1.fontFeatureSettings == aFont2.fontFeatureSettings) &&
       (aFont1.languageOverride == aFont2.languageOverride)) {
     if ((aFont1.decorations == aFont2.decorations)) {
       return NS_STYLE_HINT_NONE;
     }
     return NS_STYLE_HINT_VISUAL;
   }
   return NS_STYLE_HINT_REFLOW;
 }
--- a/layout/style/nsStyleUtil.cpp
+++ b/layout/style/nsStyleUtil.cpp
@@ -48,16 +48,17 @@
 #include "nsIDocument.h"
 #include "nsINameSpaceManager.h"
 #include "nsIURI.h"
 #include "nsNetUtil.h"
 #include "nsReadableUtils.h"
 #include "nsContentUtils.h"
 #include "nsTextFormatter.h"
 #include "nsCSSProps.h"
+#include "nsRuleNode.h"
 
 using namespace mozilla;
 
 //------------------------------------------------------------------------------
 // Font Algorithm Code
 //------------------------------------------------------------------------------
 
 nscoord
@@ -484,16 +485,69 @@ nsStyleUtil::AppendBitmaskCSSValue(nsCSS
       if (aMaskedValue) { // more left
         aResult.Append(PRUnichar(' '));
       }
     }
   }
   NS_ABORT_IF_FALSE(aMaskedValue == 0, "unexpected bit remaining in bitfield");
 }
 
+/* static */ void
+nsStyleUtil::AppendFontFeatureSettings(const nsTArray<gfxFontFeature>& aFeatures,
+                                       nsAString& aResult)
+{
+  for (PRUint32 i = 0, numFeat = aFeatures.Length(); i < numFeat; i++) {
+    const gfxFontFeature& feat = aFeatures[i];
+
+    if (i != 0) {
+        aResult.AppendLiteral(", ");
+    }
+
+    // output tag
+    char tag[7];
+    tag[0] = '"';
+    tag[1] = (feat.mTag >> 24) & 0xff;
+    tag[2] = (feat.mTag >> 16) & 0xff;
+    tag[3] = (feat.mTag >> 8) & 0xff;
+    tag[4] = feat.mTag & 0xff;
+    tag[5] = '"';
+    tag[6] = 0;
+    aResult.AppendASCII(tag);
+
+    // output value, if necessary
+    if (feat.mValue == 0) {
+      // 0 ==> off
+      aResult.AppendLiteral(" off");
+    } else if (feat.mValue > 1) {
+      aResult.AppendLiteral(" ");
+      aResult.AppendInt(feat.mValue);
+    }
+    // else, omit value if 1, implied by default
+  }
+}
+
+/* static */ void
+nsStyleUtil::AppendFontFeatureSettings(const nsCSSValue& aSrc,
+                                       nsAString& aResult)
+{
+  nsCSSUnit unit = aSrc.GetUnit();
+
+  if (unit == eCSSUnit_Normal) {
+    aResult.AppendLiteral("normal");
+    return;
+  }
+
+  NS_PRECONDITION(unit == eCSSUnit_PairList || unit == eCSSUnit_PairListDep,
+                  "improper value unit for font-feature-settings:");
+
+  nsTArray<gfxFontFeature> featureSettings;
+  nsRuleNode::ComputeFontFeatures(aSrc.GetPairListValue(), featureSettings);
+  AppendFontFeatureSettings(featureSettings, aResult);
+}
+
 /* static */ float
 nsStyleUtil::ColorComponentToFloat(PRUint8 aAlpha)
 {
   // Alpha values are expressed as decimals, so we should convert
   // back, using as few decimal places as possible for
   // round-tripping.
   // First try two decimal places:
   float rounded = NS_roundf(float(aAlpha) * 100.0f / 255.0f) / 100.0f;
--- a/layout/style/nsStyleUtil.h
+++ b/layout/style/nsStyleUtil.h
@@ -34,16 +34,19 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 #ifndef nsStyleUtil_h___
 #define nsStyleUtil_h___
 
 #include "nsCoord.h"
 #include "nsCSSProperty.h"
+#include "gfxFontFeatures.h"
+#include "nsTArray.h"
+#include "nsCSSValue.h"
 
 class nsPresContext;
 struct nsStyleBackground;
 class nsString;
 class nsStringComparator;
 class nsIContent;
 
 enum nsFontSizeType {
@@ -85,16 +88,22 @@ public:
 
   // Append a bitmask-valued property's value(s) (space-separated) to aResult.
   static void AppendBitmaskCSSValue(nsCSSProperty aProperty,
                                     PRInt32 aMaskedValue,
                                     PRInt32 aFirstMask,
                                     PRInt32 aLastMask,
                                     nsAString& aResult);
 
+  static void AppendFontFeatureSettings(const nsTArray<gfxFontFeature>& aFeatures,
+                                        nsAString& aResult);
+
+  static void AppendFontFeatureSettings(const nsCSSValue& src,
+                                        nsAString& aResult);
+
   /*
    * Convert an author-provided floating point number to an integer (0
    * ... 255) appropriate for use in the alpha component of a color.
    */
   static PRUint8 FloatToColorComponent(float aAlpha)
   {
     NS_ASSERTION(0.0 <= aAlpha && aAlpha <= 1.0, "out of range");
     return NSToIntRound(aAlpha * 255);
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -2041,26 +2041,37 @@ var gCSSProperties = {
 		other_values: [ (gInitialFontFamilyIsSansSerif ? "serif" : "sans-serif"), "Times New Roman, serif", "'Times New Roman', serif", "cursive", "fantasy", "\\\"Times New Roman", "\"Times New Roman\"", "Times, \\\"Times New Roman", "Times, \"Times New Roman\"" ],
 		invalid_values: [ "\"Times New\" Roman", "\"Times New Roman\n", "Times, \"Times New Roman\n" ]
 	},
 	"-moz-font-feature-settings": {
 		domProp: "MozFontFeatureSettings",
 		inherited: true,
 		type: CSS_TYPE_LONGHAND,
 		initial_values: [ "normal" ],
-		other_values: [ "'liga=1'", "\"liga=1\"", "'foo,bar=\"hello\"'" ],
-		invalid_values: [ "liga=1", "foo,bar=\"hello\"" ]
+		other_values: [
+			"'liga' on", "'liga'", "\"liga\" 1", "'liga', 'clig' 1",
+			"\"liga\" off", "\"liga\" 0", '"cv01" 3, "cv02" 4',
+			'"cswh", "smcp" off, "salt" 4', '"cswh" 1, "smcp" off, "salt" 4',
+			'"cswh" 0, \'blah\', "liga", "smcp" off, "salt" 4',
+			'"liga"        ,"smcp" 0         , "blah"'
+		],
+		invalid_values: [
+			'liga', 'liga 1', 'liga normal', '"liga" normal', 'normal liga', 
+			'normal "liga"', 'normal, "liga"', '"liga=1"', "'foobar' on",
+			'"blahblah" 0', '"liga" 3.14', '"liga" 1 3.14', '"liga" 1 normal',
+			'"liga" 1 off', '"liga" on off', '"liga" , 0 "smcp"', '"liga" "smcp"'
+		]
 	},
 	"-moz-font-language-override": {
 		domProp: "MozFontLanguageOverride",
 		inherited: true,
 		type: CSS_TYPE_LONGHAND,
 		initial_values: [ "normal" ],
 		other_values: [ "'ENG'", "'TRK'", "\"TRK\"", "'N\\'Ko'" ],
-		invalid_values: [ "TRK" ]
+		invalid_values: [ "TRK", "ja" ]
 	},
 	"font-size": {
 		domProp: "fontSize",
 		inherited: true,
 		type: CSS_TYPE_LONGHAND,
 		initial_values: [ "medium",
 			"1rem",
 			"-moz-calc(1rem)",
--- a/layout/style/test/test_font_face_parser.html
+++ b/layout/style/test/test_font_face_parser.html
@@ -248,46 +248,48 @@
 
     // Descending ranges and ranges outside 0-10FFFF are ignored
     // but do not invalidate the descriptor
     { rule: _("unicode-range: U+A5, U+90-30"),
       d: { "unicode-range" : "U+00A5" }, noncanonical: true },
     { rule: _("unicode-range: U+A5, U+220043"),
       d: { "unicode-range" : "U+00A5" }, noncanonical: true },
 
-    // -moz-font-feature-settings, -moz-font-language-override:
+    // -moz-font-feature-settings
     { rule: _("-moz-font-feature-settings: normal;"),
       d: { "-moz-font-feature-settings" : "normal" } },
-    { rule: _("-moz-font-feature-settings: \"dlig=1\";"),
-      d: { "-moz-font-feature-settings" : "\"dlig=1\"" } },
-    { rule: _("-moz-font-feature-settings: 'dlig=1'"),
-      d: { "-moz-font-feature-settings" : "\"dlig=1\"" }, noncanonical: true },
+    { rule: _("-moz-font-feature-settings: \"dlig\";"),
+      d: { "-moz-font-feature-settings" : "\"dlig\"" } },
+    { rule: _("-moz-font-feature-settings: \"dlig\" 1;"),
+      d: { "-moz-font-feature-settings" : "\"dlig\"" }, noncanonical: true },
+    { rule: _("-moz-font-feature-settings: 'dlig' 1"),
+      d: { "-moz-font-feature-settings" : "\"dlig\"" }, noncanonical: true },
 
+    // incorrect -moz-font-feature-settings
+    { rule: _("-moz-font-feature-settings: dlig 1"), d: {} },
+    { rule: _("-moz-font-feature-settings: none;"), d: {} },
+    { rule: _("-moz-font-feature-settings: 0;"), d: {} },
+    { rule: _("-moz-font-feature-settings: 3.14;"), d: {} },
+    { rule: _("-moz-font-feature-settings: 'blah' 3.14;"), d: {} },
+    { rule: _("-moz-font-feature-settings: 'dlig' 1 'hist' 0;"), d: {} },
+    { rule: _("-moz-font-feature-settings: 'dlig=1,hist=1'"), d: {} },
+
+    // -moz-font-language-override:
     { rule: _("-moz-font-language-override: normal;"),
       d: { "-moz-font-language-override" : "normal" } },
     { rule: _("-moz-font-language-override: \"TRK\";"),
       d: { "-moz-font-language-override" : "\"TRK\"" } },
     { rule: _("-moz-font-language-override: 'TRK'"),
       d: { "-moz-font-language-override" : "\"TRK\"" }, noncanonical: true },
 
-    // incorrect -moz-font-feature-settings, -moz-font-language-override
-    // with unquoted values and other bad types:
-    { rule: _("-moz-font-feature-settings: dlig=1"), d: {} },
+    // incorrect -moz-font-language-override
     { rule: _("-moz-font-language-override: TRK"), d: {} },
-    { rule: _("-moz-font-feature-settings: none;"), d: {} },
     { rule: _("-moz-font-language-override: none;"), d: {} },
-    { rule: _("-moz-font-feature-settings: 0;"), d: {} },
     { rule: _("-moz-font-language-override: 0;"), d: {} },
-    { rule: _("-moz-font-feature-settings: 3.14;"), d: {} },
     { rule: _("-moz-font-language-override: #999;"), d: {} },
-
-    // incorrect -moz-font-feature-settings, -moz-font-language-override
-    // with multiple values:
-    { rule: _("-moz-font-feature-settings: 'dlig=1' 'hist=1'"), d: {} },
-    { rule: _("-moz-font-feature-settings: 'dlig=1', 'hist=1'"), d: {} },
     { rule: _("-moz-font-language-override: 'TRK' 'SRB'"), d: {} },
     { rule: _("-moz-font-language-override: 'TRK', 'SRB'"), d: {} },
   ];
 
   var display = document.getElementById("display");
   var sheet = document.styleSheets[1];
 
   for (var curTest = 0; curTest < testset.length; curTest++) {
--- a/layout/style/ua.css
+++ b/layout/style/ua.css
@@ -253,16 +253,17 @@
   top: 0 !important;
   left: 0 !important;
   right: 0 !important;
   bottom: 0 !important;
   z-index: 2147483647 !important;
   background: black;
   width: 100% !important;
   height: 100% !important;
+  margin: 0 !important;
   min-width: 0 !important;
   max-width: none !important;
   min-height: 0 !important;
   max-height: none !important;
 }
 
 /* If there is a full-screen element that is not the root then
    we should hide the viewport scrollbar. We exclude the chrome
--- a/layout/svg/base/src/nsSVGGlyphFrame.cpp
+++ b/layout/svg/base/src/nsSVGGlyphFrame.cpp
@@ -1600,19 +1600,20 @@ nsSVGGlyphFrame::EnsureTextRun(float *aD
 
     const nsFont& font = fontData->mFont;
     bool printerFont = (presContext->Type() == nsPresContext::eContext_PrintPreview ||
                           presContext->Type() == nsPresContext::eContext_Print);
     gfxFontStyle fontStyle(font.style, font.weight, font.stretch, textRunSize,
                            mStyleContext->GetStyleFont()->mLanguage,
                            font.sizeAdjust, font.systemFont,
                            printerFont,
-                           font.featureSettings,
                            font.languageOverride);
 
+    font.AddFontFeaturesToStyle(&fontStyle);
+
     nsRefPtr<gfxFontGroup> fontGroup =
       gfxPlatform::GetPlatform()->CreateFontGroup(font.name, &fontStyle, presContext->GetUserFontSet());
 
     PRUint32 flags = gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX |
       GetTextRunFlags(text.Length()) |
       nsLayoutUtils::GetTextRunFlagsForStyle(GetStyleContext(), GetStyleText(), GetStyleFont());
 
     // XXX We should use a better surface here! But then we'd have to
--- a/mobile/android/base/GeckoPreferences.java
+++ b/mobile/android/base/GeckoPreferences.java
@@ -121,17 +121,18 @@ public class GeckoPreferences
     private void initGroups(PreferenceGroup preferences) {
         final int count = preferences.getPreferenceCount();
         for (int i = 0; i < count; i++) {
             Preference pref = preferences.getPreference(i);
             if (pref instanceof PreferenceGroup)
                 initGroups((PreferenceGroup)pref);
             else {
                 pref.setOnPreferenceChangeListener(this);
-                mPreferencesList.add(pref.getKey());
+                if (pref.getKey() != null)
+                    mPreferencesList.add(pref.getKey());
             }
         }
     }
 
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
             case android.R.id.home:
--- a/mobile/android/base/TabsTray.java
+++ b/mobile/android/base/TabsTray.java
@@ -55,16 +55,18 @@ public class TabsTray extends GeckoActiv
 
         LayoutInflater.from(this).setFactory(GeckoViewsFactory.getInstance());
 
         setContentView(R.layout.tabs_tray);
 
         mWaitingForClose = false;
 
         mList = (ListView) findViewById(R.id.list);
+        mList.setItemsCanFocus(true);
+
         mListContainer = (TabsListContainer) findViewById(R.id.list_container);
 
         ImageButton addTab = (ImageButton) findViewById(R.id.add_tab);
         addTab.setOnClickListener(new Button.OnClickListener() {
             public void onClick(View v) {
                 GeckoApp.mAppContext.addTab();
                 finishActivity();
             }
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -39,16 +39,17 @@
 <!ENTITY history_today_section "Today">
 <!ENTITY history_yesterday_section "Yesterday">
 <!ENTITY history_week_section "7 days ago">
 <!ENTITY history_older_section "Older than 7 days">
 
 <!ENTITY reload "Reload">
 <!ENTITY forward "Forward">
 
+<!ENTITY close_tab "Close Tab">
 <!ENTITY new_tab "New Tab">
 <!ENTITY new_tab_opened "New tab opened">
 
 <!ENTITY settings "Settings">
 <!ENTITY settings_title "Settings">
 <!ENTITY pref_category_general "General">
 <!ENTITY pref_category_privacy "Privacy &amp; Security">
 <!ENTITY pref_category_content "Content">
--- a/mobile/android/base/resources/layout/tabs_row.xml
+++ b/mobile/android/base/resources/layout/tabs_row.xml
@@ -1,10 +1,12 @@
 <?xml version="1.0" encoding="utf-8"?>
 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:focusable="true"
+                android:nextFocusLeft="@+id/close"
                 android:id="@+id/info"
                 android:layout_width="fill_parent"
                 android:layout_height="100dip"
                 android:minHeight="100dip"
                 android:background="@drawable/tabs_tray_list_selector">
 
     <ImageView android:id="@+id/thumbnail"
                android:layout_width="136dip"
@@ -44,18 +46,20 @@
               android:maxLines="4"
               android:ellipsize="middle"
               android:shadowColor="#000000"
               android:shadowRadius="1"
               android:shadowDx="0"
               android:shadowDy="1"/>
 
     <ImageButton android:id="@+id/close"
+                 android:nextFocusRight="@+id/info"
                  android:layout_width="34dip"
                  android:layout_height="34dip"
                  android:layout_alignParentLeft="true"
                  android:layout_centerVertical="true"
                  android:background="@drawable/tabs_tray_close_button"
                  android:padding="10dip"
                  android:scaleType="centerInside"
+                 android:contentDescription="@string/close_tab"
                  android:src="@drawable/tab_close"/>
 
 </RelativeLayout>
--- a/mobile/android/base/resources/layout/tabs_tray.xml
+++ b/mobile/android/base/resources/layout/tabs_tray.xml
@@ -29,16 +29,17 @@
                           android:layout_width="60dip"
                           android:layout_height="50dip"
                           android:layout_alignParentLeft="true"
                           android:paddingTop="15dip"
                           android:paddingBottom="15dip"
                           android:paddingLeft="20dip"
                           android:paddingRight="20dip"
                           android:src="@drawable/tab_new"
+                          android:contentDescription="@string/new_tab"
                           android:background="@drawable/tabs_tray_list_selector"/>
 
              <org.mozilla.gecko.LinkTextView android:id="@+id/remote_tabs"
                                              android:layout_width="wrap_content"
                                              android:layout_height="50dip"
                                              android:layout_alignParentRight="true"
                                              android:gravity="center_vertical"
                                              android:paddingRight="20dip"
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -80,16 +80,17 @@
   <string name="pref_font_size_small">&pref_font_size_small;</string>
   <string name="pref_font_size_medium">&pref_font_size_medium;</string>
   <string name="pref_font_size_large">&pref_font_size_large;</string>
   <string name="pref_font_size_xlarge">&pref_font_size_xlarge;</string>
   <string name="pref_sync">&pref_sync;</string>
 
   <string name="reload">&reload;</string>
   <string name="forward">&forward;</string>
+  <string name="close_tab">&close_tab;</string>
   <string name="new_tab">&new_tab;</string>
   <string name="new_tab_opened">&new_tab_opened;</string>
   <string name="addons">&addons;</string>
   <string name="downloads">&downloads;</string>
   <string name="char_encoding">&char_encoding;</string>
   <!-- This string only appears in developer builds, which
        is why it is not localizable. -->
   <string name="toggle_profiling">Toggle Profiling</string>
--- a/mobile/android/base/sync/setup/SyncAccounts.java
+++ b/mobile/android/base/sync/setup/SyncAccounts.java
@@ -223,22 +223,35 @@ public class SyncAccounts {
       }
       editor.commit();
     } catch (Exception e) {
       Logger.error(LOG_TAG, "Could not clear prefs path!", e);
     }
     return account;
   }
 
-  protected static void setSyncAutomatically(Account account) {
-    ContentResolver.setMasterSyncAutomatically(true);
+  public static void setIsSyncable(Account account, boolean isSyncable) {
     String authority = BrowserContract.AUTHORITY;
-    Logger.debug(LOG_TAG, "Setting authority " + authority + " to sync automatically.");
-    ContentResolver.setSyncAutomatically(account, authority, true);
-    ContentResolver.setIsSyncable(account, authority, 1);
+    ContentResolver.setIsSyncable(account, authority, isSyncable ? 1 : 0);
+  }
+
+  public static void setSyncAutomatically(Account account, boolean syncAutomatically) {
+    if (syncAutomatically) {
+      ContentResolver.setMasterSyncAutomatically(true);
+    }
+
+    String authority = BrowserContract.AUTHORITY;
+    Logger.debug(LOG_TAG, "Setting authority " + authority + " to " +
+                          (syncAutomatically ? "" : "not ") + "sync automatically.");
+    ContentResolver.setSyncAutomatically(account, authority, syncAutomatically);
+  }
+
+  public static void setSyncAutomatically(Account account) {
+    setSyncAutomatically(account, true);
+    setIsSyncable(account, true);
   }
 
   protected static void setClientRecord(Context context, AccountManager accountManager, Account account,
       String clientName, String clientGuid) {
     if (clientName != null && clientGuid != null) {
       Logger.debug(LOG_TAG, "Setting client name to " + clientName + " and client GUID to " + clientGuid + ".");
       SyncAdapter.setAccountGUID(accountManager, account, clientGuid);
       SyncAdapter.setClientName(accountManager, account, clientName);
--- a/mobile/android/base/sync/syncadapter/SyncAdapter.java
+++ b/mobile/android/base/sync/syncadapter/SyncAdapter.java
@@ -3,31 +3,33 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.sync.syncadapter;
 
 import java.io.IOException;
 import java.net.URI;
 import java.security.NoSuchAlgorithmException;
 import java.util.concurrent.TimeUnit;
+
 import org.json.simple.parser.ParseException;
 import org.mozilla.gecko.sync.AlreadySyncingException;
 import org.mozilla.gecko.sync.GlobalConstants;
 import org.mozilla.gecko.sync.GlobalSession;
 import org.mozilla.gecko.sync.Logger;
 import org.mozilla.gecko.sync.NonObjectJSONException;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.SyncConfigurationException;
 import org.mozilla.gecko.sync.SyncException;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
 import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
 import org.mozilla.gecko.sync.net.ConnectionMonitorThread;
 import org.mozilla.gecko.sync.setup.Constants;
+import org.mozilla.gecko.sync.setup.SyncAccounts;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
 
 import android.accounts.Account;
 import android.accounts.AccountManager;
 import android.accounts.AccountManagerCallback;
 import android.accounts.AccountManagerFuture;
 import android.accounts.AuthenticatorException;
 import android.accounts.OperationCanceledException;
@@ -205,18 +207,23 @@ public class SyncAdapter extends Abstrac
   }
 
   @Override
   public void onPerformSync(final Account account,
                             final Bundle extras,
                             final String authority,
                             final ContentProviderClient provider,
                             final SyncResult syncResult) {
+
     Utils.reseedSharedRandom(); // Make sure we don't work with the same random seed for too long.
 
+    // Set these so that we don't need to thread them through assorted calls and callbacks.
+    this.syncResult   = syncResult;
+    this.localAccount = account;
+
     boolean force = (extras != null) && (extras.getBoolean("force", false));
     long delay = delayMilliseconds();
     if (delay > 0) {
       if (force) {
         Log.i(LOG_TAG, "Forced sync: overruling remaining backoff of " + delay + "ms.");
       } else {
         Log.i(LOG_TAG, "Not syncing: must wait another " + delay + "ms.");
         long remainingSeconds = delay / 1000;
@@ -248,28 +255,52 @@ public class SyncAdapter extends Abstrac
           String username  = bundle.getString(Constants.OPTION_USERNAME);
           String syncKey   = bundle.getString(Constants.OPTION_SYNCKEY);
           String serverURL = bundle.getString(Constants.OPTION_SERVER);
           String password  = bundle.getString(AccountManager.KEY_AUTHTOKEN);
           Log.d(LOG_TAG, "Username: " + username);
           Log.d(LOG_TAG, "Server:   " + serverURL);
           Log.d(LOG_TAG, "Password? " + (password != null));
           Log.d(LOG_TAG, "Key?      " + (syncKey != null));
+
+          if (password  == null &&
+              username  == null &&
+              syncKey   == null &&
+              serverURL == null) {
+
+            // Totally blank. Most likely the user has two copies of Firefox
+            // installed, and something is misbehaving.
+            // Disable this account.
+            Logger.error(LOG_TAG, "No credentials attached to account. Aborting sync.");
+            try {
+              SyncAccounts.setSyncAutomatically(account, false);
+            } catch (Exception e) {
+              Logger.error(LOG_TAG, "Unable to disable account " + account.name + " for " + authority + ".", e);
+            }
+            syncResult.stats.numAuthExceptions++;
+            localAccount = null;
+            notifyMonitor();
+            return;
+          }
+
+          // Now catch the individual cases.
           if (password == null) {
             Log.e(LOG_TAG, "No password: aborting sync.");
             syncResult.stats.numAuthExceptions++;
             notifyMonitor();
             return;
           }
+
           if (syncKey == null) {
             Log.e(LOG_TAG, "No Sync Key: aborting sync.");
             syncResult.stats.numAuthExceptions++;
             notifyMonitor();
             return;
           }
+
           KeyBundle keyBundle = new KeyBundle(username, syncKey);
 
           // Support multiple accounts by mapping each server/account pair to a branch of the
           // shared preferences space.
           String prefsPath = Utils.getPrefsPath(username, serverURL);
           self.performSync(account, extras, authority, provider, syncResult,
               username, password, prefsPath, serverURL, keyBundle);
         } catch (Exception e) {
@@ -307,16 +338,21 @@ public class SyncAdapter extends Abstrac
       } finally {
         // And we're done with HTTP stuff.
         stale.shutdown();
       }
     }
  }
 
   public int getSyncInterval() {
+    // Must have been a problem that means we can't access the Account.
+    if (this.localAccount == null) {
+      return SINGLE_DEVICE_INTERVAL_MILLISECONDS;
+    }
+
     int clientsCount = this.getClientsCount();
     if (clientsCount <= 1) {
       return SINGLE_DEVICE_INTERVAL_MILLISECONDS;
     }
 
     return MULTI_DEVICE_INTERVAL_MILLISECONDS;
   }
 
@@ -340,18 +376,16 @@ public class SyncAdapter extends Abstrac
                              String serverURL, KeyBundle keyBundle)
                                  throws NoSuchAlgorithmException,
                                         SyncConfigurationException,
                                         IllegalArgumentException,
                                         AlreadySyncingException,
                                         IOException, ParseException,
                                         NonObjectJSONException {
     Log.i(LOG_TAG, "Performing sync.");
-    this.syncResult   = syncResult;
-    this.localAccount = account;
 
     // TODO: default serverURL.
     GlobalSession globalSession = new GlobalSession(SyncConfiguration.DEFAULT_USER_API,
                                                     serverURL, username, password, prefsPath,
                                                     keyBundle, this, this.mContext, extras, this);
 
     globalSession.start();
   }
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -150,20 +150,16 @@ var Strings = {};
     return Services.strings.createBundle(bundle);
   });
 });
 
 var MetadataProvider = {
   getDrawMetadata: function getDrawMetadata() {
     return JSON.stringify(BrowserApp.selectedTab.getViewport());
   },
-
-  paintingSuppressed: function paintingSuppressed() {
-    return false;
-  }
 };
 
 var BrowserApp = {
   _tabs: [],
   _selectedTab: null,
 
   deck: null,
 
--- a/netwerk/cache/nsCacheService.h
+++ b/netwerk/cache/nsCacheService.h
@@ -132,18 +132,17 @@ public:
 
     /**
      * Methods called by any cache classes
      */
 
     static
     nsCacheService * GlobalInstance()   { return gService; }
 
-    static
-    PRInt64 MemoryDeviceSize();
+    static PRInt64   MemoryDeviceSize();
     
     static nsresult  DoomEntry(nsCacheEntry * entry);
 
     static bool      IsStorageEnabledForPolicy_Locked(nsCacheStoragePolicy policy);
 
     // This method may be called to release an object while the cache service
     // lock is being held.  If a non-null target is specified and the target
     // does not correspond to the current thread, then the release will be
--- a/netwerk/mime/nsMimeTypes.h
+++ b/netwerk/mime/nsMimeTypes.h
@@ -103,16 +103,18 @@
 #define APPLICATION_MATHML_XML              "application/mathml+xml"
 #define APPLICATION_RDF_XML                 "application/rdf+xml"
 
 #define AUDIO_BASIC                         "audio/basic"
 #define AUDIO_OGG                           "audio/ogg"
 #define AUDIO_WAV                           "audio/x-wav"
 #define AUDIO_WEBM                          "audio/webm"
 
+#define BINARY_OCTET_STREAM                 "binary/octet-stream"
+
 #define IMAGE_GIF                           "image/gif"
 #define IMAGE_JPG                           "image/jpeg"
 #define IMAGE_PJPG                          "image/pjpeg"
 #define IMAGE_PNG                           "image/png"
 #define IMAGE_PPM                           "image/x-portable-pixmap"
 #define IMAGE_XBM                           "image/x-xbitmap"
 #define IMAGE_XBM2                          "image/x-xbm"
 #define IMAGE_XBM3                          "image/xbm"
--- a/netwerk/protocol/http/nsHttpChannel.cpp
+++ b/netwerk/protocol/http/nsHttpChannel.cpp
@@ -4493,28 +4493,18 @@ nsHttpChannel::OnStopRequest(nsIRequest 
         }
 
         // if this transaction has been replaced, then bail.
         if (mTransactionReplaced)
             return NS_OK;
         
         if (mUpgradeProtocolCallback && stickyConn &&
             mResponseHead && mResponseHead->Status() == 101) {
-            nsCOMPtr<nsISocketTransport>    socketTransport;
-            nsCOMPtr<nsIAsyncInputStream>   socketIn;
-            nsCOMPtr<nsIAsyncOutputStream>  socketOut;
-
-            nsresult rv;
-            rv = stickyConn->TakeTransport(getter_AddRefs(socketTransport),
-                                           getter_AddRefs(socketIn),
-                                           getter_AddRefs(socketOut));
-            if (NS_SUCCEEDED(rv))
-                mUpgradeProtocolCallback->OnTransportAvailable(socketTransport,
-                                                               socketIn,
-                                                               socketOut);
+            gHttpHandler->ConnMgr()->CompleteUpgrade(stickyConn,
+                                                     mUpgradeProtocolCallback);
         }
     }
 
     mIsPending = false;
     mStatus = status;
 
     // perform any final cache operations before we close the cache entry.
     if (mCacheEntry && (mCacheAccess & nsICache::ACCESS_WRITE) &&
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -35,16 +35,17 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsHttpConnectionMgr.h"
 #include "nsHttpConnection.h"
 #include "nsHttpPipeline.h"
 #include "nsHttpHandler.h"
+#include "nsIHttpChannelInternal.h"
 #include "nsNetCID.h"
 #include "nsCOMPtr.h"
 #include "nsNetUtil.h"
 
 #include "nsIServiceManager.h"
 
 #include "nsIObserverService.h"
 
@@ -393,16 +394,42 @@ nsHttpConnectionMgr::ReclaimConnection(n
 
     NS_ADDREF(conn);
     nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgReclaimConnection, 0, conn);
     if (NS_FAILED(rv))
         NS_RELEASE(conn);
     return rv;
 }
 
+// A structure used to marshall 2 pointers across the various necessary
+// threads to complete an HTTP upgrade. 
+class nsCompleteUpgradeData
+{
+public:
+nsCompleteUpgradeData(nsAHttpConnection *aConn,
+                      nsIHttpUpgradeListener *aListener)
+    : mConn(aConn), mUpgradeListener(aListener) {}
+        
+    nsRefPtr<nsAHttpConnection> mConn;
+    nsCOMPtr<nsIHttpUpgradeListener> mUpgradeListener;
+};
+
+nsresult
+nsHttpConnectionMgr::CompleteUpgrade(nsAHttpConnection *aConn,
+                                     nsIHttpUpgradeListener *aUpgradeListener)
+{
+    nsCompleteUpgradeData *data =
+        new nsCompleteUpgradeData(aConn, aUpgradeListener);
+    nsresult rv;
+    rv = PostEvent(&nsHttpConnectionMgr::OnMsgCompleteUpgrade, 0, data);
+    if (NS_FAILED(rv))
+        delete data;
+    return rv;
+}
+    
 nsresult
 nsHttpConnectionMgr::UpdateParam(nsParamName name, PRUint16 value)
 {
     PRUint32 param = (PRUint32(name) << 16) | PRUint32(value);
     return PostEvent(&nsHttpConnectionMgr::OnMsgUpdateParam, 0, (void *) param);
 }
 
 nsresult
@@ -1992,16 +2019,41 @@ nsHttpConnectionMgr::OnMsgReclaimConnect
         }
     }
  
     OnMsgProcessPendingQ(NS_OK, ci); // releases |ci|
     NS_RELEASE(conn);
 }
 
 void
+nsHttpConnectionMgr::OnMsgCompleteUpgrade(PRInt32, void *param)
+{
+    NS_ABORT_IF_FALSE(PR_GetCurrentThread() == gSocketThread, "wrong thread");
+    nsCompleteUpgradeData *data = (nsCompleteUpgradeData *) param;
+    LOG(("nsHttpConnectionMgr::OnMsgCompleteUpgrade "
+         "this=%p conn=%p listener=%p\n", this, data->mConn.get(),
+         data->mUpgradeListener.get()));
+
+    nsCOMPtr<nsISocketTransport> socketTransport;
+    nsCOMPtr<nsIAsyncInputStream> socketIn;
+    nsCOMPtr<nsIAsyncOutputStream> socketOut;
+
+    nsresult rv;
+    rv = data->mConn->TakeTransport(getter_AddRefs(socketTransport),
+                                    getter_AddRefs(socketIn),
+                                    getter_AddRefs(socketOut));
+
+    if (NS_SUCCEEDED(rv))
+        data->mUpgradeListener->OnTransportAvailable(socketTransport,
+                                                     socketIn,
+                                                     socketOut);
+    delete data;
+}
+
+void
 nsHttpConnectionMgr::OnMsgUpdateParam(PRInt32, void *param)
 {
     PRUint16 name  = (NS_PTR_TO_INT32(param) & 0xFFFF0000) >> 16;
     PRUint16 value =  NS_PTR_TO_INT32(param) & 0x0000FFFF;
 
     switch (name) {
     case MAX_CONNECTIONS:
         mMaxConns = value;
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -53,16 +53,18 @@
 #include "mozilla/TimeStamp.h"
 
 #include "nsIObserver.h"
 #include "nsITimer.h"
 #include "nsIX509Cert3.h"
 
 class nsHttpPipeline;
 
+class nsIHttpUpgradeListener;
+
 //-----------------------------------------------------------------------------
 
 class nsHttpConnectionMgr : public nsIObserver
 {
 public:
     NS_DECL_ISUPPORTS
     NS_DECL_NSIOBSERVER
 
@@ -142,16 +144,23 @@ public:
                                 nsIInterfaceRequestor *,
                                 nsIEventTarget *);
 
     // called when a connection is done processing a transaction.  if the 
     // connection can be reused then it will be added to the idle list, else
     // it will be closed.
     nsresult ReclaimConnection(nsHttpConnection *conn);
 
+    // called by the main thread to execute the taketransport() logic on the
+    // socket thread after a 101 response has been received and the socket
+    // needs to be transferred to an expectant upgrade listener such as
+    // websockets.
+    nsresult CompleteUpgrade(nsAHttpConnection *aConn,
+                             nsIHttpUpgradeListener *aUpgradeListener);
+
     // called to update a parameter after the connection manager has already
     // been initialized.
     nsresult UpdateParam(nsParamName name, PRUint16 value);
 
     // Lookup/Cancel HTTP->SPDY redirections
     bool GetSpdyAlternateProtocol(nsACString &key);
     void ReportSpdyAlternateProtocol(nsHttpConnection *);
     void RemoveSpdyAlternateProtocol(nsACString &key);
@@ -573,16 +582,17 @@ private:
     void OnMsgShutdown             (PRInt32, void *);
     void OnMsgNewTransaction       (PRInt32, void *);
     void OnMsgReschedTransaction   (PRInt32, void *);
     void OnMsgCancelTransaction    (PRInt32, void *);
     void OnMsgProcessPendingQ      (PRInt32, void *);
     void OnMsgPruneDeadConnections (PRInt32, void *);
     void OnMsgSpeculativeConnect   (PRInt32, void *);
     void OnMsgReclaimConnection    (PRInt32, void *);
+    void OnMsgCompleteUpgrade      (PRInt32, void *);
     void OnMsgUpdateParam          (PRInt32, void *);
     void OnMsgClosePersistentConnections (PRInt32, void *);
     void OnMsgProcessFeedback      (PRInt32, void *);
 
     // Total number of active connections in all of the ConnectionEntry objects
     // that are accessed from mCT connection table.
     PRUint16 mNumActiveConns;
     // Total number of idle connections in all of the ConnectionEntry objects
--- a/netwerk/protocol/websocket/WebSocketChannel.cpp
+++ b/netwerk/protocol/websocket/WebSocketChannel.cpp
@@ -229,16 +229,50 @@ private:
   ~CallAcknowledge() {}
 
   nsRefPtr<WebSocketChannel>        mChannel;
   PRUint32                          mSize;
 };
 NS_IMPL_THREADSAFE_ISUPPORTS1(CallAcknowledge, nsIRunnable)
 
 //-----------------------------------------------------------------------------
+// CallOnTransportAvailable
+//-----------------------------------------------------------------------------
+
+class CallOnTransportAvailable : public nsIRunnable
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  CallOnTransportAvailable(WebSocketChannel *aChannel,
+                           nsISocketTransport *aTransport,
+                           nsIAsyncInputStream *aSocketIn,
+                           nsIAsyncOutputStream *aSocketOut)
+    : mChannel(aChannel),
+      mTransport(aTransport),
+      mSocketIn(aSocketIn),
+      mSocketOut(aSocketOut) {}
+
+  NS_IMETHOD Run()
+  {
+    LOG(("WebSocketChannel::CallOnTransportAvailable %p\n", this));
+    return mChannel->OnTransportAvailable(mTransport, mSocketIn, mSocketOut);
+  }
+
+private:
+  ~CallOnTransportAvailable() {}
+
+  nsRefPtr<WebSocketChannel>     mChannel;
+  nsCOMPtr<nsISocketTransport>   mTransport;
+  nsCOMPtr<nsIAsyncInputStream>  mSocketIn;
+  nsCOMPtr<nsIAsyncOutputStream> mSocketOut;
+};
+NS_IMPL_THREADSAFE_ISUPPORTS1(CallOnTransportAvailable, nsIRunnable)
+
+//-----------------------------------------------------------------------------
 // OutboundMessage
 //-----------------------------------------------------------------------------
 
 enum WsMsgType {
   kMsgTypeString = 0,
   kMsgTypeBinaryString,
   kMsgTypeStream,
   kMsgTypePing,
@@ -688,16 +722,17 @@ WebSocketChannel::WebSocketChannel() :
   mPingOutstanding(0),
   mAllowCompression(1),
   mAutoFollowRedirects(0),
   mReleaseOnTransmit(0),
   mTCPClosed(0),
   mOpenBlocked(0),
   mOpenRunning(0),
   mChannelWasOpened(0),
+  mDataStarted(0),
   mMaxMessageSize(PR_INT32_MAX),
   mStopOnClose(NS_OK),
   mServerCloseCode(CLOSE_ABNORMAL),
   mScriptCloseCode(0),
   mFragmentOpcode(kContinuation),
   mFragmentAccumulator(0),
   mBuffered(0),
   mBufferSize(kIncomingBufferInitialSize),
@@ -1875,16 +1910,24 @@ WebSocketChannel::ApplyForAdmission()
 
 // Called after both OnStartRequest and OnTransportAvailable have
 // executed. This essentially ends the handshake and starts the websockets
 // protocol state machine.
 nsresult
 WebSocketChannel::StartWebsocketData()
 {
   LOG(("WebSocketChannel::StartWebsocketData() %p", this));
+  NS_ABORT_IF_FALSE(!mDataStarted, "StartWebsocketData twice");
+  mDataStarted = 1;
+  
+  LOG(("WebSocketChannel::StartWebsocketData Notifying Listener %p\n",
+       mListener.get()));
+
+  if (mListener)
+    mListener->OnStart(mContext);
 
   return mSocketIn->AsyncWait(this, 0, 0, mSocketThread);
 }
 
 // nsIDNSListener
 
 NS_IMETHODIMP
 WebSocketChannel::OnLookupComplete(nsICancelable *aRequest,
@@ -2379,16 +2422,23 @@ WebSocketChannel::SendMsgCommon(const ns
     nsIEventTarget::DISPATCH_NORMAL);
 }
 
 NS_IMETHODIMP
 WebSocketChannel::OnTransportAvailable(nsISocketTransport *aTransport,
                                        nsIAsyncInputStream *aSocketIn,
                                        nsIAsyncOutputStream *aSocketOut)
 {
+  if (!NS_IsMainThread()) {
+    return NS_DispatchToMainThread(new CallOnTransportAvailable(this,
+                                                                aTransport,
+                                                                aSocketIn,
+                                                                aSocketOut));
+  }
+
   LOG(("WebSocketChannel::OnTransportAvailable %p [%p %p %p] rcvdonstart=%d\n",
        this, aTransport, aSocketIn, aSocketOut, mRecvdHttpOnStartRequest));
 
   NS_ABORT_IF_FALSE(NS_IsMainThread(), "not main thread");
   NS_ABORT_IF_FALSE(!mRecvdHttpUpgradeTransport, "OTA duplicated");
   NS_ABORT_IF_FALSE(aSocketIn, "OTA with invalid socketIn");
 
   mTransport = aTransport;
@@ -2554,22 +2604,16 @@ WebSocketChannel::OnStartRequest(nsIRequ
       mProtocol.Truncate();
     }
   }
 
   rv = HandleExtensions();
   if (NS_FAILED(rv))
     return rv;
 
-  LOG(("WebSocketChannel::OnStartRequest: Notifying Listener %p\n",
-       mListener.get()));
-
-  if (mListener)
-    mListener->OnStart(mContext);
-
   mRecvdHttpOnStartRequest = 1;
   if (mRecvdHttpUpgradeTransport)
     return StartWebsocketData();
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/netwerk/protocol/websocket/WebSocketChannel.h
+++ b/netwerk/protocol/websocket/WebSocketChannel.h
@@ -213,16 +213,17 @@ private:
   PRUint32                        mPingOutstanding           : 1;
   PRUint32                        mAllowCompression          : 1;
   PRUint32                        mAutoFollowRedirects       : 1;
   PRUint32                        mReleaseOnTransmit         : 1;
   PRUint32                        mTCPClosed                 : 1;
   PRUint32                        mOpenBlocked               : 1;
   PRUint32                        mOpenRunning               : 1;
   PRUint32                        mChannelWasOpened          : 1;
+  PRUint32                        mDataStarted               : 1;
 
   PRInt32                         mMaxMessageSize;
   nsresult                        mStopOnClose;
   PRUint16                        mServerCloseCode;
   nsCString                       mServerCloseReason;
   PRUint16                        mScriptCloseCode;
   nsCString                       mScriptCloseReason;
 
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_spdy.js
@@ -0,0 +1,344 @@
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+// Generate a small and a large post with known pre-calculated md5 sums
+function generateContent(size) {
+  var content = "";
+  for (var i = 0; i < size; i++) {
+    content += "0";
+  }
+  return content;
+}
+
+var posts = [];
+posts.push(generateContent(10));
+posts.push(generateContent(128 * 1024));
+
+// pre-calculated md5sums (in hex) of the above posts
+var md5s = ['f1b708bba17f1ce948dc979f4d7092bc',
+            '8f607cfdd2c87d6a7eedb657dafbd836'];
+
+function checkIsSpdy(request) {
+  try {
+    if (request.getResponseHeader("X-Firefox-Spdy") == "1") {
+      if (request.getResponseHeader("X-Connection-Spdy") == "yes") {
+        return true;
+      }
+      return false; // Weird case, but the server disagrees with us
+    }
+  } catch (e) {
+    // Nothing to do here
+  }
+  return false;
+}
+
+var SpdyCheckListener = function() {};
+
+SpdyCheckListener.prototype = {
+  onStartRequestFired: false,
+  onDataAvailableFired: false,
+  isSpdyConnection: false,
+
+  onStartRequest: function testOnStartRequest(request, ctx) {
+    this.onStartRequestFired = true;
+
+    if (!Components.isSuccessCode(request.status))
+      do_throw("Channel should have a success code! (" + request.status + ")");
+    if (!(request instanceof Components.interfaces.nsIHttpChannel))
+      do_throw("Expecting an HTTP channel");
+
+    do_check_eq(request.responseStatus, 200);
+    do_check_eq(request.requestSucceeded, true);
+  },
+
+  onDataAvailable: function testOnDataAvailable(request, ctx, stream, off, cnt) {
+    this.onDataAvailableFired = true;
+    this.isSpdyConnection = checkIsSpdy(request);
+
+    read_stream(stream, cnt);
+  },
+
+  onStopRequest: function testOnStopRequest(request, ctx, status) {
+    do_check_true(this.onStartRequestFired);
+    do_check_true(this.onDataAvailableFired);
+    do_check_true(this.isSpdyConnection);
+
+    run_next_test();
+    do_test_finished();
+  }
+};
+
+/*
+ * Support for testing valid multiplexing of streams
+ */
+
+var multiplexContent = generateContent(30*1024);
+var completed_channels = [];
+function register_completed_channel(listener) {
+  completed_channels.push(listener);
+  if (completed_channels.length == 2) {
+    do_check_neq(completed_channels[0].streamID, completed_channels[1].streamID);
+    run_next_test();
+    do_test_finished();
+  }
+}
+
+/* Listener class to control the testing of multiplexing */
+var SpdyMultiplexListener = function() {};
+
+SpdyMultiplexListener.prototype = new SpdyCheckListener();
+
+SpdyMultiplexListener.prototype.streamID = 0;
+SpdyMultiplexListener.prototype.buffer = "";
+
+SpdyMultiplexListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
+  this.onDataAvailableFired = true;
+  this.isSpdyConnection = checkIsSpdy(request);
+  this.streamID = parseInt(request.getResponseHeader("X-Spdy-StreamID"));
+  var data = read_stream(stream, cnt);
+  this.buffer = this.buffer.concat(data);
+};
+
+SpdyMultiplexListener.prototype.onStopRequest = function(request, ctx, status) {
+  do_check_true(this.onStartRequestFired);
+  do_check_true(this.onDataAvailableFired);
+  do_check_true(this.isSpdyConnection);
+  do_check_true(this.buffer == multiplexContent);
+  
+  // This is what does most of the hard work for us
+  register_completed_channel(this);
+};
+
+// Does the appropriate checks for header gatewaying
+var SpdyHeaderListener = function(value) {
+  this.value = value
+};
+
+SpdyHeaderListener.prototype = new SpdyCheckListener();
+SpdyHeaderListener.prototype.value = "";
+
+SpdyHeaderListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
+  this.onDataAvailableFired = true;
+  this.isSpdyConnection = checkIsSpdy(request);
+  do_check_eq(request.getResponseHeader("X-Received-Test-Header"), this.value);
+  read_stream(stream, cnt);
+};
+
+// Does the appropriate checks for a large GET response
+var SpdyBigListener = function() {};
+
+SpdyBigListener.prototype = new SpdyCheckListener();
+SpdyBigListener.prototype.buffer = "";
+
+SpdyBigListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
+  this.onDataAvailableFired = true;
+  this.isSpdyConnection = checkIsSpdy(request);
+  this.buffer = this.buffer.concat(read_stream(stream, cnt));
+  // We know the server should send us the same data as our big post will be,
+  // so the md5 should be the same
+  do_check_eq(md5s[1], request.getResponseHeader("X-Expected-MD5"));
+};
+
+SpdyBigListener.prototype.onStopRequest = function(request, ctx, status) {
+  do_check_true(this.onStartRequestFired);
+  do_check_true(this.onDataAvailableFired);
+  do_check_true(this.isSpdyConnection);
+
+  // Don't want to flood output, so don't use do_check_eq
+  do_check_true(this.buffer == posts[1]);
+
+  run_next_test();
+  do_test_finished();
+};
+
+// Does the appropriate checks for POSTs
+var SpdyPostListener = function(expected_md5) {
+  this.expected_md5 = expected_md5;
+};
+
+SpdyPostListener.prototype = new SpdyCheckListener();
+SpdyPostListener.prototype.expected_md5 = "";
+
+SpdyPostListener.prototype.onDataAvailable = function(request, ctx, stream, off, cnt) {
+  this.onDataAvailableFired = true;
+  this.isSpdyConnection = checkIsSpdy(request);
+  read_stream(stream, cnt);
+  do_check_eq(this.expected_md5, request.getResponseHeader("X-Calculated-MD5"));
+};
+
+function makeChan(url) {
+  var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+  var chan = ios.newChannel(url, null, null).QueryInterface(Ci.nsIHttpChannel);
+
+  return chan;
+}
+
+// Make sure we make a spdy connection and both us and the server mark it as such
+function test_spdy_basic() {
+  var chan = makeChan("https://localhost:4443/");
+  var listener = new SpdyCheckListener();
+  chan.asyncOpen(listener, null);
+}
+
+// Support for making sure XHR works over SPDY
+function checkXhr(xhr) {
+  if (xhr.readyState != 4) {
+    return;
+  }
+
+  do_check_eq(xhr.status, 200);
+  do_check_eq(checkIsSpdy(xhr), true);
+  run_next_test();
+  do_test_finished();
+}
+
+// Fires off an XHR request over SPDY
+function test_spdy_xhr() {
+  var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+            .createInstance(Ci.nsIXMLHttpRequest);
+  req.open("GET", "https://localhost:4443/", true);
+  req.addEventListener("readystatechange", function (evt) { checkXhr(req); },
+                       false);
+  req.send(null);
+}
+
+// Test to make sure we get multiplexing right
+function test_spdy_multiplex() {
+  var chan1 = makeChan("https://localhost:4443/multiplex1");
+  var chan2 = makeChan("https://localhost:4443/multiplex2");
+  var listener1 = new SpdyMultiplexListener();
+  var listener2 = new SpdyMultiplexListener();
+  chan1.asyncOpen(listener1, null);
+  chan2.asyncOpen(listener2, null);
+}
+
+// Test to make sure we gateway non-standard headers properly
+function test_spdy_header() {
+  var chan = makeChan("https://localhost:4443/header");
+  var hvalue = "Headers are fun";
+  var listener = new SpdyHeaderListener(hvalue);
+  chan.setRequestHeader("X-Test-Header", hvalue, false);
+  chan.asyncOpen(listener, null);
+}
+
+// Make sure we handle GETs that cover more than 2 frames properly
+function test_spdy_big() {
+  var chan = makeChan("https://localhost:4443/big");
+  var listener = new SpdyBigListener();
+  chan.asyncOpen(listener, null);
+}
+
+// Support for doing a POST
+function do_post(content, chan, listener) {
+  var stream = Cc["@mozilla.org/io/string-input-stream;1"]
+               .createInstance(Ci.nsIStringInputStream);
+  stream.data = content;
+
+  var uchan = chan.QueryInterface(Ci.nsIUploadChannel);
+  uchan.setUploadStream(stream, "text/plain", stream.available());
+
+  chan.requestMethod = "POST";
+
+  chan.asyncOpen(listener, null);
+}
+
+// Make sure we can do a simple POST
+function test_spdy_post() {
+  var chan = makeChan("https://localhost:4443/post");
+  var listener = new SpdyPostListener(md5s[0]);
+  do_post(posts[0], chan, listener);
+}
+
+// Make sure we can do a POST that covers more than 2 frames
+function test_spdy_post_big() {
+  var chan = makeChan("https://localhost:4443/post");
+  var listener = new SpdyPostListener(md5s[1]);
+  do_post(posts[1], chan, listener);
+}
+
+var tests = [ test_spdy_basic
+            , test_spdy_xhr
+            , test_spdy_multiplex
+            , test_spdy_header
+            , test_spdy_big
+            , test_spdy_post
+            , test_spdy_post_big
+            ];
+var current_test = 0;
+
+function run_next_test() {
+  if (current_test < tests.length) {
+    tests[current_test]();
+    current_test++;
+    do_test_pending();
+  }
+}
+
+// Support for making sure we can talk to the invalid cert the server presents
+var CertOverrideListener = function(host, port, bits) {
+  this.host = host;
+  if (port) {
+    this.port = port;
+  }
+  this.bits = bits;
+};
+
+CertOverrideListener.prototype = {
+  host: null,
+  port: -1,
+  bits: null,
+
+  getInterface: function(aIID) {
+    return this.QueryInterface(aIID);
+  },
+
+  QueryInterface: function(aIID) {
+    if (aIID.equals(Ci.nsIBadCertListener2) ||
+        aIID.equals(Ci.nsIInterfaceRequestor) ||
+        aIID.equals(Ci.nsISupports))
+      return this;
+    throw Components.results.NS_ERROR_NO_INTERFACE;
+  },
+
+  notifyCertProblem: function(socketInfo, sslStatus, targetHost) {
+    var cert = sslStatus.QueryInterface(Ci.nsISSLStatus).serverCert;
+    var cos = Cc["@mozilla.org/security/certoverride;1"].
+              getService(Ci.nsICertOverrideService);
+    cos.rememberValidityOverride(this.host, this.port, cert, this.bits, false);
+    return true;
+  },
+};
+
+function addCertOverride(host, port, bits) {
+  var req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+            .createInstance(Ci.nsIXMLHttpRequest);
+  try {
+    var url;
+    if (port) {
+      url = "https://" + host + ":" + port + "/";
+    } else {
+      url = "https://" + host + "/";
+    }
+    req.open("GET", url, false);
+    req.channel.notificationCallbacks = new CertOverrideListener(host, port, bits);
+    req.send(null);
+  } catch (e) {
+    // This will fail since the server is not trusted yet
+  }
+}
+
+function run_test() {
+  // Set to allow the cert presented by our SPDY server
+  do_get_profile();
+  addCertOverride("localhost", 4443,
+                  Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+                  Ci.nsICertOverrideService.ERROR_MISMATCH |
+                  Ci.nsICertOverrideService.ERROR_TIME);
+
+  // Make sure spdy is enabled
+  var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+  prefs.setBoolPref("network.http.spdy.enabled", true);
+
+  // And make go!
+  run_next_test();
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -169,16 +169,19 @@ skip-if = os == "win"
 [test_resumable_channel.js]
 [test_resumable_truncate.js]
 [test_safeoutputstream.js]
 [test_simple.js]
 [test_sockettransportsvc_available.js]
 [test_socks.js]
 # Bug 675039: test hangs consistently on Android
 skip-if = os == "android"
+[test_spdy.js]
+# spdy unit tests require us to have node available to run the spdy server
+run-if = hasNode
 [test_speculative_connect.js]
 [test_standardurl.js]
 [test_standardurl_port.js]
 [test_streamcopier.js]
 [test_traceable_channel.js]
 [test_unescapestring.js]
 [test_xmlhttprequest.js]
 [test_XHR_redirects.js]
--- a/parser/htmlparser/src/nsDTDUtils.cpp
+++ b/parser/htmlparser/src/nsDTDUtils.cpp
@@ -789,17 +789,17 @@ void nsDTDContext::ReleaseGlobalObjects(
 
 
 /**************************************************************
   Now define the nsTokenAllocator class...
  **************************************************************/
 
 static const size_t  kTokenBuckets[]       ={sizeof(CStartToken),sizeof(CAttributeToken),sizeof(CCommentToken),sizeof(CEndToken)};
 static const PRInt32 kNumTokenBuckets      = sizeof(kTokenBuckets) / sizeof(size_t);
-static const PRInt32 kInitialTokenPoolSize = NS_SIZE_IN_HEAP(sizeof(CToken)) * 200;
+static const PRInt32 kInitialTokenPoolSize = sizeof(CToken) * 200;
 
 /**
  * 
  * @update  gess7/25/98
  * @param 
  */
 nsTokenAllocator::nsTokenAllocator() {
 
@@ -950,17 +950,17 @@ void RemoveNode(nsCParserNode *aNode) {
 #ifdef HEAP_ALLOCATED_NODES
 nsNodeAllocator::nsNodeAllocator():mSharedNodes(0){
 #ifdef DEBUG_TRACK_NODES
   mCount=0;
 #endif
 #else 
   static const size_t  kNodeBuckets[]       = { sizeof(nsCParserNode), sizeof(nsCParserStartNode) };
   static const PRInt32 kNumNodeBuckets      = sizeof(kNodeBuckets) / sizeof(size_t);
-  static const PRInt32 kInitialNodePoolSize = NS_SIZE_IN_HEAP(sizeof(nsCParserNode)) * 35; // optimal size based on space-trace data
+  static const PRInt32 kInitialNodePoolSize = sizeof(nsCParserNode) * 35; // optimal size based on space-trace data
 nsNodeAllocator::nsNodeAllocator() {
   mNodePool.Init("NodePool", kNodeBuckets, kNumNodeBuckets, kInitialNodePoolSize);
 #endif
   MOZ_COUNT_CTOR(nsNodeAllocator);
 }
   
 nsNodeAllocator::~nsNodeAllocator() {
   MOZ_COUNT_DTOR(nsNodeAllocator);
--- a/startupcache/StartupCache.cpp
+++ b/startupcache/StartupCache.cpp
@@ -90,40 +90,38 @@ namespace scache {
 static PRInt64
 GetStartupCacheMappingSize()
 {
     mozilla::scache::StartupCache* sc = mozilla::scache::StartupCache::GetSingleton();
     return sc ? sc->SizeOfMapping() : 0;
 }
 
 NS_MEMORY_REPORTER_IMPLEMENT(StartupCacheMapping,
-                             "explicit/startup-cache/mapping",
-                             KIND_NONHEAP,
-                             nsIMemoryReporter::UNITS_BYTES,
-                             GetStartupCacheMappingSize,
-                             "Memory used to hold the mapping of the startup "
-                             "cache from file.  This memory is likely to be "
-                             "swapped out shortly after start-up.")
+    "explicit/startup-cache/mapping",
+    KIND_NONHEAP,
+    nsIMemoryReporter::UNITS_BYTES,
+    GetStartupCacheMappingSize,
+    "Memory used to hold the mapping of the startup cache from file.  This "
+    "memory is likely to be swapped out shortly after start-up.")
 
 NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(StartupCacheDataMallocSizeOf, "startup-cache/data")
 
 static PRInt64
 GetStartupCacheDataSize()
 {
     mozilla::scache::StartupCache* sc = mozilla::scache::StartupCache::GetSingleton();
     return sc ? sc->HeapSizeOfIncludingThis(StartupCacheDataMallocSizeOf) : 0;
 }
 
 NS_MEMORY_REPORTER_IMPLEMENT(StartupCacheData,
-                             "explicit/startup-cache/data",
-                             KIND_HEAP,
-                             nsIMemoryReporter::UNITS_BYTES,
-                             GetStartupCacheDataSize,
-                             "Memory used by the startup cache for things "
-                             "other than the file mapping.")
+    "explicit/startup-cache/data",
+    KIND_HEAP,
+    nsIMemoryReporter::UNITS_BYTES,
+    GetStartupCacheDataSize,
+    "Memory used by the startup cache for things other than the file mapping.")
 
 static const char sStartupCacheName[] = "startupCache." SC_WORDSIZE "." SC_ENDIAN;
 static NS_DEFINE_CID(kZipReaderCID, NS_ZIPREADER_CID);
 
 StartupCache*
 StartupCache::GetSingleton() 
 {
   if (!gStartupCache)
--- a/testing/xpcshell/Makefile.in
+++ b/testing/xpcshell/Makefile.in
@@ -51,16 +51,18 @@ TEST_DIRS += example
 
 include $(topsrcdir)/config/rules.mk
 
 # Harness files from the srcdir
 TEST_HARNESS_FILES := \
   runxpcshelltests.py \
   remotexpcshelltests.py \
   head.js \
+  node-spdy \
+  moz-spdy \
   $(NULL)
 
 # Extra files needed from $(topsrcdir)/build
 EXTRA_BUILD_FILES := \
   automationutils.py \
   manifestparser.py \
   $(NULL)
 
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/moz-spdy/README.txt
@@ -0,0 +1,14 @@
+Test server for SPDY unit tests. To run it, you need node >= 0.7.0 (not provided)
+and node-spdy (provided). Just run
+
+node /path/to/moz-spdy.js
+
+And you will get a SPDY server listening on port 4443, then you can run the
+xpcshell unit tests in netwerk/test/unit/test_spdy.js
+
+*** A NOTE ON TLS CERTIFICATES ***
+
+The certificates used for this test (*.pem in this directory) are the ones
+provided as examples by node-spdy, and are copied directly from keys/ under
+its top-level source directory (slightly renamed to match the option names
+in the options dictionary passed to spdy.createServer).
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/moz-spdy/moz-spdy.js
@@ -0,0 +1,145 @@
+var spdy = require('../node-spdy/lib/spdy.js');
+var fs = require('fs');
+var url = require('url');
+var crypto = require('crypto');
+
+function getHttpContent(path) {
+  var content = '<!doctype html>' +
+                '<html>' +
+                '<head><title>HOORAY!</title></head>' +
+                '<body>You Win! (by requesting' + path + ')</body>' +
+                '</html>';
+  return content;
+}
+
+function getHugeContent(size) {
+  var content = '';
+
+  for (var i = 0; i < size; i++) {
+    content += '0';
+  }
+
+  return content;
+}
+
+/* This takes care of responding to the multiplexed request for us */
+var Multiplex = function() {};
+
+Multiplex.prototype = {
+  mp1res: null,
+  mp2res: null,
+  buf: null,
+  mp1start: 0,
+  mp2start: 0,
+
+  checkReady: function() {
+    if (this.mp1res != null && this.mp2res != null) {
+      this.buf = getHugeContent(30*1024);
+      this.mp1start = 0;
+      this.mp2start = 0;
+      this.send(this.mp1res, 0);
+      setTimeout(function() { this.send(this.mp2res, 0); }.bind(this), 5);
+    }
+  },
+
+  send: function(res, start) {
+    var end = start + 1024;
+    if (end > this.buf.length)
+      end = this.buf.length;
+    var content = this.buf.substring(start, end);
+    if (end < this.buf.length) {
+      res.write(content);
+      setTimeout(function() { this.send(res, end); }.bind(this), 10);
+    } else {
+      res.end(content);
+    }
+  },
+};
+
+var m = new Multiplex();
+
+var post_hash = null;
+function receivePostData(chunk) {
+  post_hash.update(chunk.toString());
+}
+
+function finishPost(res, content) {
+  var md5 = post_hash.digest('hex');
+  res.setHeader('X-Calculated-MD5', md5);
+  res.writeHead(200);
+  res.end(content);
+}
+
+function handleRequest(req, res) {
+  var u = url.parse(req.url);
+  var content = getHttpContent(u.pathname);
+
+  if (req.streamID) {
+    res.setHeader('X-Connection-Spdy', 'yes');
+    res.setHeader('X-Spdy-StreamId', '' + req.streamID);
+  } else {
+    res.setHeader('X-Connection-Spdy', 'no');
+  }
+
+  if (u.pathname == '/exit') {
+    res.setHeader('Content-Type', 'text/plain');
+    res.writeHead(200);
+    res.end('ok');
+    process.exit();
+  } else if (u.pathname == '/multiplex1' && req.streamID) {
+    res.setHeader('Content-Type', 'text/plain');
+    res.writeHead(200);
+    m.mp1res = res;
+    m.checkReady();
+    return;
+  } else if (u.pathname == '/multiplex2' && req.streamID) {
+    res.setHeader('Content-Type', 'text/plain');
+    res.writeHead(200);
+    m.mp2res = res;
+    m.checkReady();
+    return;
+  } else if (u.pathname == "/header") {
+    var val = req.headers["x-test-header"];
+    if (val) {
+      res.setHeader("X-Received-Test-Header", val);
+    }
+  } else if (u.pathname == "/big") {
+    content = getHugeContent(128 * 1024);
+    var hash = crypto.createHash('md5');
+    hash.update(content);
+    var md5 = hash.digest('hex');
+    res.setHeader("X-Expected-MD5", md5);
+  } else if (u.pathname == "/post") {
+    if (req.method != "POST") {
+      res.writeHead(405);
+      res.end('Unexpected method: ' + req.method);
+      return;
+    }
+
+    post_hash = crypto.createHash('md5');
+    req.on('data', receivePostData);
+    req.on('end', function () { finishPost(res, content); });
+
+    return;
+  }
+
+  res.setHeader('Content-Type', 'text/html');
+  res.writeHead(200);
+  res.end(content);
+}
+
+// Set up the SSL certs for our server
+var options = {
+  key: fs.readFileSync(__dirname + '/spdy-key.pem'),
+  cert: fs.readFileSync(__dirname + '/spdy-cert.pem'),
+  ca: fs.readFileSync(__dirname + '/spdy-ca.pem'),
+};
+
+spdy.createServer(options, handleRequest).listen(4443);
+console.log('SPDY server listening on port 4443');
+
+// Set up to exit when the user finishes our stdin
+process.stdin.resume();
+process.stdin.on('end', function () {
+  process.exit();
+});
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/moz-spdy/spdy-ca.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBkzCB/QIBADBUMQswCQYDVQQGEwJSVTETMBEGA1UECBMKU29tZS1TdGF0ZTEN
+MAsGA1UEBxMET21zazEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF
+3/Gnld/wcOE9J/M01y0RFiyVdPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+je
+i+mA1ZqyKXnMANZozVPSedXRGwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+
+A3vk+3j3hjJjIm79rwIDAQABoAAwDQYJKoZIhvcNAQEFBQADgYEAiNWhz6EppIVa
+FfUaB3sLeqfamb9tg9kBHtvqj/FJni0snqms0kPWaTySEPHZF0irIb7VVdq/sVCb
+3gseMVSyoDvPJ4lHC3PXqGQ7kM1mIPhDnR/4HDA3BhlGhTXSDIHgZnvI+HMBdsyC
+hC3dz5odyKqe4nmoofomALkBL9t4H8s=
+-----END CERTIFICATE REQUEST-----
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/moz-spdy/spdy-cert.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICHzCCAYgCCQCPPSUAa8QZojANBgkqhkiG9w0BAQUFADBUMQswCQYDVQQGEwJS
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTENMAsGA1UEBxMET21zazEhMB8GA1UEChMY
+SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTExMDQwOTEwMDY0NVoXDTExMDUw
+OTEwMDY0NVowVDELMAkGA1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUxDTAL
+BgNVBAcTBE9tc2sxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCB
+nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1bn25sPkv46wl70BffxradlkRd/x
+p5Xf8HDhPSfzNNctERYslXT2fX7Dmfd5w1XTVqqGqJ4izp5VewoVOHA8uavo3ovp
+gNWasil5zADWaM1T0nnV0RsFbZWzOTmm1U3D48K8rW3F5kOZ6f4yRq9QT1gF/gN7
+5Pt494YyYyJu/a8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQBuRZisIViI2G/R+w79
+vk21TzC/cJ+O7tKsseDqotXYTH8SuimEH5IWcXNgnWhNzczwN8s2362NixyvCipV
+yd4wzMpPbjIhnWGM0hluWZiK2RxfcqimIBjDParTv6CMUIuwGQ257THKY8hXGg7j
+Uws6Lif3P9UbsuRiYPxMgg98wg==
+-----END CERTIFICATE-----
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/moz-spdy/spdy-key.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF3/Gnld/wcOE9J/M01y0RFiyV
+dPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+jei+mA1ZqyKXnMANZozVPSedXR
+GwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+A3vk+3j3hjJjIm79rwIDAQAB
+AoGAAv2QI9h32epQND9TxwSCKD//dC7W/cZOFNovfKCTeZjNK6EIzKqPTGA6smvR
+C1enFl5adf+IcyWqAoe4lkqTvurIj+2EhtXdQ8DBlVuXKr3xvEFdYxXPautdTCF6
+KbXEyS/s1TZCRFjYftvCrXxc3pK45AQX/wg7z1K+YB5pyIECQQD0OJvLoxLYoXAc
+FZraIOZiDsEbGuSHqoCReFXH75EC3+XGYkH2bQ/nSIZ0h1buuwQ/ylKXOlTPT3Qt
+Xm1OQEBvAkEA4AjWsIO/rRpOm/Q2aCrynWMpoUXTZSbL2yGf8pxp/+8r2br5ier0
+M1LeBb/OPY1+k39NWLXxQoo64xoSFYk2wQJAd2wDCwX4HkR7HNCXw1hZL9QFK6rv
+20NN0VSlpboJD/3KT0MW/FiCcVduoCbaJK0Au+zEjDyy4hj5N4I4Mw6KMwJAXVAx
+I+psTsxzS4/njXG+BgIEl/C+gRYsuMQDnAi8OebDq/et8l0Tg8ETSu++FnM18neG
+ntmBeMacinUUbTXuwQJBAJp/onZdsMzeVulsGrqR1uS+Lpjc5Q1gt5ttt2cxj91D
+rio48C/ZvWuKNE8EYj2ALtghcVKRvgaWfOxt2GPguGg=
+-----END RSA PRIVATE KEY-----
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/README.md
@@ -0,0 +1,132 @@
+# SPDY Server for node.js [![Build Status](https://secure.travis-ci.org/indutny/node-spdy.png)](http://travis-ci.org/indutny/node-spdy)
+
+With this module you can create [SPDY](http://www.chromium.org/spdy) servers
+in node.js with natural http module interface and fallback to regular https
+(for browsers that doesn't support SPDY yet).
+
+## Node+OpenSSL building
+
+At the moment node-spdy requires zlib dictionary support, which will come to
+node.js only in 0.7.x version. To build 0.7.x version follow instructions below:
+
+```bash
+git clone git://github.com/joyent/node.git
+cd node
+./configure --prefix=$HOME/.node/dev # <- or any other dir
+
+make install -j4 # in -jN, N is number of CPU cores on your machine
+
+# Add node's bin to PATH env variable
+echo 'export PATH=$HOME/.node/dev/bin:$PATH' >> ~/.bashrc
+
+#
+# You have working node 0.7.x + NPN now !!!
+#
+```
+
+## Usage
+
+```javascript
+var spdy = require('spdy');
+
+var options = {
+  key: fs.readFileSync(__dirname + '/keys/spdy-key.pem'),
+  cert: fs.readFileSync(__dirname + '/keys/spdy-cert.pem'),
+  ca: fs.readFileSync(__dirname + '/keys/spdy-csr.pem')
+};
+
+spdy.createServer(options, function(req, res) {
+  res.writeHead(200);
+  res.end('hello world!');
+});
+
+spdy.listen(443);
+```
+
+## API
+
+API is compatible with `http` and `https` module, but you can use another
+function as base class for SPDYServer. For example,
+`require('express').HTTPSServer` given that as base class you'll get a server
+compatible with [express](https://github.com/visionmedia/express) API.
+
+```javascript
+spdy.createServer(
+  [base class constructor, i.e. https.Server or express.HTTPSServer],
+  { /* keys and options */ }, // <- the only one required argument
+  [request listener]
+).listen([port], [host], [callback]);
+```
+
+Request listener will receive two arguments: `request` and `response`. They're
+both instances of `http`'s `IncomingMessage` and `OutgoingMessage`. But two
+custom properties are added to both of them: `streamID` and `isSpdy`. The first
+one indicates on which spdy stream are sitting request and response. Latter one
+is always true and can be checked to ensure that incoming request wasn't
+received by HTTPS callback.
+
+### Push streams
+
+It's possible to initiate 'push' stream to send content to client, before one'll
+request it.
+
+```javascript
+spdy.createServer(options, function(req, res) {
+  var headers = { 'content-type': 'application/javascript' };
+  res.send('/main.js', headers, function(err, stream) {
+    if (err) return;
+
+    stream.end('alert("hello from push stream!");');
+  });
+
+  res.end('<script src="/main.js"></script>');
+}).listen(443);
+```
+
+`.push('full or relative url', { ... headers ... }, callback)`
+
+You can use either full ( `http://host/path` ) or relative ( `/path` ) urls with
+`.push()`. `headers` are the same as for regular response object. `callback`
+will receive two arguments: `err` (if any error is happened) and `stream`
+(stream object have API compatible with a
+[net.Socket](http://nodejs.org/docs/latest/api/net.html#net.Socket) ).
+
+### Options
+
+All options supported by
+[tls](http://nodejs.org/docs/latest/api/tls.html#tls.createServer) are working
+with node-spdy. In addition, `maxStreams` options is available. it allows you
+controlling [maximum concurrent streams][http://www.chromium.org/spdy/spdy-protocol/spdy-protocol-draft2#TOC-SETTINGS]
+protocol option (if client will start more streams than that limit, RST_STREAM
+will be sent for each additional stream).
+
+#### Contributors
+
+* [Fedor Indutny](https://github.com/indutny)
+* [Chris Storm](https://github.com/eee-c)
+* [François de Metz](https://github.com/francois2metz)
+
+#### LICENSE
+
+This software is licensed under the MIT License.
+
+Copyright Fedor Indutny, 2012.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to permit
+persons to whom the Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+USE OR OTHER DEALINGS IN THE SOFTWARE.
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/app.js
@@ -0,0 +1,64 @@
+/**
+ * Module dependencies.
+ */
+
+var fs = require('fs'),
+    io = require('socket.io'),
+    express = require('express'),
+    spdy = require('../../');
+    routes = require('./routes');
+    realtime = require('./realtime');
+
+var options = {
+  key: fs.readFileSync(__dirname + '/keys/twitlog-key.pem'),
+  cert: fs.readFileSync(__dirname + '/keys/twitlog-cert.pem'),
+  ca: fs.readFileSync(__dirname + '/keys/twitlog-csr.pem'),
+  ciphers: '!aNULL:!ADH:!eNull:!LOW:!EXP:RC4+RSA:MEDIUM:HIGH',
+  maxStreams: 15
+};
+
+var app = module.exports = spdy.createServer(express.HTTPSServer, options);
+
+io = io.listen(app, { log: false });
+io.set('transports', ['xhr-polling', 'jsonp-polling']);
+
+// Configuration
+
+app.configure(function(){
+  app.set('views', __dirname + '/views');
+  app.set('view engine', 'jade');
+  app.use(express.bodyParser());
+  app.use(express.methodOverride());
+  app.use(app.router);
+  app.use(express.staticCache());
+  app.use(express.static(__dirname + '/public'));
+});
+
+app.configure('development', function(){
+  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
+});
+
+app.configure('production', function(){
+  app.use(express.errorHandler());
+});
+
+// Routes
+
+app.get('/', routes.index);
+
+// Socket.io
+
+realtime.init(io);
+
+app.listen(8081);
+console.log(
+  'Express server listening on port %d in %s mode',
+  app.address().port,
+  app.settings.env
+);
+
+// Do not fail on exceptions
+
+process.on('uncaughtException', function(err) {
+  console.error(err.stack);
+});
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/autorestart.js
@@ -0,0 +1,15 @@
+var path = require('path'),
+    spawn = require('child_process').spawn;
+
+function start() {
+  var child = spawn(process.execPath, [path.resolve(__dirname, 'app.js')]);
+  process.stderr.write('started child with pid: ' + child.pid + '\n');
+
+  child.on('exit', function(code) {
+    process.stderr.write('child exit: ' + code + '!\n');
+    setTimeout(start, 100);
+  });
+  child.stdout.pipe(process.stdout);
+  child.stderr.pipe(process.stderr);
+}
+start();
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/keys/twitlog-cert.pem
@@ -0,0 +1,41 @@
+-----BEGIN CERTIFICATE-----
+MIIHNDCCBhygAwIBAgIDBRVaMA0GCSqGSIb3DQEBBQUAMIGMMQswCQYDVQQGEwJJ
+TDEWMBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0
+YWwgQ2VydGlmaWNhdGUgU2lnbmluZzE4MDYGA1UEAxMvU3RhcnRDb20gQ2xhc3Mg
+MSBQcmltYXJ5IEludGVybWVkaWF0ZSBTZXJ2ZXIgQ0EwHhcNMTIwMTA2MTAyMDQz
+WhcNMTMwMTA2MDc1NzQwWjBzMRkwFwYDVQQNExBqdDVKVUE2NHlXaTg4T2k3MQsw
+CQYDVQQGEwJSVTEhMB8GA1UEAxMYc3BkeS10d2l0bG9nLmluZHV0bnkuY29tMSYw
+JAYJKoZIhvcNAQkBFhdmZWRvci5pbmR1dG55QGdtYWlsLmNvbTCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBANgiMg1hIHoxk1AuMouaERtSv8g/J+JbTgAe
+lD5kF7hGpQOB+qGLuCU+7QovsjftKee7aXXNduQDkzkCD7wg7C9m3QHJ+YXDk7vE
+IycgK6XIK7G1kAfMXfJm6D2gRD3JjM4fWRY/MYTxSvLnqDoAgHIrCBrwauNNuHdl
+4x5LclRKAWAl1sixQgdyw/CzLjuFJFgmRn1Oc1T/lwgzwcXcw0tBvTQtTdvaogvJ
++cqT+TrHVm+w+BRfp+CT9slZp+4b9JQTD5u4/0k3RoxbUBHvKRJ9FKOsq/GFNdlj
+KWMIfQ8ZdHJDKWVX8zlDydhpMTpofaynvdmSPurMcFNGGmYEmkUCAwEAAaOCA7Uw
+ggOxMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgOoMBMGA1UdJQQMMAoGCCsGAQUFBwMB
+MB0GA1UdDgQWBBTy7FYuO1wZ1yws0eNEAI2CVcS5yzAfBgNVHSMEGDAWgBTrQjTQ
+mLCrn/Qbawj3zGQu7w4sRTAwBgNVHREEKTAnghhzcGR5LXR3aXRsb2cuaW5kdXRu
+eS5jb22CC2luZHV0bnkuY29tMIICIQYDVR0gBIICGDCCAhQwggIQBgsrBgEEAYG1
+NwECAjCCAf8wLgYIKwYBBQUHAgEWImh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL3Bv
+bGljeS5wZGYwNAYIKwYBBQUHAgEWKGh0dHA6Ly93d3cuc3RhcnRzc2wuY29tL2lu
+dGVybWVkaWF0ZS5wZGYwgfcGCCsGAQUFBwICMIHqMCcWIFN0YXJ0Q29tIENlcnRp
+ZmljYXRpb24gQXV0aG9yaXR5MAMCAQEagb5UaGlzIGNlcnRpZmljYXRlIHdhcyBp
+c3N1ZWQgYWNjb3JkaW5nIHRvIHRoZSBDbGFzcyAxIFZhbGlkYXRpb24gcmVxdWly
+ZW1lbnRzIG9mIHRoZSBTdGFydENvbSBDQSBwb2xpY3ksIHJlbGlhbmNlIG9ubHkg
+Zm9yIHRoZSBpbnRlbmRlZCBwdXJwb3NlIGluIGNvbXBsaWFuY2Ugb2YgdGhlIHJl
+bHlpbmcgcGFydHkgb2JsaWdhdGlvbnMuMIGcBggrBgEFBQcCAjCBjzAnFiBTdGFy
+dENvbSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTADAgECGmRMaWFiaWxpdHkgYW5k
+IHdhcnJhbnRpZXMgYXJlIGxpbWl0ZWQhIFNlZSBzZWN0aW9uICJMZWdhbCBhbmQg
+TGltaXRhdGlvbnMiIG9mIHRoZSBTdGFydENvbSBDQSBwb2xpY3kuMDUGA1UdHwQu
+MCwwKqAooCaGJGh0dHA6Ly9jcmwuc3RhcnRzc2wuY29tL2NydDEtY3JsLmNybDCB
+jgYIKwYBBQUHAQEEgYEwfzA5BggrBgEFBQcwAYYtaHR0cDovL29jc3Auc3RhcnRz
+c2wuY29tL3N1Yi9jbGFzczEvc2VydmVyL2NhMEIGCCsGAQUFBzAChjZodHRwOi8v
+YWlhLnN0YXJ0c3NsLmNvbS9jZXJ0cy9zdWIuY2xhc3MxLnNlcnZlci5jYS5jcnQw
+IwYDVR0SBBwwGoYYaHR0cDovL3d3dy5zdGFydHNzbC5jb20vMA0GCSqGSIb3DQEB
+BQUAA4IBAQClU4BNyXLLlk8PryozRpx0WkkH83e799myaLMAY0IjyroQ3EH1rlXT
+vU9QJ8d8br+xm7KnLsrva0BDC/eVeq+7qCyUNoXUAAswBzpXPeL+Rm/4oWmOAcCA
+/8MQ6z+YLFPf9MqlWdO24aA5aoN7DwCxIvJ2IfhNbRFLzcztF/kFdqnRtw34S1L4
+GvSx/o0eYZ8sgyj9yET8QqXR/EUs+NvRBmDIjMx8DmJVvLQZHNk1sHnkdx/Z0AWP
+WUjaEkhfkvLVCvX+7vGmlMnvGwRZf1U5qaj4sp+O70ANlQVG99R6e8IEcf8JFy3/
+AUumo8jFsCfPIcByKJoctlz1nx9+zEDp
+-----END CERTIFICATE-----
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/keys/twitlog-csr.pem
@@ -0,0 +1,16 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIICijCCAXICAQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUx
+ITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBANgiMg1hIHoxk1AuMouaERtSv8g/J+JbTgAelD5k
+F7hGpQOB+qGLuCU+7QovsjftKee7aXXNduQDkzkCD7wg7C9m3QHJ+YXDk7vEIycg
+K6XIK7G1kAfMXfJm6D2gRD3JjM4fWRY/MYTxSvLnqDoAgHIrCBrwauNNuHdl4x5L
+clRKAWAl1sixQgdyw/CzLjuFJFgmRn1Oc1T/lwgzwcXcw0tBvTQtTdvaogvJ+cqT
++TrHVm+w+BRfp+CT9slZp+4b9JQTD5u4/0k3RoxbUBHvKRJ9FKOsq/GFNdljKWMI
+fQ8ZdHJDKWVX8zlDydhpMTpofaynvdmSPurMcFNGGmYEmkUCAwEAAaAAMA0GCSqG
+SIb3DQEBBQUAA4IBAQCQFFq1j4zbi3mqqpRrGjS5NMhCqdS3m8lPgUtPCwDsbtAy
+ILtl6clYA5ruwxtLEQYOiowUZ7dgQQ2poqwwelk5hJ92D9JySAyU0bqWUZ3CgccC
+7LWhZMlmeyqBnDSE+oaFKsPRs9oTYGxh4zZv9OhPNL5ZFEAwUMccHXYMgsQk5n1E
+pbAdxw2KgyrliTz4PO7/hjwAfANszdSweP++qNBceix1HpWIp17hJ9aIH1Q+w5Ym
+Uzfykvz7Qc9YoQ/LHKJMxirsmZtdAjC0YApMuAOhjD42OkhL9JBVg5CFMIhXQBOU
+1fGAoWdbIKWre4owm9kjsqwe8jg+sWTZHE1h+TJD
+-----END CERTIFICATE REQUEST-----
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/keys/twitlog-key.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDYIjINYSB6MZNQ
+LjKLmhEbUr/IPyfiW04AHpQ+ZBe4RqUDgfqhi7glPu0KL7I37Snnu2l1zXbkA5M5
+Ag+8IOwvZt0ByfmFw5O7xCMnICulyCuxtZAHzF3yZug9oEQ9yYzOH1kWPzGE8Ury
+56g6AIByKwga8GrjTbh3ZeMeS3JUSgFgJdbIsUIHcsPwsy47hSRYJkZ9TnNU/5cI
+M8HF3MNLQb00LU3b2qILyfnKk/k6x1ZvsPgUX6fgk/bJWafuG/SUEw+buP9JN0aM
+W1AR7ykSfRSjrKvxhTXZYyljCH0PGXRyQyllV/M5Q8nYaTE6aH2sp73Zkj7qzHBT
+RhpmBJpFAgMBAAECggEAdEKos+O8KZ7DRE0laUy9yPnRKfE3Dh7ZLV1Flu4WiEyP
+9PwVCpLywi5AKcuQTV8ovHtmdjTIsExwBClkt2jqQ3FMjurLazXSIR2XXzOB5xJu
+1o/44wj+vCa45HVyX94r/LCGJl5lz8JP86vDJTgh38ff+0W56X1kLe3Dpwckf8u1
+fMV0apePlyYbPL85EbulnUNToiIgURu/brnsdufB62RZBp1NhUjiaok9jODQq3Ee
+8J8fpMZNBhvwZqfv7h1ykMfYUASQ8IDtZKMKk7MCMuPjLmMb9nWKrXmUUIIJDAqL
+Z/cgKBE72Swj+l+V54/onr+2g9iTu2hlxsicEYcSIQKBgQD9lRO/BtbB9AXEJ3E5
+I1fwYvV5I1TJiVBNVo7VwC2gtwHN7ftXw06bEc78z2i5Od+/F39XeXes5yBxqCbO
+tv0XKF2jOHakMduMch4C7qyMiS4q1JtDkEbJd4jkBGySwePf45gHPmtmso7peQPT
+P4OoO/9MLzJjBYDeSCPbqiMvuQKBgQDaMbdvafxpWxg44SNqZtpX54CpLViUMoaY
+mrkyDIXrHMi2+yY/PzaNBNUASqlAryy4fxhGRsHFMMssZpjdfCBr2DnXbXSk4Li3
+CK8+m9hk2om9lv2FvDOPCQ+RGigmpYQC/a7UKo8xIjI2wzDRmevXDf8xBFjvLQTc
+bpanFbrM7QKBgD1JLUeKwJaJgmdA3RVhHFzFnewUBObcX+MBG24/jwd7k10QuiEg
+27uQl0T0X6v8d734UNd0TN8l0OqHKDHnec2B/Pd4qvvN7PDJl8U/p8YjVVwWnBu9
+H86LLDNnelIRuCAhIloF1PEyEGYO0ETa4dfkADSKZ5QU/Ws7ZictvGlJAoGANfA+
+YXt4226agU0enSoJ5dsj0i6UjCYlYco15+pynJmEAL/7R31P9fJw2V6bkpL7YiyB
+CrZpJl8WisZeGbqapS5RtjCnui6XWx/5eme6ScxAaq7Nw2av9DcQMxWdQVh/VuHx
+ex9+QG4srZ75DYeYZpReNnbVqWKepgNsmKdlg00CgYBsrbksX+66b11z6GwBjEK+
+0rMpzMhnOxhxGaDw6jTuxOIZEC1EyaxI5sxryH6+XdZJZYH/pN6O5skjPD28L8fC
+b9900FSsnQlnk/QMb7G5Tqyvf2oAAxSSfvX13zAFGd1Z79TEeiZWeY04hrXACvP+
+cAeTFiSRlCMiOLLgRhFauw==
+-----END PRIVATE KEY-----
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/keys/twtilog-ca.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIGNDCCBBygAwIBAgIBGDANBgkqhkiG9w0BAQUFADB9MQswCQYDVQQGEwJJTDEW
+MBQGA1UEChMNU3RhcnRDb20gTHRkLjErMCkGA1UECxMiU2VjdXJlIERpZ2l0YWwg
+Q2VydGlmaWNhdGUgU2lnbmluZzEpMCcGA1UEAxMgU3RhcnRDb20gQ2VydGlmaWNh
+dGlvbiBBdXRob3JpdHkwHhcNMDcxMDI0MjA1NDE3WhcNMTcxMDI0MjA1NDE3WjCB
+jDELMAkGA1UEBhMCSUwxFjAUBgNVBAoTDVN0YXJ0Q29tIEx0ZC4xKzApBgNVBAsT
+IlNlY3VyZSBEaWdpdGFsIENlcnRpZmljYXRlIFNpZ25pbmcxODA2BgNVBAMTL1N0
+YXJ0Q29tIENsYXNzIDEgUHJpbWFyeSBJbnRlcm1lZGlhdGUgU2VydmVyIENBMIIB
+IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtonGrO8JUngHrJJj0PREGBiE
+gFYfka7hh/oyULTTRwbw5gdfcA4Q9x3AzhA2NIVaD5Ksg8asWFI/ujjo/OenJOJA
+pgh2wJJuniptTT9uYSAK21ne0n1jsz5G/vohURjXzTCm7QduO3CHtPn66+6CPAVv
+kvek3AowHpNz/gfK11+AnSJYUq4G2ouHI2mw5CrY6oPSvfNx23BaKA+vWjhwRRI/
+ME3NO68X5Q/LoKldSKqxYVDLNM08XMML6BDAjJvwAwNi/rJsPnIO7hxDKslIDlc5
+xDEhyBDBLIf+VJVSH1I8MRKbf+fAoKVZ1eKPPvDVqOHXcDGpxLPPr21TLwb0pwID
+AQABo4IBrTCCAakwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAQYwHQYD
+VR0OBBYEFOtCNNCYsKuf9BtrCPfMZC7vDixFMB8GA1UdIwQYMBaAFE4L7xqkQFul
+F2mHMMo0aEPQQa7yMGYGCCsGAQUFBwEBBFowWDAnBggrBgEFBQcwAYYbaHR0cDov
+L29jc3Auc3RhcnRzc2wuY29tL2NhMC0GCCsGAQUFBzAChiFodHRwOi8vd3d3LnN0
+YXJ0c3NsLmNvbS9zZnNjYS5jcnQwWwYDVR0fBFQwUjAnoCWgI4YhaHR0cDovL3d3
+dy5zdGFydHNzbC5jb20vc2ZzY2EuY3JsMCegJaAjhiFodHRwOi8vY3JsLnN0YXJ0
+c3NsLmNvbS9zZnNjYS5jcmwwgYAGA1UdIAR5MHcwdQYLKwYBBAGBtTcBAgEwZjAu
+BggrBgEFBQcCARYiaHR0cDovL3d3dy5zdGFydHNzbC5jb20vcG9saWN5LnBkZjA0
+BggrBgEFBQcCARYoaHR0cDovL3d3dy5zdGFydHNzbC5jb20vaW50ZXJtZWRpYXRl
+LnBkZjANBgkqhkiG9w0BAQUFAAOCAgEAIQlJPqWIbuALi0jaMU2P91ZXouHTYlfp
+tVbzhUV1O+VQHwSL5qBaPucAroXQ+/8gA2TLrQLhxpFy+KNN1t7ozD+hiqLjfDen
+xk+PNdb01m4Ge90h2c9W/8swIkn+iQTzheWq8ecf6HWQTd35RvdCNPdFWAwRDYSw
+xtpdPvkBnufh2lWVvnQce/xNFE+sflVHfXv0pQ1JHpXo9xLBzP92piVH0PN1Nb6X
+t1gW66pceG/sUzCv6gRNzKkC4/C2BBL2MLERPZBOVmTX3DxDX3M570uvh+v2/miI
+RHLq0gfGabDBoYvvF0nXYbFFSF87ICHpW7LM9NfpMfULFWE7epTj69m8f5SuauNi
+YpaoZHy4h/OZMn6SolK+u/hlz8nyMPyLwcKmltdfieFcNID1j0cHL7SRv7Gifl9L
+WtBbnySGBVFaaQNlQ0lxxeBvlDRr9hvYqbBMflPrj0jfyjO1SPo2ShpTpjMM0InN
+SRXNiTE8kMBy12VLUjWKRhFEuT2OKGWmPnmeXAhEKa2wNREuIU640ucQPl2Eg7PD
+wuTSxv0JS3QJ3fGz0xk+gA2iCxnwOOfFwq/iI9th4p1cbiCJSS4jarJiwUW0n6+L
+p/EiO/h94pDQehn7Skzj0n1fSoMD7SfWI55rjbRZotnvbIIp3XUZPD9MEI3vu3Un
+0q6Dp6jOW6c=
+-----END CERTIFICATE-----
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/package.json
@@ -0,0 +1,11 @@
+{
+  "name": "application-name",
+  "version": "0.0.1",
+  "private": true,
+  "dependencies": {
+    "express": "2.5.x",
+    "jade": ">= 0.0.1",
+    "socket.io": "0.8.x",
+    "ntwitter": "0.2.x"
+  }
+}
new file mode 100644
index 0000000000000000000000000000000000000000..d535769aa6af5ff1437e1c1a41db0a02d686e2d8
GIT binary patch
literal 2582
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
new file mode 100644
index 0000000000000000000000000000000000000000..c7f41c274463b16ef4b79776b73aebb20cdcd926
GIT binary patch
literal 11587
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
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/public/javascripts/main.js
@@ -0,0 +1,52 @@
+!function() {
+  var socket = io.connect(),
+      container = document.getElementById('tweets'),
+      tweets = [],
+      first;
+
+  socket.emit('reqTweets');
+  socket.on('tweet', function(tweet) {
+    if (tweets.length > 18) {
+      // Remove first tweet
+      container.removeChild(tweets.shift());
+    }
+    tweets.push(createTweet(tweet));
+  });
+
+  function createTweet(tweet) {
+    var elem = document.createElement('article'),
+        avatar = document.createElement('img'),
+        textContainer = document.createElement('div'),
+        text = document.createElement('div'),
+        clear = document.createElement('div');
+
+    avatar.className = 'avatar';
+    avatar.src = tweet.user.image.replace(
+        /^http:\/\/a2\.twimg\.com\//,
+        '//twimg0-a.akamaihd.net/'
+    );
+    avatar.title = avatar.alt = tweet.user.name;
+
+    text.className = 'text';
+    text.innerHTML = tweet.text;
+
+    clear.className = 'clear';
+
+    textContainer.className = 'text-container';
+    textContainer.appendChild(text);
+    textContainer.appendChild(clear);
+
+    elem.className = 'tweet';
+    elem.appendChild(avatar);
+    elem.appendChild(textContainer);
+
+    if (first) {
+      container.insertBefore(elem, first);
+    } else {
+      container.appendChild(elem);
+    }
+    first = elem;
+
+    return elem;
+  };
+}();
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/public/javascripts/socket.io.min.js
@@ -0,0 +1,2 @@
+/*! Socket.IO.min.js build:0.8.4, production. Copyright(c) 2011 LearnBoost <dev@learnboost.com> MIT Licensed */
+(function(a){var b=a;b.version="0.8.4",b.protocol=1,b.transports=[],b.j=[],b.sockets={},b.connect=function(a,c){var d=b.util.parseUri(a),e,f;"undefined"!=typeof document&&(d.protocol=d.protocol||document.location.protocol.slice(0,-1),d.host=d.host||document.domain,d.port=d.port||document.location.port),e=b.util.uniqueUri(d);var g={host:d.host,secure:"https"==d.protocol,port:d.port||("https"==d.protocol?443:80),query:d.query||""};b.util.merge(g,c);if(g["force new connection"]||!b.sockets[e])f=new b.Socket(g);!g["force new connection"]&&f&&(b.sockets[e]=f),f=f||b.sockets[e];return f.of(d.path.length>1?d.path:"")}})("object"==typeof module?module.exports:window.io={}),function(a,b){var c=a.util={},d=/^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/,e=["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"];c.parseUri=function(a){var b=d.exec(a||""),c={},f=14;while(f--)c[e[f]]=b[f]||"";return c},c.uniqueUri=function(a){var c=a.protocol,d=a.host,e=a.port;"document"in b?(d=d||document.domain,e=e||(c=="https"&&document.location.protocol!=="https:"?443:document.location.port)):(d=d||"localhost",!e&&c=="https"&&(e=443));return(c||"http")+"://"+d+":"+(e||80)},c.query=function(a,b){var d=c.chunkQuery(a||""),e=[];c.merge(d,c.chunkQuery(b||""));for(var f in d)d.hasOwnProperty(f)&&e.push(f+"="+d[f]);return e.length?"?"+e.join("&"):""},c.chunkQuery=function(a){var b={},c=a.split("&"),d=0,e=c.length,f;for(;d<e;++d)f=c[d].split("="),f[0]&&(b[f[0]]=decodeURIComponent(f[1]));return b};var f=!1;c.load=function(a){if("document"in b&&document.readyState==="complete"||f)return a();c.on(b,"load",a,!1)},c.on=function(a,b,c,d){a.attachEvent?a.attachEvent("on"+b,c):a.addEventListener&&a.addEventListener(b,c,d)},c.request=function(a){if("undefined"!=typeof window){if(a&&window.XDomainRequest)return new XDomainRequest;if(window.XMLHttpRequest&&(!a||c.ua.hasCORS))return new XMLHttpRequest;if(!a)try{return new window.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}return null},"undefined"!=typeof window&&c.load(function(){f=!0}),c.defer=function(a){if(!c.ua.webkit)return a();c.load(function(){setTimeout(a,100)})},c.merge=function g(a,b,d,e){var f=e||[],g=typeof d=="undefined"?2:d,h;for(h in b)b.hasOwnProperty(h)&&c.indexOf(f,h)<0&&(typeof a[h]!="object"||!g?(a[h]=b[h],f.push(b[h])):c.merge(a[h],b[h],g-1,f));return a},c.mixin=function(a,b){c.merge(a.prototype,b.prototype)},c.inherit=function(a,b){function c(){}c.prototype=b.prototype,a.prototype=new c},c.isArray=Array.isArray||function(a){return Object.prototype.toString.call(a)==="[object Array]"},c.intersect=function(a,b){var d=[],e=a.length>b.length?a:b,f=a.length>b.length?b:a;for(var g=0,h=f.length;g<h;g++)~c.indexOf(e,f[g])&&d.push(f[g]);return d},c.indexOf=function(a,b,c){if(Array.prototype.indexOf)return Array.prototype.indexOf.call(a,b,c);for(var d=a.length,c=c<0?c+d<0?0:c+d:c||0;c<d&&a[c]!==b;c++);return d<=c?-1:c},c.toArray=function(a){var b=[];for(var c=0,d=a.length;c<d;c++)b.push(a[c]);return b},c.ua={},c.ua.hasCORS="undefined"!=typeof window&&window.XMLHttpRequest&&function(){try{var a=new XMLHttpRequest}catch(b){return!1}return a.withCredentials!=undefined}(),c.ua.webkit="undefined"!=typeof navigator&&/webkit/i.test(navigator.userAgent)}("undefined"!=typeof window?io:module.exports,this),function(a,b){function c(){}a.EventEmitter=c,c.prototype.on=function(a,c){this.$events||(this.$events={}),this.$events[a]?b.util.isArray(this.$events[a])?this.$events[a].push(c):this.$events[a]=[this.$events[a],c]:this.$events[a]=c;return this},c.prototype.addListener=c.prototype.on,c.prototype.once=function(a,b){function d(){c.removeListener(a,d),b.apply(this,arguments)}var c=this;d.listener=b,this.on(a,d);return this},c.prototype.removeListener=function(a,c){if(this.$events&&this.$events[a]){var d=this.$events[a];if(b.util.isArray(d)){var e=-1;for(var f=0,g=d.length;f<g;f++)if(d[f]===c||d[f].listener&&d[f].listener===c){e=f;break}if(e<0)return this;d.splice(e,1),d.length||delete this.$events[a]}else(d===c||d.listener&&d.listener===c)&&delete this.$events[a]}return this},c.prototype.removeAllListeners=function(a){this.$events&&this.$events[a]&&(this.$events[a]=null);return this},c.prototype.listeners=function(a){this.$events||(this.$events={}),this.$events[a]||(this.$events[a]=[]),b.util.isArray(this.$events[a])||(this.$events[a]=[this.$events[a]]);return this.$events[a]},c.prototype.emit=function(a){if(!this.$events)return!1;var c=this.$events[a];if(!c)return!1;var d=Array.prototype.slice.call(arguments,1);if("function"==typeof c)c.apply(this,d);else{if(!b.util.isArray(c))return!1;var e=c.slice();for(var f=0,g=e.length;f<g;f++)e[f].apply(this,d)}return!0}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(exports,nativeJSON){function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i instanceof Date&&(i=date(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c<f;c+=1)h[c]=str(c,i)||"null";e=h.length===0?"[]":gap?"[\n"+gap+h.join(",\n"+gap)+"\n"+g+"]":"["+h.join(",")+"]",gap=g;return e}if(rep&&typeof rep=="object"){f=rep.length;for(c=0;c<f;c+=1)typeof rep[c]=="string"&&(d=rep[c],e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e))}else for(d in i)Object.prototype.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e));e=h.length===0?"{}":gap?"{\n"+gap+h.join(",\n"+gap)+"\n"+g+"}":"{"+h.join(",")+"}",gap=g;return e}}function quote(a){escapable.lastIndex=0;return escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b=="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function date(a,b){return isFinite(a.valueOf())?a.getUTCFullYear()+"-"+f(a.getUTCMonth()+1)+"-"+f(a.getUTCDate())+"T"+f(a.getUTCHours())+":"+f(a.getUTCMinutes())+":"+f(a.getUTCSeconds())+"Z":null}function f(a){return a<10?"0"+a:a}"use strict";if(nativeJSON&&nativeJSON.parse)return exports.JSON={parse:nativeJSON.parse,stringify:nativeJSON.stringify};var JSON=exports.JSON={},cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;JSON.stringify=function(a,b,c){var d;gap="",indent="";if(typeof c=="number")for(d=0;d<c;d+=1)indent+=" ";else typeof c=="string"&&(indent=c);rep=b;if(!b||typeof b=="function"||typeof b=="object"&&typeof b.length=="number")return str("",{"":a});throw new Error("JSON.stringify")},JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&typeof e=="object")for(c in e)Object.prototype.hasOwnProperty.call(e,c)&&(d=walk(e,c),d!==undefined?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,""))){j=eval("("+text+")");return typeof reviver=="function"?walk({"":j},""):j}throw new SyntaxError("JSON.parse")}}("undefined"!=typeof io?io:module.exports,typeof JSON!="undefined"?JSON:undefined),function(a,b){var c=a.parser={},d=c.packets=["disconnect","connect","heartbeat","message","json","event","ack","error","noop"],e=c.reasons=["transport not supported","client not handshaken","unauthorized"],f=c.advice=["reconnect"],g=b.JSON,h=b.util.indexOf;c.encodePacket=function(a){var b=h(d,a.type),c=a.id||"",i=a.endpoint||"",j=a.ack,k=null;switch(a.type){case"error":var l=a.reason?h(e,a.reason):"",m=a.advice?h(f,a.advice):"";if(l!==""||m!=="")k=l+(m!==""?"+"+m:"");break;case"message":a.data!==""&&(k=a.data);break;case"event":var n={name:a.name};a.args&&a.args.length&&(n.args=a.args),k=g.stringify(n);break;case"json":k=g.stringify(a.data);break;case"connect":a.qs&&(k=a.qs);break;case"ack":k=a.ackId+(a.args&&a.args.length?"+"+g.stringify(a.args):"")}var o=[b,c+(j=="data"?"+":""),i];k!==null&&k!==undefined&&o.push(k);return o.join(":")},c.encodePayload=function(a){var b="";if(a.length==1)return a[0];for(var c=0,d=a.length;c<d;c++){var e=a[c];b+="\ufffd"+e.length+"\ufffd"+a[c]}return b};var i=/([^:]+):([0-9]+)?(\+)?:([^:]+)?:?([\s\S]*)?/;c.decodePacket=function(a){var b=a.match(i);if(!b)return{};var c=b[2]||"",a=b[5]||"",h={type:d[b[1]],endpoint:b[4]||""};c&&(h.id=c,b[3]?h.ack="data":h.ack=!0);switch(h.type){case"error":var b=a.split("+");h.reason=e[b[0]]||"",h.advice=f[b[1]]||"";break;case"message":h.data=a||"";break;case"event":try{var j=g.parse(a);h.name=j.name,h.args=j.args}catch(k){}h.args=h.args||[];break;case"json":try{h.data=g.parse(a)}catch(k){}break;case"connect":h.qs=a||"";break;case"ack":var b=a.match(/^([0-9]+)(\+)?(.*)/);if(b){h.ackId=b[1],h.args=[];if(b[3])try{h.args=b[3]?g.parse(b[3]):[]}catch(k){}}break;case"disconnect":case"heartbeat":}return h},c.decodePayload=function(a){if(a.charAt(0)=="\ufffd"){var b=[];for(var d=1,e="";d<a.length;d++)a.charAt(d)=="\ufffd"?(b.push(c.decodePacket(a.substr(d+1).substr(0,e))),d+=Number(e)+1,e=""):e+=a.charAt(d);return b}return[c.decodePacket(a)]}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b){function c(a,b){this.socket=a,this.sessid=b}a.Transport=c,b.util.mixin(c,b.EventEmitter),c.prototype.onData=function(a){this.clearCloseTimeout(),this.setCloseTimeout();if(a!==""){var c=b.parser.decodePayload(a);if(c&&c.length)for(var d=0,e=c.length;d<e;d++)this.onPacket(c[d])}return this},c.prototype.onPacket=function(a){if(a.type=="heartbeat")return this.onHeartbeat();a.type=="connect"&&a.endpoint==""&&this.onConnect(),this.socket.onPacket(a);return this},c.prototype.setCloseTimeout=function(){if(!this.closeTimeout){var a=this;this.closeTimeout=setTimeout(function(){a.onDisconnect()},this.socket.closeTimeout)}},c.prototype.onDisconnect=function(){this.close&&this.close(),this.clearTimeouts(),this.socket.onDisconnect();return this},c.prototype.onConnect=function(){this.socket.onConnect();return this},c.prototype.clearCloseTimeout=function(){this.closeTimeout&&(clearTimeout(this.closeTimeout),this.closeTimeout=null)},c.prototype.clearTimeouts=function(){this.clearCloseTimeout(),this.reopenTimeout&&clearTimeout(this.reopenTimeout)},c.prototype.packet=function(a){this.send(b.parser.encodePacket(a))},c.prototype.onHeartbeat=function(a){this.packet({type:"heartbeat"})},c.prototype.onOpen=function(){this.open=!0,this.clearCloseTimeout(),this.socket.onOpen()},c.prototype.onClose=function(){var a=this;this.open=!1,this.setCloseTimeout(),this.socket.onClose()},c.prototype.prepareUrl=function(){var a=this.socket.options;return this.scheme()+"://"+a.host+":"+a.port+"/"+a.resource+"/"+b.protocol+"/"+this.name+"/"+this.sessid},c.prototype.ready=function(a,b){b.call(this)}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b,c){function e(){}function d(a){this.options={port:80,secure:!1,document:"document"in c?document:!1,resource:"socket.io",transports:b.transports,"connect timeout":1e4,"try multiple transports":!0,reconnect:!0,"reconnection delay":500,"reconnection limit":Infinity,"reopen delay":3e3,"max reconnection attempts":10,"sync disconnect on unload":!0,"auto connect":!0,"flash policy port":10843},b.util.merge(this.options,a),this.connected=!1,this.open=!1,this.connecting=!1,this.reconnecting=!1,this.namespaces={},this.buffer=[],this.doBuffer=!1;if(this.options["sync disconnect on unload"]&&(!this.isXDomain()||b.util.ua.hasCORS)){var d=this;b.util.on(c,"beforeunload",function(){d.disconnectSync()},!1)}this.options["auto connect"]&&this.connect()}a.Socket=d,b.util.mixin(d,b.EventEmitter),d.prototype.of=function(a){this.namespaces[a]||(this.namespaces[a]=new b.SocketNamespace(this,a),a!==""&&this.namespaces[a].packet({type:"connect"}));return this.namespaces[a]},d.prototype.publish=function(){this.emit.apply(this,arguments);var a;for(var b in this.namespaces)this.namespaces.hasOwnProperty(b)&&(a=this.of(b),a.$emit.apply(a,arguments))},d.prototype.handshake=function(a){function f(b){b instanceof Error?c.onError(b.message):a.apply(null,b.split(":"))}var c=this,d=this.options,g=["http"+(d.secure?"s":"")+":/",d.host+":"+d.port,this.options.resource,b.protocol,b.util.query(this.options.query,"t="+ +(new Date))].join("/");if(this.isXDomain()){var h=document.getElementsByTagName("script")[0],i=document.createElement("script");i.src=g+"&jsonp="+b.j.length,h.parentNode.insertBefore(i,h),b.j.push(function(a){f(a),i.parentNode.removeChild(i)})}else{var j=b.util.request();j.open("GET",g,!0),j.onreadystatechange=function(){j.readyState==4&&(j.onreadystatechange=e,j.status==200?f(j.responseText):!c.reconnecting&&c.onError(j.responseText))},j.send(null)}},d.prototype.getTransport=function(a){var c=a||this.transports,d;for(var e=0,f;f=c[e];e++)if(b.Transport[f]&&b.Transport[f].check(this)&&(!this.isXDomain()||b.Transport[f].xdomainCheck()))return new b.Transport[f](this,this.sessionid);return null},d.prototype.connect=function(a){if(this.connecting)return this;var c=this;this.handshake(function(d,e,f,g){function h(a){c.transport&&c.transport.clearTimeouts(),c.transport=c.getTransport(a);if(!c.transport)return c.publish("connect_failed");c.transport.ready(c,function(){c.connecting=!0,c.publish("connecting",c.transport.name),c.transport.open(),c.options["connect timeout"]&&(c.connectTimeoutTimer=setTimeout(function(){if(!c.connected){c.connecting=!1;if(c.options["try multiple transports"]){c.remainingTransports||(c.remainingTransports=c.transports.slice(0));var a=c.remainingTransports;while(a.length>0&&a.splice(0,1)[0]!=c.transport.name);a.length?h(a):c.publish("connect_failed")}}},c.options["connect timeout"]))})}c.sessionid=d,c.closeTimeout=f*1e3,c.heartbeatTimeout=e*1e3,c.transports=b.util.intersect(g.split(","),c.options.transports),h(),c.once("connect",function(){clearTimeout(c.connectTimeoutTimer),a&&typeof a=="function"&&a()})});return this},d.prototype.packet=function(a){this.connected&&!this.doBuffer?this.transport.packet(a):this.buffer.push(a);return this},d.prototype.setBuffer=function(a){this.doBuffer=a,!a&&this.connected&&this.buffer.length&&(this.transport.payload(this.buffer),this.buffer=[])},d.prototype.disconnect=function(){this.connected&&(this.open&&this.of("").packet({type:"disconnect"}),this.onDisconnect("booted"));return this},d.prototype.disconnectSync=function(){var a=b.util.request(),c=this.resource+"/"+b.protocol+"/"+this.sessionid;a.open("GET",c,!0),this.onDisconnect("booted")},d.prototype.isXDomain=function(){var a=window.location.port||("https:"==window.location.protocol?443:80);return this.options.host!==document.domain||this.options.port!=a},d.prototype.onConnect=function(){this.connected||(this.connected=!0,this.connecting=!1,this.doBuffer||this.setBuffer(!1),this.emit("connect"))},d.prototype.onOpen=function(){this.open=!0},d.prototype.onClose=function(){this.open=!1},d.prototype.onPacket=function(a){this.of(a.endpoint).onPacket(a)},d.prototype.onError=function(a){a&&a.advice&&a.advice==="reconnect"&&this.connected&&(this.disconnect(),this.reconnect()),this.publish("error",a&&a.reason?a.reason:a)},d.prototype.onDisconnect=function(a){var b=this.connected;this.connected=!1,this.connecting=!1,this.open=!1,b&&(this.transport.close(),this.transport.clearTimeouts(),this.publish("disconnect",a),"booted"!=a&&this.options.reconnect&&!this.reconnecting&&this.reconnect())},d.prototype.reconnect=function(){function f(){if(!!a.reconnecting){if(a.connected)return e();if(a.connecting&&a.reconnecting)return a.reconnectionTimer=setTimeout(f,1e3);a.reconnectionAttempts++>=b?a.redoTransports?(a.publish("reconnect_failed"),e()):(a.on("connect_failed",f),a.options["try multiple transports"]=!0,a.transport=a.getTransport(),a.redoTransports=!0,a.connect()):(a.reconnectionDelay<d&&(a.reconnectionDelay*=2),a.connect(),a.publish("reconnecting",a.reconnectionDelay,a.reconnectionAttempts),a.reconnectionTimer=setTimeout(f,a.reconnectionDelay))}}function e(){if(a.connected){for(var b in a.namespaces)a.namespaces.hasOwnProperty(b)&&""!==b&&a.namespaces[b].packet({type:"connect"});a.publish("reconnect",a.transport.name,a.reconnectionAttempts)}a.removeListener("connect_failed",f),a.removeListener("connect",f),a.reconnecting=!1,delete a.reconnectionAttempts,delete a.reconnectionDelay,delete a.reconnectionTimer,delete a.redoTransports,a.options["try multiple transports"]=c}this.reconnecting=!0,this.reconnectionAttempts=0,this.reconnectionDelay=this.options["reconnection delay"];var a=this,b=this.options["max reconnection attempts"],c=this.options["try multiple transports"],d=this.options["reconnection limit"];this.options["try multiple transports"]=!1,this.reconnectionTimer=setTimeout(f,this.reconnectionDelay),this.on("connect",f)}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports,this),function(a,b){function d(a,b){this.namespace=a,this.name=b}function c(a,b){this.socket=a,this.name=b||"",this.flags={},this.json=new d(this,"json"),this.ackPackets=0,this.acks={}}a.SocketNamespace=c,b.util.mixin(c,b.EventEmitter),c.prototype.$emit=b.EventEmitter.prototype.emit,c.prototype.of=function(){return this.socket.of.apply(this.socket,arguments)},c.prototype.packet=function(a){a.endpoint=this.name,this.socket.packet(a),this.flags={};return this},c.prototype.send=function(a,b){var c={type:this.flags.json?"json":"message",data:a};"function"==typeof b&&(c.id=++this.ackPackets,c.ack=!0,this.acks[c.id]=b);return this.packet(c)},c.prototype.emit=function(a){var b=Array.prototype.slice.call(arguments,1),c=b[b.length-1],d={type:"event",name:a};"function"==typeof c&&(d.id=++this.ackPackets,d.ack="data",this.acks[d.id]=c,b=b.slice(0,b.length-1)),d.args=b;return this.packet(d)},c.prototype.disconnect=function(){this.name===""?this.socket.disconnect():(this.packet({type:"disconnect"}),this.$emit("disconnect"));return this},c.prototype.onPacket=function(a){function d(){c.packet({type:"ack",args:b.util.toArray(arguments),ackId:a.id})}var c=this;switch(a.type){case"connect":this.$emit("connect");break;case"disconnect":this.name===""?this.socket.onDisconnect(a.reason||"booted"):this.$emit("disconnect",a.reason);break;case"message":case"json":var e=["message",a.data];a.ack=="data"?e.push(d):a.ack&&this.packet({type:"ack",ackId:a.id}),this.$emit.apply(this,e);break;case"event":var e=[a.name].concat(a.args);a.ack=="data"&&e.push(d),this.$emit.apply(this,e);break;case"ack":this.acks[a.ackId]&&(this.acks[a.ackId].apply(this,a.args),delete this.acks[a.ackId]);break;case"error":a.advice?this.socket.onError(a):a.reason=="unauthorized"?this.$emit("connect_failed",a.reason):this.$emit("error",a.reason)}},d.prototype.send=function(){this.namespace.flags[this.name]=!0,this.namespace.send.apply(this.namespace,arguments)},d.prototype.emit=function(){this.namespace.flags[this.name]=!0,this.namespace.emit.apply(this.namespace,arguments)}}("undefined"!=typeof io?io:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b){function c(a){b.Transport.apply(this,arguments)}a.websocket=c,b.util.inherit(c,b.Transport),c.prototype.name="websocket",c.prototype.open=function(){var a=b.util.query(this.socket.options.query),c=this,d;d||(d=window.MozWebSocket||window.WebSocket),this.websocket=new d(this.prepareUrl()+a),this.websocket.onopen=function(){c.onOpen(),c.socket.setBuffer(!1)},this.websocket.onmessage=function(a){c.onData(a.data)},this.websocket.onclose=function(){c.onClose(),c.socket.setBuffer(!0)},this.websocket.onerror=function(a){c.onError(a)};return this},c.prototype.send=function(a){this.websocket.send(a);return this},c.prototype.payload=function(a){for(var b=0,c=a.length;b<c;b++)this.packet(a[b]);return this},c.prototype.close=function(){this.websocket.close();return this},c.prototype.onError=function(a){this.socket.onError(a)},c.prototype.scheme=function(){return this.socket.options.secure?"wss":"ws"},c.check=function(){return"WebSocket"in window&&!("__addTask"in WebSocket)||"MozWebSocket"in window},c.xdomainCheck=function(){return!0},b.transports.push("websocket")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b){function c(){b.Transport.websocket.apply(this,arguments)}a.flashsocket=c,b.util.inherit(c,b.Transport.websocket),c.prototype.name="flashsocket",c.prototype.open=function(){var a=this,c=arguments;WebSocket.__addTask(function(){b.Transport.websocket.prototype.open.apply(a,c)});return this},c.prototype.send=function(){var a=this,c=arguments;WebSocket.__addTask(function(){b.Transport.websocket.prototype.send.apply(a,c)});return this},c.prototype.close=function(){WebSocket.__tasks.length=0,b.Transport.websocket.prototype.close.call(this);return this},c.prototype.ready=function(a,d){function e(){var b=a.options,e=b["flash policy port"],g=["http"+(b.secure?"s":"")+":/",b.host+":"+b.port,b.resource,"static/flashsocket","WebSocketMain"+(a.isXDomain()?"Insecure":"")+".swf"];c.loaded||(typeof WEB_SOCKET_SWF_LOCATION=="undefined"&&(WEB_SOCKET_SWF_LOCATION=g.join("/")),e!==843&&WebSocket.loadFlashPolicyFile("xmlsocket://"+b.host+":"+e),WebSocket.__initialize(),c.loaded=!0),d.call(f)}var f=this;if(document.body)return e();b.util.load(e)},c.check=function(){return typeof WebSocket!="undefined"&&"__initialize"in WebSocket&&!!swfobject?swfobject.getFlashPlayerVersion().major>=10:!1},c.xdomainCheck=function(){return!0},typeof window!="undefined"&&(WEB_SOCKET_DISABLE_AUTO_INITIALIZATION=!0),b.transports.push("flashsocket")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports);var swfobject=function(){function V(b){var c=/[\\\"<>\.;]/,d=c.exec(b)!=null;return d&&typeof encodeURIComponent!=a?encodeURIComponent(b):b}function U(a,b){if(!!x){var c=b?"visible":"hidden";t&&P(a)?P(a).style.visibility=c:T("#"+a,"visibility:"+c)}}function T(c,d,e,f){if(!y.ie||!y.mac){var g=i.getElementsByTagName("head")[0];if(!g)return;var h=e&&typeof e=="string"?e:"screen";f&&(v=null,w=null);if(!v||w!=h){var j=Q("style");j.setAttribute("type","text/css"),j.setAttribute("media",h),v=g.appendChild(j),y.ie&&y.win&&typeof i.styleSheets!=a&&i.styleSheets.length>0&&(v=i.styleSheets[i.styleSheets.length-1]),w=h}y.ie&&y.win?v&&typeof v.addRule==b&&v.addRule(c,d):v&&typeof i.createTextNode!=a&&v.appendChild(i.createTextNode(c+" {"+d+"}"))}}function S(a){var b=y.pv,c=a.split(".");c[0]=parseInt(c[0],10),c[1]=parseInt(c[1],10)||0,c[2]=parseInt(c[2],10)||0;return b[0]>c[0]||b[0]==c[0]&&b[1]>c[1]||b[0]==c[0]&&b[1]==c[1]&&b[2]>=c[2]?!0:!1}function R(a,b,c){a.attachEvent(b,c),o[o.length]=[a,b,c]}function Q(a){return i.createElement(a)}function P(a){var b=null;try{b=i.getElementById(a)}catch(c){}return b}function O(a){var b=P(a);if(b){for(var c in b)typeof b[c]=="function"&&(b[c]=null);b.parentNode.removeChild(b)}}function N(a){var b=P(a);b&&b.nodeName=="OBJECT"&&(y.ie&&y.win?(b.style.display="none",function(){b.readyState==4?O(a):setTimeout(arguments.callee,10)}()):b.parentNode.removeChild(b))}function M(a,b,c){var d=Q("param");d.setAttribute("name",b),d.setAttribute("value",c),a.appendChild(d)}function L(c,d,f){var g,h=P(f);if(y.wk&&y.wk<312)return g;if(h){typeof c.id==a&&(c.id=f);if(y.ie&&y.win){var i="";for(var j in c)c[j]!=Object.prototype[j]&&(j.toLowerCase()=="data"?d.movie=c[j]:j.toLowerCase()=="styleclass"?i+=' class="'+c[j]+'"':j.toLowerCase()!="classid"&&(i+=" "+j+'="'+c[j]+'"'));var k="";for(var l in d)d[l]!=Object.prototype[l]&&(k+='<param name="'+l+'" value="'+d[l]+'" />');h.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+i+">"+k+"</object>",n[n.length]=c.id,g=P(c.id)}else{var m=Q(b);m.setAttribute("type",e);for(var o in c)c[o]!=Object.prototype[o]&&(o.toLowerCase()=="styleclass"?m.setAttribute("class",c[o]):o.toLowerCase()!="classid"&&m.setAttribute(o,c[o]));for(var p in d)d[p]!=Object.prototype[p]&&p.toLowerCase()!="movie"&&M(m,p,d[p]);h.parentNode.replaceChild(m,h),g=m}}return g}function K(a){var c=Q("div");if(y.win&&y.ie)c.innerHTML=a.innerHTML;else{var d=a.getElementsByTagName(b)[0];if(d){var e=d.childNodes;if(e){var f=e.length;for(var g=0;g<f;g++)(e[g].nodeType!=1||e[g].nodeName!="PARAM")&&e[g].nodeType!=8&&c.appendChild(e[g].cloneNode(!0))}}}return c}function J(a){if(y.ie&&y.win&&a.readyState!=4){var b=Q("div");a.parentNode.insertBefore(b,a),b.parentNode.replaceChild(K(a),b),a.style.display="none",function(){a.readyState==4?a.parentNode.removeChild(a):setTimeout(arguments.callee,10)}()}else a.parentNode.replaceChild(K(a),a)}function I(b,c,d,e){u=!0,r=e||null,s={success:!1,id:d};var g=P(d);if(g){g.nodeName=="OBJECT"?(p=K(g),q=null):(p=g,q=d),b.id=f;if(typeof b.width==a||!/%$/.test(b.width)&&parseInt(b.width,10)<310)b.width="310";if(typeof b.height==a||!/%$/.test(b.height)&&parseInt(b.height,10)<137)b.height="137";i.title=i.title.slice(0,47)+" - Flash Player Installation";var j=y.ie&&y.win?"ActiveX":"PlugIn",k="MMredirectURL="+h.location.toString().replace(/&/g,"%26")+"&MMplayerType="+j+"&MMdoctitle="+i.title;typeof c.flashvars!=a?c.flashvars+="&"+k:c.flashvars=k;if(y.ie&&y.win&&g.readyState!=4){var l=Q("div");d+="SWFObjectNew",l.setAttribute("id",d),g.parentNode.insertBefore(l,g),g.style.display="none",function(){g.readyState==4?g.parentNode.removeChild(g):setTimeout(arguments.callee,10)}()}L(b,c,d)}}function H(){return!u&&S("6.0.65")&&(y.win||y.mac)&&!(y.wk&&y.wk<312)}function G(c){var d=null,e=P(c);if(e&&e.nodeName=="OBJECT")if(typeof e.SetVariable!=a)d=e;else{var f=e.getElementsByTagName(b)[0];f&&(d=f)}return d}function F(){var b=m.length;if(b>0)for(var c=0;c<b;c++){var d=m[c].id,e=m[c].callbackFn,f={success:!1,id:d};if(y.pv[0]>0){var g=P(d);if(g)if(S(m[c].swfVersion)&&!(y.wk&&y.wk<312))U(d,!0),e&&(f.success=!0,f.ref=G(d),e(f));else if(m[c].expressInstall&&H()){var h={};h.data=m[c].expressInstall,h.width=g.getAttribute("width")||"0",h.height=g.getAttribute("height")||"0",g.getAttribute("class")&&(h.styleclass=g.getAttribute("class")),g.getAttribute("align")&&(h.align=g.getAttribute("align"));var i={},j=g.getElementsByTagName("param"),k=j.length;for(var l=0;l<k;l++)j[l].getAttribute("name").toLowerCase()!="movie"&&(i[j[l].getAttribute("name")]=j[l].getAttribute("value"));I(h,i,d,e)}else J(g),e&&e(f)}else{U(d,!0);if(e){var n=G(d);n&&typeof n.SetVariable!=a&&(f.success=!0,f.ref=n),e(f)}}}}function E(){var c=i.getElementsByTagName("body")[0],d=Q(b);d.setAttribute("type",e);var f=c.appendChild(d);if(f){var g=0;(function(){if(typeof f.GetVariable!=a){var b=f.GetVariable("$version");b&&(b=b.split(" ")[1].split(","),y.pv=[parseInt(b[0],10),parseInt(b[1],10),parseInt(b[2],10)])}else if(g<10){g++,setTimeout(arguments.callee,10);return}c.removeChild(d),f=null,F()})()}else F()}function D(){k?E():F()}function C(b){if(typeof h.addEventListener!=a)h.addEventListener("load",b,!1);else if(typeof i.addEventListener!=a)i.addEventListener("load",b,!1);else if(typeof h.attachEvent!=a)R(h,"onload",b);else if(typeof h.onload=="function"){var c=h.onload;h.onload=function(){c(),b()}}else h.onload=b}function B(a){t?a():l[l.length]=a}function A(){if(!t){try{var a=i.getElementsByTagName("body")[0].appendChild(Q("span"));a.parentNode.removeChild(a)}catch(b){return}t=!0;var c=l.length;for(var d=0;d<c;d++)l[d]()}}var a="undefined",b="object",c="Shockwave Flash",d="ShockwaveFlash.ShockwaveFlash",e="application/x-shockwave-flash",f="SWFObjectExprInst",g="onreadystatechange",h=window,i=document,j=navigator,k=!1,l=[D],m=[],n=[],o=[],p,q,r,s,t=!1,u=!1,v,w,x=!0,y=function(){var f=typeof i.getElementById!=a&&typeof i.getElementsByTagName!=a&&typeof i.createElement!=a,g=j.userAgent.toLowerCase(),l=j.platform.toLowerCase(),m=l?/win/.test(l):/win/.test(g),n=l?/mac/.test(l):/mac/.test(g),o=/webkit/.test(g)?parseFloat(g.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):!1,p=!1,q=[0,0,0],r=null;if(typeof j.plugins!=a&&typeof j.plugins[c]==b)r=j.plugins[c].description,r&&(typeof j.mimeTypes==a||!j.mimeTypes[e]||!!j.mimeTypes[e].enabledPlugin)&&(k=!0,p=!1,r=r.replace(/^.*\s+(\S+\s+\S+$)/,"$1"),q[0]=parseInt(r.replace(/^(.*)\..*$/,"$1"),10),q[1]=parseInt(r.replace(/^.*\.(.*)\s.*$/,"$1"),10),q[2]=/[a-zA-Z]/.test(r)?parseInt(r.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0);else if(typeof h.ActiveXObject!=a)try{var s=new ActiveXObject(d);s&&(r=s.GetVariable("$version"),r&&(p=!0,r=r.split(" ")[1].split(","),q=[parseInt(r[0],10),parseInt(r[1],10),parseInt(r[2],10)]))}catch(t){}return{w3:f,pv:q,wk:o,ie:p,win:m,mac:n}}(),z=function(){!y.w3||((typeof i.readyState!=a&&i.readyState=="complete"||typeof i.readyState==a&&(i.getElementsByTagName("body")[0]||i.body))&&A(),t||(typeof i.addEventListener!=a&&i.addEventListener("DOMContentLoaded",A,!1),y.ie&&y.win&&(i.attachEvent(g,function(){i.readyState=="complete"&&(i.detachEvent(g,arguments.callee),A())}),h==top&&function(){if(!t){try{i.documentElement.doScroll("left")}catch(a){setTimeout(arguments.callee,0);return}A()}}()),y.wk&&function(){if(!t){if(!/loaded|complete/.test(i.readyState)){setTimeout(arguments.callee,0);return}A()}}(),C(A)))}(),W=function(){y.ie&&y.win&&window.attachEvent("onunload",function(){var a=o.length;for(var b=0;b<a;b++)o[b][0].detachEvent(o[b][1],o[b][2]);var c=n.length;for(var d=0;d<c;d++)N(n[d]);for(var e in y)y[e]=null;y=null;for(var f in swfobject)swfobject[f]=null;swfobject=null})}();return{registerObject:function(a,b,c,d){if(y.w3&&a&&b){var e={};e.id=a,e.swfVersion=b,e.expressInstall=c,e.callbackFn=d,m[m.length]=e,U(a,!1)}else d&&d({success:!1,id:a})},getObjectById:function(a){if(y.w3)return G(a)},embedSWF:function(c,d,e,f,g,h,i,j,k,l){var m={success:!1,id:d};y.w3&&!(y.wk&&y.wk<312)&&c&&d&&e&&f&&g?(U(d,!1),B(function(){e+="",f+="";var n={};if(k&&typeof k===b)for(var o in k)n[o]=k[o];n.data=c,n.width=e,n.height=f;var p={};if(j&&typeof j===b)for(var q in j)p[q]=j[q];if(i&&typeof i===b)for(var r in i)typeof p.flashvars!=a?p.flashvars+="&"+r+"="+i[r]:p.flashvars=r+"="+i[r];if(S(g)){var s=L(n,p,d);n.id==d&&U(d,!0),m.success=!0,m.ref=s}else{if(h&&H()){n.data=h,I(n,p,d,l);return}U(d,!0)}l&&l(m)})):l&&l(m)},switchOffAutoHideShow:function(){x=!1},ua:y,getFlashPlayerVersion:function(){return{major:y.pv[0],minor:y.pv[1],release:y.pv[2]}},hasFlashPlayerVersion:S,createSWF:function(a,b,c){return y.w3?L(a,b,c):undefined},showExpressInstall:function(a,b,c,d){y.w3&&H()&&I(a,b,c,d)},removeSWF:function(a){y.w3&&N(a)},createCSS:function(a,b,c,d){y.w3&&T(a,b,c,d)},addDomLoadEvent:B,addLoadEvent:C,getQueryParamValue:function(a){var b=i.location.search||i.location.hash;if(b){/\?/.test(b)&&(b=b.split("?")[1]);if(a==null)return V(b);var c=b.split("&");for(var d=0;d<c.length;d++)if(c[d].substring(0,c[d].indexOf("="))==a)return V(c[d].substring(c[d].indexOf("=")+1))}return""},expressInstallCallback:function(){if(u){var a=P(f);a&&p&&(a.parentNode.replaceChild(p,a),q&&(U(q,!0),y.ie&&y.win&&(p.style.display="block")),r&&r(s)),u=!1}}}}();(function(){if(!window.WebSocket){var a=window.console;if(!a||!a.log||!a.error)a={log:function(){},error:function(){}};if(!swfobject.hasFlashPlayerVersion("10.0.0")){a.error("Flash Player >= 10.0.0 is required.");return}location.protocol=="file:"&&a.error("WARNING: web-socket-js doesn't work in file:///... URL unless you set Flash Security Settings properly. Open the page via Web server i.e. http://..."),WebSocket=function(a,b,c,d,e){var f=this;f.__id=WebSocket.__nextId++,WebSocket.__instances[f.__id]=f,f.readyState=WebSocket.CONNECTING,f.bufferedAmount=0,f.__events={},b?typeof b=="string"&&(b=[b]):b=[],setTimeout(function(){WebSocket.__addTask(function(){WebSocket.__flash.create(f.__id,a,b,c||null,d||0,e||null)})},0)},WebSocket.prototype.send=function(a){if(this.readyState==WebSocket.CONNECTING)throw"INVALID_STATE_ERR: Web Socket connection has not been established";var b=WebSocket.__flash.send(this.__id,encodeURIComponent(a));if(b<0)return!0;this.bufferedAmount+=b;return!1},WebSocket.prototype.close=function(){this.readyState!=WebSocket.CLOSED&&this.readyState!=WebSocket.CLOSING&&(this.readyState=WebSocket.CLOSING,WebSocket.__flash.close(this.__id))},WebSocket.prototype.addEventListener=function(a,b,c){a in this.__events||(this.__events[a]=[]),this.__events[a].push(b)},WebSocket.prototype.removeEventListener=function(a,b,c){if(a in this.__events){var d=this.__events[a];for(var e=d.length-1;e>=0;--e)if(d[e]===b){d.splice(e,1);break}}},WebSocket.prototype.dispatchEvent=function(a){var b=this.__events[a.type]||[];for(var c=0;c<b.length;++c)b[c](a);var d=this["on"+a.type];d&&d(a)},WebSocket.prototype.__handleEvent=function(a){"readyState"in a&&(this.readyState=a.readyState),"protocol"in a&&(this.protocol=a.protocol);var b;if(a.type=="open"||a.type=="error")b=this.__createSimpleEvent(a.type);else if(a.type=="close")b=this.__createSimpleEvent("close");else{if(a.type!="message")throw"unknown event type: "+a.type;var c=decodeURIComponent(a.message);b=this.__createMessageEvent("message",c)}this.dispatchEvent(b)},WebSocket.prototype.__createSimpleEvent=function(a){if(document.createEvent&&window.Event){var b=document.createEvent("Event");b.initEvent(a,!1,!1);return b}return{type:a,bubbles:!1,cancelable:!1}},WebSocket.prototype.__createMessageEvent=function(a,b){if(document.createEvent&&window.MessageEvent&&!window.opera){var c=document.createEvent("MessageEvent");c.initMessageEvent("message",!1,!1,b,null,null,window,null);return c}return{type:a,data:b,bubbles:!1,cancelable:!1}},WebSocket.CONNECTING=0,WebSocket.OPEN=1,WebSocket.CLOSING=2,WebSocket.CLOSED=3,WebSocket.__flash=null,WebSocket.__instances={},WebSocket.__tasks=[],WebSocket.__nextId=0,WebSocket.loadFlashPolicyFile=function(a){WebSocket.__addTask(function(){WebSocket.__flash.loadManualPolicyFile(a)})},WebSocket.__initialize=function(){if(!WebSocket.__flash){WebSocket.__swfLocation&&(window.WEB_SOCKET_SWF_LOCATION=WebSocket.__swfLocation);if(!window.WEB_SOCKET_SWF_LOCATION){a.error("[WebSocket] set WEB_SOCKET_SWF_LOCATION to location of WebSocketMain.swf");return}var b=document.createElement("div");b.id="webSocketContainer",b.style.position="absolute",WebSocket.__isFlashLite()?(b.style.left="0px",b.style.top="0px"):(b.style.left="-100px",b.style.top="-100px");var c=document.createElement("div");c.id="webSocketFlash",b.appendChild(c),document.body.appendChild(b),swfobject.embedSWF(WEB_SOCKET_SWF_LOCATION,"webSocketFlash","1","1","10.0.0",null,null,{hasPriority:!0,swliveconnect:!0,allowScriptAccess:"always"},null,function(b){b.success||a.error("[WebSocket] swfobject.embedSWF failed")})}},WebSocket.__onFlashInitialized=function(){setTimeout(function(){WebSocket.__flash=document.getElementById("webSocketFlash"),WebSocket.__flash.setCallerUrl(location.href),WebSocket.__flash.setDebug(!!window.WEB_SOCKET_DEBUG);for(var a=0;a<WebSocket.__tasks.length;++a)WebSocket.__tasks[a]();WebSocket.__tasks=[]},0)},WebSocket.__onFlashEvent=function(){setTimeout(function(){try{var b=WebSocket.__flash.receiveEvents();for(var c=0;c<b.length;++c)WebSocket.__instances[b[c].webSocketId].__handleEvent(b[c])}catch(d){a.error(d)}},0);return!0},WebSocket.__log=function(b){a.log(decodeURIComponent(b))},WebSocket.__error=function(b){a.error(decodeURIComponent(b))},WebSocket.__addTask=function(a){WebSocket.__flash?a():WebSocket.__tasks.push(a)},WebSocket.__isFlashLite=function(){if(!window.navigator||!window.navigator.mimeTypes)return!1;var a=window.navigator.mimeTypes["application/x-shockwave-flash"];return!a||!a.enabledPlugin||!a.enabledPlugin.filename?!1:a.enabledPlugin.filename.match(/flashlite/i)?!0:!1},window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION||(window.addEventListener?window.addEventListener("load",function(){WebSocket.__initialize()},!1):window.attachEvent("onload",function(){WebSocket.__initialize()}))}})(),function(a,b,c){function e(){}function d(a){!a||(b.Transport.apply(this,arguments),this.sendBuffer=[])}a.XHR=d,b.util.inherit(d,b.Transport),d.prototype.open=function(){this.socket.setBuffer(!1),this.onOpen(),this.get(),this.setCloseTimeout();return this},d.prototype.payload=function(a){var c=[];for(var d=0,e=a.length;d<e;d++)c.push(b.parser.encodePacket(a[d]));this.send(b.parser.encodePayload(c))},d.prototype.send=function(a){this.post(a);return this},d.prototype.post=function(a){function f(){this.onload=e,b.socket.setBuffer(!1)}function d(){this.readyState==4&&(this.onreadystatechange=e,b.posting=!1,this.status==200?b.socket.setBuffer(!1):b.onClose())}var b=this;this.socket.setBuffer(!0),this.sendXHR=this.request("POST"),c.XDomainRequest&&this.sendXHR instanceof XDomainRequest?this.sendXHR.onload=this.sendXHR.onerror=f:this.sendXHR.onreadystatechange=d,this.sendXHR.send(a)},d.prototype.close=function(){this.onClose();return this},d.prototype.request=function(a){var c=b.util.request(this.socket.isXDomain()),d=b.util.query(this.socket.options.query,"t="+ +(new Date));c.open(a||"GET",this.prepareUrl()+d,!0);if(a=="POST")try{c.setRequestHeader?c.setRequestHeader("Content-type","text/plain;charset=UTF-8"):c.contentType="text/plain"}catch(e){}return c},d.prototype.scheme=function(){return this.socket.options.secure?"https":"http"},d.check=function(a,c){try{if(b.util.request(c))return!0}catch(d){}return!1},d.xdomainCheck=function(){return d.check(null,!0)}}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports,this),function(a,b){function c(a){b.Transport.XHR.apply(this,arguments)}a.htmlfile=c,b.util.inherit(c,b.Transport.XHR),c.prototype.name="htmlfile",c.prototype.get=function(){this.doc=new ActiveXObject("htmlfile"),this.doc.open(),this.doc.write("<html></html>"),this.doc.close(),this.doc.parentWindow.s=this;var a=this.doc.createElement("div");a.className="socketio",this.doc.body.appendChild(a),this.iframe=this.doc.createElement("iframe"),a.appendChild(this.iframe);var c=this,d=b.util.query(this.socket.options.query,"t="+ +(new Date));this.iframe.src=this.prepareUrl()+d,b.util.on(window,"unload",function(){c.destroy()})},c.prototype._=function(a,b){this.onData(a);try{var c=b.getElementsByTagName("script")[0];c.parentNode.removeChild(c)}catch(d){}},c.prototype.destroy=function(){if(this.iframe){try{this.iframe.src="about:blank"}catch(a){}this.doc=null,this.iframe.parentNode.removeChild(this.iframe),this.iframe=null,CollectGarbage()}},c.prototype.close=function(){this.destroy();return b.Transport.XHR.prototype.close.call(this)},c.check=function(){if("ActiveXObject"in window)try{var a=new ActiveXObject("htmlfile");return a&&b.Transport.XHR.check()}catch(c){}return!1},c.xdomainCheck=function(){return!1},b.transports.push("htmlfile")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports),function(a,b,c){function e(){}function d(){b.Transport.XHR.apply(this,arguments)}a["xhr-polling"]=d,b.util.inherit(d,b.Transport.XHR),b.util.merge(d,b.Transport.XHR),d.prototype.name="xhr-polling",d.prototype.open=function(){var a=this;b.Transport.XHR.prototype.open.call(a);return!1},d.prototype.get=function(){function d(){this.onload=e,a.onData(this.responseText),a.get()}function b(){this.readyState==4&&(this.onreadystatechange=e,this.status==200?(a.onData(this.responseText),a.get()):a.onClose())}if(!!this.open){var a=this;this.xhr=this.request(),c.XDomainRequest&&this.xhr instanceof XDomainRequest?this.xhr.onload=this.xhr.onerror=d:this.xhr.onreadystatechange=b,this.xhr.send(null)}},d.prototype.onClose=function(){b.Transport.XHR.prototype.onClose.call(this);if(this.xhr){this.xhr.onreadystatechange=this.xhr.onload=e;try{this.xhr.abort()}catch(a){}this.xhr=null}},d.prototype.ready=function(a,c){var d=this;b.util.defer(function(){c.call(d)})},b.transports.push("xhr-polling")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports,this),function(a,b){function c(a){b.Transport["xhr-polling"].apply(this,arguments),this.index=b.j.length;var c=this;b.j.push(function(a){c._(a)})}a["jsonp-polling"]=c,b.util.inherit(c,b.Transport["xhr-polling"]),c.prototype.name="jsonp-polling",c.prototype.post=function(a){function j(){c.iframe&&c.form.removeChild(c.iframe);try{h=document.createElement('<iframe name="'+c.iframeId+'">')}catch(a){h=document.createElement("iframe"),h.name=c.iframeId}h.id=c.iframeId,c.form.appendChild(h),c.iframe=h}function i(){j(),c.socket.setBuffer(!1)}var c=this,d=b.util.query(this.socket.options.query,"t="+ +(new Date)+"&i="+this.index);if(!this.form){var e=document.createElement("form"),f=document.createElement("textarea"),g=this.iframeId="socketio_iframe_"+this.index,h;e.className="socketio",e.style.position="absolute",e.style.top="-1000px",e.style.left="-1000px",e.target=g,e.method="POST",e.setAttribute("accept-charset","utf-8"),f.name="d",e.appendChild(f),document.body.appendChild(e),this.form=e,this.area=f}this.form.action=this.prepareUrl()+d,j(),this.area.value=a;try{this.form.submit()}catch(k){}this.iframe.attachEvent?h.onreadystatechange=function(){c.iframe.readyState=="complete"&&i()}:this.iframe.onload=i,this.socket.setBuffer(!0)},c.prototype.get=function(){var a=this,c=document.createElement("script"),d=b.util.query(this.socket.options.query,"t="+ +(new Date)+"&i="+this.index);this.script&&(this.script.parentNode.removeChild(this.script),this.script=null),c.async=!0,c.src=this.prepareUrl()+d,c.onerror=function(){a.onClose()};var e=document.getElementsByTagName("script")[0];e.parentNode.insertBefore(c,e),this.script=c},c.prototype._=function(a){this.onData(a),this.open&&this.get();return this},c.check=function(){return!0},c.xdomainCheck=function(){return!0},b.transports.push("jsonp-polling")}("undefined"!=typeof io?io.Transport:module.exports,"undefined"!=typeof io?io:module.parent.exports)
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/public/stylesheets/bootstrap.min.css
@@ -0,0 +1,356 @@
+html,body{margin:0;padding:0;}
+h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,cite,code,del,dfn,em,img,q,s,samp,small,strike,strong,sub,sup,tt,var,dd,dl,dt,li,ol,ul,fieldset,form,label,legend,button,table,caption,tbody,tfoot,thead,tr,th,td{margin:0;padding:0;border:0;font-weight:normal;font-style:normal;font-size:100%;line-height:1;font-family:inherit;}
+table{border-collapse:collapse;border-spacing:0;}
+ol,ul{list-style:none;}
+q:before,q:after,blockquote:before,blockquote:after{content:"";}
+html{overflow-y:scroll;font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}
+a:focus{outline:thin dotted;}
+a:hover,a:active{outline:0;}
+article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;}
+audio,canvas,video{display:inline-block;*display:inline;*zoom:1;}
+audio:not([controls]){display:none;}
+sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline;}
+sup{top:-0.5em;}
+sub{bottom:-0.25em;}
+img{border:0;-ms-interpolation-mode:bicubic;}
+button,input,select,textarea{font-size:100%;margin:0;vertical-align:baseline;*vertical-align:middle;}
+button,input{line-height:normal;*overflow:visible;}
+button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0;}
+button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;}
+input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;}
+input[type="search"]::-webkit-search-decoration{-webkit-appearance:none;}
+textarea{overflow:auto;vertical-align:top;}
+body{background-color:#ffffff;margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:18px;color:#404040;}
+.container{width:940px;margin-left:auto;margin-right:auto;zoom:1;}.container:before,.container:after{display:table;content:"";zoom:1;}
+.container:after{clear:both;}
+.container-fluid{position:relative;min-width:940px;padding-left:20px;padding-right:20px;zoom:1;}.container-fluid:before,.container-fluid:after{display:table;content:"";zoom:1;}
+.container-fluid:after{clear:both;}
+.container-fluid>.sidebar{position:absolute;top:0;left:20px;width:220px;}
+.container-fluid>.content{margin-left:240px;}
+a{color:#0069d6;text-decoration:none;line-height:inherit;font-weight:inherit;}a:hover{color:#00438a;text-decoration:underline;}
+.pull-right{float:right;}
+.pull-left{float:left;}
+.hide{display:none;}
+.show{display:block;}
+.row{zoom:1;margin-left:-20px;}.row:before,.row:after{display:table;content:"";zoom:1;}
+.row:after{clear:both;}
+.row>[class*="span"]{display:inline;float:left;margin-left:20px;}
+.span1{width:40px;}
+.span2{width:100px;}
+.span3{width:160px;}
+.span4{width:220px;}
+.span5{width:280px;}
+.span6{width:340px;}
+.span7{width:400px;}
+.span8{width:460px;}
+.span9{width:520px;}
+.span10{width:580px;}
+.span11{width:640px;}
+.span12{width:700px;}
+.span13{width:760px;}
+.span14{width:820px;}
+.span15{width:880px;}
+.span16{width:940px;}
+.span17{width:1000px;}
+.span18{width:1060px;}
+.span19{width:1120px;}
+.span20{width:1180px;}
+.span21{width:1240px;}
+.span22{width:1300px;}
+.span23{width:1360px;}
+.span24{width:1420px;}
+.row>.offset1{margin-left:80px;}
+.row>.offset2{margin-left:140px;}
+.row>.offset3{margin-left:200px;}
+.row>.offset4{margin-left:260px;}
+.row>.offset5{margin-left:320px;}
+.row>.offset6{margin-left:380px;}
+.row>.offset7{margin-left:440px;}
+.row>.offset8{margin-left:500px;}
+.row>.offset9{margin-left:560px;}
+.row>.offset10{margin-left:620px;}
+.row>.offset11{margin-left:680px;}
+.row>.offset12{margin-left:740px;}
+.span-one-third{width:300px;}
+.span-two-thirds{width:620px;}
+.row>.offset-one-third{margin-left:340px;}
+.row>.offset-two-thirds{margin-left:660px;}
+p{font-size:13px;font-weight:normal;line-height:18px;margin-bottom:9px;}p small{font-size:11px;color:#bfbfbf;}
+h1,h2,h3,h4,h5,h6{font-weight:bold;color:#404040;}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{color:#bfbfbf;}
+h1{margin-bottom:18px;font-size:30px;line-height:36px;}h1 small{font-size:18px;}
+h2{font-size:24px;line-height:36px;}h2 small{font-size:14px;}
+h3,h4,h5,h6{line-height:36px;}
+h3{font-size:18px;}h3 small{font-size:14px;}
+h4{font-size:16px;}h4 small{font-size:12px;}
+h5{font-size:14px;}
+h6{font-size:13px;color:#bfbfbf;text-transform:uppercase;}
+ul,ol{margin:0 0 18px 25px;}
+ul ul,ul ol,ol ol,ol ul{margin-bottom:0;}
+ul{list-style:disc;}
+ol{list-style:decimal;}
+li{line-height:18px;color:#808080;}
+ul.unstyled{list-style:none;margin-left:0;}
+dl{margin-bottom:18px;}dl dt,dl dd{line-height:18px;}
+dl dt{font-weight:bold;}
+dl dd{margin-left:9px;}
+hr{margin:20px 0 19px;border:0;border-bottom:1px solid #eee;}
+strong{font-style:inherit;font-weight:bold;}
+em{font-style:italic;font-weight:inherit;line-height:inherit;}
+.muted{color:#bfbfbf;}
+blockquote{margin-bottom:18px;border-left:5px solid #eee;padding-left:15px;}blockquote p{font-size:14px;font-weight:300;line-height:18px;margin-bottom:0;}
+blockquote small{display:block;font-size:12px;font-weight:300;line-height:18px;color:#bfbfbf;}blockquote small:before{content:'\2014 \00A0';}
+address{display:block;line-height:18px;margin-bottom:18px;}
+code,pre{padding:0 3px 2px;font-family:Monaco, Andale Mono, Courier New, monospace;font-size:12px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+code{background-color:#fee9cc;color:rgba(0, 0, 0, 0.75);padding:1px 3px;}
+pre{background-color:#f5f5f5;display:block;padding:8.5px;margin:0 0 18px;line-height:18px;font-size:12px;border:1px solid #ccc;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;white-space:pre;white-space:pre-wrap;word-wrap:break-word;}
+form{margin-bottom:18px;}
+fieldset{margin-bottom:18px;padding-top:18px;}fieldset legend{display:block;padding-left:150px;font-size:19.5px;line-height:1;color:#404040;*padding:0 0 5px 145px;*line-height:1.5;}
+form .clearfix{margin-bottom:18px;zoom:1;}form .clearfix:before,form .clearfix:after{display:table;content:"";zoom:1;}
+form .clearfix:after{clear:both;}
+label,input,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:normal;}
+label{padding-top:6px;font-size:13px;line-height:18px;float:left;width:130px;text-align:right;color:#404040;}
+form .input{margin-left:150px;}
+input[type=checkbox],input[type=radio]{cursor:pointer;}
+input,textarea,select,.uneditable-input{display:inline-block;width:210px;height:18px;padding:4px;font-size:13px;line-height:18px;color:#808080;border:1px solid #ccc;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}
+select{padding:initial;}
+input[type=checkbox],input[type=radio]{width:auto;height:auto;padding:0;margin:3px 0;*margin-top:0;line-height:normal;border:none;}
+input[type=file]{background-color:#ffffff;padding:initial;border:initial;line-height:initial;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
+input[type=button],input[type=reset],input[type=submit]{width:auto;height:auto;}
+select,input[type=file]{height:27px;*height:auto;line-height:27px;*margin-top:4px;}
+select[multiple]{height:inherit;background-color:#ffffff;}
+textarea{height:auto;}
+.uneditable-input{background-color:#ffffff;display:block;border-color:#eee;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.025);cursor:not-allowed;}
+:-moz-placeholder{color:#bfbfbf;}
+::-webkit-input-placeholder{color:#bfbfbf;}
+input,textarea{-webkit-transition:border linear 0.2s,box-shadow linear 0.2s;-moz-transition:border linear 0.2s,box-shadow linear 0.2s;-ms-transition:border linear 0.2s,box-shadow linear 0.2s;-o-transition:border linear 0.2s,box-shadow linear 0.2s;transition:border linear 0.2s,box-shadow linear 0.2s;-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1);}
+input:focus,textarea:focus{outline:0;border-color:rgba(82, 168, 236, 0.8);-webkit-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);-moz-box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);box-shadow:inset 0 1px 3px rgba(0, 0, 0, 0.1),0 0 8px rgba(82, 168, 236, 0.6);}
+input[type=file]:focus,input[type=checkbox]:focus,select:focus{-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;outline:1px dotted #666;}
+form .clearfix.error>label,form .clearfix.error .help-block,form .clearfix.error .help-inline{color:#b94a48;}
+form .clearfix.error input,form .clearfix.error textarea{color:#b94a48;border-color:#ee5f5b;}form .clearfix.error input:focus,form .clearfix.error textarea:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7;}
+form .clearfix.error .input-prepend .add-on,form .clearfix.error .input-append .add-on{color:#b94a48;background-color:#fce6e6;border-color:#b94a48;}
+form .clearfix.warning>label,form .clearfix.warning .help-block,form .clearfix.warning .help-inline{color:#c09853;}
+form .clearfix.warning input,form .clearfix.warning textarea{color:#c09853;border-color:#ccae64;}form .clearfix.warning input:focus,form .clearfix.warning textarea:focus{border-color:#be9a3f;-webkit-box-shadow:0 0 6px #e5d6b1;-moz-box-shadow:0 0 6px #e5d6b1;box-shadow:0 0 6px #e5d6b1;}
+form .clearfix.warning .input-prepend .add-on,form .clearfix.warning .input-append .add-on{color:#c09853;background-color:#d2b877;border-color:#c09853;}
+form .clearfix.success>label,form .clearfix.success .help-block,form .clearfix.success .help-inline{color:#468847;}
+form .clearfix.success input,form .clearfix.success textarea{color:#468847;border-color:#57a957;}form .clearfix.success input:focus,form .clearfix.success textarea:focus{border-color:#458845;-webkit-box-shadow:0 0 6px #9acc9a;-moz-box-shadow:0 0 6px #9acc9a;box-shadow:0 0 6px #9acc9a;}
+form .clearfix.success .input-prepend .add-on,form .clearfix.success .input-append .add-on{color:#468847;background-color:#bcddbc;border-color:#468847;}
+.input-mini,input.mini,textarea.mini,select.mini{width:60px;}
+.input-small,input.small,textarea.small,select.small{width:90px;}
+.input-medium,input.medium,textarea.medium,select.medium{width:150px;}
+.input-large,input.large,textarea.large,select.large{width:210px;}
+.input-xlarge,input.xlarge,textarea.xlarge,select.xlarge{width:270px;}
+.input-xxlarge,input.xxlarge,textarea.xxlarge,select.xxlarge{width:530px;}
+textarea.xxlarge{overflow-y:auto;}
+input.span1,textarea.span1{display:inline-block;float:none;width:30px;margin-left:0;}
+input.span2,textarea.span2{display:inline-block;float:none;width:90px;margin-left:0;}
+input.span3,textarea.span3{display:inline-block;float:none;width:150px;margin-left:0;}
+input.span4,textarea.span4{display:inline-block;float:none;width:210px;margin-left:0;}
+input.span5,textarea.span5{display:inline-block;float:none;width:270px;margin-left:0;}
+input.span6,textarea.span6{display:inline-block;float:none;width:330px;margin-left:0;}
+input.span7,textarea.span7{display:inline-block;float:none;width:390px;margin-left:0;}
+input.span8,textarea.span8{display:inline-block;float:none;width:450px;margin-left:0;}
+input.span9,textarea.span9{display:inline-block;float:none;width:510px;margin-left:0;}
+input.span10,textarea.span10{display:inline-block;float:none;width:570px;margin-left:0;}
+input.span11,textarea.span11{display:inline-block;float:none;width:630px;margin-left:0;}
+input.span12,textarea.span12{display:inline-block;float:none;width:690px;margin-left:0;}
+input.span13,textarea.span13{display:inline-block;float:none;width:750px;margin-left:0;}
+input.span14,textarea.span14{display:inline-block;float:none;width:810px;margin-left:0;}
+input.span15,textarea.span15{display:inline-block;float:none;width:870px;margin-left:0;}
+input.span16,textarea.span16{display:inline-block;float:none;width:930px;margin-left:0;}
+input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{background-color:#f5f5f5;border-color:#ddd;cursor:not-allowed;}
+.actions{background:#f5f5f5;margin-top:18px;margin-bottom:18px;padding:17px 20px 18px 150px;border-top:1px solid #ddd;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;}.actions .secondary-action{float:right;}.actions .secondary-action a{line-height:30px;}.actions .secondary-action a:hover{text-decoration:underline;}
+.help-inline,.help-block{font-size:13px;line-height:18px;color:#bfbfbf;}
+.help-inline{padding-left:5px;*position:relative;*top:-5px;}
+.help-block{display:block;max-width:600px;}
+.inline-inputs{color:#808080;}.inline-inputs span{padding:0 2px 0 1px;}
+.input-prepend input,.input-append input{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;}
+.input-prepend .add-on,.input-append .add-on{position:relative;background:#f5f5f5;border:1px solid #ccc;z-index:2;float:left;display:block;width:auto;min-width:16px;height:18px;padding:4px 4px 4px 5px;margin-right:-1px;font-weight:normal;line-height:18px;color:#bfbfbf;text-align:center;text-shadow:0 1px 0 #ffffff;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
+.input-prepend .active,.input-append .active{background:#a9dba9;border-color:#46a546;}
+.input-prepend .add-on{*margin-top:1px;}
+.input-append input{float:left;-webkit-border-radius:3px 0 0 3px;-moz-border-radius:3px 0 0 3px;border-radius:3px 0 0 3px;}
+.input-append .add-on{-webkit-border-radius:0 3px 3px 0;-moz-border-radius:0 3px 3px 0;border-radius:0 3px 3px 0;margin-right:0;margin-left:-1px;}
+.inputs-list{margin:0 0 5px;width:100%;}.inputs-list li{display:block;padding:0;width:100%;}
+.inputs-list label{display:block;float:none;width:auto;padding:0;margin-left:20px;line-height:18px;text-align:left;white-space:normal;}.inputs-list label strong{color:#808080;}
+.inputs-list label small{font-size:11px;font-weight:normal;}
+.inputs-list .inputs-list{margin-left:25px;margin-bottom:10px;padding-top:0;}
+.inputs-list:first-child{padding-top:6px;}
+.inputs-list li+li{padding-top:2px;}
+.inputs-list input[type=radio],.inputs-list input[type=checkbox]{margin-bottom:0;margin-left:-20px;float:left;}
+.form-stacked{padding-left:20px;}.form-stacked fieldset{padding-top:9px;}
+.form-stacked legend{padding-left:0;}
+.form-stacked label{display:block;float:none;width:auto;font-weight:bold;text-align:left;line-height:20px;padding-top:0;}
+.form-stacked .clearfix{margin-bottom:9px;}.form-stacked .clearfix div.input{margin-left:0;}
+.form-stacked .inputs-list{margin-bottom:0;}.form-stacked .inputs-list li{padding-top:0;}.form-stacked .inputs-list li label{font-weight:normal;padding-top:0;}
+.form-stacked div.clearfix.error{padding-top:10px;padding-bottom:10px;padding-left:10px;margin-top:0;margin-left:-10px;}
+.form-stacked .actions{margin-left:-20px;padding-left:20px;}
+table{width:100%;margin-bottom:18px;padding:0;font-size:13px;border-collapse:collapse;}table th,table td{padding:10px 10px 9px;line-height:18px;text-align:left;}
+table th{padding-top:9px;font-weight:bold;vertical-align:middle;}
+table td{vertical-align:top;border-top:1px solid #ddd;}
+table tbody th{border-top:1px solid #ddd;vertical-align:top;}
+.condensed-table th,.condensed-table td{padding:5px 5px 4px;}
+.bordered-table{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}.bordered-table th+th,.bordered-table td+td,.bordered-table th+td{border-left:1px solid #ddd;}
+.bordered-table thead tr:first-child th:first-child,.bordered-table tbody tr:first-child td:first-child{-webkit-border-radius:4px 0 0 0;-moz-border-radius:4px 0 0 0;border-radius:4px 0 0 0;}
+.bordered-table thead tr:first-child th:last-child,.bordered-table tbody tr:first-child td:last-child{-webkit-border-radius:0 4px 0 0;-moz-border-radius:0 4px 0 0;border-radius:0 4px 0 0;}
+.bordered-table tbody tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;}
+.bordered-table tbody tr:last-child td:last-child{-webkit-border-radius:0 0 4px 0;-moz-border-radius:0 0 4px 0;border-radius:0 0 4px 0;}
+table .span1{width:20px;}
+table .span2{width:60px;}
+table .span3{width:100px;}
+table .span4{width:140px;}
+table .span5{width:180px;}
+table .span6{width:220px;}
+table .span7{width:260px;}
+table .span8{width:300px;}
+table .span9{width:340px;}
+table .span10{width:380px;}
+table .span11{width:420px;}
+table .span12{width:460px;}
+table .span13{width:500px;}
+table .span14{width:540px;}
+table .span15{width:580px;}
+table .span16{width:620px;}
+.zebra-striped tbody tr:nth-child(odd) td,.zebra-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9;}
+.zebra-striped tbody tr:hover td,.zebra-striped tbody tr:hover th{background-color:#f5f5f5;}
+table .header{cursor:pointer;}table .header:after{content:"";float:right;margin-top:7px;border-width:0 4px 4px;border-style:solid;border-color:#000 transparent;visibility:hidden;}
+table .headerSortUp,table .headerSortDown{background-color:rgba(141, 192, 219, 0.25);text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);}
+table .header:hover:after{visibility:visible;}
+table .headerSortDown:after,table .headerSortDown:hover:after{visibility:visible;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;}
+table .headerSortUp:after{border-bottom:none;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #000;visibility:visible;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;filter:alpha(opacity=60);-khtml-opacity:0.6;-moz-opacity:0.6;opacity:0.6;}
+table .blue{color:#049cdb;border-bottom-color:#049cdb;}
+table .headerSortUp.blue,table .headerSortDown.blue{background-color:#ade6fe;}
+table .green{color:#46a546;border-bottom-color:#46a546;}
+table .headerSortUp.green,table .headerSortDown.green{background-color:#cdeacd;}
+table .red{color:#9d261d;border-bottom-color:#9d261d;}
+table .headerSortUp.red,table .headerSortDown.red{background-color:#f4c8c5;}
+table .yellow{color:#ffc40d;border-bottom-color:#ffc40d;}
+table .headerSortUp.yellow,table .headerSortDown.yellow{background-color:#fff6d9;}
+table .orange{color:#f89406;border-bottom-color:#f89406;}
+table .headerSortUp.orange,table .headerSortDown.orange{background-color:#fee9cc;}
+table .purple{color:#7a43b6;border-bottom-color:#7a43b6;}
+table .headerSortUp.purple,table .headerSortDown.purple{background-color:#e2d5f0;}
+.topbar{height:40px;position:fixed;top:0;left:0;right:0;z-index:10000;overflow:visible;}.topbar a{color:#bfbfbf;text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);}
+.topbar h3 a:hover,.topbar .brand:hover,.topbar ul .active>a{background-color:#333;background-color:rgba(255, 255, 255, 0.05);color:#ffffff;text-decoration:none;}
+.topbar h3{position:relative;}
+.topbar h3 a,.topbar .brand{float:left;display:block;padding:8px 20px 12px;margin-left:-20px;color:#ffffff;font-size:20px;font-weight:200;line-height:1;}
+.topbar p{margin:0;line-height:40px;}.topbar p a:hover{background-color:transparent;color:#ffffff;}
+.topbar form{float:left;margin:5px 0 0 0;position:relative;filter:alpha(opacity=100);-khtml-opacity:1;-moz-opacity:1;opacity:1;}
+.topbar form.pull-right{float:right;}
+.topbar input{background-color:#444;background-color:rgba(255, 255, 255, 0.3);font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:normal;font-weight:13px;line-height:1;padding:4px 9px;color:#ffffff;color:rgba(255, 255, 255, 0.75);border:1px solid #111;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 2px rgba(0, 0, 0, 0.1),0 1px 0px rgba(255, 255, 255, 0.25);-webkit-transition:none;-moz-transition:none;-ms-transition:none;-o-transition:none;transition:none;}.topbar input:-moz-placeholder{color:#e6e6e6;}
+.topbar input::-webkit-input-placeholder{color:#e6e6e6;}
+.topbar input:hover{background-color:#bfbfbf;background-color:rgba(255, 255, 255, 0.5);color:#ffffff;}
+.topbar input:focus,.topbar input.focused{outline:0;background-color:#ffffff;color:#404040;text-shadow:0 1px 0 #ffffff;border:0;padding:5px 10px;-webkit-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);-moz-box-shadow:0 0 3px rgba(0, 0, 0, 0.15);box-shadow:0 0 3px rgba(0, 0, 0, 0.15);}
+.topbar-inner,.topbar .fill{background-color:#222;background-color:#222222;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#333333), to(#222222));background-image:-moz-linear-gradient(top, #333333, #222222);background-image:-ms-linear-gradient(top, #333333, #222222);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #333333), color-stop(100%, #222222));background-image:-webkit-linear-gradient(top, #333333, #222222);background-image:-o-linear-gradient(top, #333333, #222222);background-image:linear-gradient(top, #333333, #222222);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#333333', endColorstr='#222222', GradientType=0);-webkit-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);-moz-box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);box-shadow:0 1px 3px rgba(0, 0, 0, 0.25),inset 0 -1px 0 rgba(0, 0, 0, 0.1);}
+.topbar div>ul,.nav{display:block;float:left;margin:0 10px 0 0;position:relative;left:0;}.topbar div>ul>li,.nav>li{display:block;float:left;}
+.topbar div>ul a,.nav a{display:block;float:none;padding:10px 10px 11px;line-height:19px;text-decoration:none;}.topbar div>ul a:hover,.nav a:hover{color:#ffffff;text-decoration:none;}
+.topbar div>ul .active>a,.nav .active>a{background-color:#222;background-color:rgba(0, 0, 0, 0.5);}
+.topbar div>ul.secondary-nav,.nav.secondary-nav{float:right;margin-left:10px;margin-right:0;}.topbar div>ul.secondary-nav .menu-dropdown,.nav.secondary-nav .menu-dropdown,.topbar div>ul.secondary-nav .dropdown-menu,.nav.secondary-nav .dropdown-menu{right:0;border:0;}
+.topbar div>ul a.menu:hover,.nav a.menu:hover,.topbar div>ul li.open .menu,.nav li.open .menu,.topbar div>ul .dropdown-toggle:hover,.nav .dropdown-toggle:hover,.topbar div>ul .dropdown.open .dropdown-toggle,.nav .dropdown.open .dropdown-toggle{background:#444;background:rgba(255, 255, 255, 0.05);}
+.topbar div>ul .menu-dropdown,.nav .menu-dropdown,.topbar div>ul .dropdown-menu,.nav .dropdown-menu{background-color:#333;}.topbar div>ul .menu-dropdown a.menu,.nav .menu-dropdown a.menu,.topbar div>ul .dropdown-menu a.menu,.nav .dropdown-menu a.menu,.topbar div>ul .menu-dropdown .dropdown-toggle,.nav .menu-dropdown .dropdown-toggle,.topbar div>ul .dropdown-menu .dropdown-toggle,.nav .dropdown-menu .dropdown-toggle{color:#ffffff;}.topbar div>ul .menu-dropdown a.menu.open,.nav .menu-dropdown a.menu.open,.topbar div>ul .dropdown-menu a.menu.open,.nav .dropdown-menu a.menu.open,.topbar div>ul .menu-dropdown .dropdown-toggle.open,.nav .menu-dropdown .dropdown-toggle.open,.topbar div>ul .dropdown-menu .dropdown-toggle.open,.nav .dropdown-menu .dropdown-toggle.open{background:#444;background:rgba(255, 255, 255, 0.05);}
+.topbar div>ul .menu-dropdown li a,.nav .menu-dropdown li a,.topbar div>ul .dropdown-menu li a,.nav .dropdown-menu li a{color:#999;text-shadow:0 1px 0 rgba(0, 0, 0, 0.5);}.topbar div>ul .menu-dropdown li a:hover,.nav .menu-dropdown li a:hover,.topbar div>ul .dropdown-menu li a:hover,.nav .dropdown-menu li a:hover{background-color:#191919;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#292929), to(#191919));background-image:-moz-linear-gradient(top, #292929, #191919);background-image:-ms-linear-gradient(top, #292929, #191919);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #292929), color-stop(100%, #191919));background-image:-webkit-linear-gradient(top, #292929, #191919);background-image:-o-linear-gradient(top, #292929, #191919);background-image:linear-gradient(top, #292929, #191919);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#292929', endColorstr='#191919', GradientType=0);color:#ffffff;}
+.topbar div>ul .menu-dropdown .active a,.nav .menu-dropdown .active a,.topbar div>ul .dropdown-menu .active a,.nav .dropdown-menu .active a{color:#ffffff;}
+.topbar div>ul .menu-dropdown .divider,.nav .menu-dropdown .divider,.topbar div>ul .dropdown-menu .divider,.nav .dropdown-menu .divider{background-color:#222;border-color:#444;}
+.topbar ul .menu-dropdown li a,.topbar ul .dropdown-menu li a{padding:4px 15px;}
+li.menu,.dropdown{position:relative;}
+a.menu:after,.dropdown-toggle:after{width:0;height:0;display:inline-block;content:"&darr;";text-indent:-99999px;vertical-align:top;margin-top:8px;margin-left:4px;border-left:4px solid transparent;border-right:4px solid transparent;border-top:4px solid #ffffff;filter:alpha(opacity=50);-khtml-opacity:0.5;-moz-opacity:0.5;opacity:0.5;}
+.menu-dropdown,.dropdown-menu{background-color:#ffffff;float:left;display:none;position:absolute;top:40px;z-index:900;min-width:160px;max-width:220px;_width:160px;margin-left:0;margin-right:0;padding:6px 0;zoom:1;border-color:#999;border-color:rgba(0, 0, 0, 0.2);border-style:solid;border-width:0 1px 1px;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-moz-box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);box-shadow:0 2px 4px rgba(0, 0, 0, 0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.menu-dropdown li,.dropdown-menu li{float:none;display:block;background-color:none;}
+.menu-dropdown .divider,.dropdown-menu .divider{height:1px;margin:5px 0;overflow:hidden;background-color:#eee;border-bottom:1px solid #ffffff;}
+.topbar .dropdown-menu a,.dropdown-menu a{display:block;padding:4px 15px;clear:both;font-weight:normal;line-height:18px;color:#808080;text-shadow:0 1px 0 #ffffff;}.topbar .dropdown-menu a:hover,.dropdown-menu a:hover,.topbar .dropdown-menu a.hover,.dropdown-menu a.hover{background-color:#dddddd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#eeeeee), to(#dddddd));background-image:-moz-linear-gradient(top, #eeeeee, #dddddd);background-image:-ms-linear-gradient(top, #eeeeee, #dddddd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #eeeeee), color-stop(100%, #dddddd));background-image:-webkit-linear-gradient(top, #eeeeee, #dddddd);background-image:-o-linear-gradient(top, #eeeeee, #dddddd);background-image:linear-gradient(top, #eeeeee, #dddddd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#dddddd', GradientType=0);color:#404040;text-decoration:none;-webkit-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);-moz-box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);box-shadow:inset 0 1px 0 rgba(0, 0, 0, 0.025),inset 0 -1px rgba(0, 0, 0, 0.025);}
+.open .menu,.dropdown.open .menu,.open .dropdown-toggle,.dropdown.open .dropdown-toggle{color:#ffffff;background:#ccc;background:rgba(0, 0, 0, 0.3);}
+.open .menu-dropdown,.dropdown.open .menu-dropdown,.open .dropdown-menu,.dropdown.open .dropdown-menu{display:block;}
+.tabs,.pills{margin:0 0 18px;padding:0;list-style:none;zoom:1;}.tabs:before,.pills:before,.tabs:after,.pills:after{display:table;content:"";zoom:1;}
+.tabs:after,.pills:after{clear:both;}
+.tabs>li,.pills>li{float:left;}.tabs>li>a,.pills>li>a{display:block;}
+.tabs{border-color:#ddd;border-style:solid;border-width:0 0 1px;}.tabs>li{position:relative;margin-bottom:-1px;}.tabs>li>a{padding:0 15px;margin-right:2px;line-height:34px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;}.tabs>li>a:hover{text-decoration:none;background-color:#eee;border-color:#eee #eee #ddd;}
+.tabs .active>a,.tabs .active>a:hover{color:#808080;background-color:#ffffff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default;}
+.tabs .menu-dropdown,.tabs .dropdown-menu{top:35px;border-width:1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px;}
+.tabs a.menu:after,.tabs .dropdown-toggle:after{border-top-color:#999;margin-top:15px;margin-left:5px;}
+.tabs li.open.menu .menu,.tabs .open.dropdown .dropdown-toggle{border-color:#999;}
+.tabs li.open a.menu:after,.tabs .dropdown.open .dropdown-toggle:after{border-top-color:#555;}
+.pills a{margin:5px 3px 5px 0;padding:0 15px;line-height:30px;text-shadow:0 1px 1px #ffffff;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px;}.pills a:hover{color:#ffffff;text-decoration:none;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);background-color:#00438a;}
+.pills .active a{color:#ffffff;text-shadow:0 1px 1px rgba(0, 0, 0, 0.25);background-color:#0069d6;}
+.pills-vertical>li{float:none;}
+.tab-content>.tab-pane,.pill-content>.pill-pane,.tab-content>div,.pill-content>div{display:none;}
+.tab-content>.active,.pill-content>.active{display:block;}
+.breadcrumb{padding:7px 14px;margin:0 0 18px;background-color:#f5f5f5;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ffffff), to(#f5f5f5));background-image:-moz-linear-gradient(top, #ffffff, #f5f5f5);background-image:-ms-linear-gradient(top, #ffffff, #f5f5f5);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(100%, #f5f5f5));background-image:-webkit-linear-gradient(top, #ffffff, #f5f5f5);background-image:-o-linear-gradient(top, #ffffff, #f5f5f5);background-image:linear-gradient(top, #ffffff, #f5f5f5);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#f5f5f5', GradientType=0);border:1px solid #ddd;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;}.breadcrumb li{display:inline;text-shadow:0 1px 0 #ffffff;}
+.breadcrumb .divider{padding:0 5px;color:#bfbfbf;}
+.breadcrumb .active a{color:#404040;}
+.hero-unit{background-color:#f5f5f5;margin-bottom:30px;padding:60px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;}
+.hero-unit p{font-size:18px;font-weight:200;line-height:27px;}
+footer{margin-top:17px;padding-top:17px;border-top:1px solid #eee;}
+.page-header{margin-bottom:17px;border-bottom:1px solid #ddd;-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);box-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}.page-header h1{margin-bottom:8px;}
+.btn.danger,.alert-message.danger,.btn.danger:hover,.alert-message.danger:hover,.btn.error,.alert-message.error,.btn.error:hover,.alert-message.error:hover,.btn.success,.alert-message.success,.btn.success:hover,.alert-message.success:hover,.btn.info,.alert-message.info,.btn.info:hover,.alert-message.info:hover{color:#ffffff;}
+.btn .close,.alert-message .close{font-family:Arial,sans-serif;line-height:18px;}
+.btn.danger,.alert-message.danger,.btn.error,.alert-message.error{background-color:#c43c35;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#ee5f5b), to(#c43c35));background-image:-moz-linear-gradient(top, #ee5f5b, #c43c35);background-image:-ms-linear-gradient(top, #ee5f5b, #c43c35);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #ee5f5b), color-stop(100%, #c43c35));background-image:-webkit-linear-gradient(top, #ee5f5b, #c43c35);background-image:-o-linear-gradient(top, #ee5f5b, #c43c35);background-image:linear-gradient(top, #ee5f5b, #c43c35);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ee5f5b', endColorstr='#c43c35', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#c43c35 #c43c35 #882a25;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);}
+.btn.success,.alert-message.success{background-color:#57a957;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#62c462), to(#57a957));background-image:-moz-linear-gradient(top, #62c462, #57a957);background-image:-ms-linear-gradient(top, #62c462, #57a957);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #62c462), color-stop(100%, #57a957));background-image:-webkit-linear-gradient(top, #62c462, #57a957);background-image:-o-linear-gradient(top, #62c462, #57a957);background-image:linear-gradient(top, #62c462, #57a957);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#62c462', endColorstr='#57a957', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#57a957 #57a957 #3d773d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);}
+.btn.info,.alert-message.info{background-color:#339bb9;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#5bc0de), to(#339bb9));background-image:-moz-linear-gradient(top, #5bc0de, #339bb9);background-image:-ms-linear-gradient(top, #5bc0de, #339bb9);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #5bc0de), color-stop(100%, #339bb9));background-image:-webkit-linear-gradient(top, #5bc0de, #339bb9);background-image:-o-linear-gradient(top, #5bc0de, #339bb9);background-image:linear-gradient(top, #5bc0de, #339bb9);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#5bc0de', endColorstr='#339bb9', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#339bb9 #339bb9 #22697d;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);}
+.btn{cursor:pointer;display:inline-block;background-color:#e6e6e6;background-repeat:no-repeat;background-image:-webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), color-stop(25%, #ffffff), to(#e6e6e6));background-image:-webkit-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-moz-linear-gradient(top, #ffffff, #ffffff 25%, #e6e6e6);background-image:-ms-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:-o-linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);background-image:linear-gradient(#ffffff, #ffffff 25%, #e6e6e6);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffff', endColorstr='#e6e6e6', GradientType=0);padding:5px 14px 6px;text-shadow:0 1px 1px rgba(255, 255, 255, 0.75);color:#333;font-size:13px;line-height:normal;border:1px solid #ccc;border-bottom-color:#bbb;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.2),0 1px 2px rgba(0, 0, 0, 0.05);-webkit-transition:0.1s linear all;-moz-transition:0.1s linear all;-ms-transition:0.1s linear all;-o-transition:0.1s linear all;transition:0.1s linear all;}.btn:hover{background-position:0 -15px;color:#333;text-decoration:none;}
+.btn:focus{outline:1px dotted #666;}
+.btn.primary{color:#ffffff;background-color:#0064cd;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#049cdb), to(#0064cd));background-image:-moz-linear-gradient(top, #049cdb, #0064cd);background-image:-ms-linear-gradient(top, #049cdb, #0064cd);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #049cdb), color-stop(100%, #0064cd));background-image:-webkit-linear-gradient(top, #049cdb, #0064cd);background-image:-o-linear-gradient(top, #049cdb, #0064cd);background-image:linear-gradient(top, #049cdb, #0064cd);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#049cdb', endColorstr='#0064cd', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#0064cd #0064cd #003f81;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);}
+.btn.active,.btn:active{-webkit-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:inset 0 2px 4px rgba(0, 0, 0, 0.25),0 1px 2px rgba(0, 0, 0, 0.05);}
+.btn.disabled{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
+.btn[disabled]{cursor:default;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);filter:alpha(opacity=65);-khtml-opacity:0.65;-moz-opacity:0.65;opacity:0.65;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}
+.btn.large{font-size:15px;line-height:normal;padding:9px 14px 9px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;}
+.btn.small{padding:7px 9px 7px;font-size:11px;}
+:root .alert-message,:root .btn{border-radius:0 \0;}
+button.btn::-moz-focus-inner,input[type=submit].btn::-moz-focus-inner{padding:0;border:0;}
+.close{float:right;color:#000000;font-size:20px;font-weight:bold;line-height:13.5px;text-shadow:0 1px 0 #ffffff;filter:alpha(opacity=25);-khtml-opacity:0.25;-moz-opacity:0.25;opacity:0.25;}.close:hover{color:#000000;text-decoration:none;filter:alpha(opacity=40);-khtml-opacity:0.4;-moz-opacity:0.4;opacity:0.4;}
+.alert-message{position:relative;padding:7px 15px;margin-bottom:18px;color:#404040;background-color:#eedc94;background-repeat:repeat-x;background-image:-khtml-gradient(linear, left top, left bottom, from(#fceec1), to(#eedc94));background-image:-moz-linear-gradient(top, #fceec1, #eedc94);background-image:-ms-linear-gradient(top, #fceec1, #eedc94);background-image:-webkit-gradient(linear, left top, left bottom, color-stop(0%, #fceec1), color-stop(100%, #eedc94));background-image:-webkit-linear-gradient(top, #fceec1, #eedc94);background-image:-o-linear-gradient(top, #fceec1, #eedc94);background-image:linear-gradient(top, #fceec1, #eedc94);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fceec1', endColorstr='#eedc94', GradientType=0);text-shadow:0 -1px 0 rgba(0, 0, 0, 0.25);border-color:#eedc94 #eedc94 #e4c652;border-color:rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);border-width:1px;border-style:solid;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:inset 0 1px 0 rgba(255, 255, 255, 0.25);}.alert-message .close{margin-top:1px;*margin-top:0;}
+.alert-message a{font-weight:bold;color:#404040;}
+.alert-message.danger p a,.alert-message.error p a,.alert-message.success p a,.alert-message.info p a{color:#ffffff;}
+.alert-message h5{line-height:18px;}
+.alert-message p{margin-bottom:0;}
+.alert-message div{margin-top:5px;margin-bottom:2px;line-height:28px;}
+.alert-message .btn{-webkit-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);-moz-box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);box-shadow:0 1px 0 rgba(255, 255, 255, 0.25);}
+.alert-message.block-message{background-image:none;background-color:#fdf5d9;filter:progid:DXImageTransform.Microsoft.gradient(enabled = false);padding:14px;border-color:#fceec1;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none;}.alert-message.block-message ul,.alert-message.block-message p{margin-right:30px;}
+.alert-message.block-message ul{margin-bottom:0;}
+.alert-message.block-message li{color:#404040;}
+.alert-message.block-message .alert-actions{margin-top:5px;}
+.alert-message.block-message.error,.alert-message.block-message.success,.alert-message.block-message.info{color:#404040;text-shadow:0 1px 0 rgba(255, 255, 255, 0.5);}
+.alert-message.block-message.error{background-color:#fddfde;border-color:#fbc7c6;}
+.alert-message.block-message.success{background-color:#d1eed1;border-color:#bfe7bf;}
+.alert-message.block-message.info{background-color:#ddf4fb;border-color:#c6edf9;}
+.alert-message.block-message.danger p a,.alert-message.block-message.error p a,.alert-message.block-message.success p a,.alert-message.block-message.info p a{color:#404040;}
+.pagination{height:36px;margin:18px 0;}.pagination ul{float:left;margin:0;border:1px solid #ddd;border:1px solid rgba(0, 0, 0, 0.15);-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);-moz-box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);box-shadow:0 1px 2px rgba(0, 0, 0, 0.05);}
+.pagination li{display:inline;}
+.pagination a{float:left;padding:0 14px;line-height:34px;border-right:1px solid;border-right-color:#ddd;border-right-color:rgba(0, 0, 0, 0.15);*border-right-color:#ddd;text-decoration:none;}
+.pagination a:hover,.pagination .active a{background-color:#c7eefe;}
+.pagination .disabled a,.pagination .disabled a:hover{background-color:transparent;color:#bfbfbf;}
+.pagination .next a{border:0;}
+.well{background-color:#f5f5f5;margin-bottom:20px;padding:19px;min-height:20px;border:1px solid #eee;border:1px solid rgba(0, 0, 0, 0.05);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);-moz-box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);box-shadow:inset 0 1px 1px rgba(0, 0, 0, 0.05);}.well blockquote{border-color:#ddd;border-color:rgba(0, 0, 0, 0.15);}
+.modal-backdrop{background-color:#000000;position:fixed;top:0;left:0;right:0;bottom:0;z-index:10000;}.modal-backdrop.fade{opacity:0;}
+.modal-backdrop,.modal-backdrop.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}
+.modal{position:fixed;top:50%;left:50%;z-index:11000;width:560px;margin:-250px 0 0 -280px;background-color:#ffffff;border:1px solid #999;border:1px solid rgba(0, 0, 0, 0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.modal .close{margin-top:7px;}
+.modal.fade{-webkit-transition:opacity .3s linear, top .3s ease-out;-moz-transition:opacity .3s linear, top .3s ease-out;-ms-transition:opacity .3s linear, top .3s ease-out;-o-transition:opacity .3s linear, top .3s ease-out;transition:opacity .3s linear, top .3s ease-out;top:-25%;}
+.modal.fade.in{top:50%;}
+.modal-header{border-bottom:1px solid #eee;padding:5px 15px;}
+.modal-body{padding:15px;}
+.modal-body form{margin-bottom:0;}
+.modal-footer{background-color:#f5f5f5;padding:14px 15px 15px;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;-webkit-box-shadow:inset 0 1px 0 #ffffff;-moz-box-shadow:inset 0 1px 0 #ffffff;box-shadow:inset 0 1px 0 #ffffff;zoom:1;margin-bottom:0;}.modal-footer:before,.modal-footer:after{display:table;content:"";zoom:1;}
+.modal-footer:after{clear:both;}
+.modal-footer .btn{float:right;margin-left:5px;}
+.modal .popover,.modal .twipsy{z-index:12000;}
+.twipsy{display:block;position:absolute;visibility:visible;padding:5px;font-size:11px;z-index:1000;filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}.twipsy.fade.in{filter:alpha(opacity=80);-khtml-opacity:0.8;-moz-opacity:0.8;opacity:0.8;}
+.twipsy.above .twipsy-arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
+.twipsy.left .twipsy-arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;}
+.twipsy.below .twipsy-arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;}
+.twipsy.right .twipsy-arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;}
+.twipsy-inner{padding:3px 8px;background-color:#000000;color:white;text-align:center;max-width:200px;text-decoration:none;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;}
+.twipsy-arrow{position:absolute;width:0;height:0;}
+.popover{position:absolute;top:0;left:0;z-index:1000;padding:5px;display:none;}.popover.above .arrow{bottom:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-top:5px solid #000000;}
+.popover.right .arrow{top:50%;left:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-right:5px solid #000000;}
+.popover.below .arrow{top:0;left:50%;margin-left:-5px;border-left:5px solid transparent;border-right:5px solid transparent;border-bottom:5px solid #000000;}
+.popover.left .arrow{top:50%;right:0;margin-top:-5px;border-top:5px solid transparent;border-bottom:5px solid transparent;border-left:5px solid #000000;}
+.popover .arrow{position:absolute;width:0;height:0;}
+.popover .inner{background:#000000;background:rgba(0, 0, 0, 0.8);padding:3px;overflow:hidden;width:280px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);-moz-box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);box-shadow:0 3px 7px rgba(0, 0, 0, 0.3);}
+.popover .title{background-color:#f5f5f5;padding:9px 15px;line-height:1;-webkit-border-radius:3px 3px 0 0;-moz-border-radius:3px 3px 0 0;border-radius:3px 3px 0 0;border-bottom:1px solid #eee;}
+.popover .content{background-color:#ffffff;padding:14px;-webkit-border-radius:0 0 3px 3px;-moz-border-radius:0 0 3px 3px;border-radius:0 0 3px 3px;-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box;}.popover .content p,.popover .content ul,.popover .content ol{margin-bottom:0;}
+.fade{-webkit-transition:opacity 0.15s linear;-moz-transition:opacity 0.15s linear;-ms-transition:opacity 0.15s linear;-o-transition:opacity 0.15s linear;transition:opacity 0.15s linear;opacity:0;}.fade.in{opacity:1;}
+.label{padding:1px 3px 2px;font-size:9.75px;font-weight:bold;color:#ffffff;text-transform:uppercase;white-space:nowrap;background-color:#bfbfbf;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;}.label.important{background-color:#c43c35;}
+.label.warning{background-color:#f89406;}
+.label.success{background-color:#46a546;}
+.label.notice{background-color:#62cffc;}
+.media-grid{margin-left:-20px;margin-bottom:0;zoom:1;}.media-grid:before,.media-grid:after{display:table;content:"";zoom:1;}
+.media-grid:after{clear:both;}
+.media-grid li{display:inline;}
+.media-grid a{float:left;padding:4px;margin:0 0 18px 20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);-moz-box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);box-shadow:0 1px 1px rgba(0, 0, 0, 0.075);}.media-grid a img{display:block;}
+.media-grid a:hover{border-color:#0069d6;-webkit-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);-moz-box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);box-shadow:0 1px 4px rgba(0, 105, 214, 0.25);}
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/public/stylesheets/style.css
@@ -0,0 +1,92 @@
+html, body {
+  padding: 0;
+  margin: 0;
+  height: 100%;
+
+  background: white url(/images/nodejs.png) 50% 50% no-repeat;
+}
+
+header {
+  background: #333;
+  padding: 0 16px;
+}
+
+header h1 {
+  color: white;
+  line-height: 40px;
+}
+
+footer {
+  width: 100%;
+  height: 40px;
+  margin-top: 0;
+  padding: 16px 16px 0 16px;
+  line-height: 24px;
+  position: fixed;
+  bottom: 0;
+  background: white;
+}
+
+section#content {
+  padding: 64px 32px;
+}
+
+section#notice {
+  font-size: 32px;
+  line-height: 48px;
+}
+
+section#tweets {
+}
+
+section#tweets article.tweet {
+  padding: 2px 8px;
+}
+
+section#tweets article.tweet .clear {
+  clear: both;
+}
+
+section#tweets article.tweet img.avatar {
+  display: inline;
+  float: left;
+  width: 24px;
+  height: 24px;
+  padding: 0;
+  margin: 0 4px 0 0;
+}
+
+section#tweets article.tweet div.text-container {
+  overflow: hidden;
+  height: 24px;
+  position: relative;
+}
+
+section#tweets article.tweet div.text-container div.text {
+  display: inline-block;
+  float: left;
+  line-height: 24px;
+  width: 10000px;
+}
+
+section#tweets article.tweet div.text:before {
+  position: absolute;
+  top: 0;
+  right: 0px;
+
+  background: -webkit-linear-gradient(right, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
+  background: -moz-linear-gradient(right, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
+  background: linear-gradient(right, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
+  content: '';
+  width: 50px;
+  height: 24px;
+}
+
+div#tweet-button {
+  line-height: 32px;
+}
+
+div#tweet-button iframe {
+  position: relative;
+  top: 6px;
+}
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/realtime/index.js
@@ -0,0 +1,83 @@
+var twitter = new (require('ntwitter'))({
+  consumer_key: '',
+  consumer_secret: '',
+  access_token_key: '',
+  access_token_secret: ''
+});
+
+exports.init = function(io) {
+  var tweets = [];
+  twitter.search(
+    '#spdytwitlog',
+    {
+      result_type: 'recent',
+      include_entities: 1
+    },
+    function(err, result) {
+      if (err) return console.error(err);
+      result.results.sort(function(a, b) {
+        return (+new Date(a.created_at)) -
+               (+new Date(b.created_at));
+      }).forEach(receive);
+    }
+  );
+
+  function watchStream(method, query) {
+    twitter.stream(method, query, function(stream) {
+      stream.on('data', receive);
+
+      stream.on('end', retry);
+      stream.on('destroy', retry);
+
+      var once = false;
+      function retry() {
+        if (once) return;
+        once = true;
+
+        setTimeout(function() {
+          watchStream(method, query);
+        }, 5000);
+      }
+    });
+  }
+
+  watchStream('statuses/filter', {
+    track: '#spdytwitlog',
+    include_entities: 1
+  });
+
+  function receive(tweet) {
+    if (tweet.entities && tweet.entities.urls) {
+      tweet.entities.urls.sort(function(a, b) {
+        return b.indices[0] - a.indices[0];
+      }).forEach(function(url) {
+        tweet.text = tweet.text.slice(0, url.indices[0]) +
+                     url.display_url.link(url.expanded_url || url.url) +
+                     tweet.text.slice(url.indices[1]);
+      });
+    }
+    tweet = {
+      text: tweet.text,
+      user: tweet.user ? {
+        name: tweet.user.screen_name,
+        image: tweet.user.profile_image_url
+      } : {
+        name: tweet.from_user,
+        image: tweet.profile_image_url
+      }
+    };
+    io.sockets.emit('tweet', tweet);
+
+    tweets.push(tweet);
+    // remember only last 18 tweets
+    tweets = tweets.slice(tweets.length - 18, tweets.length);
+  };
+
+  io.sockets.on('connection', function(socket) {
+    socket.on('reqTweets', function() {
+      tweets.forEach(function(tweet) {
+        socket.emit('tweet', tweet);
+      });
+    });
+  });
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/routes/index.js
@@ -0,0 +1,36 @@
+var fs = require('fs');
+
+/*
+ * GET home page.
+ */
+
+var image = fs.readFileSync(__dirname + '/../public/images/nodejs.png');
+
+exports.index = function(req, res){
+  if (res.isSpdy) {
+    var ua = req.headers['user-agent'];
+
+    // Firefox doesn't support push streams now
+    // should be fixed once: https://bugzilla.mozilla.org/show_bug.cgi?id=718210
+    // will reach all it's builds
+    if (!ua || ua.match(/Firefox/i) === null) {
+      // Push stream image
+      res.push(
+        '/images/nodejs.png',
+        { 'content-type': 'image/png' },
+        function(err, stream) {
+          if (err) return;
+          stream.on('error', function() {});
+          stream.end(image);
+        }
+      );
+    }
+  }
+  res.render('index', {
+    title: 'SPDY - Twitlog',
+    notice: req.isSpdy ?
+      'Yay! This page was requested via SPDY protocol'
+      :
+      'Oh, no... your browser requested this page via HTTPS'
+  });
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/views/index.jade
@@ -0,0 +1,6 @@
+div(id='tweet-button')
+  = 'Post a '
+  a(href='https://twitter.com/intent/tweet?button_hashtag=spdytwitlog&text=This%20text%20will%20appear%20on',class='twitter-hashtag-button',data-size='small',data-related='indutny',data-url='https://spdy-twitlog.indutny.com/') tweet
+  = ' and it\'ll be displayed here'
+
+section(id='tweets')
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/examples/twitlog/views/layout.jade
@@ -0,0 +1,29 @@
+!!! 5
+html
+  head
+    title= title
+    link(rel='icon', type='image/png', href='favicon.png')
+    link(rel='stylesheet', href='/stylesheets/bootstrap.min.css')
+    link(rel='stylesheet', href='/stylesheets/style.css')
+  body
+    header.topbar
+      h1= title
+    section#content
+      - if (notice)
+        section#notice= notice
+      != body
+    footer
+      = 'Made using '
+      a(href='http://nodejs.org/') node.js
+      = ', '
+      a(href='https://github.com/indutny/node-spdy') node-spdy
+      = ', '
+      a(href='http://socket.io/') socket.io
+      = ' and '
+      a(href='http://twitter.github.com/bootstrap/') twitter bootstrap
+
+    script(src='//platform.twitter.com/widgets.js', async=true, defer=true)
+    script(src='/javascripts/socket.io.min.js')
+    script(src='/javascripts/main.js')
+
+    script var _gaq =_gaq||[];_gaq.push(['_setAccount', 'UA-28159099-1']);_gaq.push(['_trackPageview']);(function() {var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);})();
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/keys/spdy-cert.pem
@@ -0,0 +1,14 @@
+-----BEGIN CERTIFICATE-----
+MIICHzCCAYgCCQCPPSUAa8QZojANBgkqhkiG9w0BAQUFADBUMQswCQYDVQQGEwJS
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTENMAsGA1UEBxMET21zazEhMB8GA1UEChMY
+SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTExMDQwOTEwMDY0NVoXDTExMDUw
+OTEwMDY0NVowVDELMAkGA1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUxDTAL
+BgNVBAcTBE9tc2sxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCB
+nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1bn25sPkv46wl70BffxradlkRd/x
+p5Xf8HDhPSfzNNctERYslXT2fX7Dmfd5w1XTVqqGqJ4izp5VewoVOHA8uavo3ovp
+gNWasil5zADWaM1T0nnV0RsFbZWzOTmm1U3D48K8rW3F5kOZ6f4yRq9QT1gF/gN7
+5Pt494YyYyJu/a8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQBuRZisIViI2G/R+w79
+vk21TzC/cJ+O7tKsseDqotXYTH8SuimEH5IWcXNgnWhNzczwN8s2362NixyvCipV
+yd4wzMpPbjIhnWGM0hluWZiK2RxfcqimIBjDParTv6CMUIuwGQ257THKY8hXGg7j
+Uws6Lif3P9UbsuRiYPxMgg98wg==
+-----END CERTIFICATE-----
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/keys/spdy-csr.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBkzCB/QIBADBUMQswCQYDVQQGEwJSVTETMBEGA1UECBMKU29tZS1TdGF0ZTEN
+MAsGA1UEBxMET21zazEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF
+3/Gnld/wcOE9J/M01y0RFiyVdPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+je
+i+mA1ZqyKXnMANZozVPSedXRGwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+
+A3vk+3j3hjJjIm79rwIDAQABoAAwDQYJKoZIhvcNAQEFBQADgYEAiNWhz6EppIVa
+FfUaB3sLeqfamb9tg9kBHtvqj/FJni0snqms0kPWaTySEPHZF0irIb7VVdq/sVCb
+3gseMVSyoDvPJ4lHC3PXqGQ7kM1mIPhDnR/4HDA3BhlGhTXSDIHgZnvI+HMBdsyC
+hC3dz5odyKqe4nmoofomALkBL9t4H8s=
+-----END CERTIFICATE REQUEST-----
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/keys/spdy-key.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF3/Gnld/wcOE9J/M01y0RFiyV
+dPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+jei+mA1ZqyKXnMANZozVPSedXR
+GwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+A3vk+3j3hjJjIm79rwIDAQAB
+AoGAAv2QI9h32epQND9TxwSCKD//dC7W/cZOFNovfKCTeZjNK6EIzKqPTGA6smvR
+C1enFl5adf+IcyWqAoe4lkqTvurIj+2EhtXdQ8DBlVuXKr3xvEFdYxXPautdTCF6
+KbXEyS/s1TZCRFjYftvCrXxc3pK45AQX/wg7z1K+YB5pyIECQQD0OJvLoxLYoXAc
+FZraIOZiDsEbGuSHqoCReFXH75EC3+XGYkH2bQ/nSIZ0h1buuwQ/ylKXOlTPT3Qt
+Xm1OQEBvAkEA4AjWsIO/rRpOm/Q2aCrynWMpoUXTZSbL2yGf8pxp/+8r2br5ier0
+M1LeBb/OPY1+k39NWLXxQoo64xoSFYk2wQJAd2wDCwX4HkR7HNCXw1hZL9QFK6rv
+20NN0VSlpboJD/3KT0MW/FiCcVduoCbaJK0Au+zEjDyy4hj5N4I4Mw6KMwJAXVAx
+I+psTsxzS4/njXG+BgIEl/C+gRYsuMQDnAi8OebDq/et8l0Tg8ETSu++FnM18neG
+ntmBeMacinUUbTXuwQJBAJp/onZdsMzeVulsGrqR1uS+Lpjc5Q1gt5ttt2cxj91D
+rio48C/ZvWuKNE8EYj2ALtghcVKRvgaWfOxt2GPguGg=
+-----END RSA PRIVATE KEY-----
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy.js
@@ -0,0 +1,31 @@
+var spdy = exports;
+
+// Exports utils
+spdy.utils = require('./spdy/utils');
+
+// Export parser&framer
+spdy.protocol = {};
+
+try {
+  spdy.protocol.generic = require('./spdy/protocol/generic.node');
+} catch (e) {
+  spdy.protocol.generic = require('./spdy/protocol/generic.js');
+}
+
+// Only SPDY v2 is supported now
+spdy.protocol[2] = require('./spdy/protocol/v2');
+
+spdy.parser = require('./spdy/parser');
+
+// Export ServerResponse
+spdy.response = require('./spdy/response');
+
+// Export Scheduler
+spdy.scheduler = require('./spdy/scheduler');
+
+// Export ZlibPool
+spdy.zlibpool = require('./spdy/zlib-pool');
+
+// Export server
+spdy.server = require('./spdy/server');
+spdy.createServer = spdy.server.create;
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy/parser.js
@@ -0,0 +1,185 @@
+var parser = exports;
+
+var spdy = require('../spdy'),
+    util = require('util'),
+    stream = require('stream'),
+    Buffer = require('buffer').Buffer;
+
+//
+// ### function Parser (connection, deflate, inflate)
+// #### @connection {spdy.Connection} connection
+// #### @deflate {zlib.Deflate} Deflate stream
+// #### @inflate {zlib.Inflate} Inflate stream
+// SPDY protocol frames parser's @constructor
+//
+function Parser(connection, deflate, inflate) {
+  stream.Stream.call(this);
+
+  this.drained = true;
+  this.paused = false;
+  this.buffer = [];
+  this.buffered = 0;
+  this.waiting = 8;
+
+  this.state = { type: 'frame-head' };
+  this.deflate = deflate;
+  this.inflate = inflate;
+  this.framer = null;
+
+  this.connection = connection;
+
+  this.readable = this.writable = true;
+}
+util.inherits(Parser, stream.Stream);
+
+//
+// ### function create (connection, deflate, inflate)
+// #### @connection {spdy.Connection} connection
+// #### @deflate {zlib.Deflate} Deflate stream
+// #### @inflate {zlib.Inflate} Inflate stream
+// @constructor wrapper
+//
+parser.create = function create(connection, deflate, inflate) {
+  return new Parser(connection, deflate, inflate);
+};
+
+//
+// ### function write (data)
+// #### @data {Buffer} chunk of data
+// Writes or buffers data to parser
+//
+Parser.prototype.write = function write(data) {
+  if (data !== undefined) {
+    // Buffer data
+    this.buffer.push(data);
+    this.buffered += data.length;
+  }
+
+  // Notify caller about state (for piping)
+  if (this.paused) return false;
+
+  // We shall not do anything until we get all expected data
+  if (this.buffered < this.waiting) return;
+
+  // Mark parser as not drained
+  if (data !== undefined) this.drained = false;
+
+  var self = this,
+      buffer = new Buffer(this.waiting),
+      sliced = 0,
+      offset = 0;
+
+  while (this.waiting > offset && sliced < this.buffer.length) {
+    var chunk = this.buffer[sliced++],
+        overmatched = false;
+
+    // Copy chunk into `buffer`
+    if (chunk.length > this.waiting - offset) {
+      chunk.copy(buffer, offset, 0, this.waiting - offset);
+
+      this.buffer[--sliced] = chunk.slice(this.waiting - offset);
+      this.buffered += this.buffer[sliced].length;
+
+      overmatched = true;
+    } else {
+      chunk.copy(buffer, offset);
+    }
+
+    // Move offset and decrease amount of buffered data
+    offset += chunk.length;
+    this.buffered -= chunk.length;
+
+    if (overmatched) break;
+  }
+
+  // Remove used buffers
+  this.buffer = this.buffer.slice(sliced);
+
+  // Executed parser for buffered data
+  this.paused = true;
+  this.execute(this.state, buffer, function (err, waiting) {
+    // And unpause once execution finished
+    self.paused = false;
+
+    // Propagate errors
+    if (err) return self.emit('error', err);
+
+    // Set new `waiting`
+    self.waiting = waiting;
+
+    if (self.waiting <= self.buffered) {
+      self.write();
+    } else {
+      process.nextTick(function() {
+        if (self.drained) return;
+
+        // Mark parser as drained
+        self.drained = true;
+        self.emit('drain');
+      });
+    }
+  });
+};
+
+//
+// ### function end ()
+// Stream's end() implementation
+//
+Parser.prototype.end = function end() {
+  this.emit('end');
+};
+
+//
+// ### function execute (state, data, callback)
+// #### @state {Object} Parser's state
+// #### @data {Buffer} Incoming data
+// #### @callback {Function} continuation callback
+// Parse buffered data
+//
+Parser.prototype.execute = function execute(state, data, callback) {
+  if (state.type === 'frame-head') {
+    var header = state.header = spdy.protocol.generic.parseHeader(data);
+
+    // Lazily create framer
+    if (!this.framer && header.control) {
+      if (spdy.protocol[header.version]) {
+        this.framer = new spdy.protocol[header.version].Framer(
+          spdy.utils.zwrap(this.deflate),
+          spdy.utils.zwrap(this.inflate)
+        );
+
+        // Propagate framer to connection
+        this.connection.framer = this.framer;
+        this.emit('_framer', this.framer);
+      }
+    }
+
+    state.type = 'frame-body';
+    callback(null, header.length);
+  } else if (state.type === 'frame-body') {
+    var self = this;
+
+    // Data frame
+    if (!state.header.control) {
+      return onFrame(null, {
+        type: 'DATA',
+        id: state.header.id,
+        fin: (state.header.flags & 0x01) === 0x01,
+        compressed: (state.header.flags & 0x02) === 0x02,
+        data: data
+      });
+    } else {
+    // Control frame
+      this.framer.execute(state.header, data, onFrame);
+    }
+
+    function onFrame(err, frame) {
+      if (err) return callback(err);
+
+      self.emit('frame', frame);
+
+      state.type = 'frame-head';
+      callback(null, 8);
+    };
+  }
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy/protocol/generic.js
@@ -0,0 +1,24 @@
+//
+// ### function parseHeader (data)
+// ### @data {Buffer} incoming data
+// Returns parsed SPDY frame header
+//
+exports.parseHeader = function parseHeader(data) {
+  var header = {
+    control: (data.readUInt8(0) & 0x80) === 0x80 ? true : false,
+    version: null,
+    type: null,
+    id: null,
+    flags: data.readUInt8(4),
+    length: data.readUInt32BE(4) & 0x00ffffff
+  };
+
+  if (header.control) {
+    header.version = data.readUInt16BE(0) & 0x7fff;
+    header.type = data.readUInt16BE(2);
+  } else {
+    header.id = data.readUInt32BE(0) & 0x7fffffff;
+  }
+
+  return header;
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy/protocol/v2/framer.js
@@ -0,0 +1,280 @@
+var framer = exports;
+
+var spdy = require('../../../spdy'),
+    Buffer = require('buffer').Buffer,
+    protocol = require('./');
+
+//
+// ### function Framer (deflate, inflate)
+// #### @deflate {zlib.Deflate} Deflate stream
+// #### @inflate {zlib.Inflate} Inflate stream
+// Framer constructor
+//
+function Framer(deflate, inflate) {
+  this.deflate = deflate;
+  this.inflate = inflate;
+}
+exports.Framer = Framer;
+
+
+//
+// ### function execute (header, body, callback)
+// #### @header {Object} Frame headers
+// #### @body {Buffer} Frame's body
+// #### @callback {Function} Continuation callback
+// Parse frame (decompress data and create streams)
+//
+Framer.prototype.execute = function execute(header, body, callback) {
+  // SYN_STREAM or SYN_REPLY
+  if (header.type === 0x01 || header.type === 0x02) {
+    var frame = protocol.parseSynHead(header.type, header.flags, body);
+
+    body = body.slice(frame._offset);
+
+    this.inflate(body, function(err, chunks, length) {
+      var pairs = new Buffer(length);
+      for (var i = 0, offset = 0; i < chunks.length; i++) {
+        chunks[i].copy(pairs, offset);
+        offset += chunks[i].length;
+      }
+
+      frame.headers = protocol.parseHeaders(pairs);
+
+      callback(null, frame);
+    });
+  // RST_STREAM
+  } else if (header.type === 0x03) {
+    callback(null, protocol.parseRst(body));
+  // SETTINGS
+  } else if (header.type === 0x04) {
+    callback(null, { type: 'SETTINGS' });
+  } else if (header.type === 0x05) {
+    callback(null, { type: 'NOOP' });
+  // PING
+  } else if (header.type === 0x06) {
+    callback(null, { type: 'PING', pingId: body });
+  // GOAWAY
+  } else if (header.type === 0x07) {
+    callback(null, protocol.parseGoaway(body));
+  } else {
+    callback(null, { type: 'unknown: ' + header.type, body: body });
+  }
+};
+
+//
+// internal, converts object into spdy dictionary
+//
+function headersToDict(headers, preprocess) {
+  function stringify(value) {
+    if (value !== undefined) {
+      if (Array.isArray(value)) {
+        return value.join('\x00');
+      } else if (typeof value === 'string') {
+        return value;
+      } else {
+        return value.toString();
+      }
+    } else {
+      return '';
+    }
+  }
+
+  // Lower case of all headers keys
+  var loweredHeaders = {};
+  Object.keys(headers || {}).map(function(key) {
+    loweredHeaders[key.toLowerCase()] = headers[key];
+  });
+
+  // Allow outer code to add custom headers or remove something
+  if (preprocess) preprocess(loweredHeaders);
+
+  // Transform object into kv pairs
+  var len = 2,
+      pairs = Object.keys(loweredHeaders).filter(function(key) {
+        var lkey = key.toLowerCase();
+        return lkey !== 'connection' && lkey !== 'keep-alive' &&
+               lkey !== 'proxy-connection' && lkey !== 'transfer-encoding';
+      }).map(function(key) {
+        var klen = Buffer.byteLength(key),
+            value = stringify(loweredHeaders[key]),
+            vlen = Buffer.byteLength(value);
+
+        len += 4 + klen + vlen;
+        return [klen, key, vlen, value];
+      }),
+      result = new Buffer(len);
+
+  result.writeUInt16BE(pairs.length, 0, true);
+
+  var offset = 2;
+  pairs.forEach(function(pair) {
+    // Write key length
+    result.writeUInt16BE(pair[0], offset, true);
+    // Write key
+    result.write(pair[1], offset + 2);
+
+    offset += pair[0] + 2;
+
+    // Write value length
+    result.writeUInt16BE(pair[2], offset, true);
+    // Write value
+    result.write(pair[3], offset + 2);
+
+    offset += pair[2] + 2;
+  });
+
+  return result;
+};
+
+Framer.prototype._synFrame = function _synFrame(type, id, assoc, dict,
+                                                callback) {
+  // Compress headers
+  this.deflate(dict, function (err, chunks, size) {
+    if (err) return callback(err);
+
+    var offset = type === 'SYN_STREAM' ? 18 : 14,
+        total = (type === 'SYN_STREAM' ? 10 : 6) + size,
+        frame = new Buffer(offset + size);;
+
+    frame.writeUInt16BE(0x8002, 0, true); // Control + Version
+    frame.writeUInt16BE(type === 'SYN_STREAM' ? 1 : 2, 2, true); // type
+    frame.writeUInt32BE(total & 0x00ffffff, 4, true); // No flag support
+    frame.writeUInt32BE(id & 0x7fffffff, 8, true); // Stream-ID
+
+    if (type === 'SYN_STREAM') {
+      frame[4] = 2;
+      frame.writeUInt32BE(assoc & 0x7fffffff, 12, true); // Stream-ID
+    }
+
+    for (var i = 0; i < chunks.length; i++) {
+      chunks[i].copy(frame, offset);
+      offset += chunks[i].length;
+    }
+
+    callback(null, frame);
+  });
+};
+
+//
+// ### function replyFrame (id, code, reason, headers, callback)
+// #### @id {Number} Stream ID
+// #### @code {Number} HTTP Status Code
+// #### @reason {String} (optional)
+// #### @headers {Object|Array} (optional) HTTP headers
+// #### @callback {Function} Continuation function
+// Sends SYN_REPLY frame
+//
+Framer.prototype.replyFrame = function replyFrame(id, code, reason, headers,
+                                                  callback) {
+  var dict = headersToDict(headers, function(headers) {
+        headers.status = code + ' ' + reason;
+        headers.version = 'HTTP/1.1';
+      });
+
+  this._synFrame('SYN_REPLY', id, null, dict, callback);
+};
+
+//
+// ### function streamFrame (id, assoc, headers, callback)
+// #### @id {Number} stream id
+// #### @assoc {Number} associated stream id
+// #### @meta {Object} meta headers ( method, scheme, url, version )
+// #### @headers {Object} stream headers
+// #### @callback {Function} continuation callback
+// Create SYN_STREAM frame
+// (needed for server push and testing)
+//
+Framer.prototype.streamFrame = function streamFrame(id, assoc, meta, headers,
+                                                    callback) {
+  var dict = headersToDict(headers, function(headers) {
+    headers.status = 200;
+    headers.version = 'HTTP/1.1';
+    headers.url = meta.url;
+  });
+
+  this._synFrame('SYN_STREAM', id, assoc, dict, callback);
+};
+
+//
+// ### function dataFrame (id, fin, data)
+// #### @id {Number} Stream id
+// #### @fin {Bool} Is this data frame last frame
+// #### @data {Buffer} Response data
+// Sends DATA frame
+//
+Framer.prototype.dataFrame = function dataFrame(id, fin, data) {
+  if (!fin && !data.length) return [];
+
+  var frame = new Buffer(8 + data.length);
+
+  frame.writeUInt32BE(id & 0x7fffffff, 0, true);
+  frame.writeUInt32BE(data.length & 0x00ffffff, 4, true);
+  frame.writeUInt8(fin ? 0x01 : 0x0, 4, true);
+
+  if (data.length) data.copy(frame, 8);
+
+  return frame;
+};
+
+//
+// ### function pingFrame (id)
+// #### @id {Buffer} Ping ID
+// Sends PING frame
+//
+Framer.prototype.pingFrame = function pingFrame(id) {
+  var header = new Buffer(12);
+
+  header.writeUInt32BE(0x80020006, 0, true); // Version and type
+  header.writeUInt32BE(0x00000004, 4, true); // Length
+  id.copy(header, 8, 0, 4); // ID
+
+  return header;
+};
+
+//
+// ### function rstFrame (id, code)
+// #### @id {Number} Stream ID
+// #### @code {NUmber} RST Code
+// Sends PING frame
+//
+Framer.prototype.rstFrame = function rstFrame(id, code) {
+  var header;
+
+  if (!(header = Framer.rstCache[code])) {
+    header = new Buffer(16);
+
+    header.writeUInt32BE(0x80020003, 0, true); // Version and type
+    header.writeUInt32BE(0x00000008, 4, true); // Length
+    header.writeUInt32BE(id & 0x7fffffff, 8, true); // Stream ID
+    header.writeUInt32BE(code, 12, true); // Status Code
+
+    Framer.rstCache[code] = header;
+  }
+
+  return header;
+};
+Framer.rstCache = {};
+
+//
+// ### function maxStreamsFrame (count)
+// #### @count {Number} Max Concurrent Streams count
+// Sends SETTINGS frame with MAX_CONCURRENT_STREAMS
+//
+Framer.prototype.maxStreamsFrame = function maxStreamsFrame(count) {
+  var settings;
+
+  if (!(settings = Framer.settingsCache[count])) {
+    settings = new Buffer(20);
+
+    settings.writeUInt32BE(0x80020004, 0, true); // Version and type
+    settings.writeUInt32BE(0x0000000C, 4, true); // length
+    settings.writeUInt32BE(0x00000001, 8, true); // Count of entries
+    settings.writeUInt32LE(0x01000004, 12, true); // Entry ID and Persist flag
+    settings.writeUInt32BE(count, 16, true); // 100 Streams
+
+    Framer.settingsCache[count] = settings;
+  }
+
+  return settings;
+};
+Framer.settingsCache = {};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy/protocol/v2/index.js
@@ -0,0 +1,10 @@
+var v2;
+
+try {
+  v2 = require('./protocol.node');
+} catch (e) {
+  v2 = require('./protocol.js');
+}
+module.exports = v2;
+
+v2.Framer = require('./framer').Framer;
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy/protocol/v2/protocol.js
@@ -0,0 +1,67 @@
+var protocol = exports;
+
+//
+// ### function parseSynHead (type, flags, data)
+// #### @type {Number} Frame type
+// #### @flags {Number} Frame flags
+// #### @data {Buffer} input data
+// Returns parsed syn_* frame's head
+//
+protocol.parseSynHead = function parseSynHead(type, flags, data) {
+  var stream = type === 0x01;
+
+  return {
+    type: stream ? 'SYN_STREAM' : 'SYN_REPLY',
+    id: data.readUInt32BE(0, true) & 0x7fffffff,
+    associated: stream ? data.readUInt32BE(4, true) & 0x7fffffff : 0,
+    priority: stream ? data[8] >> 6 : 0,
+    fin: (flags & 0x01) === 0x01,
+    unidir: (flags & 0x02) === 0x02,
+    _offset: stream ? 10 : 6
+  };
+};
+
+//
+// ### function parseHeaders (pairs)
+// #### @pairs {Buffer} header pairs
+// Returns hashmap of parsed headers
+//
+protocol.parseHeaders = function parseHeaders(pairs) {
+  var count = pairs.readUInt16BE(0, true),
+      headers = {};
+
+  pairs = pairs.slice(2);
+
+  function readString() {
+    var len = pairs.readUInt16BE(0, true),
+        value = pairs.slice(2, 2 + len);
+
+    pairs = pairs.slice(2 + len);
+
+    return value.toString();
+  }
+
+  while(count > 0) {
+    headers[readString()] = readString();
+    count--;
+  }
+
+  return headers;
+};
+
+//
+// ### function parsesRst frame
+protocol.parseRst = function parseRst(data) {
+  return {
+    type: 'RST_STREAM',
+    id: data.readUInt32BE(0, true) & 0x7fffffff,
+    status: data.readUInt32BE(4, true)
+  };
+};
+
+protocol.parseGoaway = function parseGoaway(data) {
+  return {
+    type: 'GOAWAY',
+    lastId: data.readUInt32BE(0, true) & 0x7fffffff
+  };
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy/response.js
@@ -0,0 +1,146 @@
+var spdy = require('../spdy'),
+    http = require('http');
+
+//
+// ### function _renderHeaders ()
+// Copy pasted from lib/http.js
+// (added lowercase)
+//
+exports._renderHeaders = function() {
+  if (this._header) {
+    throw new Error("Can't render headers after they are sent to the client.");
+  }
+
+  if (!this._headers) return {};
+
+  var headers = {};
+  var keys = Object.keys(this._headers);
+  for (var i = 0, l = keys.length; i < l; i++) {
+    var key = keys[i];
+    headers[(this._headerNames[key] || '').toLowerCase()] = this._headers[key];
+  }
+  return headers;
+};
+
+//
+// ### function writeHead (statusCode)
+// #### @statusCode {Number} HTTP Status code
+// .writeHead() wrapper
+// (Sorry, copy pasted from lib/http.js)
+//
+exports.writeHead = function(statusCode) {
+  if (this._headerSent) return;
+  this._headerSent = true;
+
+  var reasonPhrase, headers, headerIndex;
+
+  if (typeof arguments[1] == 'string') {
+    reasonPhrase = arguments[1];
+    headerIndex = 2;
+  } else {
+    reasonPhrase = http.STATUS_CODES[statusCode] || 'unknown';
+    headerIndex = 1;
+  }
+  this.statusCode = statusCode;
+
+  var obj = arguments[headerIndex];
+
+  if (obj && this._headers) {
+    // Slow-case: when progressive API and header fields are passed.
+    headers = this._renderHeaders();
+
+    // handle object case
+    var keys = Object.keys(obj);
+    for (var i = 0; i < keys.length; i++) {
+      var k = keys[i];
+      if (k) headers[k] = obj[k];
+    }
+  } else if (this._headers) {
+    // only progressive api is used
+    headers = this._renderHeaders();
+  } else {
+    // only writeHead() called
+    headers = obj;
+  }
+
+  // cleanup
+  this._header = '';
+
+  // Do not send data to new connections after GOAWAY
+  if (this.socket.isGoaway()) return;
+
+  this.socket.lock(function() {
+    var socket = this;
+
+    this.framer.replyFrame(
+      this.id,
+      statusCode,
+      reasonPhrase,
+      headers,
+      function (err, frame) {
+        // TODO: Handle err
+        socket.connection.write(frame);
+        socket.unlock();
+      }
+    );
+  });
+};
+
+//
+// ### function push (url, headers, callback)
+// #### @url {String} absolute or relative url (from root anyway)
+// #### @headers {Object} response headers
+// #### @callbacks {Function} continuation that will receive stream object
+// Initiates push stream
+//
+exports.push = function push(url, headers, callback) {
+  if (this.socket._destroyed) {
+    return callback(Error('Can\'t open push stream, parent socket destroyed'));
+  }
+
+  this.socket.lock(function() {
+    var socket = this,
+        id = socket.connection.pushId += 2,
+        fullUrl = /^\//.test(url) ?
+                      this.frame.headers.scheme + '://' +
+                      (this.frame.headers.host || 'localhost') +
+                      url
+                      :
+                      url;
+
+    this.framer.streamFrame(
+      id,
+      this.id,
+      {
+        method: 'GET',
+        url: fullUrl,
+        schema: 'https',
+        version: 'HTTP/1.1'
+      },
+      headers,
+      function(err, frame) {
+        if (err) {
+          socket.unlock();
+          callback(err);
+        } else {
+          socket.connection.write(frame);
+          socket.unlock();
+
+          var stream = new spdy.server.Stream(socket.connection, {
+            type: 'SYN_STREAM',
+            push: true,
+            id: id,
+            assoc: socket.id,
+            priority: 0,
+            headers: {}
+          });
+
+          socket.connection.streams[id] = stream;
+          socket.pushes.push(stream);
+
+          callback(null, stream);
+        }
+      }
+    );
+  });
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy/scheduler.js
@@ -0,0 +1,60 @@
+var scheduler = exports;
+
+//
+// ### function Scheduler (connection)
+// #### @connection {spdy.Connection} active connection
+// Connection's streams scheduler
+//
+function Scheduler(connection) {
+  this.connection = connection;
+  this.priorities = [[], [], [], []];
+  this._tickListener = null;
+}
+
+//
+// ### function create (connection)
+// #### @connection {spdy.Connection} active connection
+//
+exports.create = function create(connection) {
+  return new Scheduler(connection);
+};
+
+//
+// ### function schedule (stream, data)
+// #### @stream {spdy.Stream} Source stream
+// #### @data {Buffer} data to write on tick
+// Use stream priority to invoke callbacks in right order
+//
+Scheduler.prototype.schedule = function schedule(stream, data) {
+  this.priorities[stream.priority].push(data);
+};
+
+//
+// ### function tick ()
+// Add .nextTick callback if not already present
+//
+Scheduler.prototype.tick = function tick() {
+  if (this._tickListener !== null) return;
+  var self = this;
+  this._tickListener = function() {
+    var priorities = self.priorities;
+
+    self._tickListener = null;
+    self.priorities = [[], [], [], []];
+
+    // Run all priorities
+    for (var i = 0; i < 4; i++) {
+      for (var j = 0; j < priorities[i].length; j++) {
+        self.connection.write(
+          priorities[i][j]
+        );
+      }
+    }
+  };
+
+  if (this.connection.parser.drained) {
+    process.nextTick(this._tickListener);
+  } else {
+    this.connection.parser.once('drain', this._tickListener);
+  }
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy/server.js
@@ -0,0 +1,544 @@
+var spdy = require('../spdy'),
+    util = require('util'),
+    https = require('https'),
+    stream = require('stream'),
+    Buffer = require('buffer').Buffer;
+
+//
+// ### function instantiate (HTTPSServer)
+// #### @HTTPSServer {https.Server|Function} Base server class
+// Will return constructor for SPDY Server, based on the HTTPSServer class
+//
+function instantiate(HTTPSServer) {
+  //
+  // ### function Server (options, requestListener)
+  // #### @options {Object} tls server options
+  // #### @requestListener {Function} (optional) request callback
+  // SPDY Server @constructor
+  //
+  function Server(options, requestListener) {
+    if (!options) options = {};
+    if (!options.maxStreams) options.maxStreams = 100;
+    options.NPNProtocols = ['spdy/2', 'http/1.1', 'http/1.0'];
+
+    HTTPSServer.call(this, options, requestListener);
+
+    // Use https if NPN is not supported
+    if (!process.features.tls_npn && !options.debug) return;
+
+    // Wrap connection handler
+    var self = this,
+        connectionHandler = this.listeners('secureConnection')[0];
+
+    var pool = spdy.zlibpool.create();
+
+    this.removeAllListeners('secureConnection');
+    this.on('secureConnection', function secureConnection(socket) {
+      // Fallback to HTTPS if needed
+      if (socket.npnProtocol !== 'spdy/2' && !options.debug) {
+        return connectionHandler.call(this, socket);
+      }
+
+      // Wrap incoming socket into abstract class
+      var connection = new Connection(socket, pool, options);
+
+      // Emulate each stream like connection
+      connection.on('stream', connectionHandler);
+
+      connection.on('request', function(req, res) {
+        res._renderHeaders = spdy.response._renderHeaders;
+        res.writeHead = spdy.response.writeHead;
+        res.push = spdy.response.push;
+        res.streamID = req.streamID = req.socket.id;
+        res.isSpdy = req.isSpdy = true;
+
+        res.on('finish', function() {
+          req.connection.end();
+        });
+        self.emit('request', req, res);
+      });
+
+      connection.on('error', function(e) {
+        socket.destroy(e.errno === 'EPIPE' ? undefined : e);
+      });
+    });
+  }
+  util.inherits(Server, HTTPSServer);
+
+  return Server;
+}
+exports.instantiate = instantiate;
+
+// Export Server instantiated from https.Server
+var Server = instantiate(https.Server);
+exports.Server = Server;
+
+//
+// ### function create (base, options, requestListener)
+// #### @base {Function} (optional) base server class (https.Server)
+// #### @options {Object} tls server options
+// #### @requestListener {Function} (optional) request callback
+// @constructor wrapper
+//
+exports.create = function create(base, options, requestListener) {
+  var server;
+  if (typeof base === 'function') {
+    server = instantiate(base);
+  } else {
+    server = Server;
+
+    requestListener = options;
+    options = base;
+    base = null;
+  }
+
+  return new server(options, requestListener);
+};
+
+//
+// ### function Connection (socket, pool, options)
+// #### @socket {net.Socket} server's connection
+// #### @pool {spdy.ZlibPool} zlib pool
+// #### @options {Object} server's options
+// Abstract connection @constructor
+//
+function Connection(socket, pool, options) {
+  process.EventEmitter.call(this);
+
+  var self = this;
+
+  this._closed = false;
+
+  this.pool = pool;
+  var pair = pool.get();
+
+  this.deflate = pair.deflate;
+  this.inflate = pair.inflate;
+
+  // Init streams list
+  this.streams = {};
+  this.streamsCount = 0;
+  this.pushId = 0;
+  this.goaway = false;
+
+  this.framer = null;
+
+  // Initialize parser
+  this.parser = spdy.parser.create(this, this.deflate, this.inflate);
+  this.parser.on('frame', function (frame) {
+    if (this._closed) return;
+
+    var stream;
+
+    // Create new stream
+    if (frame.type === 'SYN_STREAM') {
+      self.streamsCount++;
+
+      stream = self.streams[frame.id] = new Stream(self, frame);
+
+      // If we reached stream limit
+      if (self.streamsCount > options.maxStreams) {
+        stream.once('error', function() {});
+        // REFUSED_STREAM
+        stream.rstCode = 3;
+        stream.destroy(true);
+      } else {
+        self.emit('stream', stream);
+
+        stream.init();
+      }
+    } else {
+      if (frame.id) {
+        // Load created one
+        stream = self.streams[frame.id];
+
+        // Fail if not found
+        if (stream === undefined) {
+          if (frame.type === 'RST_STREAM') return;
+          return self.emit('error', 'Stream ' + frame.id + ' not found');
+        }
+      }
+
+      // Emit 'data' event
+      if (frame.type === 'DATA') {
+        if (frame.data.length > 0){
+          if (stream.closedBy.client) {
+            stream.rstCode = 2;
+            stream.emit('error', 'Writing to half-closed stream');
+          } else {
+            stream._read(frame.data);
+          }
+        }
+      // Destroy stream if we was asked to do this
+      } else if (frame.type === 'RST_STREAM') {
+        stream.rstCode = 0;
+        if (frame.status === 5) {
+          // If client "cancels" connection - close stream and
+          // all associated push streams without error
+          stream.pushes.forEach(function(stream) {
+            stream.close();
+          });
+          stream.close();
+        } else {
+          // Emit error on destroy
+          stream.destroy(new Error('Received rst: ' + frame.status));
+        }
+      // Respond with same PING
+      } else if (frame.type === 'PING') {
+        self.write(self.framer.pingFrame(frame.pingId));
+      // Ignore SETTINGS for now
+      } else if (frame.type === 'SETTINGS' || frame.type === 'NOOP') {
+        // TODO: Handle something?
+      } else if (frame.type === 'GOAWAY') {
+        self.goaway = frame.lastId;
+      } else {
+        console.error('Unknown type: ', frame.type);
+      }
+    }
+
+    // Handle half-closed
+    if (frame.fin) {
+      // Don't allow to close stream twice
+      if (stream.closedBy.client) {
+        stream.rstCode = 2;
+        stream.emit('error', 'Already half-closed');
+      } else {
+        stream.closedBy.client = true;
+        stream.handleClose();
+      }
+    }
+  });
+
+  this.parser.on('_framer', function(framer) {
+    // Generate custom settings frame and send
+    self.write(
+      framer.maxStreamsFrame(options.maxStreams)
+    );
+  });
+
+  // Initialize scheduler
+  this.scheduler = spdy.scheduler.create(this);
+
+  // Store socket and pipe it to parser
+  this.socket = socket;
+
+  socket.pipe(this.parser);
+
+  // 2 minutes socket timeout
+  socket.setTimeout(2 * 60 * 1000);
+  socket.once('timeout', function() {
+    socket.destroy();
+  });
+
+  // Allow high-level api to catch socket errors
+  socket.on('error', function(e) {
+    self.emit('error', e);
+  });
+
+  socket.on('close', function() {
+    self._closed = true;
+    pool.put(pair);
+  });
+
+  socket.on('drain', function () {
+    self.emit('drain');
+  });
+}
+util.inherits(Connection, process.EventEmitter);
+exports.Connection = Connection;
+
+//
+// ### function write (data, encoding)
+// #### @data {String|Buffer} data
+// #### @encoding {String} (optional) encoding
+// Writes data to socket
+//
+Connection.prototype.write = function write(data, encoding) {
+  if (this.socket.writable) {
+    return this.socket.write(data, encoding);
+  }
+};
+
+//
+// ### function Stream (connection, frame)
+// #### @connection {Connection} SPDY Connection
+// #### @frame {Object} SYN_STREAM data
+// Abstract stream @constructor
+//
+function Stream(connection, frame) {
+  var self = this;
+  stream.Stream.call(this);
+
+  this.connection = connection;
+  this.framer = connection.framer;
+
+  this.ondata = this.onend = null;
+
+  // RST_STREAM code if any
+  this.rstCode = 1;
+  this._destroyed = false;
+
+  this.closedBy = {
+    client: false,
+    server: false
+  };
+
+  // Lock data
+  this.locked = false;
+  this.lockBuffer = [];
+
+  // Store id
+  this.id = frame.id;
+
+  // Store priority
+  this.priority = frame.priority;
+
+  // Array of push streams associated to that one
+  this.pushes = [];
+
+  this._paused = false;
+  this._buffer = [];
+
+  // Create compression streams
+  this.deflate = connection.deflate;
+  this.inflate = connection.inflate;
+
+  // Store headers
+  this.headers = frame.headers;
+
+  this.frame = frame;
+
+  this.readable = this.writable = true;
+}
+util.inherits(Stream, stream.Stream);
+exports.Stream = Stream;
+
+//
+// ### function isGoaway ()
+// Returns true if any writes to that stream should be ignored
+//
+Stream.prototype.isGoaway = function isGoaway() {
+  return this.connection.goaway && this.id > this.connection.goaway;
+};
+
+//
+// ### function init ()
+// Initialize stream, internal
+//
+Stream.prototype.init = function init() {
+  var headers = this.headers,
+      req = [headers.method + ' ' + headers.url + ' ' + headers.version];
+
+  Object.keys(headers).forEach(function (key) {
+    if (key !== 'method' && key !== 'url' && key !== 'version' &&
+        key !== 'scheme') {
+      req.push(key + ': ' + headers[key]);
+    }
+  });
+
+  // Add '\r\n\r\n'
+  req.push('', '');
+
+  req = new Buffer(req.join('\r\n'));
+
+  this._read(req);
+};
+
+//
+// ### function lock (callback)
+// #### @callback {Function} continuation callback
+// Acquire lock
+//
+Stream.prototype.lock = function lock(callback) {
+  if (!callback) return;
+
+  if (this.locked) {
+    this.lockBuffer.push(callback);
+  } else {
+    this.locked = true;
+    callback.call(this, null);
+  }
+};
+
+//
+// ### function unlock ()
+// Release lock and call all buffered callbacks
+//
+Stream.prototype.unlock = function unlock() {
+  if (this.locked) {
+    this.locked = false;
+    this.lock(this.lockBuffer.shift());
+  }
+};
+
+//
+// ### function setTimeout ()
+// TODO: use timers.enroll, timers.active, timers.unenroll
+//
+Stream.prototype.setTimeout = function setTimeout(time) {};
+
+//
+// ### function handleClose ()
+// Close stream if it was closed by both server and client
+//
+Stream.prototype.handleClose = function handleClose() {
+  if (this.closedBy.client && this.closedBy.server) {
+    this.close();
+  }
+};
+
+//
+// ### function close ()
+// Destroys stream
+//
+Stream.prototype.close = function close() {
+  this.destroy();
+};
+
+//
+// ### function destroy (error)
+// #### @error {Error} (optional) error
+// Destroys stream
+//
+Stream.prototype.destroy = function destroy(error) {
+  if (this._destroyed) return;
+  this._destroyed = true;
+
+  delete this.connection.streams[this.id];
+  this.connection.streamsCount--;
+
+  if (error) {
+    if (this.rstCode) {
+      this.connection.write(this.framer.rstFrame(this.id, this.rstCode));
+    }
+  }
+
+  var self = this;
+  process.nextTick(function() {
+    if (error) self.emit('error', error);
+    self.emit('close');
+  });
+};
+
+//
+// ### function _writeData (fin, buffer)
+// #### @fin {Boolean}
+// #### @buffer {Buffer}
+// Internal function
+//
+Stream.prototype._writeData = function _writeData(fin, buffer) {
+  this.lock(function() {
+    var stream = this,
+        frame = this.framer.dataFrame(this.id, fin, buffer);
+
+    stream.connection.scheduler.schedule(stream, frame);
+    stream.connection.scheduler.tick();
+
+    if (fin) this.close();
+
+    this.unlock();
+  });
+};
+
+//
+// ### function write (data, encoding)
+// #### @data {Buffer|String} data
+// #### @encoding {String} data encoding
+// Writes data to connection
+//
+Stream.prototype.write = function write(data, encoding) {
+  // Do not send data to new connections after GOAWAY
+  if (this.isGoaway()) return;
+
+  var buffer;
+
+  // Send DATA
+  if (typeof data === 'string') {
+    buffer = new Buffer(data, encoding);
+  } else {
+    buffer = data;
+  }
+
+  // TCP Frame size is equal to:
+  // data frame size + data frame headers (8 bytes) +
+  // tls frame headers and data (encrypted) ~ 140 bytes
+  // So setting MTU equal to 1300 is most optimal
+  // as end packet will have size ~ 1442 bytes
+  // (My MTU is 1492 bytes)
+  var MTU = 1300;
+
+  // Try to fit MTU
+  if (buffer.length < MTU) {
+    this._writeData(false, buffer);
+  } else {
+    var len = buffer.length - MTU;
+    for (var offset = 0; offset < len; offset += MTU) {
+      this._writeData(false, buffer.slice(offset, offset + MTU));
+    }
+    this._writeData(false, buffer.slice(offset));
+  }
+};
+
+//
+// ### function end (data)
+// #### @data {Buffer|String} (optional) data to write before ending stream
+// #### @encoding {String} (optional) string encoding
+// Send FIN data frame
+//
+Stream.prototype.end = function end(data, encoding) {
+  // Do not send data to new connections after GOAWAY
+  if (this.isGoaway()) return;
+
+  if (data) this.write(data, encoding);
+
+  this._writeData(true, []);
+  this.closedBy.server = true;
+  this.handleClose();
+};
+
+//
+// ### function pause ()
+// Start buffering all data
+//
+Stream.prototype.pause = function pause() {
+  if (this._paused) return;
+  this._paused = true;
+};
+
+//
+// ### function resume ()
+// Start buffering all data
+//
+Stream.prototype.resume = function resume() {
+  if (!this._paused) return;
+  this._paused = false;
+
+  var self = this,
+      buffer = this._buffer;
+
+  this._buffer = [];
+
+  process.nextTick(function() {
+    buffer.forEach(function(chunk) {
+      self._read(chunk);
+    });
+  });
+};
+
+//
+// ### function _read (data)
+// #### @data {Buffer} buffer to read
+// (internal)
+//
+Stream.prototype._read = function _read(data) {
+  if (this._paused) {
+    this._buffer.push(data);
+    return;
+  }
+
+  var self = this;
+  process.nextTick(function() {
+    if (self.ondata) self.ondata(data, 0, data.length);
+    self.emit('data', data);
+  });
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy/utils.js
@@ -0,0 +1,120 @@
+var utils = exports;
+
+var zlib = require('zlib'),
+    Buffer = require('buffer').Buffer;
+
+// SPDY deflate/inflate dictionary
+var dictionary = new Buffer([
+  'optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-',
+  'languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi',
+  'f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser',
+  '-agent10010120020120220320420520630030130230330430530630740040140240340440',
+  '5406407408409410411412413414415416417500501502503504505accept-rangesageeta',
+  'glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic',
+  'ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran',
+  'sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati',
+  'oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo',
+  'ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe',
+  'pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic',
+  'ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1',
+  '.1statusversionurl\x00'
+].join(''));
+
+//
+// ### function createDeflate ()
+// Creates deflate stream with SPDY dictionary
+//
+utils.createDeflate = function createDeflate() {
+  var deflate = zlib.createDeflate({ dictionary: dictionary, windowBits: 11 });
+
+  // Define lock information early
+  deflate.locked = false;
+  deflate.lockBuffer = [];
+
+  return deflate;
+};
+
+//
+// ### function createInflate ()
+// Creates inflate stream with SPDY dictionary
+//
+utils.createInflate = function createInflate() {
+  var inflate = zlib.createInflate({ dictionary: dictionary, windowBits: 15 });
+
+  // Define lock information early
+  inflate.locked = false;
+  inflate.lockBuffer = [];
+
+  return inflate;
+};
+
+//
+// ### function resetZlibStream (stream)
+// #### @stream {zlib.Stream} stream
+// Resets zlib stream and associated locks
+//
+utils.resetZlibStream = function resetZlibStream(stream, callback) {
+  if (stream.locked) {
+    stream.lockBuffer.push(function() {
+      resetZlibStream(stream, callback);
+    });
+    return;
+  }
+
+  stream.reset();
+  stream.lockBuffer = [];
+
+  callback(null);
+};
+
+var delta = 0;
+//
+// ### function zstream (stream, buffer, callback)
+// #### @stream {Deflate|Inflate} One of streams above
+// #### @buffer {Buffer} Input data (to compress or to decompress)
+// #### @callback {Function} Continuation to callback
+// Compress/decompress data and pass it to callback
+//
+utils.zstream = function zstream(stream, buffer, callback) {
+  var flush = stream._flush,
+      chunks = [],
+      total = 0;
+
+  if (stream.locked) {
+    stream.lockBuffer.push(function() {
+      zstream(stream, buffer, callback);
+    });
+    return;
+  }
+  stream.locked = true;
+
+  function collect(chunk) {
+    chunks.push(chunk);
+    total += chunk.length;
+  }
+  stream.on('data', collect);
+  stream.write(buffer);
+
+  stream.flush(function() {
+    stream.removeAllListeners('data');
+    stream._flush = flush;
+
+    callback(null, chunks, total);
+
+    stream.locked = false;
+    var deferred = stream.lockBuffer.shift();
+    if (deferred) deferred();
+  });
+};
+
+//
+// ### function zwrap (stream)
+// #### @stream {zlib.Stream} stream to wrap
+// Wraps stream within function to allow simple deflate/inflate
+//
+utils.zwrap = function zwrap(stream) {
+  return function(data, callback) {
+    utils.zstream(stream, data, callback);
+  };
+};
+
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/lib/spdy/zlib-pool.js
@@ -0,0 +1,52 @@
+var zlibpool = exports,
+    spdy = require('../spdy');
+
+//
+// ### function Pool ()
+// Zlib streams pool
+//
+function Pool() {
+  this.pool = [];
+}
+
+//
+// ### function create ()
+// Returns instance of Pool
+//
+zlibpool.create = function create() {
+  return new Pool();
+};
+
+var x = 0;
+//
+// ### function get ()
+// Returns pair from pool or a new one
+//
+Pool.prototype.get = function get(callback) {
+  if (this.pool.length > 0) {
+    return this.pool.pop();
+  } else {
+    return {
+      deflate: spdy.utils.createDeflate(),
+      inflate: spdy.utils.createInflate()
+    };
+  }
+};
+
+//
+// ### function put (pair)
+// Puts pair into pool
+//
+Pool.prototype.put = function put(pair) {
+  var self = this,
+      waiting = 2;
+
+  spdy.utils.resetZlibStream(pair.inflate, done);
+  spdy.utils.resetZlibStream(pair.deflate, done);
+
+  function done() {
+    if (--waiting === 0) {
+      self.pool.push(pair);
+    }
+  }
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/package.json
@@ -0,0 +1,33 @@
+{
+  "name": "spdy",
+  "version": "1.2.0",
+  "description": "Implementation of the SPDY protocol on node.js.",
+  "keywords": [
+    "spdy"
+  ],
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/indutny/node-spdy.git"
+  },
+  "homepage": "https://github.com/indutny/node-spdy",
+  "bugs": {
+    "email": "node-spdy+bugs@indutny.com",
+    "url": "https://github.com/indunty/node-spdy/issues"
+  },
+  "author": "Fedor Indutny <fedor.indutny@gmail.com>",
+  "contributors": [
+    "Chris Storm <github@eeecooks.com>",
+    "François de Metz <francois@2metz.fr>"
+  ],
+  "dependencies": {},
+  "devDependencies": {
+    "mocha": "~ 0.10.1"
+  },
+  "scripts": {
+    "test": "mocha --ui tdd --growl --reporter spec test/unit/*-test.js"
+  },
+  "engines": [
+    "node ~ 0.7.0"
+  ],
+  "main": "./lib/spdy"
+}
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/test/benchmarks/syn.js
@@ -0,0 +1,48 @@
+var tls = require('tls'),
+    frames = require('../fixtures/frames');
+
+var uri = require('url').parse(process.argv[2]),
+    host = uri.hostname,
+    port = +uri.port,
+    url = uri.path;
+
+frames.createSynStream(host, url, function(syn_stream) {
+  var start = +new Date,
+      num = 3000;
+
+  batch(port, host, syn_stream, 100, num, function() {
+    var end = +new Date;
+    console.log('requests/sec : %d', 1000 * num / (end - start));
+  });
+});
+
+function request(port, host, data, callback) {
+  var socket = tls.connect(port, host, {NPNProtocols: ['spdy/2']}, function() {
+    socket.write(data);
+    socket.once('data', function() {
+      socket.destroy();
+      callback();
+    });
+  });
+
+  return socket;
+};
+
+function batch(port, host, data, parallel, num, callback) {
+  var left = num,
+      done = 0;
+
+  for (var i = 0; i < parallel; i++) {
+    run(i);
+  }
+
+  function run(i) {
+    left--;
+    if (left < 0) return;
+
+    request(port, host, data, function() {
+      if (++done ===  num) return callback();
+      run(i);
+    });
+  }
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/test/fixtures/frames.js
@@ -0,0 +1,38 @@
+var spdy = require('../../lib/spdy'),
+    Buffer = require('buffer').Buffer;
+
+exports.createSynStream = function(host, url, callback) {
+  var deflate = spdy.utils.createDeflate(),
+      chunks = [],
+      chunksTotal = 0,
+      syn_stream;
+
+  deflate.on('data', function(chunk) {
+    chunks.push(chunk);
+    chunksTotal += chunk.length;
+  });
+  deflate.write(new Buffer([ 0x00, 0x02, 0x00, 0x04 ]));
+  deflate.write('host');
+  deflate.write(new Buffer([ 0x00, host.length ]));
+  deflate.write(host);
+  deflate.write(new Buffer([ 0x00, 0x03 ]));
+  deflate.write('url');
+  deflate.write(new Buffer([ 0x00, url.length ]));
+  deflate.write(url);
+
+  deflate.flush(function() {
+    syn_stream = new Buffer(18 + chunksTotal);
+    syn_stream.writeUInt32BE(0x80020001, 0);
+    syn_stream.writeUInt32BE(chunksTotal + 8, 4);
+    syn_stream.writeUInt32BE(0x00000001, 8);
+    syn_stream.writeUInt32BE(0x00000000, 12);
+
+    var offset = 18;
+    chunks.forEach(function(chunk) {
+      chunk.copy(syn_stream, offset);
+      offset += chunk.length;
+    });
+
+    callback(syn_stream);
+  });
+};
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/test/fixtures/keys.js
@@ -0,0 +1,11 @@
+var fs = require('fs'),
+    path = require('path');
+
+var keysDir = require('path').resolve(__dirname, '../../keys/'),
+    options = {
+      key: fs.readFileSync(keysDir + '/spdy-key.pem'),
+      cert: fs.readFileSync(keysDir + '/spdy-cert.pem'),
+      ca: fs.readFileSync(keysDir + '/spdy-csr.pem')
+    };
+
+module.exports = options;
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/test/unit/framer-test.js
@@ -0,0 +1,190 @@
+var assert = require('assert'),
+    spdy = require('../../'),
+    Buffer = require('buffer').Buffer,
+    Stream = require('stream').Stream;
+
+suite('A Framer of SPDY module', function() {
+  var inflate,
+      deflate,
+      framer;
+
+  setup(function() {
+    inflate = spdy.utils.zwrap(spdy.utils.createInflate());
+    deflate = spdy.utils.zwrap(spdy.utils.createDeflate());
+    framer = new spdy.protocol[2].Framer(deflate, inflate);
+  });
+
+  /*
+    deflate.on('data', function(b) {console.log(b)});
+    deflate.write(new Buffer([
+      0x00, 0x02, // Number of name+value
+      0, 0x04, // Name length
+      0x68, 0x6f, 0x73, 0x74, // 'host'
+      0, 0x09, // Value length
+      0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74, // 'localhost'
+      0, 0x06,
+      0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, // 'custom',
+      0, 0x1,
+      0x31 // '1'
+    ]));
+    deflate.flush();
+   */
+
+  suite('frame parsing', function() {
+    test('given a SYN_STREAM should return correct frame', function(done) {
+      var body = new Buffer([
+        0x00, 0x00, 0x00, 0x01, // Stream ID
+        0x00, 0x00, 0x00, 0x00, // Associated Stream ID
+        0x00, 0x00, // Priority + Unused
+        0x78, 0xbb, 0xdf, 0xa2, 0x51, 0xb2, // Deflated Name/Value pairs
+        0x62, 0x60, 0x62, 0x60, 0x01, 0xe5, 0x12,
+        0x06, 0x4e, 0x50, 0x50, 0xe6, 0x80, 0x99,
+        0x6c, 0xc9, 0xa5, 0xc5, 0x25, 0xf9, 0xb9,
+        0x0c, 0x8c, 0x86, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff
+      ]);
+      framer.execute({
+        control: true,
+        type: 1,
+        length: body.length
+      }, body, function(err, frame) {
+        assert.ok(!err);
+        assert.equal(frame.type, 'SYN_STREAM');
+        assert.equal(frame.id, 1);
+        assert.equal(frame.associated, 0);
+        assert.equal(frame.headers.host, 'localhost');
+        assert.equal(frame.headers.custom, '1');
+        done();
+      });
+    });
+
+    test('given a SYN_REPLY should return correct frame', function(done) {
+      var body = new Buffer([
+        0x00, 0x00, 0x00, 0x01, // Stream ID
+        0x00, 0x00, // Unused
+        0x78, 0xbb, 0xdf, 0xa2, 0x51, 0xb2, // Deflated Name/Value pairs
+        0x62, 0x60, 0x62, 0x60, 0x01, 0xe5, 0x12,
+        0x06, 0x4e, 0x50, 0x50, 0xe6, 0x80, 0x99,
+        0x6c, 0xc9, 0xa5, 0xc5, 0x25, 0xf9, 0xb9,
+        0x0c, 0x8c, 0x86, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff
+      ]);
+      framer.execute({
+        control: true,
+        type: 2,
+        length: body.length
+      }, body, function(err, frame) {
+        assert.ok(!err);
+        assert.equal(frame.type, 'SYN_REPLY');
+        assert.equal(frame.id, 1);
+        assert.equal(frame.headers.host, 'localhost');
+        assert.equal(frame.headers.custom, '1');
+        done();
+      });
+    });
+
+    test('given a RST_STREAM should return correct frame', function(done) {
+      var body = new Buffer([0, 0, 0, 1, 0, 0, 0, 2]);
+      framer.execute({
+        control: true,
+        type: 3,
+        length: body.length
+      }, body, function(err, frame) {
+        assert.ok(!err);
+        assert.equal(frame.type, 'RST_STREAM');
+        assert.equal(frame.id, 1);
+        assert.equal(frame.status, 2);
+        done();
+      });
+    });
+
+    test('given a NOOP frame should return correct frame', function(done) {
+      framer.execute({
+        control: true,
+        type: 5,
+        length: 0
+      }, new Buffer(0), function(err, frame) {
+        assert.ok(!err);
+        assert.equal(frame.type, 'NOOP');
+        done();
+      });
+    });
+
+    test('given a PING frame should return correct frame', function(done) {
+      framer.execute({
+        control: true,
+        type: 6,
+        length: 0
+      }, new Buffer(0), function(err, frame) {
+        assert.ok(!err);
+        assert.equal(frame.type, 'PING');
+        done();
+      });
+    });
+
+    test('given a GOAWAY frame should return correct frame', function(done) {
+      var body = new Buffer([0, 0, 0, 1]);
+      framer.execute({
+        control: true,
+        type: 7,
+        length: body.length
+      }, body, function(err, frame) {
+        assert.ok(!err);
+        assert.equal(frame.type, 'GOAWAY');
+        assert.equal(frame.lastId, 1);
+        done();
+      });
+    });
+  });
+
+  suite('frame generation', function() {
+    test('.replyFrame() should generate correct frame', function(done) {
+      framer.replyFrame(1, 200, 'ok', {}, function(err, chunks) {
+        assert.equal(err, null);
+        assert.ok(chunks.length > 1);
+        done();
+      });
+    });
+
+    test('.streamFrame() should generate correct frame', function(done) {
+      framer.streamFrame(2, 1, { url : '/' }, {}, function(err, chunks) {
+        assert.equal(err, null);
+        assert.ok(chunks.length > 1);
+        done();
+      });
+    });
+
+    test('.dataFrame() w/o fin should generate correct frame', function() {
+      var frame = framer.dataFrame(1, false, new Buffer(123));
+      assert.equal(frame[4], 0);
+      assert.ok(frame.length > 8);
+    });
+
+    test('.dataFrame() with fin should generate correct frame', function() {
+      var frame = framer.dataFrame(1, true, new Buffer(123));
+      assert.equal(frame[4], 1);
+      assert.ok(frame.length > 8);
+    });
+
+    test('.pingFrame() should generate correct frame', function() {
+      var frame = framer.pingFrame(new Buffer([0, 1, 2, 3]));
+      assert.ok(frame.length > 0);
+    });
+
+    test('.rstFrame() should generate correct frame', function() {
+      var frame = framer.rstFrame(1, 2);
+      assert.ok(frame.length > 0);
+
+      // Verify that cache works
+      var frame = framer.rstFrame(1, 2);
+      assert.ok(frame.length > 0);
+    });
+
+    test('.maxStreamsFrame() should generate correct frame', function() {
+      var frame = framer.maxStreamsFrame(13);
+      assert.ok(frame.length > 0);
+
+      // Verify that cache works
+      var frame = framer.maxStreamsFrame(13);
+      assert.ok(frame.length > 0);
+    });
+  });
+});
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/test/unit/parser-test.js
@@ -0,0 +1,91 @@
+var assert = require('assert'),
+    spdy = require('../../'),
+    Buffer = require('buffer').Buffer;
+
+suite('A Parser of SPDY module', function() {
+  var parser;
+
+  setup(function() {
+    var deflate = spdy.utils.createDeflate(),
+        inflate = spdy.utils.createInflate();
+
+    parser = new spdy.parser.create(deflate, inflate);
+  });
+
+  test('should wait for headers initially', function() {
+    assert.equal(parser.waiting, 8);
+  });
+
+  test('should update buffered property once given < 8 bytes', function() {
+    parser.write(new Buffer(5));
+    assert.equal(parser.buffered, 5);
+  });
+
+  test('given SYN_STREAM header should start waiting for body', function() {
+    parser.write(new Buffer([
+      0x80, 0x02, 0x00, 0x01, // Control frame, version, type (SYN_STREAM)
+      0x00, 0x00, 0x12, 0x34
+    ]));
+
+    assert.equal(parser.waiting, 0x1234);
+    assert.equal(parser.state.type, 'frame-body');
+    assert.ok(parser.state.header.control);
+    assert.equal(parser.state.header.flags, 0);
+    assert.equal(parser.state.header.length, 0x1234);
+  });
+
+  test('given DATA header should start waiting for body', function() {
+    parser.write(new Buffer([
+      0x00, 0x00, 0x00, 0x01, // Data frame, stream ID
+      0x00, 0x00, 0x12, 0x34
+    ]));
+
+    assert.equal(parser.waiting, 0x1234);
+    assert.equal(parser.state.type, 'frame-body');
+    assert.ok(!parser.state.header.control);
+    assert.equal(parser.state.header.id, 1);
+    assert.equal(parser.state.header.flags, 0);
+    assert.equal(parser.state.header.length, 0x1234);
+  });
+
+  test('given chunked header should not fail', function() {
+    parser.write(new Buffer([
+      0x80, 0x02, 0x00, 0x01 // Control frame, version, type (SYN_STREAM)
+    ]));
+    assert.equal(parser.buffered, 4);
+
+    parser.write(new Buffer([
+      0x00, 0x00, 0x12, 0x34
+    ]));
+    assert.equal(parser.buffered, 0);
+
+    assert.equal(parser.waiting, 0x1234);
+    assert.equal(parser.state.type, 'frame-body');
+    assert.ok(parser.state.header.control);
+    assert.equal(parser.state.header.flags, 0);
+    assert.equal(parser.state.header.length, 0x1234);
+  });
+
+  test('given header and body should emit `frame`', function(done) {
+    parser.on('frame', function(frame) {
+      assert.ok(frame.type === 'DATA');
+      assert.equal(frame.id, 1);
+      assert.equal(frame.data.length, 4);
+      assert.equal(frame.data[0], 0x01);
+      assert.equal(frame.data[1], 0x02);
+      assert.equal(frame.data[2], 0x03);
+      assert.equal(frame.data[3], 0x04);
+      done();
+    });
+
+    parser.write(new Buffer([
+      0x00, 0x00, 0x00, 0x01, // Data frame, stream ID
+      0x00, 0x00, 0x00, 0x04,
+      0x01, 0x02, 0x03, 0x04  // Body
+    ]));
+
+    // Waits for next frame
+    assert.equal(parser.waiting, 8);
+    assert.equal(parser.state.type, 'frame-head');
+  });
+});
new file mode 100644
--- /dev/null
+++ b/testing/xpcshell/node-spdy/test/unit/server-test.js
@@ -0,0 +1,142 @@
+var assert = require('assert'),
+    spdy = require('../../'),
+    keys = require('../fixtures/keys'),
+    https = require('https'),
+    tls = require('tls'),request
+    Buffer = require('buffer').Buffer;
+
+
+suite('A SPDY Server', function() {
+  var server;
+  setup(function(done) {
+    server = spdy.createServer(keys, function(req, res) {
+      res.end('ok');
+    });
+
+    server.listen(8081, done);
+  });
+
+  teardown(function(done) {
+    server.once('close', done);
+    server.close();
+  });
+
+  test('should respond on regular https requests', function(done) {
+    https.request({
+      host: 'localhost',
+      port: 8081,
+      path: '/',
+      method: 'GET'
+    }, function(res) {
+      assert.equal(res.statusCode, 200);
+      done();
+    }).end();
+  });
+
+  test('should respond on spdy requests', function(done) {
+    var socket = tls.connect(
+      8081,
+      'localhost',
+      { NPNProtocols: ['spdy/2'] },
+      function() {
+        var deflate = spdy.utils.createDeflate(),
+            chunks = [],
+            length = 0;
+
+        deflate.on('data', function(chunk) {
+          chunks.push(chunk);
+          length += chunk.length;
+        });
+
+        // Deflate headers
+        deflate.write(new Buffer([
+          0x00, 0x04, // method, url, version = 3 fields
+          0x00, 0x06,
+          0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, // method
+          0x00, 0x03,
+          0x47, 0x45, 0x54, // get
+          0x00, 0x03,
+          0x75, 0x72, 0x6c, // url
+          0x00, 0x01,
+          0x2f, // '/'
+          0x00, 0x07,
+          0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, // version
+          0x00, 0x08,
+          0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, // HTTP/1.1
+          0x00, 0x04,
+          0x68, 0x6f, 0x73, 0x74, // host
+          0x00, 0x09,
+          0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x68, 0x6f, 0x73, 0x74 //localhost
+        ]));
+
+        deflate.flush(function() {
+          // StreamID + Associated StreamID
+          length += 10;
+
+          // Headers
+          socket.write(new Buffer([
+            0x80, 0x02, 0x00, 0x01, // Control, Version, SYN_STREAM
+            0x00, 0x00, 0x00, length, // Flags, length (1 byte in this case)
+            0x00, 0x00, 0x00, 0x01, // StreamID
+            0x00, 0x00, 0x00, 0x00, // Associated StreamID
+            0x00, 0x00 // Priority + Unused
+          ]));
+
+          // Write compressed headers
+          chunks.forEach(function(chunk) {
+            socket.write(chunk);
+          });
+        });
+
+        var response = new Buffer(85),
+            offset = 0;
+
+        socket.on('data', function(chunk) {
+          assert.ok(offset + chunk.length <= 85);
+
+          chunk.copy(response, offset);
+          offset += chunk.length;
+
+          if (offset === 85) {
+            var frames = [];
+
+            offset = 0;
+            while (offset < response.length) {
+              var len = (response.readUInt32BE(offset + 4) & 0x00ffffff) + 8;
+              frames.push(response.slice(offset, offset + len));
+
+              offset += len;
+            }
+
+            // SYN_STREAM frame
+            assert.ok(frames.some(function(frame) {
+              return frame[0] === 0x80 && // Control frame
+                     frame[1] === 0x02 && // Version
+                     frame.readUInt16BE(2) === 0x0002 && // SYN_STREAM
+                     frame.readUInt32BE(8) === 0x0001; // StreamID
+            }));
+
+            // Data frames
+            assert.ok(frames.some(function(frame) {
+              return frame[0] === 0x00 && // Data frame
+                     frame.readUInt32BE(0) === 0x0001 && // StreamID
+                     frame.slice(8).toString() === 'ok';
+            }));
+
+            socket.destroy();
+          }
+        });
+
+        socket.on('close', function() {
+          done();
+        });
+      }
+    );
+
+    server.on('request', function(req, res) {
+      assert.equal(req.url, '/');
+      assert.equal(req.method, 'GET');
+      res.end('ok');
+    });
+  });
+});
--- a/testing/xpcshell/runxpcshelltests.py
+++ b/testing/xpcshell/runxpcshelltests.py
@@ -64,20 +64,21 @@ def markGotSIGINT(signum, stackFrame):
   gotSIGINT = True
 
 class XPCShellTests(object):
 
   log = logging.getLogger()
   oldcwd = os.getcwd()
 
   def __init__(self, log=sys.stdout):
-    """ Init logging """
+    """ Init logging and node status """
     handler = logging.StreamHandler(log)
     self.log.setLevel(logging.INFO)
     self.log.addHandler(handler)
+    self.nodeProc = None
 
   def buildTestList(self):
     """
       read the xpcshell.ini manifest and set self.alltests to be 
       an array of test objects.
 
       if we are chunking tests, it will be done here as well
     """
@@ -388,16 +389,64 @@ class XPCShellTests(object):
   def buildCmdTestFile(self, name):
     """
       Build the command line arguments for the test file.
       On a remote system, this may be overloaded to use a remote path structure.
     """
     return ['-e', 'const _TEST_FILE = ["%s"];' %
               replaceBackSlashes(name)]
 
+  def trySetupNode(self):
+    """
+      Run node for SPDY tests, if available, and updates mozinfo as appropriate.
+    """
+    nodeMozInfo = {'hasNode': False} # Assume the worst
+    nodeBin = None
+
+    # We try to find the node executable in the path given to us by the user in
+    # the MOZ_NODE_PATH environment variable
+    localPath = os.getenv('MOZ_NODE_PATH', None)
+    if localPath and os.path.exists(localPath) and os.path.isfile(localPath):
+      nodeBin = localPath
+
+    if nodeBin:
+      self.log.info('Found node at %s' % (nodeBin,))
+      myDir = os.path.split(os.path.abspath(__file__))[0]
+      mozSpdyJs = os.path.join(myDir, 'moz-spdy', 'moz-spdy.js')
+
+      if os.path.exists(mozSpdyJs):
+        # OK, we found our SPDY server, let's try to get it running
+        self.log.info('Found moz-spdy at %s' % (mozSpdyJs,))
+        stdout, stderr = self.getPipes()
+        try:
+          # We pipe stdin to node because the spdy server will exit when its
+          # stdin reaches EOF
+          self.nodeProc = Popen([nodeBin, mozSpdyJs], stdin=PIPE, stdout=PIPE,
+                  stderr=STDOUT, env=self.env, cwd=os.getcwd())
+
+          # Check to make sure the server starts properly by waiting for it to
+          # tell us it's started
+          msg = self.nodeProc.stdout.readline()
+          if msg.startswith('SPDY server listening'):
+              nodeMozInfo['hasNode'] = True
+        except OSError, e:
+          # This occurs if the subprocess couldn't be started
+          self.log.error('Could not run node SPDY server: %s' % (str(e),))
+
+    mozinfo.update(nodeMozInfo)
+
+  def shutdownNode(self):
+    """
+      Shut down our node process, if it exists
+    """
+    if self.nodeProc:
+      self.log.info('Node SPDY server shutting down ...')
+      # moz-spdy exits when its stdin reaches EOF, so force that to happen here
+      self.nodeProc.communicate()
+
   def writeXunitResults(self, results, name=None, filename=None, fh=None):
     """
       Write Xunit XML from results.
 
       The function receives an iterable of results dicts. Each dict must have
       the following keys:
 
         classname - The "class" name of the test.
@@ -592,16 +641,20 @@ class XPCShellTests(object):
     # Handle filenames in mozInfo
     if not isinstance(self.mozInfo, dict):
       mozInfoFile = self.mozInfo
       if not os.path.isfile(mozInfoFile):
         self.log.error("Error: couldn't find mozinfo.json at '%s'. Perhaps you need to use --build-info-json?" % mozInfoFile)
         return False
       self.mozInfo = parse_json(open(mozInfoFile).read())
     mozinfo.update(self.mozInfo)
+
+    # We have to do this before we build the test list so we know whether or
+    # not to run tests that depend on having the node spdy server
+    self.trySetupNode()
     
     pStdout, pStderr = self.getPipes()
 
     self.buildTestList()
 
     if shuffle:
       random.shuffle(self.alltests)
 
@@ -764,16 +817,18 @@ class XPCShellTests(object):
         if (keepGoing):
           gotSIGINT = False
         else:
           xunitResults.append(xunitResult)
           break
 
       xunitResults.append(xunitResult)
 
+    self.shutdownNode()
+
     if self.testCount == 0:
       self.log.error("TEST-UNEXPECTED-FAIL | runxpcshelltests.py | No tests run. Did you pass an invalid --test-path?")
       self.failCount = 1
 
     self.log.info("""INFO | Result summary:
 INFO | Passed: %d
 INFO | Failed: %d
 INFO | Todo: %d""" % (self.passCount, self.failCount, self.todoCount))
--- a/toolkit/components/aboutmemory/content/aboutMemory.js
+++ b/toolkit/components/aboutmemory/content/aboutMemory.js
@@ -206,61 +206,57 @@ function processMemoryReporters(aMgr, aI
 
   function handleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount,
                         aDescription)
   {
     checkReport(aUnsafePath, aKind, aUnits, aAmount, aDescription);
     aHandleReport(aProcess, aUnsafePath, aKind, aUnits, aAmount, aDescription);
   }
 
-  function handleException(aReporterStr, aUnsafePathOrName, aE)
-  {
-    // There are two exception cases that must be distinguished here.
-    //
-    // - We want to halt proceedings on exceptions thrown within this file
-    //   (i.e. assertion failures in handleReport);  such exceptions contain
-    //   gAssertionFailureMsgPrefix in their string representation.
-    //
-    // - We want to continue on when faced with exceptions thrown outside this
-    //   file (i.e. in collectReports).
-
-    let str = aE.toString();
-    if (str.search(gAssertionFailureMsgPrefix) >= 0) {
-      throw(aE); 
-    } else {
-      debug("Bad memory " + aReporterStr + " '" + aUnsafePathOrName +
-            "': " + aE);
+  let e = aMgr.enumerateReporters();
+  while (e.hasMoreElements()) {
+    let rOrig = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
+    let unsafePath;
+    try {
+      unsafePath = rOrig.path;
+      if (!aIgnoreSingle(unsafePath)) {
+        handleReport(rOrig.process, unsafePath, rOrig.kind, rOrig.units, 
+                     rOrig.amount, rOrig.description);
+      }
+    }
+    catch (ex) {
+      debug("Exception thrown by memory reporter: " + unsafePath + ": " + ex);
     }
   }
 
-  let e = aMgr.enumerateReporters();
-  while (e.hasMoreElements()) {
-    let rOrig = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
-    let unsafePath = rOrig.path;
-    try {
-      if (!aIgnoreSingle(unsafePath)) {
-        handleReport(rOrig.process, unsafePath, rOrig.kind, rOrig.units,
-                     rOrig.amount, rOrig.description);
-      }
-    }
-    catch (e) {
-      handleException("reporter", unsafePath, e);
-    }
-  }
   let e = aMgr.enumerateMultiReporters();
   while (e.hasMoreElements()) {
     let mrOrig = e.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
     let name = mrOrig.name;
     try {
       if (!aIgnoreMulti(name)) {
         mrOrig.collectReports(handleReport, null);
       }
     }
-    catch (e) {
-      handleException("multi-reporter", name, e);
+    catch (ex) {
+      // There are two exception cases that must be distinguished here.
+      //
+      // - We want to halt proceedings on exceptions thrown within this file
+      //   (i.e. assertion failures in handleReport);  such exceptions contain
+      //   gAssertionFailureMsgPrefix in their string representation.
+      //
+      // - We want to continue on when faced with exceptions thrown outside
+      //   this file (i.e. when measuring an amount in collectReports).
+      let str = ex.toString();
+      if (str.search(gAssertionFailureMsgPrefix) >= 0) {
+        throw(ex); 
+      } else {
+        debug("Exception thrown within memory multi-reporter: " + name + ": " +
+              ex);
+      }
     }
   }
 }
 
 // This regexp matches sentences and sentence fragments, i.e. strings that
 // start with a capital letter and ends with a '.'.  (The final sentence may be
 // in parentheses, so a ')' might appear after the '.'.)
 const gSentenceRegExp = /^[A-Z].*\.\)?$/m;
@@ -325,18 +321,16 @@ function appendElementWithText(aP, aTagN
   appendTextNode(e, aText);
   return e;
 }
 
 //---------------------------------------------------------------------------
 // Code specific to about:memory
 //---------------------------------------------------------------------------
 
-const kUnknown = -1;    // used for an unknown _amount
-
 const kTreeDescriptions = {
   'explicit' :
 "This tree covers explicit memory allocations by the application, both at the \
 operating system level (via calls to functions such as VirtualAlloc, \
 vm_allocate, and mmap), and at the heap allocation level (via functions such \
 as malloc, calloc, realloc, memalign, operator new, and operator new[]).\
 \n\n\
 It excludes memory that is mapped implicitly such as code and data segments, \
@@ -510,26 +504,21 @@ function Report(aUnsafePath, aKind, aUni
   this._units       = aUnits;
   this._amount      = aAmount;
   this._description = aDescription;
   // this._nMerged is only defined if > 1
   // this._done is defined and set to true when the Report's amount is read
 }
 
 Report.prototype = {
-  // Sum the values (accounting for possible kUnknown amounts), and mark |this|
-  // as a dup.  We mark dups because it's useful to know when a report is
-  // duplicated;  it might be worth investigating and splitting up to have
-  // non-duplicated names.
+  // Sum the values and mark |this| as a dup.  We mark dups because it's useful
+  // to know when a report is duplicated;  it might be worth investigating and
+  // splitting up to have non-duplicated names.
   merge: function(r) {
-    if (this._amount !== kUnknown && r._amount !== kUnknown) {
-      this._amount += r._amount;
-    } else if (this._amount === kUnknown && r._amount !== kUnknown) {
-      this._amount = r._amount;
-    }
+    this._amount += r._amount;
     this._nMerged = this._nMerged ? this._nMerged + 1 : 2;
   },
 
   treeNameMatches: function(aTreeName) {
     // Nb: the '/' must be present, because we have a KIND_OTHER reporter
     // called "explicit" which is not part of the "explicit" tree.
     return this._unsafePath.startsWith(aTreeName) &&
            this._unsafePath.charAt(aTreeName.length) === '/';
@@ -590,24 +579,23 @@ function getReportsByProcess(aMgr)
 // - Non-leaf TreeNodes are just scaffolding nodes for the tree;  their values
 //   are derived from their children.
 function TreeNode(aUnsafeName)
 {
   // Nb: _units is not needed, it's always UNITS_BYTES.
   this._unsafeName = aUnsafeName;
   this._kids = [];
   // Leaf TreeNodes have these properties added immediately after construction:
-  // - _amount (which is never |kUnknown|)
+  // - _amount
   // - _description
   // - _kind
   // - _nMerged (only defined if > 1)
-  // - _isUnknown (only defined if true)
   //
   // Non-leaf TreeNodes have these properties added later:
-  // - _amount (which is never |kUnknown|)
+  // - _amount
   // - _description
   // - _hideKids (only defined if true)
 }
 
 TreeNode.prototype = {
   findKid: function(aUnsafeName) {
     for (let i = 0; i < this._kids.length; i++) {
       if (this._kids[i]._unsafeName === aUnsafeName) {
@@ -671,37 +659,31 @@ function buildTree(aReports, aTreeName)
           u = uMatch;
         } else {
           let v = new TreeNode(unsafeName);
           u._kids.push(v);
           u = v;
         }
       }
       // Fill in extra details in the leaf node from the Report object.
-      if (r._amount !== kUnknown) {
-        u._amount = r._amount;
-      } else {
-        u._amount = 0;
-        u._isUnknown = true;
-      }
+      u._amount = r._amount;
       u._description = r._description;
       u._kind = r._kind;
       if (r._nMerged) {
         u._nMerged = r._nMerged;
       }
       r._done = true;
     }
   }
 
   // Using falseRoot makes the above code simpler.  Now discard it, leaving
   // aTreeName at the root.
   t = t._kids[0];
 
   // Next, fill in the remaining properties bottom-up.
-  // Note that this function never returns kUnknown.
   function fillInNonLeafNodes(aT)
   {
     if (aT._kids.length === 0) {
       // Leaf node.  Has already been filled in.
       assert(aT._kind !== undefined, "aT._kind is undefined for leaf node");
     } else {
       // Non-leaf node.  Derive its _amount and _description entirely
       // from its children.
@@ -709,17 +691,16 @@ function buildTree(aReports, aTreeName)
       let childrenBytes = 0;
       for (let i = 0; i < aT._kids.length; i++) {
         childrenBytes += fillInNonLeafNodes(aT._kids[i]);
       }
       aT._amount = childrenBytes;
       aT._description = "The sum of all entries below '" +
                         flipBackslashes(aT._unsafeName) + "'.";
     }
-    assert(aT._amount !== kUnknown, "aT._amount !== kUnknown");
     return aT._amount;
   }
 
   fillInNonLeafNodes(t);
 
   // Reduce the depth of the tree by the number of occurrences of '/' in
   // aTreeName.  (Thus the tree named 'foo/bar/baz' will be rooted at 'baz'.)
   let slashCount = 0;
@@ -779,39 +760,32 @@ function fixUpExplicitTree(aT, aReports)
     }
     return n;
   }
 
   // A special case:  compute the derived "heap-unclassified" value.  Don't
   // mark "heap-allocated" when we get its size because we want it to appear
   // in the "Other Measurements" list.
   let heapAllocatedReport = aReports["heap-allocated"];
-  assert(heapAllocatedReport, "no 'heap-allocated' report");
+  if (heapAllocatedReport === undefined)
+    return false;
+
   let heapAllocatedBytes = heapAllocatedReport._amount;
   let heapUnclassifiedT = new TreeNode("heap-unclassified");
-  let hasKnownHeapAllocated = heapAllocatedBytes !== kUnknown;
-  if (hasKnownHeapAllocated) {
-    heapUnclassifiedT._amount =
-      heapAllocatedBytes - getKnownHeapUsedBytes(aT);
-  } else {
-    heapUnclassifiedT._amount = 0;
-    heapUnclassifiedT._isUnknown = true;
-  }
+  heapUnclassifiedT._amount = heapAllocatedBytes - getKnownHeapUsedBytes(aT);
   // This kindToString() ensures the "(Heap)" prefix is set without having to
   // set the _kind property, which would mean that there is a corresponding
   // Report object for this TreeNode object (which isn't true)
   heapUnclassifiedT._description = kindToString(KIND_HEAP) +
       "Memory not classified by a more specific reporter. This includes " +
       "slop bytes due to internal fragmentation in the heap allocator " +
       "(caused when the allocator rounds up request sizes).";
-
   aT._kids.push(heapUnclassifiedT);
   aT._amount += heapUnclassifiedT._amount;
-
-  return hasKnownHeapAllocated;
+  return true;
 }
 
 /**
  * Sort all kid nodes from largest to smallest, and insert aggregate nodes
  * where appropriate.
  *
  * @param aTotalBytes
  *        The size of the tree's root node.
@@ -820,17 +794,16 @@ function fixUpExplicitTree(aT, aReports)
  */
 function sortTreeAndInsertAggregateNodes(aTotalBytes, aT)
 {
   const kSignificanceThresholdPerc = 1;
 
   function isInsignificant(aT)
   {
     return !gVerbose &&
-           aTotalBytes !== kUnknown &&
            (100 * aT._amount / aTotalBytes) < kSignificanceThresholdPerc;
   }
 
   if (aT._kids.length === 0) {
     return;
   }
 
   aT._kids.sort(TreeNode.compare);
@@ -892,25 +865,25 @@ let gUnsafePathsWithInvalidValuesForThis
 
 function appendWarningElements(aP, aHasKnownHeapAllocated,
                                aHasMozMallocUsableSize)
 {
   if (!aHasKnownHeapAllocated && !aHasMozMallocUsableSize) {
     appendElementWithText(aP, "p", "", 
       "WARNING: the 'heap-allocated' memory reporter and the " +
       "moz_malloc_usable_size() function do not work for this platform " +
-      "and/or configuration.  This means that 'heap-unclassified' is zero " +
-      "and the 'explicit' tree shows much less memory than it should.");
+      "and/or configuration.  This means that 'heap-unclassified' is not " +
+      "shown and the 'explicit' tree shows much less memory than it should.");
     appendTextNode(aP, "\n\n");
 
   } else if (!aHasKnownHeapAllocated) {
     appendElementWithText(aP, "p", "", 
       "WARNING: the 'heap-allocated' memory reporter does not work for this " +
       "platform and/or configuration. This means that 'heap-unclassified' " +
-      "is zero and the 'explicit' tree shows less memory than it should.");
+      "is not shown and the 'explicit' tree shows less memory than it should.");
     appendTextNode(aP, "\n\n");
 
   } else if (!aHasMozMallocUsableSize) {
     appendElementWithText(aP, "p", "", 
       "WARNING: the moz_malloc_usable_size() function does not work for " +
       "this platform and/or configuration.  This means that much of the " +
       "heap-allocated memory is not measured by individual memory reporters " +
       "and so will fall under 'heap-unclassified'.");
@@ -1146,17 +1119,17 @@ function kindToString(aKind)
 }
 
 // Possible states for kids.
 const kNoKids   = 0;
 const kHideKids = 1;
 const kShowKids = 2;
 
 function appendMrNameSpan(aP, aKind, aKidsState, aDescription, aUnsafeName,
-                          aIsUnknown, aIsInvalid, aNMerged)
+                          aIsInvalid, aNMerged)
 {
   let text = "";
   if (aKidsState === kNoKids) {
     appendElementWithText(aP, "span", "mrSep", kDoubleHorizontalSep);
   } else if (aKidsState === kHideKids) {
     appendElementWithText(aP, "span", "mrSep",        " ++ ");
     appendElementWithText(aP, "span", "mrSep hidden", " -- ");
   } else if (aKidsState === kShowKids) {
@@ -1165,21 +1138,16 @@ function appendMrNameSpan(aP, aKind, aKi
   } else {
     assert(false, "bad aKidsState");
   }
 
   let nameSpan = appendElementWithText(aP, "span", "mrName",
                                        flipBackslashes(aUnsafeName));
   nameSpan.title = kindToString(aKind) + aDescription;
 
-  if (aIsUnknown) {
-    let noteSpan = appendElementWithText(aP, "span", "mrNote", " [*]");
-    noteSpan.title =
-      "Warning: this memory reporter was unable to compute a useful value. ";
-  }
   if (aIsInvalid) {
     let noteSpan = appendElementWithText(aP, "span", "mrNote", " [?!]");
     noteSpan.title =
       "Warning: this value is invalid and indicates a bug in one or more " +
       "memory reporters. ";
   }
   if (aNMerged) {
     let noteSpan = appendElementWithText(aP, "span", "mrNote",
@@ -1367,17 +1335,17 @@ function appendTreeElements(aPOuter, aT,
 
     appendMrValueSpan(d, tString, tIsInvalid);
     appendElementWithText(d, "span", "mrPerc", percText);
 
     // We don't want to show '(nonheap)' on a tree like 'smaps/vsize', since
     // the whole tree is non-heap.
     let kind = isExplicitTree ? aT._kind : undefined;
     appendMrNameSpan(d, kind, kidsState, aT._description, aT._unsafeName,
-                     aT._isUnknown, tIsInvalid, aT._nMerged);
+                     tIsInvalid, aT._nMerged);
     appendTextNode(d, "\n");
 
     // In non-verbose mode, invalid nodes can be hidden in collapsed sub-trees.
     // But it's good to always see them, so force this.
     if (!gVerbose && tIsInvalid) {
       expandPathToThisElement(d);
     }
 
@@ -1419,22 +1387,17 @@ function appendTreeElements(aPOuter, aT,
 
 //---------------------------------------------------------------------------
 
 function OtherReport(aUnsafePath, aUnits, aAmount, aDescription, aNMerged)
 {
   // Nb: _kind is not needed, it's always KIND_OTHER.
   this._unsafePath = aUnsafePath;
   this._units    = aUnits;
-  if (aAmount === kUnknown) {
-    this._amount     = 0;
-    this._isUnknown = true;
-  } else {
-    this._amount = aAmount;
-  }
+  this._amount = aAmount;
   this._description = aDescription;
   this._asString = this.toString();
 }
 
 OtherReport.prototype = {
   toString: function() {
     switch (this._units) {
       case UNITS_BYTES:            return formatBytes(this._amount);
@@ -1446,19 +1409,18 @@ OtherReport.prototype = {
     }
   },
 
   isInvalid: function() {
     let n = this._amount;
     switch (this._units) {
       case UNITS_BYTES:
       case UNITS_COUNT:
-      case UNITS_COUNT_CUMULATIVE: return (n !== kUnknown && n < 0);
-      case UNITS_PERCENTAGE:       return (n !== kUnknown &&
-                                           !(0 <= n && n <= 10000));
+      case UNITS_COUNT_CUMULATIVE: return n < 0;
+      case UNITS_PERCENTAGE:       return !(0 <= n && n <= 10000);
       default:
         assert(false, "bad units in OtherReport.isInvalid");
     }
   }
 };
 
 OtherReport.compare = function(a, b) {
   return a._unsafePath < b._unsafePath ? -1 :
@@ -1509,17 +1471,17 @@ function appendOtherElements(aP, aReport
     let oIsInvalid = o.isInvalid();
     if (oIsInvalid) {
       gUnsafePathsWithInvalidValuesForThisProcess.push(o._unsafePath);
       reportAssertionFailure("Invalid value for " +
                              flipBackslashes(o._unsafePath));
     }
     appendMrValueSpan(pre, pad(o._asString, maxStringLength, ' '), oIsInvalid);
     appendMrNameSpan(pre, KIND_OTHER, kNoKids, o._description, o._unsafePath,
-                     o._isUnknown, oIsInvalid);
+                     oIsInvalid);
     appendTextNode(pre, "\n");
   }
 
   appendTextNode(aP, "\n");  // gives nice spacing when we cut and paste
 }
 
 function appendSectionHeader(aP, aText)
 {
--- a/toolkit/components/aboutmemory/tests/test_aboutmemory.xul
+++ b/toolkit/components/aboutmemory/tests/test_aboutmemory.xul
@@ -16,16 +16,17 @@
 
   <!-- test code goes here -->
   <script type="application/javascript">
   <![CDATA[
   "use strict";
 
   const Cc = Components.classes;
   const Ci = Components.interfaces;
+  const Cr = Components.results;
   let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
             getService(Ci.nsIMemoryReporterManager);
 
   // Remove all the real reporters and multi-reporters;  save them to
   // restore at the end.
   let e = mgr.enumerateReporters();
   let realReporters = [];
   while (e.hasMoreElements()) {
@@ -39,17 +40,16 @@
     let r = e.getNext().QueryInterface(Ci.nsIMemoryMultiReporter);
     mgr.unregisterMultiReporter(r);
     realMultiReporters.push(r);
   }
 
   // Setup various fake-but-deterministic reporters.
   const KB = 1024;
   const MB = KB * KB;
-  const kUnknown = -1;
   const NONHEAP = Ci.nsIMemoryReporter.KIND_NONHEAP;
   const HEAP    = Ci.nsIMemoryReporter.KIND_HEAP;
   const OTHER   = Ci.nsIMemoryReporter.KIND_OTHER;
 
   const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
   const COUNT = Ci.nsIMemoryReporter.UNITS_COUNT;
   const COUNT_CUMULATIVE = Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE;
   const PERCENTAGE = Ci.nsIMemoryReporter.UNITS_PERCENTAGE;
@@ -149,17 +149,25 @@
     mgr.registerMultiReporter(fakeMultiReporters[i]);
   }
 
   // mgr.explicit sums "heap-allocated" and all the appropriate NONHEAP ones:
   // - "explicit/c", "explicit/cc" x 2, "explicit/d", "explicit/e"
   // - but *not* "explicit/c/d" x 2
   // Check explicit now before we add the fake reporters for the fake 2nd
   // and subsequent processes.
-  is(mgr.explicit, 500*MB + (100 + 13 + 10)*MB + 599*KB, "mgr.explicit");
+  //
+  // Nb: mgr.explicit will throw NS_ERROR_NOT_AVAILABLE if this is a
+  // --enable-trace-malloc build.  Allow for that exception, but *only* that
+  // exception.
+  try {
+    is(mgr.explicit, 500*MB + (100 + 13 + 10)*MB + 599*KB, "mgr.explicit");
+  } catch (ex) {
+    is(ex.result, Cr.NS_ERROR_NOT_AVAILABLE, "mgr.explicit exception");
+  }
  
   let fakeReporters2 = [
     f("2nd", "heap-allocated",  OTHER,  1000 * MB),
     f("2nd", "heap-unallocated",OTHER,   100 * MB),
     f("2nd", "explicit/a/b/c",  HEAP,    497 * MB),
     f("2nd", "explicit/a/b/c",  HEAP,      1 * MB), // dup: merge
     f("2nd", "explicit/a/b/c",  HEAP,      1 * MB), // dup: merge
     f("2nd", "explicit/flip\\the\\backslashes",
@@ -171,29 +179,19 @@
     // Even though the "smaps" reporter is a multi-reporter, if its in a
     // child process it'll be passed to the main process as single reports.
     // The fact that we skip the "smaps" multi-reporter in the main
     // process won't cause these to be skipped;  the fall-back skipping will
     // be hit instead.
     f("2nd", "smaps/vsize/e",   NONHEAP, 24*4*KB),
     f("2nd", "smaps/vsize/f",   NONHEAP, 24*4*KB),
 
-    // kUnknown should be handled gracefully for "heap-allocated", non-leaf
-    // reporters, leaf-reporters, "other" reporters, and duplicated reporters.
-    f("3rd", "heap-allocated",  OTHER,   kUnknown),
+    // Check that we can handle "heap-allocated" not being present.
     f("3rd", "explicit/a/b",    HEAP,    333 * MB),
     f("3rd", "explicit/a/c",    HEAP,    444 * MB),
-    f("3rd", "explicit/a/c",    HEAP,    kUnknown), // dup: merge
-    f("3rd", "explicit/a/d",    HEAP,    kUnknown),
-    f("3rd", "explicit/a/d",    HEAP,    kUnknown), // dup: merge
-    f("3rd", "explicit/b",      NONHEAP, kUnknown),
-    f2("3rd", "other1",         OTHER,   BYTES, kUnknown),
-    f2("3rd", "other2",         OTHER,   COUNT, kUnknown),
-    f2("3rd", "other3",         OTHER,   COUNT_CUMULATIVE, kUnknown),
-    f2("3rd", "other4",         OTHER,   PERCENTAGE, kUnknown),
 
     // Invalid values (negative, too-big) should be identified.
     f("4th", "heap-allocated",   OTHER,   100 * MB),
     f("4th", "explicit/js/compartment(http:\\\\too-big.com\\)/stuff",
                                  HEAP,    150 * MB),
     f("4th", "explicit/ok",      HEAP,      5 * MB),
     f("4th", "explicit/neg1",    NONHEAP,  -2 * MB),
     // -111 becomes "-0.00MB" in non-verbose mode, and getting the negative
@@ -280,32 +278,25 @@ 1,000.00 MB (100.0%) -- explicit\n\
 Other Measurements\n\
 1,000.00 MB ── heap-allocated\n\
   100.00 MB ── heap-unallocated\n\
   666.00 MB ── other0\n\
   111.00 MB ── other1\n\
 \n\
 3rd Process\n\
 \n\
-WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is zero and the 'explicit' tree shows less memory than it should.\n\
+WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is not shown and the 'explicit' tree shows less memory than it should.\n\
 \n\
 Explicit Allocations\n\
 777.00 MB (100.0%) -- explicit\n\
-├──777.00 MB (100.0%) -- a\n\
-│  ├──444.00 MB (57.14%) ── c [2]\n\
-│  ├──333.00 MB (42.86%) ── b\n\
-│  └────0.00 MB (00.00%) ── d [*] [2]\n\
-└────0.00 MB (00.00%) ++ (2 tiny)\n\
+└──777.00 MB (100.0%) -- a\n\
+   ├──444.00 MB (57.14%) ── c\n\
+   └──333.00 MB (42.86%) ── b\n\
 \n\
 Other Measurements\n\
-0.00 MB ── heap-allocated [*]\n\
-0.00 MB ── other1 [*]\n\
-      0 ── other2 [*]\n\
-      0 ── other3 [*]\n\
-  0.00% ── other4 [*]\n\
 \n\
 4th Process\n\
 \n\
 WARNING: the following values are negative or unreasonably large.\n\
  explicit/js\n\
  explicit/js/compartment(http://too-big.com/)\n\
  explicit/js/compartment(http://too-big.com/)/stuff\n\
  explicit/(2 tiny)\n\
@@ -439,33 +430,25 @@ 196,608 B (100.0%) ++ vsize\n\
 Other Measurements\n\
 1,048,576,000 B ── heap-allocated\n\
   104,857,600 B ── heap-unallocated\n\
   698,351,616 B ── other0\n\
   116,391,936 B ── other1\n\
 \n\
 3rd Process\n\
 \n\
-WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is zero and the 'explicit' tree shows less memory than it should.\n\
+WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is not shown and the 'explicit' tree shows less memory than it should.\n\
 \n\
 Explicit Allocations\n\
 814,743,552 B (100.0%) -- explicit\n\
-├──814,743,552 B (100.0%) -- a\n\
-│  ├──465,567,744 B (57.14%) ── c [2]\n\
-│  ├──349,175,808 B (42.86%) ── b\n\
-│  └────────────0 B (00.00%) ── d [*] [2]\n\
-├────────────0 B (00.00%) ── b [*]\n\
-└────────────0 B (00.00%) ── heap-unclassified [*]\n\
+└──814,743,552 B (100.0%) -- a\n\
+   ├──465,567,744 B (57.14%) ── c\n\
+   └──349,175,808 B (42.86%) ── b\n\
 \n\
 Other Measurements\n\
-  0 B ── heap-allocated [*]\n\
-  0 B ── other1 [*]\n\
-    0 ── other2 [*]\n\
-    0 ── other3 [*]\n\
-0.00% ── other4 [*]\n\
 \n\
 4th Process\n\
 \n\
 WARNING: the following values are negative or unreasonably large.\n\
  explicit/js\n\
  explicit/js/compartment(http://too-big.com/)\n\
  explicit/js/compartment(http://too-big.com/)/stuff\n\
  explicit/neg1\n\
--- a/toolkit/components/aboutmemory/tests/test_memoryReporters.xul
+++ b/toolkit/components/aboutmemory/tests/test_memoryReporters.xul
@@ -19,16 +19,17 @@
   // Nb: this test is all JS and so should be done with an xpcshell test,
   // but bug 671753 is preventing the memory-reporter-manager from being
   // accessed from xpcshell.
 
   "use strict";
 
   const Cc = Components.classes;
   const Ci = Components.interfaces;
+  const Cr = Components.results;
 
   const kUnknown = -1;
   const NONHEAP = Ci.nsIMemoryReporter.KIND_NONHEAP;
   const HEAP    = Ci.nsIMemoryReporter.KIND_HEAP;
   const OTHER   = Ci.nsIMemoryReporter.KIND_OTHER;
 
   const BYTES = Ci.nsIMemoryReporter.UNITS_BYTES;
   const COUNT = Ci.nsIMemoryReporter.UNITS_COUNT;
@@ -89,17 +90,28 @@
     }
   }
 
   let mgr = Cc["@mozilla.org/memory-reporter-manager;1"].
             getService(Ci.nsIMemoryReporterManager);
 
   // Access mgr.explicit and mgr.resident just to make sure they don't crash.
   // We can't check their actual values because they're non-deterministic.
-  let dummy = mgr.explicit;
+  //
+  // Nb: mgr.explicit will throw NS_ERROR_NOT_AVAILABLE if this is a
+  // --enable-trace-malloc build.  Allow for that exception, but *only* that
+  // exception.
+  let dummy;
+  let haveExplicit = true;
+  try {
+    dummy = mgr.explicit;
+  } catch (ex) {
+    is(ex.result, Cr.NS_ERROR_NOT_AVAILABLE, "mgr.explicit exception");
+    haveExplicit = false;
+  }
   dummy = mgr.resident;
 
   let e = mgr.enumerateReporters();
   while (e.hasMoreElements()) {
     let r = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
     handleReport(r.process, r.path, r.kind, r.units, r.amount, r.description);
   }
   e = mgr.enumerateMultiReporters();
@@ -117,22 +129,25 @@
   {
     ok(aAmounts.length == 1, aName + " has exactly 1 report");
     let n = aAmounts[0];
     // Check the size is reasonable -- i.e. not ridiculously large or small.
     ok(n === kUnknown || (100 * 1000 <= n && n <= 10 * 1000 * 1000 * 1000),
        aName + "'s size is reasonable");
   }
 
-  checkSpecialReport("explicit",                          explicitAmounts);
-  checkSpecialReport("vsize",                             vsizeAmounts);
-  checkSpecialReport("resident",                          residentAmounts);
+  // If mgr.explicit failed, we won't have "heap-allocated" either.
+  if (haveExplicit) {
+    checkSpecialReport("explicit",       explicitAmounts);
+    checkSpecialReport("heap-allocated", heapAllocatedAmounts);
+  }
+  checkSpecialReport("vsize",          vsizeAmounts);
+  checkSpecialReport("resident",       residentAmounts);
   checkSpecialReport("js-main-runtime-gc-heap-committed", jsGcHeapAmounts);
-  checkSpecialReport("heap-allocated",                    heapAllocatedAmounts);
-  checkSpecialReport("storage-sqlite",                    storageSqliteAmounts);
+  checkSpecialReport("storage-sqlite", storageSqliteAmounts);
 
   ok(areJsCompartmentsPresent,  "js compartments are present");
   ok(isSandboxLocationShown,    "sandbox locations are present");
   ok(areWindowObjectsPresent,   "window objects are present");
   ok(isPlacesPresent,           "places is present");
   ok(isImagesPresent,           "images is present");
   ok(isXptiWorkingSetPresent,   "xpti-working-set is present");
   ok(isAtomTablePresent,        "atom-table is present");
--- a/toolkit/components/places/History.cpp
+++ b/toolkit/components/places/History.cpp
@@ -1429,28 +1429,27 @@ StoreAndNotifyEmbedVisit(VisitData& aPla
 }
 
 NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(HistoryLinksHashtableMallocSizeOf,
                                      "history-links-hashtable")
 
 PRInt64 GetHistoryObserversSize()
 {
   History* history = History::GetService();
-  if (!history)
-    return 0;
-  return history->SizeOfIncludingThis(HistoryLinksHashtableMallocSizeOf);
+  return history ?
+         history->SizeOfIncludingThis(HistoryLinksHashtableMallocSizeOf) : 0;
 }
 
 NS_MEMORY_REPORTER_IMPLEMENT(HistoryService,
-    "explicit/history-links-hashtable",
-    KIND_HEAP,
-    UNITS_BYTES,
-    GetHistoryObserversSize,
-    "Memory used by the hashtable of observers Places uses to notify objects of "
-    "changes to links' visited state.")
+  "explicit/history-links-hashtable",
+  KIND_HEAP,
+  UNITS_BYTES,
+  GetHistoryObserversSize,
+  "Memory used by the hashtable of observers Places uses to notify objects of "
+  "changes to links' visited state.")
 
 } // anonymous namespace
 
 ////////////////////////////////////////////////////////////////////////////////
 //// History
 
 History* History::gService = NULL;
 
--- a/toolkit/components/telemetry/TelemetryPing.js
+++ b/toolkit/components/telemetry/TelemetryPing.js
@@ -365,53 +365,56 @@ TelemetryPing.prototype = {
     } catch (e) {
       // OK to skip memory reporters in xpcshell
       return;
     }
 
     let e = mgr.enumerateReporters();
     while (e.hasMoreElements()) {
       let mr = e.getNext().QueryInterface(Ci.nsIMemoryReporter);
-      let id = MEM_HISTOGRAMS[mr.path];
-      if (!id) {
-        continue;
-      }
-      // mr.amount is expensive to read in some cases, so get it only once.
-      let amount = mr.amount;
-      if (amount == -1) {
+      let id, mrPath, mrAmount, mrUnits;
+      try {
+        mrPath = mr.path;
+        id = MEM_HISTOGRAMS[mrPath];
+        if (!id) {
+          continue;
+        }
+        mrAmount = mr.amount;
+        mrUnits = mr.units;
+      } catch (ex) {
         continue;
       }
 
       let val;
-      if (mr.units == Ci.nsIMemoryReporter.UNITS_BYTES) {
-        val = Math.floor(amount / 1024);
+      if (mrUnits == Ci.nsIMemoryReporter.UNITS_BYTES) {
+        val = Math.floor(mrAmount / 1024);
       }
-      else if (mr.units == Ci.nsIMemoryReporter.UNITS_COUNT) {
-        val = amount;
+      else if (mrUnits == Ci.nsIMemoryReporter.UNITS_COUNT) {
+        val = mrAmount;
       }
-      else if (mr.units == Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE) {
+      else if (mrUnits == Ci.nsIMemoryReporter.UNITS_COUNT_CUMULATIVE) {
         // If the reporter gives us a cumulative count, we'll report the
         // difference in its value between now and our previous ping.
 
-        if (!(mr.path in this._prevValues)) {
+        if (!(mrPath in this._prevValues)) {
           // If this is the first time we're reading this reporter, store its
           // current value but don't report it in the telemetry ping, so we
           // ignore the effect startup had on the reporter.
-          this._prevValues[mr.path] = amount;
+          this._prevValues[mrPath] = mrAmount;
           continue;
         }
 
-        val = amount - this._prevValues[mr.path];
-        this._prevValues[mr.path] = amount;
+        val = mrAmount - this._prevValues[mrPath];
+        this._prevValues[mrPath] = mrAmount;
       }
       else {
-        NS_ASSERT(false, "Can't handle memory reporter with units " + mr.units);
+        NS_ASSERT(false, "Can't handle memory reporter with units " + mrUnits);
         continue;
       }
-      this.addValue(mr.path, id, val);
+      this.addValue(mrPath, id, val);
     }
   },
 
   /**
    * Return true if we're interested in having a STARTUP_* histogram for
    * the given histogram name.
    */
   isInterestingStartupHistogram: function isInterestingStartupHistogram(name) {
--- a/widget/android/nsIAndroidBridge.idl
+++ b/widget/android/nsIAndroidBridge.idl
@@ -1,20 +1,14 @@
 #include "nsISupports.idl"
 #include "nsIDOMWindow.idl"
 
-[scriptable, uuid(38b5c83a-3e8d-45c2-8311-6e36bd5116c0)]
+[scriptable, uuid(56fd8e18-a5cf-4e7a-92ba-4f68b4ad50ac)]
 interface nsIAndroidDrawMetadataProvider : nsISupports {
   AString getDrawMetadata();
-
-  /*
-   * Returns true if the presentation shell corresponding to the currently-viewed document is
-   * suppressing painting (which occurs during page transitions) and false otherwise.
-   */
-  boolean paintingSuppressed();
 };
 
 [scriptable, uuid(0843f3c1-043e-4c64-9d8c-091370548c05)]
 interface nsIBrowserTab : nsISupports {
   readonly attribute nsIDOMWindow window;
   readonly attribute float scale;
 };
 
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -1113,34 +1113,16 @@ nsWindow::OnDraw(AndroidGeckoEvent *ae)
 
     AndroidBridge::AutoLocalJNIFrame jniFrame;
 #ifdef MOZ_JAVA_COMPOSITOR
     // We're paused, or we haven't been given a window-size yet, so do nothing
     if (sCompositorPaused || gAndroidBounds.width <= 0 || gAndroidBounds.height <= 0) {
         return;
     }
 
-    /*
-     * Check to see whether the presentation shell corresponding to the document on the screen
-     * is suppressing painting. If it is, we bail out, as continuing would result in a mismatch
-     * between the content on the screen and the current viewport metrics.
-     */
-    nsCOMPtr<nsIAndroidDrawMetadataProvider> metadataProvider =
-        AndroidBridge::Bridge()->GetDrawMetadataProvider();
-
-    layers::renderTraceEventStart("Check supress", "424242");
-    bool paintingSuppressed = false;
-    if (metadataProvider) {
-        metadataProvider->PaintingSuppressed(&paintingSuppressed);
-    }
-    if (paintingSuppressed) {
-        return;
-    }
-    layers::renderTraceEventEnd("Check supress", "424242");
-
     layers::renderTraceEventStart("Get surface", "424545");
     static unsigned char bits2[32 * 32 * 2];
     nsRefPtr<gfxImageSurface> targetSurface =
         new gfxImageSurface(bits2, gfxIntSize(32, 32), 32 * 2,
                             gfxASurface::ImageFormatRGB16_565);
     layers::renderTraceEventEnd("Get surface", "424545");
 
     layers::renderTraceEventStart("Widget draw to", "434646");
@@ -1617,17 +1599,17 @@ static unsigned int ConvertAndroidKeyCod
     switch (androidKeyCode) {
         // KEYCODE_UNKNOWN (0) ... KEYCODE_HOME (3)
         case AndroidKeyEvent::KEYCODE_BACK:               return NS_VK_ESCAPE;
         // KEYCODE_CALL (5) ... KEYCODE_POUND (18)
         case AndroidKeyEvent::KEYCODE_DPAD_UP:            return NS_VK_UP;
         case AndroidKeyEvent::KEYCODE_DPAD_DOWN:          return NS_VK_DOWN;
         case AndroidKeyEvent::KEYCODE_DPAD_LEFT:          return NS_VK_LEFT;
         case AndroidKeyEvent::KEYCODE_DPAD_RIGHT:         return NS_VK_RIGHT;
-        case AndroidKeyEvent::KEYCODE_DPAD_CENTER:        return NS_VK_RETURN;
+        case AndroidKeyEvent::KEYCODE_DPAD_CENTER:        return NS_VK_ENTER;
         // KEYCODE_VOLUME_UP (24) ... KEYCODE_Z (54)
         case AndroidKeyEvent::KEYCODE_COMMA:              return NS_VK_COMMA;
         case AndroidKeyEvent::KEYCODE_PERIOD:             return NS_VK_PERIOD;
         case AndroidKeyEvent::KEYCODE_ALT_LEFT:           return NS_VK_ALT;
         case AndroidKeyEvent::KEYCODE_ALT_RIGHT:          return NS_VK_ALT;
         case AndroidKeyEvent::KEYCODE_SHIFT_LEFT:         return NS_VK_SHIFT;
         case AndroidKeyEvent::KEYCODE_SHIFT_RIGHT:        return NS_VK_SHIFT;
         case AndroidKeyEvent::KEYCODE_TAB:                return NS_VK_TAB;
--- a/xpcom/base/nsCycleCollector.cpp
+++ b/xpcom/base/nsCycleCollector.cpp
@@ -3279,33 +3279,33 @@ nsCycleCollector::WasFreed(nsISupports *
 #endif
 
 
 ////////////////////////
 // Memory reporter
 ////////////////////////
 
 static PRInt64
-ReportCycleCollectorMem()
+GetCycleCollectorSize()
 {
     if (!sCollector)
         return 0;
     PRInt64 size = sizeof(nsCycleCollector) + 
         sCollector->mPurpleBuf.BlocksSize() +
         sCollector->mGraph.BlocksSize();
     if (sCollector->mWhiteNodes)
         size += sCollector->mWhiteNodes->Capacity() * sizeof(PtrInfo*);
     return size;
 }
 
 NS_MEMORY_REPORTER_IMPLEMENT(CycleCollector,
                              "explicit/cycle-collector",
                              KIND_HEAP,
                              UNITS_BYTES,
-                             ReportCycleCollectorMem,
+                             GetCycleCollectorSize,
                              "Memory used by the cycle collector.  This "
                              "includes the cycle collector structure, the "
                              "purple buffer, the graph, and the white nodes.  "
                              "The latter two are expected to be empty when the "
                              "cycle collector is idle.")
 
 
 ////////////////////////////////////////////////////////////////////////
--- a/xpcom/base/nsIMemoryReporter.idl
+++ b/xpcom/base/nsIMemoryReporter.idl
@@ -204,18 +204,18 @@ interface nsIMemoryReporter : nsISupport
   const PRInt32 UNITS_PERCENTAGE = 3;
 
   /*
    * The units on the reporter's amount.  See UNITS_* above.
    */
   readonly attribute PRInt32 units;
 
   /*
-   * The numeric value reported by this memory reporter.  -1 means "unknown",
-   * ie. something went wrong when getting the amount.
+   * The numeric value reported by this memory reporter.  Accesses can fail if
+   * something goes wrong when getting the amount.
    */
   readonly attribute PRInt64 amount;