Merge m-i to m-c
authorPhil Ringnalda <philringnalda@gmail.com>
Sat, 26 Oct 2013 11:14:26 -0700
changeset 166141 f63b098e8ac72a9949c05833e47e94d153e4c7f8
parent 166109 39ddf519b6e46c07028a90674c37d2b39bc29d76 (current diff)
parent 166140 7163aa55ea70320bb4d42bf0ad1eeeb75e866ec0 (diff)
child 166146 c41239469e316686d7407f07be02a9feb499a73c
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-i to m-c
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -1254,70 +1254,89 @@ window.addEventListener('ContentStart', 
       type: 'visible-audio-channel-changed',
       channel: aData
     });
     shell.visibleNormalAudioActive = (aData == 'normal');
 }, "visible-audio-channel-changed", false);
 })();
 
 (function recordingStatusTracker() {
-  let gRecordingActiveCount = 0;
+  // Recording status is tracked per process with following data structure:
+  // {<processId>: {count: <N>,
+  //                requestURL: <requestURL>,
+  //                isApp: <isApp>,
+  //                audioCount: <N>,
+  //                videoCount: <N>}}
   let gRecordingActiveProcesses = {};
 
   let recordingHandler = function(aSubject, aTopic, aData) {
-    let oldCount = gRecordingActiveCount;
-
-    let processId = (!aSubject) ? 'main'
-                                : aSubject.QueryInterface(Ci.nsIPropertyBag2).get('childID');
+    let props = aSubject.QueryInterface(Ci.nsIPropertyBag2);
+    let processId = (props.hasKey('childID')) ? props.get('childID')
+                                              : 'main';
     if (processId && !gRecordingActiveProcesses.hasOwnProperty(processId)) {
-      gRecordingActiveProcesses[processId] = 0;
+      gRecordingActiveProcesses[processId] = {count: 0,
+                                              requestURL: props.get('requestURL'),
+                                              isApp: props.get('isApp'),
+                                              audioCount: 0,
+                                              videoCount: 0 };
     }
 
     let currentActive = gRecordingActiveProcesses[processId];
+    let wasActive = (currentActive['count'] > 0);
+    let wasAudioActive = (currentActive['audioCount'] > 0);
+    let wasVideoActive = (currentActive['videoCount'] > 0);
+
     switch (aData) {
       case 'starting':
-        gRecordingActiveCount++;
-        currentActive++;
+        currentActive['count']++;
+        currentActive['audioCount'] += (props.get('isAudio')) ? 1 : 0;
+        currentActive['videoCount'] += (props.get('isVideo')) ? 1 : 0;
         break;
       case 'shutdown':
-        // Bug 928206 will make shutdown be sent even if no starting.
-        if (currentActive > 0) {
-          gRecordingActiveCount--;
-          currentActive--;
-        }
+        currentActive['count']--;
+        currentActive['audioCount'] -= (props.get('isAudio')) ? 1 : 0;
+        currentActive['videoCount'] -= (props.get('isVideo')) ? 1 : 0;
         break;
       case 'content-shutdown':
-        gRecordingActiveCount -= currentActive;
-        currentActive = 0;
+        currentActive['count'] = 0;
+        currentActive['audioCount'] = 0;
+        currentActive['videoCount'] = 0;
         break;
     }
 
-    if (currentActive > 0) {
+    if (currentActive['count'] > 0) {
       gRecordingActiveProcesses[processId] = currentActive;
     } else {
       delete gRecordingActiveProcesses[processId];
     }
 
-    // We need to track changes from N <-> 0
-    if ((oldCount === 0 && gRecordingActiveCount > 0) ||
-        (gRecordingActiveCount === 0 && oldCount > 0)) {
+    // We need to track changes if any active state is changed.
+    let isActive = (currentActive['count'] > 0);
+    let isAudioActive = (currentActive['audioCount'] > 0);
+    let isVideoActive = (currentActive['videoCount'] > 0);
+    if ((isActive != wasActive) ||
+        (isAudioActive != wasAudioActive) ||
+        (isVideoActive != wasVideoActive)) {
       shell.sendChromeEvent({
         type: 'recording-status',
-        active: (gRecordingActiveCount > 0)
+        active: isActive,
+        requestURL: currentActive['requestURL'],
+        isApp: currentActive['isApp'],
+        isAudio: isAudioActive,
+        isVideo: isVideoActive
       });
     }
   };
   Services.obs.addObserver(recordingHandler, 'recording-device-events', false);
   Services.obs.addObserver(recordingHandler, 'recording-device-ipc-events', false);
 
   Services.obs.addObserver(function(aSubject, aTopic, aData) {
     // send additional recording events if content process is being killed
-    let props = aSubject.QueryInterface(Ci.nsIPropertyBag2);
-    let childId = aSubject.get('childID');
-    if (gRecordingActiveProcesses.hasOwnProperty(childId) >= 0) {
+    let processId = aSubject.QueryInterface(Ci.nsIPropertyBag2).get('childID');
+    if (gRecordingActiveProcesses.hasOwnProperty(processId)) {
       Services.obs.notifyObservers(aSubject, 'recording-device-ipc-events', 'content-shutdown');
     }
   }, 'ipc:content-shutdown', false);
 })();
 
 (function volumeStateTracker() {
   Services.obs.addObserver(function(aSubject, aTopic, aData) {
     shell.sendChromeEvent({
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -94,17 +94,17 @@ let AboutHomeListener = {
     // Inject search engine and snippets URL.
     let docElt = doc.documentElement;
     // set the following attributes BEFORE searchEngineName, which triggers to
     // show the snippets when it's set.
     docElt.setAttribute("snippetsURL", aData.snippetsURL);
     if (aData.showKnowYourRights)
       docElt.setAttribute("showKnowYourRights", "true");
     docElt.setAttribute("snippetsVersion", aData.snippetsVersion);
-    docElt.setAttribute("searchEngineName", Services.search.defaultEngine.name);
+    docElt.setAttribute("searchEngineName", aData.defaultEngineName);
   },
 
   onPageLoad: function() {
     let doc = content.document;
     if (doc.documentURI.toLowerCase() != "about:home" ||
         doc.documentElement.hasAttribute("hasBrowserHandlers")) {
       return;
     }
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -222,16 +222,22 @@ Sanitizer.prototype = {
 
         // Clear last URL of the Open Web Location dialog
         var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                               .getService(Components.interfaces.nsIPrefBranch);
         try {
           prefs.clearUserPref("general.open_location.last_url");
         }
         catch (e) { }
+
+        try {
+          var seer = Components.classes["@mozilla.org/network/seer;1"]
+                               .getService(Components.interfaces.nsINetworkSeer);
+          seer.reset();
+        } catch (e) { }
       },
 
       get canClear()
       {
         // bug 347231: Always allow clearing history due to dependencies on
         // the browser:purge-session-history notification. (like error console)
         return true;
       }
--- a/browser/modules/AboutHome.jsm
+++ b/browser/modules/AboutHome.jsm
@@ -182,17 +182,18 @@ let AboutHome = {
     Components.utils.import("resource:///modules/sessionstore/SessionStore.jsm",
       wrapper);
     let ss = wrapper.SessionStore;
     ss.promiseInitialized.then(function() {
       let data = {
         showRestoreLastSession: ss.canRestoreLastSession,
         snippetsURL: AboutHomeUtils.snippetsURL,
         showKnowYourRights: AboutHomeUtils.showKnowYourRights,
-        snippetsVersion: AboutHomeUtils.snippetsVersion
+        snippetsVersion: AboutHomeUtils.snippetsVersion,
+        defaultEngineName: Services.search.defaultEngine.name
       };
 
       if (AboutHomeUtils.showKnowYourRights) {
         // Set pref to indicate we've shown the notification.
         let currentVersion = Services.prefs.getIntPref("browser.rights.version");
         Services.prefs.setBoolPref("browser.rights." + currentVersion + ".shown", true);
       }
 
--- a/content/base/src/nsFrameLoader.h
+++ b/content/base/src/nsFrameLoader.h
@@ -305,16 +305,18 @@ public:
 
   /**
    * Applies a new set of sandbox flags. These are merged with the sandbox
    * flags from our owning content's owning document with a logical OR, this
    * ensures that we can only add restrictions and never remove them.
    */
   void ApplySandboxFlags(uint32_t sandboxFlags);
 
+  void GetURL(nsString& aURL);
+
 private:
 
   void SetOwnerContent(mozilla::dom::Element* aContent);
 
   bool ShouldUseRemoteProcess();
 
   /**
    * Is this a frameloader for a bona fide <iframe mozbrowser> or
@@ -353,17 +355,16 @@ private:
   already_AddRefed<mozIApplication> GetContainingApp();
 
   /**
    * If we are an IPC frame, set mRemoteFrame. Otherwise, create and
    * initialize mDocShell.
    */
   nsresult MaybeCreateDocShell();
   nsresult EnsureMessageManager();
-  NS_HIDDEN_(void) GetURL(nsString& aURL);
 
   // Properly retrieves documentSize of any subdocument type.
   nsresult GetWindowDimensions(nsRect& aRect);
 
   // Updates the subdocument position and size. This gets called only
   // when we have our own in-process DocShell.
   NS_HIDDEN_(nsresult) UpdateBaseWindowPositionAndSize(nsSubDocumentFrame *aIFrame);
   nsresult CheckURILoad(nsIURI* aURI);
--- a/content/base/src/nsFrameMessageManager.cpp
+++ b/content/base/src/nsFrameMessageManager.cpp
@@ -46,24 +46,36 @@
 #  undef SendMessage
 # endif
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::dom::ipc;
 
+static PLDHashOperator
+CycleCollectorTraverseListeners(const nsAString& aKey,
+                                nsTArray<nsMessageListenerInfo>* aListeners,
+                                void* aCb)
+{
+  nsCycleCollectionTraversalCallback* cb =
+    static_cast<nsCycleCollectionTraversalCallback*> (aCb);
+  uint32_t count = aListeners->Length();
+  for (uint32_t i = 0; i < count; ++i) {
+    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(*cb, "listeners[i] mStrongListener");
+    cb->NoteXPCOMChild((*aListeners)[i].mStrongListener.get());
+  }
+  return PL_DHASH_NEXT;
+}
+
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsFrameMessageManager)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsFrameMessageManager)
-  uint32_t count = tmp->mListeners.Length();
-  for (uint32_t i = 0; i < count; i++) {
-    NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mListeners[i] mStrongListener");
-    cb.NoteXPCOMChild(tmp->mListeners[i].mStrongListener.get());
-  }
+  tmp->mListeners.EnumerateRead(CycleCollectorTraverseListeners,
+                                static_cast<void*>(&cb));
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChildManagers)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsFrameMessageManager)
   tmp->mListeners.Clear();
   for (int32_t i = tmp->mChildManagers.Count(); i > 0; --i) {
     static_cast<nsFrameMessageManager*>(tmp->mChildManagers[i - 1])->
       Disconnect(false);
@@ -241,100 +253,136 @@ SameProcessCpowHolder::ToObject(JSContex
 }
 
 // nsIMessageListenerManager
 
 NS_IMETHODIMP
 nsFrameMessageManager::AddMessageListener(const nsAString& aMessage,
                                           nsIMessageListener* aListener)
 {
-  nsCOMPtr<nsIAtom> message = do_GetAtom(aMessage);
-  uint32_t len = mListeners.Length();
-  for (uint32_t i = 0; i < len; ++i) {
-    if (mListeners[i].mMessage == message &&
-      mListeners[i].mStrongListener == aListener) {
-      return NS_OK;
+  nsTArray<nsMessageListenerInfo>* listeners = mListeners.Get(aMessage);
+  if (!listeners) {
+    listeners = new nsTArray<nsMessageListenerInfo>();
+    mListeners.Put(aMessage, listeners);
+  } else {
+    uint32_t len = listeners->Length();
+    for (uint32_t i = 0; i < len; ++i) {
+      if ((*listeners)[i].mStrongListener == aListener) {
+        return NS_OK;
+      }
     }
   }
-  nsMessageListenerInfo* entry = mListeners.AppendElement();
+
+  nsMessageListenerInfo* entry = listeners->AppendElement();
   NS_ENSURE_TRUE(entry, NS_ERROR_OUT_OF_MEMORY);
-  entry->mMessage = message;
   entry->mStrongListener = aListener;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFrameMessageManager::RemoveMessageListener(const nsAString& aMessage,
                                              nsIMessageListener* aListener)
 {
-  nsCOMPtr<nsIAtom> message = do_GetAtom(aMessage);
-  uint32_t len = mListeners.Length();
+  nsTArray<nsMessageListenerInfo>* listeners = mListeners.Get(aMessage);
+  if (!listeners) {
+    return NS_OK;
+  }
+
+  uint32_t len = listeners->Length();
   for (uint32_t i = 0; i < len; ++i) {
-    if (mListeners[i].mMessage == message &&
-      mListeners[i].mStrongListener == aListener) {
-      mListeners.RemoveElementAt(i);
+    if ((*listeners)[i].mStrongListener == aListener) {
+      listeners->RemoveElementAt(i);
       return NS_OK;
     }
   }
   return NS_OK;
 }
 
+#ifdef DEBUG
+typedef struct
+{
+  nsCOMPtr<nsISupports> mCanonical;
+  nsWeakPtr mWeak;
+} CanonicalCheckerParams;
+
+static PLDHashOperator
+CanonicalChecker(const nsAString& aKey,
+                 nsTArray<nsMessageListenerInfo>* aListeners,
+                 void* aParams)
+{
+  CanonicalCheckerParams* params =
+    static_cast<CanonicalCheckerParams*> (aParams);
+
+  uint32_t count = aListeners->Length();
+  for (uint32_t i = 0; i < count; i++) {
+    if (!(*aListeners)[i].mWeakListener) {
+      continue;
+    }
+    nsCOMPtr<nsISupports> otherCanonical =
+      do_QueryReferent((*aListeners)[i].mWeakListener);
+    MOZ_ASSERT((params->mCanonical == otherCanonical) ==
+               (params->mWeak == (*aListeners)[i].mWeakListener));
+  }
+  return PL_DHASH_NEXT;
+}
+#endif
+
 NS_IMETHODIMP
 nsFrameMessageManager::AddWeakMessageListener(const nsAString& aMessage,
                                               nsIMessageListener* aListener)
 {
   nsWeakPtr weak = do_GetWeakReference(aListener);
   NS_ENSURE_TRUE(weak, NS_ERROR_NO_INTERFACE);
 
 #ifdef DEBUG
   // It's technically possible that one object X could give two different
   // nsIWeakReference*'s when you do_GetWeakReference(X).  We really don't want
   // this to happen; it will break e.g. RemoveWeakMessageListener.  So let's
   // check that we're not getting ourselves into that situation.
   nsCOMPtr<nsISupports> canonical = do_QueryInterface(aListener);
-  for (uint32_t i = 0; i < mListeners.Length(); ++i) {
-    if (!mListeners[i].mWeakListener) {
-      continue;
-    }
-
-    nsCOMPtr<nsISupports> otherCanonical =
-      do_QueryReferent(mListeners[i].mWeakListener);
-    MOZ_ASSERT((canonical == otherCanonical) ==
-               (weak == mListeners[i].mWeakListener));
-  }
+  CanonicalCheckerParams params;
+  params.mCanonical = canonical;
+  params.mWeak = weak;
+  mListeners.EnumerateRead(CanonicalChecker, (void*)&params);
 #endif
 
-  nsCOMPtr<nsIAtom> message = do_GetAtom(aMessage);
-  uint32_t len = mListeners.Length();
-  for (uint32_t i = 0; i < len; ++i) {
-    if (mListeners[i].mMessage == message &&
-        mListeners[i].mWeakListener == weak) {
-      return NS_OK;
+  nsTArray<nsMessageListenerInfo>* listeners = mListeners.Get(aMessage);
+  if (!listeners) {
+    listeners = new nsTArray<nsMessageListenerInfo>();
+    mListeners.Put(aMessage, listeners);
+  } else {
+    uint32_t len = listeners->Length();
+    for (uint32_t i = 0; i < len; ++i) {
+      if ((*listeners)[i].mWeakListener == weak) {
+        return NS_OK;
+      }
     }
   }
 
-  nsMessageListenerInfo* entry = mListeners.AppendElement();
-  entry->mMessage = message;
+  nsMessageListenerInfo* entry = listeners->AppendElement();
   entry->mWeakListener = weak;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsFrameMessageManager::RemoveWeakMessageListener(const nsAString& aMessage,
                                                  nsIMessageListener* aListener)
 {
   nsWeakPtr weak = do_GetWeakReference(aListener);
   NS_ENSURE_TRUE(weak, NS_OK);
 
-  nsCOMPtr<nsIAtom> message = do_GetAtom(aMessage);
-  uint32_t len = mListeners.Length();
+  nsTArray<nsMessageListenerInfo>* listeners = mListeners.Get(aMessage);
+  if (!listeners) {
+    return NS_OK;
+  }
+
+  uint32_t len = listeners->Length();
   for (uint32_t i = 0; i < len; ++i) {
-    if (mListeners[i].mMessage == message &&
-        mListeners[i].mWeakListener == weak) {
-      mListeners.RemoveElementAt(i);
+    if ((*listeners)[i].mWeakListener == weak) {
+      listeners->RemoveElementAt(i);
       return NS_OK;
     }
   }
 
   return NS_OK;
 }
 
 // nsIFrameScriptLoader
@@ -787,143 +835,141 @@ nsresult
 nsFrameMessageManager::ReceiveMessage(nsISupports* aTarget,
                                       const nsAString& aMessage,
                                       bool aIsSync,
                                       const StructuredCloneData* aCloneData,
                                       CpowHolder* aCpows,
                                       InfallibleTArray<nsString>* aJSONRetVal)
 {
   AutoSafeJSContext ctx;
+  nsTArray<nsMessageListenerInfo>* listeners = mListeners.Get(aMessage);
+  if (listeners) {
 
-  if (mListeners.Length()) {
-    nsCOMPtr<nsIAtom> name = do_GetAtom(aMessage);
     MMListenerRemover lr(this);
 
-    for (uint32_t i = 0; i < mListeners.Length(); ++i) {
+    for (uint32_t i = 0; i < listeners->Length(); ++i) {
       // Remove mListeners[i] if it's an expired weak listener.
       nsCOMPtr<nsISupports> weakListener;
-      if (mListeners[i].mWeakListener) {
-        weakListener = do_QueryReferent(mListeners[i].mWeakListener);
+      if ((*listeners)[i].mWeakListener) {
+        weakListener = do_QueryReferent((*listeners)[i].mWeakListener);
         if (!weakListener) {
-          mListeners.RemoveElementAt(i--);
+          listeners->RemoveElementAt(i--);
           continue;
         }
       }
 
-      if (mListeners[i].mMessage == name) {
-        nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS;
-        if (weakListener) {
-          wrappedJS = do_QueryInterface(weakListener);
-        } else {
-          wrappedJS = do_QueryInterface(mListeners[i].mStrongListener);
-        }
+      nsCOMPtr<nsIXPConnectWrappedJS> wrappedJS;
+      if (weakListener) {
+        wrappedJS = do_QueryInterface(weakListener);
+      } else {
+        wrappedJS = do_QueryInterface((*listeners)[i].mStrongListener);
+      }
+
+      if (!wrappedJS) {
+        continue;
+      }
+      JS::Rooted<JSObject*> object(ctx, wrappedJS->GetJSObject());
+      if (!object) {
+        continue;
+      }
+      JSAutoCompartment ac(ctx, object);
+
+      // The parameter for the listener function.
+      JS::Rooted<JSObject*> param(ctx,
+        JS_NewObject(ctx, nullptr, nullptr, nullptr));
+      NS_ENSURE_TRUE(param, NS_ERROR_OUT_OF_MEMORY);
 
-        if (!wrappedJS) {
-          continue;
+      JS::Rooted<JS::Value> targetv(ctx);
+      JS::Rooted<JSObject*> global(ctx, JS_GetGlobalForObject(ctx, object));
+      nsContentUtils::WrapNative(ctx, global, aTarget, &targetv,
+                                 nullptr, true);
+
+      JS::RootedObject cpows(ctx);
+      if (aCpows) {
+        if (!aCpows->ToObject(ctx, &cpows)) {
+          return NS_ERROR_UNEXPECTED;
         }
-        JS::Rooted<JSObject*> object(ctx, wrappedJS->GetJSObject());
-        if (!object) {
-          continue;
+      }
+
+      if (!cpows) {
+        cpows = JS_NewObject(ctx, nullptr, nullptr, nullptr);
+        if (!cpows) {
+          return NS_ERROR_UNEXPECTED;
         }
-        JSAutoCompartment ac(ctx, object);
+      }
+
+      JS::RootedValue cpowsv(ctx, JS::ObjectValue(*cpows));
 
-        // The parameter for the listener function.
-        JS::Rooted<JSObject*> param(ctx,
-          JS_NewObject(ctx, nullptr, nullptr, nullptr));
-        NS_ENSURE_TRUE(param, NS_ERROR_OUT_OF_MEMORY);
+      JS::Rooted<JS::Value> json(ctx, JS::NullValue());
+      if (aCloneData && aCloneData->mDataLength &&
+          !ReadStructuredClone(ctx, *aCloneData, &json)) {
+        JS_ClearPendingException(ctx);
+        return NS_OK;
+      }
+      JS::Rooted<JSString*> jsMessage(ctx,
+        JS_NewUCStringCopyN(ctx,
+                            static_cast<const jschar*>(aMessage.BeginReading()),
+                            aMessage.Length()));
+      NS_ENSURE_TRUE(jsMessage, NS_ERROR_OUT_OF_MEMORY);
+      JS_DefineProperty(ctx, param, "target", targetv, nullptr, nullptr, JSPROP_ENUMERATE);
+      JS_DefineProperty(ctx, param, "name",
+                        STRING_TO_JSVAL(jsMessage), nullptr, nullptr, JSPROP_ENUMERATE);
+      JS_DefineProperty(ctx, param, "sync",
+                        BOOLEAN_TO_JSVAL(aIsSync), nullptr, nullptr, JSPROP_ENUMERATE);
+      JS_DefineProperty(ctx, param, "json", json, nullptr, nullptr, JSPROP_ENUMERATE); // deprecated
+      JS_DefineProperty(ctx, param, "data", json, nullptr, nullptr, JSPROP_ENUMERATE);
+      JS_DefineProperty(ctx, param, "objects", cpowsv, nullptr, nullptr, JSPROP_ENUMERATE);
 
-        JS::Rooted<JS::Value> targetv(ctx);
-        JS::Rooted<JSObject*> global(ctx, JS_GetGlobalForObject(ctx, object));
-        nsContentUtils::WrapNative(ctx, global, aTarget, &targetv,
-                                   nullptr, true);
+      JS::Rooted<JS::Value> thisValue(ctx, JS::UndefinedValue());
+
+      JS::Rooted<JS::Value> funval(ctx);
+      if (JS_ObjectIsCallable(ctx, object)) {
+        // If the listener is a JS function:
+        funval.setObject(*object);
 
-        JS::RootedObject cpows(ctx);
-        if (aCpows) {
-          if (!aCpows->ToObject(ctx, &cpows)) {
-            return NS_ERROR_UNEXPECTED;
-          }
+        // A small hack to get 'this' value right on content side where
+        // messageManager is wrapped in TabChildGlobal.
+        nsCOMPtr<nsISupports> defaultThisValue;
+        if (mChrome) {
+          defaultThisValue = do_QueryObject(this);
+        } else {
+          defaultThisValue = aTarget;
         }
+        JS::Rooted<JSObject*> global(ctx, JS_GetGlobalForObject(ctx, object));
+        nsContentUtils::WrapNative(ctx, global, defaultThisValue,
+                                   &thisValue, nullptr, true);
+      } else {
+        // If the listener is a JS object which has receiveMessage function:
+        if (!JS_GetProperty(ctx, object, "receiveMessage", &funval) ||
+            !funval.isObject())
+          return NS_ERROR_UNEXPECTED;
 
-        if (!cpows) {
-          cpows = JS_NewObject(ctx, nullptr, nullptr, nullptr);
-          if (!cpows) {
-            return NS_ERROR_UNEXPECTED;
-          }
+        // Check if the object is even callable.
+        NS_ENSURE_STATE(JS_ObjectIsCallable(ctx, &funval.toObject()));
+        thisValue.setObject(*object);
+      }
+
+      JS::Rooted<JS::Value> rval(ctx, JSVAL_VOID);
+      JS::Rooted<JS::Value> argv(ctx, JS::ObjectValue(*param));
+
+      {
+        JS::Rooted<JSObject*> thisObject(ctx, thisValue.toObjectOrNull());
+
+        JSAutoCompartment tac(ctx, thisObject);
+        if (!JS_WrapValue(ctx, argv.address())) {
+          return NS_ERROR_UNEXPECTED;
         }
 
-        JS::RootedValue cpowsv(ctx, JS::ObjectValue(*cpows));
-
-        JS::Rooted<JS::Value> json(ctx, JS::NullValue());
-        if (aCloneData && aCloneData->mDataLength &&
-            !ReadStructuredClone(ctx, *aCloneData, &json)) {
-          JS_ClearPendingException(ctx);
-          return NS_OK;
-        }
-        JS::Rooted<JSString*> jsMessage(ctx,
-          JS_NewUCStringCopyN(ctx,
-                              static_cast<const jschar*>(aMessage.BeginReading()),
-                              aMessage.Length()));
-        NS_ENSURE_TRUE(jsMessage, NS_ERROR_OUT_OF_MEMORY);
-        JS_DefineProperty(ctx, param, "target", targetv, nullptr, nullptr, JSPROP_ENUMERATE);
-        JS_DefineProperty(ctx, param, "name",
-                          STRING_TO_JSVAL(jsMessage), nullptr, nullptr, JSPROP_ENUMERATE);
-        JS_DefineProperty(ctx, param, "sync",
-                          BOOLEAN_TO_JSVAL(aIsSync), nullptr, nullptr, JSPROP_ENUMERATE);
-        JS_DefineProperty(ctx, param, "json", json, nullptr, nullptr, JSPROP_ENUMERATE); // deprecated
-        JS_DefineProperty(ctx, param, "data", json, nullptr, nullptr, JSPROP_ENUMERATE);
-        JS_DefineProperty(ctx, param, "objects", cpowsv, nullptr, nullptr, JSPROP_ENUMERATE);
-
-        JS::Rooted<JS::Value> thisValue(ctx, JS::UndefinedValue());
-
-        JS::Rooted<JS::Value> funval(ctx);
-        if (JS_ObjectIsCallable(ctx, object)) {
-          // If the listener is a JS function:
-          funval.setObject(*object);
-
-          // A small hack to get 'this' value right on content side where
-          // messageManager is wrapped in TabChildGlobal.
-          nsCOMPtr<nsISupports> defaultThisValue;
-          if (mChrome) {
-            defaultThisValue = do_QueryObject(this);
-          } else {
-            defaultThisValue = aTarget;
-          }
-          JS::Rooted<JSObject*> global(ctx, JS_GetGlobalForObject(ctx, object));
-          nsContentUtils::WrapNative(ctx, global, defaultThisValue,
-                                     &thisValue, nullptr, true);
-        } else {
-          // If the listener is a JS object which has receiveMessage function:
-          if (!JS_GetProperty(ctx, object, "receiveMessage", &funval) ||
-              !funval.isObject())
-            return NS_ERROR_UNEXPECTED;
-
-          // Check if the object is even callable.
-          NS_ENSURE_STATE(JS_ObjectIsCallable(ctx, &funval.toObject()));
-          thisValue.setObject(*object);
-        }
-
-        JS::Rooted<JS::Value> rval(ctx, JSVAL_VOID);
-        JS::Rooted<JS::Value> argv(ctx, JS::ObjectValue(*param));
-
-        {
-          JS::Rooted<JSObject*> thisObject(ctx, thisValue.toObjectOrNull());
-
-          JSAutoCompartment tac(ctx, thisObject);
-          if (!JS_WrapValue(ctx, argv.address())) {
-            return NS_ERROR_UNEXPECTED;
-          }
-
-          JS_CallFunctionValue(ctx, thisObject,
-                               funval, 1, argv.address(), rval.address());
-          if (aJSONRetVal) {
-            nsString json;
-            if (JS_Stringify(ctx, &rval, JS::NullPtr(), JS::NullHandleValue,
-                             JSONCreator, &json)) {
-              aJSONRetVal->AppendElement(json);
-            }
+        JS_CallFunctionValue(ctx, thisObject,
+                             funval, 1, argv.address(), rval.address());
+        if (aJSONRetVal) {
+          nsString json;
+          if (JS_Stringify(ctx, &rval, JS::NullPtr(), JS::NullHandleValue,
+                           JSONCreator, &json)) {
+            aJSONRetVal->AppendElement(json);
           }
         }
       }
     }
   }
   nsRefPtr<nsFrameMessageManager> kungfuDeathGrip = mParentManager;
   return mParentManager ? mParentManager->ReceiveMessage(aTarget, aMessage,
                                                          aIsSync, aCloneData,
@@ -1006,77 +1052,97 @@ nsFrameMessageManager::Disconnect(bool a
   mOwnedCallback = nullptr;
   if (!mHandlingMessage) {
     mListeners.Clear();
   }
 }
 
 namespace {
 
-struct MessageManagerReferentCount {
-  MessageManagerReferentCount() : strong(0), weakAlive(0), weakDead(0) {}
-  size_t strong;
-  size_t weakAlive;
-  size_t weakDead;
-  nsCOMArray<nsIAtom> suspectMessages;
-  nsDataHashtable<nsPtrHashKey<nsIAtom>, uint32_t> messageCounter;
+struct MessageManagerReferentCount
+{
+  MessageManagerReferentCount() : mStrong(0), mWeakAlive(0), mWeakDead(0) {}
+  size_t mStrong;
+  size_t mWeakAlive;
+  size_t mWeakDead;
+  nsTArray<nsString> mSuspectMessages;
+  nsDataHashtable<nsStringHashKey, uint32_t> mMessageCounter;
 };
 
 } // anonymous namespace
 
 namespace mozilla {
 namespace dom {
 
 class MessageManagerReporter MOZ_FINAL : public nsIMemoryReporter
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIMEMORYREPORTER
+
+  static const size_t kSuspectReferentCount = 300;
 protected:
-  static const size_t kSuspectReferentCount = 300;
   void CountReferents(nsFrameMessageManager* aMessageManager,
                       MessageManagerReferentCount* aReferentCount);
 };
 
 NS_IMPL_ISUPPORTS1(MessageManagerReporter, nsIMemoryReporter)
 
-void
-MessageManagerReporter::CountReferents(nsFrameMessageManager* aMessageManager,
-                                       MessageManagerReferentCount* aReferentCount)
+static PLDHashOperator
+CollectMessageListenerData(const nsAString& aKey,
+                           nsTArray<nsMessageListenerInfo>* aListeners,
+                           void* aData)
 {
-  for (uint32_t i = 0; i < aMessageManager->mListeners.Length(); i++) {
-    const nsMessageListenerInfo& listenerInfo = aMessageManager->mListeners[i];
+  MessageManagerReferentCount* referentCount =
+    static_cast<MessageManagerReferentCount*>(aData);
+
+  uint32_t listenerCount = aListeners->Length();
+  if (!listenerCount) {
+    return PL_DHASH_NEXT;
+  }
 
+  nsString key(aKey);
+  uint32_t oldCount = 0;
+  referentCount->mMessageCounter.Get(key, &oldCount);
+  uint32_t currentCount = oldCount + listenerCount;
+  referentCount->mMessageCounter.Put(key, currentCount);
+
+  // Keep track of messages that have a suspiciously large
+  // number of referents (symptom of leak).
+  if (currentCount == MessageManagerReporter::kSuspectReferentCount) {
+    referentCount->mSuspectMessages.AppendElement(key);
+  }
+
+  for (uint32_t i = 0; i < listenerCount; ++i) {
+    const nsMessageListenerInfo& listenerInfo = (*aListeners)[i];
     if (listenerInfo.mWeakListener) {
       nsCOMPtr<nsISupports> referent =
         do_QueryReferent(listenerInfo.mWeakListener);
       if (referent) {
-        aReferentCount->weakAlive++;
+        referentCount->mWeakAlive++;
       } else {
-        aReferentCount->weakDead++;
+        referentCount->mWeakDead++;
       }
     } else {
-      aReferentCount->strong++;
-    }
-
-    uint32_t oldCount = 0;
-    aReferentCount->messageCounter.Get(listenerInfo.mMessage, &oldCount);
-    uint32_t currentCount = oldCount + 1;
-    aReferentCount->messageCounter.Put(listenerInfo.mMessage, currentCount);
-
-    // Keep track of messages that have a suspiciously large
-    // number of referents (symptom of leak).
-    if (currentCount == kSuspectReferentCount) {
-      aReferentCount->suspectMessages.AppendElement(listenerInfo.mMessage);
+      referentCount->mStrong++;
     }
   }
+  return PL_DHASH_NEXT;
+}
+
+void
+MessageManagerReporter::CountReferents(nsFrameMessageManager* aMessageManager,
+                                       MessageManagerReferentCount* aReferentCount)
+{
+  aMessageManager->mListeners.EnumerateRead(CollectMessageListenerData,
+                                            aReferentCount);
 
   // Add referent count in child managers because the listeners
   // participate in messages dispatched from parent message manager.
-  for (uint32_t i = 0; i < aMessageManager->mChildManagers.Length(); i++) {
+  for (uint32_t i = 0; i < aMessageManager->mChildManagers.Length(); ++i) {
     nsRefPtr<nsFrameMessageManager> mm =
       static_cast<nsFrameMessageManager*>(aMessageManager->mChildManagers[i]);
     CountReferents(mm, aReferentCount);
   }
 }
 
 NS_IMETHODIMP
 MessageManagerReporter::GetName(nsACString& aName)
@@ -1097,35 +1163,35 @@ ReportReferentCount(const char* aManager
       rv = aCb->Callback(EmptyCString(), _path,                               \
                          nsIMemoryReporter::KIND_OTHER,                       \
                          nsIMemoryReporter::UNITS_COUNT, _amount,             \
                          _desc, aClosure);                                    \
       NS_ENSURE_SUCCESS(rv, rv);                                              \
     } while (0)
 
   REPORT(nsPrintfCString("message-manager/referent/%s/strong", aManagerType),
-         aReferentCount.strong,
+         aReferentCount.mStrong,
          nsPrintfCString("The number of strong referents held by the message "
                          "manager in the %s manager.", aManagerType));
   REPORT(nsPrintfCString("message-manager/referent/%s/weak/alive", aManagerType),
-         aReferentCount.weakAlive,
+         aReferentCount.mWeakAlive,
          nsPrintfCString("The number of weak referents that are still alive "
                          "held by the message manager in the %s manager.",
                          aManagerType));
   REPORT(nsPrintfCString("message-manager/referent/%s/weak/dead", aManagerType),
-         aReferentCount.weakDead,
+         aReferentCount.mWeakDead,
          nsPrintfCString("The number of weak referents that are dead "
                          "held by the message manager in the %s manager.",
                          aManagerType));
 
-  for (uint32_t i = 0; i < aReferentCount.suspectMessages.Length(); i++) {
+  for (uint32_t i = 0; i < aReferentCount.mSuspectMessages.Length(); i++) {
     uint32_t totalReferentCount = 0;
-    aReferentCount.messageCounter.Get(aReferentCount.suspectMessages[i],
-                                      &totalReferentCount);
-    nsAtomCString suspect(aReferentCount.suspectMessages[i]);
+    aReferentCount.mMessageCounter.Get(aReferentCount.mSuspectMessages[i],
+                                       &totalReferentCount);
+    NS_ConvertUTF16toUTF8 suspect(aReferentCount.mSuspectMessages[i]);
     REPORT(nsPrintfCString("message-manager-suspect/%s/referent(message=%s)",
                            aManagerType, suspect.get()), totalReferentCount,
            nsPrintfCString("A message in the %s message manager with a "
                            "suspiciously large number of referents (symptom "
                            "of a leak).", aManagerType));
   }
 
 #undef REPORT
@@ -1706,22 +1772,32 @@ NS_NewChildProcessMessageManager(nsISync
   }
   nsFrameMessageManager* mm = new nsFrameMessageManager(cb,
                                                         nullptr,
                                                         MM_PROCESSMANAGER | MM_OWNSCALLBACK);
   nsFrameMessageManager::sChildProcessManager = mm;
   return CallQueryInterface(mm, aResult);
 }
 
+static PLDHashOperator
+CycleCollectorMarkListeners(const nsAString& aKey,
+                            nsTArray<nsMessageListenerInfo>* aListeners,
+                            void* aData)
+{
+  uint32_t count = aListeners->Length();
+  for (uint32_t i = 0; i < count; i++) {
+    if ((*aListeners)[i].mStrongListener) {
+      xpc_TryUnmarkWrappedGrayObject((*aListeners)[i].mStrongListener);
+    }
+  }
+  return PL_DHASH_NEXT;
+}
+
 bool
 nsFrameMessageManager::MarkForCC()
 {
-  uint32_t len = mListeners.Length();
-  for (uint32_t i = 0; i < len; ++i) {
-    if (mListeners[i].mStrongListener) {
-      xpc_TryUnmarkWrappedGrayObject(mListeners[i].mStrongListener);
-    }
-  }
+  mListeners.EnumerateRead(CycleCollectorMarkListeners, nullptr);
+
   if (mRefCnt.IsPurple()) {
     mRefCnt.RemovePurple();
   }
   return true;
 }
--- a/content/base/src/nsFrameMessageManager.h
+++ b/content/base/src/nsFrameMessageManager.h
@@ -13,16 +13,17 @@
 #include "nsCOMArray.h"
 #include "nsTArray.h"
 #include "nsIAtom.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsTArray.h"
 #include "nsIPrincipal.h"
 #include "nsIXPConnect.h"
 #include "nsDataHashtable.h"
+#include "nsClassHashtable.h"
 #include "mozilla/Services.h"
 #include "nsIObserverService.h"
 #include "nsThreadUtils.h"
 #include "nsWeakPtr.h"
 #include "mozilla/Attributes.h"
 #include "js/RootingAPI.h"
 
 namespace mozilla {
@@ -111,17 +112,16 @@ StructuredCloneData UnpackClonedMessageD
 
 class nsAXPCNativeCallContext;
 
 struct nsMessageListenerInfo
 {
   // Exactly one of mStrongListener and mWeakListener must be non-null.
   nsCOMPtr<nsIMessageListener> mStrongListener;
   nsWeakPtr mWeakListener;
-  nsCOMPtr<nsIAtom> mMessage;
 };
 
 class CpowHolder
 {
   public:
     virtual bool ToObject(JSContext* cx, JS::MutableHandleObject objp) = 0;
 };
 
@@ -263,17 +263,19 @@ private:
                        const JS::Value& aJSON,
                        const JS::Value& aObjects,
                        JSContext* aCx,
                        uint8_t aArgc,
                        JS::Value* aRetval,
                        bool aIsSync);
 protected:
   friend class MMListenerRemover;
-  nsTArray<nsMessageListenerInfo> mListeners;
+  // We keep the message listeners as arrays in a hastable indexed by the
+  // message name. That gives us fast lookups in ReceiveMessage().
+  nsClassHashtable<nsStringHashKey, nsTArray<nsMessageListenerInfo>> mListeners;
   nsCOMArray<nsIContentFrameMessageManager> mChildManagers;
   bool mChrome;     // true if we're in the chrome process
   bool mGlobal;     // true if we're the global frame message manager
   bool mIsProcessManager; // true if the message manager belongs to the process realm
   bool mIsBroadcaster; // true if the message manager is a broadcaster
   bool mOwnsCallback;
   bool mHandlingMessage;
   bool mDisconnected;
--- a/content/base/src/nsScriptLoader.cpp
+++ b/content/base/src/nsScriptLoader.cpp
@@ -42,16 +42,17 @@
 #include "nsIChannelPolicy.h"
 #include "nsChannelPolicy.h"
 #include "nsCRT.h"
 #include "nsContentCreatorFunctions.h"
 #include "mozilla/dom/Element.h"
 #include "nsCrossSiteListenerProxy.h"
 #include "nsSandboxFlags.h"
 #include "nsContentTypeParser.h"
+#include "nsINetworkSeer.h"
 
 #include "mozilla/CORSMode.h"
 #include "mozilla/Attributes.h"
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo* gCspPRLog;
 #endif
 
@@ -324,16 +325,20 @@ nsScriptLoader::StartLoad(nsScriptLoadRe
   if (httpChannel) {
     // HTTP content negotation has little value in this context.
     httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Accept"),
                                   NS_LITERAL_CSTRING("*/*"),
                                   false);
     httpChannel->SetReferrer(mDocument->GetDocumentURI());
   }
 
+  nsCOMPtr<nsILoadContext> loadContext(do_QueryInterface(docshell));
+  mozilla::net::SeerLearn(aRequest->mURI, mDocument->GetDocumentURI(),
+      nsINetworkSeer::LEARN_LOAD_SUBRESOURCE, loadContext);
+
   nsCOMPtr<nsIStreamLoader> loader;
   rv = NS_NewStreamLoader(getter_AddRefs(loader), this);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIStreamListener> listener = loader.get();
 
   if (aRequest->mCORSMode != CORS_NONE) {
     bool withCredentials = (aRequest->mCORSMode == CORS_USE_CREDENTIALS);
--- a/content/media/AudioSegment.cpp
+++ b/content/media/AudioSegment.cpp
@@ -150,19 +150,22 @@ AudioSegment::WriteTo(uint64_t aID, Audi
                                      duration, c.mVolume,
                                      outputChannels,
                                      buf.Elements());
         }
       } else {
         // Assumes that a bit pattern of zeroes == 0.0f
         memset(buf.Elements(), 0, buf.Length()*sizeof(AudioDataValue));
       }
-      aOutput->Write(buf.Elements(), int32_t(duration));
-      if(!c.mTimeStamp.IsNull())
-        LogLatency(AsyncLatencyLogger::AudioMediaStreamTrack, aID,
-                   (mozilla::TimeStamp::Now() - c.mTimeStamp).ToMilliseconds());
+      aOutput->Write(buf.Elements(), int32_t(duration), &(c.mTimeStamp));
+      if(!c.mTimeStamp.IsNull()) {
+        TimeStamp now = TimeStamp::Now();
+        // would be more efficient to c.mTimeStamp to ms on create time then pass here
+        LogTime(AsyncLatencyLogger::AudioMediaStreamTrack, aID,
+                (now - c.mTimeStamp).ToMilliseconds(), c.mTimeStamp);
+      }
       offset += duration;
     }
   }
   aOutput->Start();
 }
 
 }
--- a/content/media/AudioStream.cpp
+++ b/content/media/AudioStream.cpp
@@ -138,17 +138,18 @@ static cubeb_stream_type ConvertChannelT
 }
 #endif
 
 AudioStream::AudioStream()
 : mInRate(0),
   mOutRate(0),
   mChannels(0),
   mWritten(0),
-  mAudioClock(MOZ_THIS_IN_INITIALIZER_LIST())
+  mAudioClock(MOZ_THIS_IN_INITIALIZER_LIST()),
+  mReadPoint(0)
 {}
 
 void AudioStream::InitLibrary()
 {
 #ifdef PR_LOGGING
   gAudioStreamLog = PR_NewLogModule("AudioStream");
 #endif
   gAudioPrefsLock = new Mutex("AudioStream::gAudioPrefsLock");
@@ -323,28 +324,29 @@ class BufferedAudioStream : public Audio
  public:
   BufferedAudioStream();
   ~BufferedAudioStream();
 
   nsresult Init(int32_t aNumChannels, int32_t aRate,
                 const dom::AudioChannelType aAudioChannelType,
                 AudioStream::LatencyRequest aLatencyRequest);
   void Shutdown();
-  nsresult Write(const AudioDataValue* aBuf, uint32_t aFrames);
+  nsresult Write(const AudioDataValue* aBuf, uint32_t aFrames, TimeStamp *aTime = nullptr);
   uint32_t Available();
   void SetVolume(double aVolume);
   void Drain();
   void Start();
   void Pause();
   void Resume();
   int64_t GetPosition();
   int64_t GetPositionInFrames();
   int64_t GetPositionInFramesInternal();
   int64_t GetLatencyInFrames();
   bool IsPaused();
+  void GetBufferInsertTime(int64_t &aTimeMs);
   // This method acquires the monitor and forward the call to the base
   // class, to prevent a race on |mTimeStretcher|, in
   // |AudioStream::EnsureTimeStretcherInitialized|.
   nsresult EnsureTimeStretcherInitialized();
 
 private:
   static long DataCallback_S(cubeb_stream*, void* aThis, void* aBuffer, long aFrames)
   {
@@ -354,20 +356,19 @@ private:
   static void StateCallback_S(cubeb_stream*, void* aThis, cubeb_state aState)
   {
     static_cast<BufferedAudioStream*>(aThis)->StateCallback(aState);
   }
 
   long DataCallback(void* aBuffer, long aFrames);
   void StateCallback(cubeb_state aState);
 
-  long GetUnprocessed(void* aBuffer, long aFrames);
-
-  long GetTimeStretched(void* aBuffer, long aFrames);
-
+  // aTime is the time in ms the samples were inserted into MediaStreamGraph
+  long GetUnprocessed(void* aBuffer, long aFrames, int64_t &aTime);
+  long GetTimeStretched(void* aBuffer, long aFrames, int64_t &aTime);
 
   // Shared implementation of underflow adjusted position calculation.
   // Caller must own the monitor.
   int64_t GetPositionInFramesUnlocked();
 
   void StartUnlocked();
 
   // The monitor is held to protect all access to member variables.  Write()
@@ -537,26 +538,26 @@ WriteDumpFile(FILE* aDumpFile, AudioStre
   fwrite(output, 2, samples, aDumpFile);
   fflush(aDumpFile);
 }
 
 BufferedAudioStream::BufferedAudioStream()
   : mMonitor("BufferedAudioStream"), mLostFrames(0), mDumpFile(nullptr),
     mVolume(1.0), mBytesPerFrame(0), mState(INITIALIZED)
 {
-  AsyncLatencyLogger::Get(true)->AddRef();
+  // keep a ref in case we shut down later than nsLayoutStatics
+  mLatencyLog = AsyncLatencyLogger::Get(true);
 }
 
 BufferedAudioStream::~BufferedAudioStream()
 {
   Shutdown();
   if (mDumpFile) {
     fclose(mDumpFile);
   }
-  AsyncLatencyLogger::Get()->Release();
 }
 
 nsresult
 BufferedAudioStream::EnsureTimeStretcherInitialized()
 {
   MonitorAutoLock mon(mMonitor);
   return AudioStream::EnsureTimeStretcherInitialized();
 }
@@ -567,16 +568,18 @@ BufferedAudioStream::Init(int32_t aNumCh
                           AudioStream::LatencyRequest aLatencyRequest)
 {
   cubeb* cubebContext = GetCubebContext();
 
   if (!cubebContext || aNumChannels < 0 || aRate < 0) {
     return NS_ERROR_FAILURE;
   }
 
+  PR_LOG(gAudioStreamLog, PR_LOG_DEBUG,
+    ("%s  channels: %d, rate: %d", __FUNCTION__, aNumChannels, aRate));
   mInRate = mOutRate = aRate;
   mChannels = aNumChannels;
 
   mDumpFile = OpenDumpFile(this);
 
   cubeb_stream_params params;
   params.rate = aRate;
   params.channels = aNumChannels;
@@ -640,29 +643,46 @@ BufferedAudioStream::Shutdown()
   if (mState == STARTED) {
     Pause();
   }
   if (mCubebStream) {
     mCubebStream.reset();
   }
 }
 
+// aTime is the time in ms the samples were inserted into MediaStreamGraph
 nsresult
-BufferedAudioStream::Write(const AudioDataValue* aBuf, uint32_t aFrames)
+BufferedAudioStream::Write(const AudioDataValue* aBuf, uint32_t aFrames, TimeStamp *aTime)
 {
   MonitorAutoLock mon(mMonitor);
   if (!mCubebStream || mState == ERRORED) {
     return NS_ERROR_FAILURE;
   }
   NS_ASSERTION(mState == INITIALIZED || mState == STARTED,
     "Stream write in unexpected state.");
 
   const uint8_t* src = reinterpret_cast<const uint8_t*>(aBuf);
   uint32_t bytesToCopy = FramesToBytes(aFrames);
 
+  // XXX this will need to change if we want to enable this on-the-fly!
+  if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG)) {
+    // Record the position and time this data was inserted
+    int64_t timeMs;
+    if (aTime && !aTime->IsNull()) {
+      if (mStartTime.IsNull()) {
+        AsyncLatencyLogger::Get(true)->GetStartTime(mStartTime);
+      }
+      timeMs = (*aTime - mStartTime).ToMilliseconds();
+    } else {
+      timeMs = 0;
+    }
+    struct Inserts insert = { timeMs, aFrames};
+    mInserts.AppendElement(insert);
+  }
+
   while (bytesToCopy > 0) {
     uint32_t available = std::min(bytesToCopy, mBuffer.Available());
     NS_ABORT_IF_FALSE(available % mBytesPerFrame == 0,
         "Must copy complete frames.");
 
     mBuffer.AppendElements(src, available);
     src += available;
     bytesToCopy -= available;
@@ -841,18 +861,35 @@ BufferedAudioStream::GetLatencyInFrames(
 
 bool
 BufferedAudioStream::IsPaused()
 {
   MonitorAutoLock mon(mMonitor);
   return mState == STOPPED;
 }
 
+void
+BufferedAudioStream::GetBufferInsertTime(int64_t &aTimeMs)
+{
+  if (mInserts.Length() > 0) {
+    // Find the right block, but don't leave the array empty
+    while (mInserts.Length() > 1 && mReadPoint >= mInserts[0].mFrames) {
+      mReadPoint -= mInserts[0].mFrames;
+      mInserts.RemoveElementAt(0);
+    }
+    // offset for amount already read
+    // XXX Note: could misreport if we couldn't find a block in the right timeframe
+    aTimeMs = mInserts[0].mTimeMs + ((mReadPoint * 1000) / mOutRate);
+  } else {
+    aTimeMs = INT64_MAX;
+  }
+}
+
 long
-BufferedAudioStream::GetUnprocessed(void* aBuffer, long aFrames)
+BufferedAudioStream::GetUnprocessed(void* aBuffer, long aFrames, int64_t &aTimeMs)
 {
   uint8_t* wpos = reinterpret_cast<uint8_t*>(aBuffer);
 
   // Flush the timestretcher pipeline, if we were playing using a playback rate
   // other than 1.0.
   uint32_t flushedFrames = 0;
   if (mTimeStretcher && mTimeStretcher->numSamples()) {
     flushedFrames = mTimeStretcher->receiveSamples(reinterpret_cast<AudioDataValue*>(wpos), aFrames);
@@ -862,21 +899,26 @@ BufferedAudioStream::GetUnprocessed(void
   uint32_t available = std::min(toPopBytes, mBuffer.Length());
 
   void* input[2];
   uint32_t input_size[2];
   mBuffer.PopElements(available, &input[0], &input_size[0], &input[1], &input_size[1]);
   memcpy(wpos, input[0], input_size[0]);
   wpos += input_size[0];
   memcpy(wpos, input[1], input_size[1]);
+
+  // First time block now has our first returned sample
+  mReadPoint += BytesToFrames(available);
+  GetBufferInsertTime(aTimeMs);
+
   return BytesToFrames(available) + flushedFrames;
 }
 
 long
-BufferedAudioStream::GetTimeStretched(void* aBuffer, long aFrames)
+BufferedAudioStream::GetTimeStretched(void* aBuffer, long aFrames, int64_t &aTimeMs)
 {
   long processedFrames = 0;
 
   // We need to call the non-locking version, because we already have the lock.
   if (AudioStream::EnsureTimeStretcherInitialized() != NS_OK) {
     return 0;
   }
 
@@ -891,77 +933,87 @@ BufferedAudioStream::GetTimeStretched(vo
       void* input[2];
       uint32_t input_size[2];
       available = std::min(mBuffer.Length(), toPopBytes);
       if (available != toPopBytes) {
         lowOnBufferedData = true;
       }
       mBuffer.PopElements(available, &input[0], &input_size[0],
                                      &input[1], &input_size[1]);
+      mReadPoint += BytesToFrames(available);
       for(uint32_t i = 0; i < 2; i++) {
         mTimeStretcher->putSamples(reinterpret_cast<AudioDataValue*>(input[i]), BytesToFrames(input_size[i]));
       }
     }
     uint32_t receivedFrames = mTimeStretcher->receiveSamples(reinterpret_cast<AudioDataValue*>(wpos), aFrames - processedFrames);
     wpos += FramesToBytes(receivedFrames);
     processedFrames += receivedFrames;
   } while (processedFrames < aFrames && !lowOnBufferedData);
 
+  GetBufferInsertTime(aTimeMs);
+
   return processedFrames;
 }
 
 long
 BufferedAudioStream::DataCallback(void* aBuffer, long aFrames)
 {
   MonitorAutoLock mon(mMonitor);
   uint32_t available = std::min(static_cast<uint32_t>(FramesToBytes(aFrames)), mBuffer.Length());
   NS_ABORT_IF_FALSE(available % mBytesPerFrame == 0, "Must copy complete frames");
+  AudioDataValue* output = reinterpret_cast<AudioDataValue*>(aBuffer);
   uint32_t underrunFrames = 0;
   uint32_t servicedFrames = 0;
+  int64_t insertTime;
 
   if (available) {
-    AudioDataValue* output = reinterpret_cast<AudioDataValue*>(aBuffer);
     if (mInRate == mOutRate) {
-      servicedFrames = GetUnprocessed(output, aFrames);
+      servicedFrames = GetUnprocessed(output, aFrames, insertTime);
     } else {
-      servicedFrames = GetTimeStretched(output, aFrames);
+      servicedFrames = GetTimeStretched(output, aFrames, insertTime);
     }
     float scaled_volume = float(GetVolumeScale() * mVolume);
 
     ScaleAudioSamples(output, aFrames * mChannels, scaled_volume);
 
     NS_ABORT_IF_FALSE(mBuffer.Length() % mBytesPerFrame == 0, "Must copy complete frames");
 
     // Notify any blocked Write() call that more space is available in mBuffer.
     mon.NotifyAll();
+  } else {
+    GetBufferInsertTime(insertTime);
   }
 
   underrunFrames = aFrames - servicedFrames;
 
   if (mState != DRAINING) {
     uint8_t* rpos = static_cast<uint8_t*>(aBuffer) + FramesToBytes(aFrames - underrunFrames);
     memset(rpos, 0, FramesToBytes(underrunFrames));
-#ifdef PR_LOGGING
     if (underrunFrames) {
       PR_LOG(gAudioStreamLog, PR_LOG_WARNING,
              ("AudioStream %p lost %d frames", this, underrunFrames));
     }
-#endif
     mLostFrames += underrunFrames;
     servicedFrames += underrunFrames;
   }
 
   WriteDumpFile(mDumpFile, this, aFrames, aBuffer);
-  if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG)) {
+  // Don't log if we're not interested or if the stream is inactive
+  if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG) &&
+      insertTime != INT64_MAX && servicedFrames > underrunFrames) {
     uint32_t latency = UINT32_MAX;
     if (cubeb_stream_get_latency(mCubebStream, &latency)) {
       NS_WARNING("Could not get latency from cubeb.");
     }
-    mLatencyLog->Log(AsyncLatencyLogger::AudioStream, 0, (mBuffer.Length() * 1000) / mOutRate);
-    mLatencyLog->Log(AsyncLatencyLogger::Cubeb, 0, (latency * 1000) / mOutRate);
+    TimeStamp now = TimeStamp::Now();
+
+    mLatencyLog->Log(AsyncLatencyLogger::AudioStream, reinterpret_cast<uint64_t>(this),
+                     insertTime, now);
+    mLatencyLog->Log(AsyncLatencyLogger::Cubeb, reinterpret_cast<uint64_t>(mCubebStream.get()),
+                     (latency * 1000) / mOutRate, now);
   }
 
   mAudioClock.UpdateWritePosition(servicedFrames);
   return servicedFrames;
 }
 
 void
 BufferedAudioStream::StateCallback(cubeb_state aState)
--- a/content/media/AudioStream.h
+++ b/content/media/AudioStream.h
@@ -129,18 +129,19 @@ public:
                         LatencyRequest aLatencyRequest) = 0;
 
   // Closes the stream. All future use of the stream is an error.
   virtual void Shutdown() = 0;
 
   // Write audio data to the audio hardware.  aBuf is an array of AudioDataValues
   // AudioDataValue of length aFrames*mChannels.  If aFrames is larger
   // than the result of Available(), the write will block until sufficient
-  // buffer space is available.
-  virtual nsresult Write(const mozilla::AudioDataValue* aBuf, uint32_t aFrames) = 0;
+  // buffer space is available.  aTime is the time in ms associated with the first sample
+  // for latency calculations
+  virtual nsresult Write(const mozilla::AudioDataValue* aBuf, uint32_t aFrames, TimeStamp *aTime = nullptr) = 0;
 
   // Return the number of audio frames that can be written without blocking.
   virtual uint32_t Available() = 0;
 
   // Set the current volume of the audio playback. This is a value from
   // 0 (meaning muted) to 1 (meaning full volume).  Thread-safe.
   virtual void SetVolume(double aVolume) = 0;
 
@@ -198,13 +199,26 @@ protected:
   // Output rate in Hz (characteristic of the playback rate)
   int mOutRate;
   int mChannels;
   // Number of frames written to the buffers.
   int64_t mWritten;
   AudioClock mAudioClock;
   nsAutoPtr<soundtouch::SoundTouch> mTimeStretcher;
   nsRefPtr<AsyncLatencyLogger> mLatencyLog;
+
+  // copy of Latency logger's starting time for offset calculations
+  TimeStamp mStartTime;
+  // Where in the current mInserts[0] block cubeb has read to
+  int64_t mReadPoint;
+  // Keep track of each inserted block of samples and the time it was inserted
+  // so we can estimate the clock time for a specific sample's insertion (for when
+  // we send data to cubeb).  Blocks are aged out as needed.
+  struct Inserts {
+    int64_t mTimeMs;
+    int64_t mFrames;
+  };
+  nsAutoTArray<Inserts,8> mInserts;
 };
 
 } // namespace mozilla
 
 #endif
--- a/content/media/Latency.cpp
+++ b/content/media/Latency.cpp
@@ -19,17 +19,28 @@
 
 using namespace mozilla;
 
 const char* LatencyLogIndex2Strings[] = {
   "Audio MediaStreamTrack",
   "Video MediaStreamTrack",
   "Cubeb",
   "AudioStream",
-  "NetStat"
+  "NetEQ",
+  "AudioCapture Base",
+  "AudioCapture Samples",
+  "AudioTrackInsertion",
+  "MediaPipeline Audio Insertion",
+  "AudioTransmit",
+  "AudioReceive",
+  "MediaPipelineAudioPlayout",
+  "MediaStream Create",
+  "AudioStream Create",
+  "AudioSendRTP",
+  "AudioRecvRTP"
 };
 
 static StaticRefPtr<AsyncLatencyLogger> gAsyncLogger;
 
 PRLogModuleInfo*
 GetLatencyLog()
 {
   static PRLogModuleInfo* sLog;
@@ -38,39 +49,72 @@ GetLatencyLog()
   }
   return sLog;
 }
 
 
 class LogEvent : public nsRunnable
 {
 public:
+  LogEvent(AsyncLatencyLogger::LatencyLogIndex aIndex, uint64_t aID, int64_t aValue,
+           TimeStamp aTimeStamp) :
+    mIndex(aIndex),
+    mID(aID),
+    mValue(aValue),
+    mTimeStamp(aTimeStamp)
+  {}
   LogEvent(AsyncLatencyLogger::LatencyLogIndex aIndex, uint64_t aID, int64_t aValue) :
     mIndex(aIndex),
     mID(aID),
-    mValue(aValue)
+    mValue(aValue),
+    mTimeStamp(TimeStamp())
   {}
   ~LogEvent() {}
 
   NS_IMETHOD Run() {
-    AsyncLatencyLogger::Get(true)->WriteLog(mIndex, mID, mValue);
+    AsyncLatencyLogger::Get(true)->WriteLog(mIndex, mID, mValue, mTimeStamp);
     return NS_OK;
   }
 
 protected:
   AsyncLatencyLogger::LatencyLogIndex mIndex;
   uint64_t mID;
   int64_t mValue;
+  TimeStamp mTimeStamp;
 };
 
 void LogLatency(AsyncLatencyLogger::LatencyLogIndex aIndex, uint64_t aID, int64_t aValue)
 {
   AsyncLatencyLogger::Get()->Log(aIndex, aID, aValue);
 }
 
+void LogTime(AsyncLatencyLogger::LatencyLogIndex aIndex, uint64_t aID, int64_t aValue)
+{
+  TimeStamp now = TimeStamp::Now();
+  AsyncLatencyLogger::Get()->Log(aIndex, aID, aValue, now);
+}
+
+void LogTime(AsyncLatencyLogger::LatencyLogIndex aIndex, uint64_t aID, int64_t aValue, TimeStamp &aTime)
+{
+  AsyncLatencyLogger::Get()->Log(aIndex, aID, aValue, aTime);
+}
+
+void LogTime(uint32_t aIndex, uint64_t aID, int64_t aValue)
+{
+  LogTime(static_cast<AsyncLatencyLogger::LatencyLogIndex>(aIndex), aID, aValue);
+}
+void LogTime(uint32_t aIndex, uint64_t aID, int64_t aValue, TimeStamp &aTime)
+{
+  LogTime(static_cast<AsyncLatencyLogger::LatencyLogIndex>(aIndex), aID, aValue, aTime);
+}
+void LogLatency(uint32_t aIndex, uint64_t aID, int64_t aValue)
+{
+  LogLatency(static_cast<AsyncLatencyLogger::LatencyLogIndex>(aIndex), aID, aValue);
+}
+
 /* static */
 void AsyncLatencyLogger::InitializeStatics()
 {
   NS_ASSERTION(NS_IsMainThread(), "Main thread only");
   GetLatencyLog();
   gAsyncLogger = new AsyncLatencyLogger();
 }
 
@@ -123,42 +167,62 @@ void AsyncLatencyLogger::Init()
   MutexAutoLock lock(mMutex);
   if (mStart.IsNull()) {
     nsresult rv = NS_NewNamedThread("Latency Logger", getter_AddRefs(mThread));
     NS_ENSURE_SUCCESS_VOID(rv);
     mStart = TimeStamp::Now();
   }
 }
 
+void AsyncLatencyLogger::GetStartTime(TimeStamp &aStart)
+{
+  MutexAutoLock lock(mMutex);
+  aStart = mStart;
+}
+
 nsresult
 AsyncLatencyLogger::Observe(nsISupports* aSubject, const char* aTopic,
                             const PRUnichar* aData)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
     Shutdown();
   }
   return NS_OK;
 }
 
 // aID is a sub-identifier (in particular a specific MediaStramTrack)
-void AsyncLatencyLogger::WriteLog(LatencyLogIndex aIndex, uint64_t aID, int64_t aValue)
+void AsyncLatencyLogger::WriteLog(LatencyLogIndex aIndex, uint64_t aID, int64_t aValue,
+                                  TimeStamp aTimeStamp)
 {
-  PR_LOG(GetLatencyLog(), PR_LOG_DEBUG,
-         ("%s,%llu,%lld.,%lld.",
-          LatencyLogIndex2Strings[aIndex], aID, GetTimeStamp(), aValue));
+  if (aTimeStamp.IsNull()) {
+    PR_LOG(GetLatencyLog(), PR_LOG_DEBUG,
+      ("Latency: %s,%llu,%lld,%lld",
+       LatencyLogIndex2Strings[aIndex], aID, GetTimeStamp(), aValue));
+  } else {
+    PR_LOG(GetLatencyLog(), PR_LOG_DEBUG,
+      ("Latency: %s,%llu,%lld,%lld,%lld",
+       LatencyLogIndex2Strings[aIndex], aID, GetTimeStamp(), aValue,
+       static_cast<int64_t>((aTimeStamp - gAsyncLogger->mStart).ToMilliseconds())));
+  }
 }
 
 int64_t AsyncLatencyLogger::GetTimeStamp()
 {
   TimeDuration t = TimeStamp::Now() - mStart;
   return t.ToMilliseconds();
 }
 
 void AsyncLatencyLogger::Log(LatencyLogIndex aIndex, uint64_t aID, int64_t aValue)
 {
+  TimeStamp null;
+  Log(aIndex, aID, aValue, null);
+}
+
+void AsyncLatencyLogger::Log(LatencyLogIndex aIndex, uint64_t aID, int64_t aValue, TimeStamp &aTime)
+{
   if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG)) {
-    nsCOMPtr<nsIRunnable> event = new LogEvent(aIndex, aID, aValue);
+    nsCOMPtr<nsIRunnable> event = new LogEvent(aIndex, aID, aValue, aTime);
     if (mThread) {
       mThread->Dispatch(event, NS_DISPATCH_NORMAL);
     }
   }
 }
--- a/content/media/Latency.h
+++ b/content/media/Latency.h
@@ -3,17 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_LATENCY_H
 #define MOZILLA_LATENCY_H
 
 #include "mozilla/TimeStamp.h"
-#include "prlog.h"
+#include "nspr/prlog.h"
 #include "nsCOMPtr.h"
 #include "nsIThread.h"
 #include "mozilla/Monitor.h"
 #include "nsISupportsImpl.h"
 #include "nsObserverService.h"
 
 class AsyncLatencyLogger;
 class LogEvent;
@@ -24,25 +24,44 @@ PRLogModuleInfo* GetLatencyLog();
 class AsyncLatencyLogger : public nsIObserver
 {
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
 public:
 
   enum LatencyLogIndex {
-    AudioMediaStreamTrack,
+    AudioMediaStreamTrack = 0,
     VideoMediaStreamTrack,
     Cubeb,
     AudioStream,
     NetEQ,
+    AudioCaptureBase, // base time for capturing an audio stream
+    AudioCapture, // records number of samples captured and the time
+    AudioTrackInsertion, // # of samples inserted into a mediastreamtrack and the time
+    MediaPipelineAudioInsertion, // Timestamp and time of timestamp
+    AudioTransmit, // Timestamp and socket send time
+    AudioReceive, // Timestamp and receive time
+    MediaPipelineAudioPlayout, // Timestamp and playout into MST time
+    MediaStreamCreate, // Source and TrackUnion streams
+    AudioStreamCreate, // TrackUnion stream and AudioStream
+    AudioSendRTP,
+    AudioRecvRTP,
     _MAX_INDEX
   };
-  void Log(LatencyLogIndex index, uint64_t aID, int64_t value);
-  void WriteLog(LatencyLogIndex index, uint64_t aID, int64_t value);
+  // Log with a null timestamp
+  void Log(LatencyLogIndex index, uint64_t aID, int64_t aValue);
+  // Log with a timestamp
+  void Log(LatencyLogIndex index, uint64_t aID, int64_t aValue,
+           mozilla::TimeStamp &aTime);
+  // Write a log message to NSPR
+  void WriteLog(LatencyLogIndex index, uint64_t aID, int64_t aValue,
+                mozilla::TimeStamp timestamp);
+  // Get the base time used by the logger for delta calculations
+  void GetStartTime(mozilla::TimeStamp &aStart);
 
   static AsyncLatencyLogger* Get(bool aStartTimer = false);
   static void InitializeStatics();
   // After this is called, the global log object may go away
   static void ShutdownLogger();
 private:
   AsyncLatencyLogger();
   virtual ~AsyncLatencyLogger();
@@ -58,11 +77,24 @@ private:
   // thread only.
   mozilla::TimeStamp mStart;
   // This monitor protects mStart and mMediaLatencyLog for the
   // initialization sequence. It is initialized at layout startup, and
   // destroyed at layout shutdown.
   mozilla::Mutex mMutex;
 };
 
-void LogLatency(AsyncLatencyLogger::LatencyLogIndex index, uint64_t aID, int64_t value);
+// need uint32_t versions for access from webrtc/trunk code
+// Log without a time delta
+void LogLatency(AsyncLatencyLogger::LatencyLogIndex index, uint64_t aID, int64_t aValue);
+void LogLatency(uint32_t index, uint64_t aID, int64_t aValue);
+// Log TimeStamp::Now() (as delta)
+void LogTime(AsyncLatencyLogger::LatencyLogIndex index, uint64_t aID, int64_t aValue);
+void LogTime(uint32_t index, uint64_t aID, int64_t aValue);
+// Log the specified time (as delta)
+void LogTime(AsyncLatencyLogger::LatencyLogIndex index, uint64_t aID, int64_t aValue,
+             mozilla::TimeStamp &aTime);
+
+// For generating unique-ish ids for logged sources
+#define LATENCY_STREAM_ID(source, trackID) \
+  ((((uint64_t) (source)) & ~0x0F) | (trackID))
 
 #endif
--- a/content/media/MediaSegment.h
+++ b/content/media/MediaSegment.h
@@ -228,16 +228,23 @@ public:
     MediaSegmentBase<C, Chunk>& mSegment;
     uint32_t mIndex;
   };
 
   void RemoveLeading(TrackTicks aDuration)
   {
     RemoveLeading(aDuration, 0);
   }
+
+#ifdef MOZILLA_INTERNAL_API
+  void GetStartTime(TimeStamp &aTime) {
+    aTime = mChunks[0].mTimeStamp;
+  }
+#endif
+
 protected:
   MediaSegmentBase(Type aType) : MediaSegment(aType) {}
 
   /**
    * Appends the contents of aSource to this segment, clearing aSource.
    */
   void AppendFromInternal(MediaSegmentBase<C, Chunk>* aSource)
   {
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -767,16 +767,20 @@ MediaStreamGraphImpl::CreateOrDestroyAud
           aStream->mAudioOutputStreams.AppendElement();
         audioOutputStream->mAudioPlaybackStartTime = aAudioOutputStartTime;
         audioOutputStream->mBlockedAudioTime = 0;
         audioOutputStream->mStream = AudioStream::AllocateStream();
         // XXX for now, allocate stereo output. But we need to fix this to
         // match the system's ideal channel configuration.
         audioOutputStream->mStream->Init(2, tracks->GetRate(), AUDIO_CHANNEL_NORMAL, AudioStream::LowLatency);
         audioOutputStream->mTrackID = tracks->GetID();
+
+        LogLatency(AsyncLatencyLogger::AudioStreamCreate,
+                   reinterpret_cast<uint64_t>(aStream),
+                   reinterpret_cast<int64_t>(audioOutputStream->mStream.get()));
       }
     }
   }
 
   for (int32_t i = audioOutputStreamsFound.Length() - 1; i >= 0; --i) {
     if (!audioOutputStreamsFound[i]) {
       aStream->mAudioOutputStreams[i].mStream->Shutdown();
       aStream->mAudioOutputStreams.RemoveElementAt(i);
@@ -849,18 +853,19 @@ MediaStreamGraphImpl::PlayAudio(MediaStr
         output.AppendNullData(endTicks - sliceEnd);
         NS_ASSERTION(endTicks == sliceEnd || track->IsEnded(),
                      "Ran out of data but track not ended?");
         output.ApplyVolume(volume);
         LOG(PR_LOG_DEBUG+1, ("MediaStream %p writing samples for %f to %f (samples %lld to %lld)",
                              aStream, MediaTimeToSeconds(t), MediaTimeToSeconds(end),
                              startTicks, endTicks));
       }
-      // XXX need unique id for stream & track
-      output.WriteTo((((uint64_t)i) << 32) | track->GetID(), audioOutput.mStream);
+      // Need unique id for stream & track - and we want it to match the inserter
+      output.WriteTo(LATENCY_STREAM_ID(aStream, track->GetID()),
+                     audioOutput.mStream);
       t = end;
     }
   }
 }
 
 static void
 SetImageToBlackPixel(PlanarYCbCrImage* aImage)
 {
--- a/content/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/content/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -151,23 +151,27 @@ MediaEngineWebRTCAudioSource::Start(Sour
   {
     MonitorAutoLock lock(mMonitor);
     mSources.AppendElement(aStream);
   }
 
   AudioSegment* segment = new AudioSegment();
   aStream->AddTrack(aID, SAMPLE_FREQUENCY, 0, segment);
   aStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
-  LOG(("Initial audio"));
-  mTrackID = aID;
+  LOG(("Start audio for stream %p", aStream));
 
   if (mState == kStarted) {
+    MOZ_ASSERT(aID == mTrackID);
     return NS_OK;
   }
   mState = kStarted;
+  mTrackID = aID;
+
+  // Make sure logger starts before capture
+  AsyncLatencyLogger::Get(true);
 
   // Configure audio processing in webrtc code
   Config(mEchoOn, webrtc::kEcUnchanged,
          mAgcOn, webrtc::kAgcUnchanged,
          mNoiseOn, webrtc::kNsUnchanged);
 
   if (mVoEBase->StartReceive(mChannel)) {
     return NS_ERROR_FAILURE;
@@ -365,21 +369,28 @@ MediaEngineWebRTCAudioSource::Process(in
 
     sample* dest = static_cast<sample*>(buffer->Data());
     memcpy(dest, audio10ms, length * sizeof(sample));
 
     AudioSegment segment;
     nsAutoTArray<const sample*,1> channels;
     channels.AppendElement(dest);
     segment.AppendFrames(buffer.forget(), channels, length);
+    TimeStamp insertTime;
+    segment.GetStartTime(insertTime);
 
     SourceMediaStream *source = mSources[i];
     if (source) {
       // This is safe from any thread, and is safe if the track is Finished
-      // or Destroyed
+      // or Destroyed.
+      // Make sure we include the stream and the track.
+      // The 0:1 is a flag to note when we've done the final insert for a given input block.
+      LogTime(AsyncLatencyLogger::AudioTrackInsertion, LATENCY_STREAM_ID(source, mTrackID),
+              (i+1 < len) ? 0 : 1, insertTime);
+
       source->AppendToTrack(mTrackID, &segment);
     }
   }
 
   return;
 }
 
 }
--- a/content/media/wmf/WMF.h
+++ b/content/media/wmf/WMF.h
@@ -26,16 +26,23 @@ which makes Windows Media Foundation una
 #include <mfobjects.h>
 #include <stdio.h>
 #include <mferror.h>
 #include <propvarutil.h>
 #include <wmcodecdsp.h>
 #include <initguid.h>
 #include <d3d9.h>
 #include <dxva2api.h>
+#include <wmcodecdsp.h>
+
+// Some SDK versions don't define the AAC decoder CLSID.
+#ifndef CLSID_CMSAACDecMFT
+extern const CLSID CLSID_CMSAACDecMFT;
+#define WMF_MUST_DEFINE_AAC_MFT_CLSID
+#endif
 
 namespace mozilla {
 namespace wmf {
 
 // Loads/Unloads all the DLLs in which the WMF functions are located.
 // The DLLs must be loaded before any of the WMF functions below will work.
 // All the function definitions below are wrappers which locate the
 // corresponding WMF function in the appropriate DLL (hence why LoadDLL()
@@ -99,16 +106,23 @@ HRESULT MFTEnumEx(GUID guidCategory,
 
 HRESULT MFGetService(IUnknown *punkObject,
                      REFGUID guidService,
                      REFIID riid,
                      LPVOID *ppvObject);
 
 HRESULT DXVA2CreateDirect3DDeviceManager9(UINT *pResetToken,
                                           IDirect3DDeviceManager9 **ppDXVAManager);
+
+HRESULT MFCreateSample(IMFSample **ppIMFSample);
+
+HRESULT MFCreateAlignedMemoryBuffer(DWORD cbMaxLength,
+                                    DWORD fAlignmentFlags,
+                                    IMFMediaBuffer **ppBuffer);
+
 } // end namespace wmf
 } // end namespace mozilla
 
 
 
 #pragma pop_macro("WINVER")
 
 #endif
--- a/content/media/wmf/WMFReader.cpp
+++ b/content/media/wmf/WMFReader.cpp
@@ -279,103 +279,16 @@ GetSourceReaderCanSeek(IMFSourceReader* 
   hr = wmf::PropVariantToUInt32(var, &flags);
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
 
   aOutCanSeek = ((flags & MFMEDIASOURCE_CAN_SEEK) == MFMEDIASOURCE_CAN_SEEK);
 
   return S_OK;
 }
 
-static HRESULT
-GetDefaultStride(IMFMediaType *aType, uint32_t* aOutStride)
-{
-  // Try to get the default stride from the media type.
-  HRESULT hr = aType->GetUINT32(MF_MT_DEFAULT_STRIDE, aOutStride);
-  if (SUCCEEDED(hr)) {
-    return S_OK;
-  }
-
-  // Stride attribute not set, calculate it.
-  GUID subtype = GUID_NULL;
-  uint32_t width = 0;
-  uint32_t height = 0;
-
-  hr = aType->GetGUID(MF_MT_SUBTYPE, &subtype);
-  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
-
-  hr = MFGetAttributeSize(aType, MF_MT_FRAME_SIZE, &width, &height);
-  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
-
-  hr = wmf::MFGetStrideForBitmapInfoHeader(subtype.Data1, width, (LONG*)(aOutStride));
-  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
-
-  return hr;
-}
-
-static int32_t
-MFOffsetToInt32(const MFOffset& aOffset)
-{
-  return int32_t(aOffset.value + (aOffset.fract / 65536.0f));
-}
-
-// Gets the sub-region of the video frame that should be displayed.
-// See: http://msdn.microsoft.com/en-us/library/windows/desktop/bb530115(v=vs.85).aspx
-static HRESULT
-GetPictureRegion(IMFMediaType* aMediaType, nsIntRect& aOutPictureRegion)
-{
-  // Determine if "pan and scan" is enabled for this media. If it is, we
-  // only display a region of the video frame, not the entire frame.
-  BOOL panScan = MFGetAttributeUINT32(aMediaType, MF_MT_PAN_SCAN_ENABLED, FALSE);
-
-  // If pan and scan mode is enabled. Try to get the display region.
-  HRESULT hr = E_FAIL;
-  MFVideoArea videoArea;
-  memset(&videoArea, 0, sizeof(MFVideoArea));
-  if (panScan) {
-    hr = aMediaType->GetBlob(MF_MT_PAN_SCAN_APERTURE,
-                             (UINT8*)&videoArea,
-                             sizeof(MFVideoArea),
-                             nullptr);
-  }
-
-  // If we're not in pan-and-scan mode, or the pan-and-scan region is not set,
-  // check for a minimimum display aperture.
-  if (!panScan || hr == MF_E_ATTRIBUTENOTFOUND) {
-    hr = aMediaType->GetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE,
-                             (UINT8*)&videoArea,
-                             sizeof(MFVideoArea),
-                             nullptr);
-  }
-
-  if (hr == MF_E_ATTRIBUTENOTFOUND) {
-    // Minimum display aperture is not set, for "backward compatibility with
-    // some components", check for a geometric aperture.
-    hr = aMediaType->GetBlob(MF_MT_GEOMETRIC_APERTURE,
-                             (UINT8*)&videoArea,
-                             sizeof(MFVideoArea),
-                             nullptr);
-  }
-
-  if (SUCCEEDED(hr)) {
-    // The media specified a picture region, return it.
-    aOutPictureRegion = nsIntRect(MFOffsetToInt32(videoArea.OffsetX),
-                                  MFOffsetToInt32(videoArea.OffsetY),
-                                  videoArea.Area.cx,
-                                  videoArea.Area.cy);
-    return S_OK;
-  }
-
-  // No picture region defined, fall back to using the entire video area.
-  UINT32 width = 0, height = 0;
-  hr = MFGetAttributeSize(aMediaType, MF_MT_FRAME_SIZE, &width, &height);
-  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
-  aOutPictureRegion = nsIntRect(0, 0, width, height);
-  return S_OK;
-}
-
 HRESULT
 WMFReader::ConfigureVideoFrameGeometry(IMFMediaType* aMediaType)
 {
   NS_ENSURE_TRUE(aMediaType != nullptr, E_POINTER);
   HRESULT hr;
 
   // Verify that the video subtype is what we expect it to be.
   // When using hardware acceleration/DXVA2 the video format should
@@ -634,49 +547,16 @@ WMFReader::ReadMetadata(MediaInfo* aInfo
   *aInfo = mInfo;
   *aTags = nullptr;
   // aTags can be retrieved using techniques like used here:
   // http://blogs.msdn.com/b/mf/archive/2010/01/12/mfmediapropdump.aspx
 
   return NS_OK;
 }
 
-static int64_t
-GetSampleDuration(IMFSample* aSample)
-{
-  int64_t duration = 0;
-  aSample->GetSampleDuration(&duration);
-  return HNsToUsecs(duration);
-}
-
-HRESULT
-HNsToFrames(int64_t aHNs, uint32_t aRate, int64_t* aOutFrames)
-{
-  MOZ_ASSERT(aOutFrames);
-  const int64_t HNS_PER_S = USECS_PER_S * 10;
-  CheckedInt<int64_t> i = aHNs;
-  i *= aRate;
-  i /= HNS_PER_S;
-  NS_ENSURE_TRUE(i.isValid(), E_FAIL);
-  *aOutFrames = i.value();
-  return S_OK;
-}
-
-HRESULT
-FramesToUsecs(int64_t aSamples, uint32_t aRate, int64_t* aOutUsecs)
-{
-  MOZ_ASSERT(aOutUsecs);
-  CheckedInt<int64_t> i = aSamples;
-  i *= USECS_PER_S;
-  i /= aRate;
-  NS_ENSURE_TRUE(i.isValid(), E_FAIL);
-  *aOutUsecs = i.value();
-  return S_OK;
-}
-
 bool
 WMFReader::DecodeAudioData()
 {
   NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
   HRESULT hr;
   hr = mSourceReader->ReadSample(MF_SOURCE_READER_FIRST_AUDIO_STREAM,
                                  0, // control flags
--- a/content/media/wmf/WMFUtils.cpp
+++ b/content/media/wmf/WMFUtils.cpp
@@ -6,19 +6,27 @@
 
 #include "WMFUtils.h"
 #include <stdint.h>
 #include "mozilla/RefPtr.h"
 #include "prlog.h"
 #include "nsThreadUtils.h"
 #include "WinUtils.h"
 #include "nsWindowsHelpers.h"
+#include "mozilla/CheckedInt.h"
+#include "VideoUtils.h"
 
 using namespace mozilla::widget;
 
+#ifdef WMF_MUST_DEFINE_AAC_MFT_CLSID
+// Some SDK versions don't define the AAC decoder CLSID.
+// {32D186A7-218F-4C75-8876-DD77273A8999}
+DEFINE_GUID(CLSID_CMSAACDecMFT, 0x32D186A7, 0x218F, 0x4C75, 0x88, 0x76, 0xDD, 0x77, 0x27, 0x3A, 0x89, 0x99);
+#endif
+
 namespace mozilla {
 
 struct GuidToName {
   GUID guid;
   const char* name;
 };
 
 #define GUID_TO_NAME_ENTRY(g) { g, #g }
@@ -208,23 +216,148 @@ DoGetInterface(IUnknown* aUnknown, void*
 {
   if (!aInterface)
     return E_POINTER;
   *aInterface = aUnknown;
   aUnknown->AddRef();
   return S_OK;
 }
 
-namespace wmf {
+HRESULT
+HNsToFrames(int64_t aHNs, uint32_t aRate, int64_t* aOutFrames)
+{
+  MOZ_ASSERT(aOutFrames);
+  const int64_t HNS_PER_S = USECS_PER_S * 10;
+  CheckedInt<int64_t> i = aHNs;
+  i *= aRate;
+  i /= HNS_PER_S;
+  NS_ENSURE_TRUE(i.isValid(), E_FAIL);
+  *aOutFrames = i.value();
+  return S_OK;
+}
+
+HRESULT
+FramesToUsecs(int64_t aSamples, uint32_t aRate, int64_t* aOutUsecs)
+{
+  MOZ_ASSERT(aOutUsecs);
+  CheckedInt<int64_t> i = aSamples;
+  i *= USECS_PER_S;
+  i /= aRate;
+  NS_ENSURE_TRUE(i.isValid(), E_FAIL);
+  *aOutUsecs = i.value();
+  return S_OK;
+}
+
+HRESULT
+GetDefaultStride(IMFMediaType *aType, uint32_t* aOutStride)
+{
+  // Try to get the default stride from the media type.
+  HRESULT hr = aType->GetUINT32(MF_MT_DEFAULT_STRIDE, aOutStride);
+  if (SUCCEEDED(hr)) {
+    return S_OK;
+  }
+
+  // Stride attribute not set, calculate it.
+  GUID subtype = GUID_NULL;
+  uint32_t width = 0;
+  uint32_t height = 0;
+
+  hr = aType->GetGUID(MF_MT_SUBTYPE, &subtype);
+  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+  hr = MFGetAttributeSize(aType, MF_MT_FRAME_SIZE, &width, &height);
+  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+  hr = wmf::MFGetStrideForBitmapInfoHeader(subtype.Data1, width, (LONG*)(aOutStride));
+  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+
+  return hr;
+}
+
+int32_t
+MFOffsetToInt32(const MFOffset& aOffset)
+{
+  return int32_t(aOffset.value + (aOffset.fract / 65536.0f));
+}
+
+int64_t
+GetSampleDuration(IMFSample* aSample)
+{
+  NS_ENSURE_TRUE(aSample, -1);
+  int64_t duration = 0;
+  aSample->GetSampleDuration(&duration);
+  return HNsToUsecs(duration);
+}
 
-// Some SDK versions don't define the AAC decoder CLSID.
-#ifndef CLSID_CMSAACDecMFT
-// {32D186A7-218F-4C75-8876-DD77273A8999}
-DEFINE_GUID(CLSID_CMSAACDecMFT, 0x32D186A7, 0x218F, 0x4C75, 0x88, 0x76, 0xDD, 0x77, 0x27, 0x3A, 0x89, 0x99);
-#endif
+int64_t
+GetSampleTime(IMFSample* aSample)
+{
+  NS_ENSURE_TRUE(aSample, -1);
+  LONGLONG timestampHns = 0;
+  HRESULT hr = aSample->GetSampleTime(&timestampHns);
+  NS_ENSURE_TRUE(SUCCEEDED(hr), -1);
+  return HNsToUsecs(timestampHns);
+}
+
+// Gets the sub-region of the video frame that should be displayed.
+// See: http://msdn.microsoft.com/en-us/library/windows/desktop/bb530115(v=vs.85).aspx
+HRESULT
+GetPictureRegion(IMFMediaType* aMediaType, nsIntRect& aOutPictureRegion)
+{
+  // Determine if "pan and scan" is enabled for this media. If it is, we
+  // only display a region of the video frame, not the entire frame.
+  BOOL panScan = MFGetAttributeUINT32(aMediaType, MF_MT_PAN_SCAN_ENABLED, FALSE);
+
+  // If pan and scan mode is enabled. Try to get the display region.
+  HRESULT hr = E_FAIL;
+  MFVideoArea videoArea;
+  memset(&videoArea, 0, sizeof(MFVideoArea));
+  if (panScan) {
+    hr = aMediaType->GetBlob(MF_MT_PAN_SCAN_APERTURE,
+                             (UINT8*)&videoArea,
+                             sizeof(MFVideoArea),
+                             nullptr);
+  }
+
+  // If we're not in pan-and-scan mode, or the pan-and-scan region is not set,
+  // check for a minimimum display aperture.
+  if (!panScan || hr == MF_E_ATTRIBUTENOTFOUND) {
+    hr = aMediaType->GetBlob(MF_MT_MINIMUM_DISPLAY_APERTURE,
+                             (UINT8*)&videoArea,
+                             sizeof(MFVideoArea),
+                             nullptr);
+  }
+
+  if (hr == MF_E_ATTRIBUTENOTFOUND) {
+    // Minimum display aperture is not set, for "backward compatibility with
+    // some components", check for a geometric aperture.
+    hr = aMediaType->GetBlob(MF_MT_GEOMETRIC_APERTURE,
+                             (UINT8*)&videoArea,
+                             sizeof(MFVideoArea),
+                             nullptr);
+  }
+
+  if (SUCCEEDED(hr)) {
+    // The media specified a picture region, return it.
+    aOutPictureRegion = nsIntRect(MFOffsetToInt32(videoArea.OffsetX),
+                                  MFOffsetToInt32(videoArea.OffsetY),
+                                  videoArea.Area.cx,
+                                  videoArea.Area.cy);
+    return S_OK;
+  }
+
+  // No picture region defined, fall back to using the entire video area.
+  UINT32 width = 0, height = 0;
+  hr = MFGetAttributeSize(aMediaType, MF_MT_FRAME_SIZE, &width, &height);
+  NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
+  aOutPictureRegion = nsIntRect(0, 0, width, height);
+  return S_OK;
+}
+
+namespace wmf {
 
 static bool
 IsSupportedDecoder(const GUID& aDecoderGUID)
 {
   return aDecoderGUID == CLSID_CMSH264DecoderMFT ||
          aDecoderGUID == CLSID_CMSAACDecMFT ||
          aDecoderGUID == CLSID_CMP3DecMediaObject;
 }
@@ -528,10 +661,28 @@ HRESULT
 DXVA2CreateDirect3DDeviceManager9(UINT *pResetToken,
                                   IDirect3DDeviceManager9 **ppDXVAManager)
 {
   DECL_FUNCTION_PTR(DXVA2CreateDirect3DDeviceManager9, UINT*, IDirect3DDeviceManager9 **);
   ENSURE_FUNCTION_PTR(DXVA2CreateDirect3DDeviceManager9, dxva2.dll)
   return (DXVA2CreateDirect3DDeviceManager9Ptr)(pResetToken, ppDXVAManager);
 }
 
+HRESULT
+MFCreateSample(IMFSample **ppIMFSample)
+{
+  DECL_FUNCTION_PTR(MFCreateSample, IMFSample **);
+  ENSURE_FUNCTION_PTR(MFCreateSample, mfplat.dll)
+  return (MFCreateSamplePtr)(ppIMFSample);
+}
+
+HRESULT
+MFCreateAlignedMemoryBuffer(DWORD cbMaxLength,
+                            DWORD fAlignmentFlags,
+                            IMFMediaBuffer **ppBuffer)
+{
+  DECL_FUNCTION_PTR(MFCreateAlignedMemoryBuffer, DWORD, DWORD, IMFMediaBuffer**);
+  ENSURE_FUNCTION_PTR(MFCreateAlignedMemoryBuffer, mfplat.dll)
+  return (MFCreateAlignedMemoryBufferPtr)(cbMaxLength, fAlignmentFlags, ppBuffer);
+}
+
 } // end namespace wmf
 } // end namespace mozilla
--- a/content/media/wmf/WMFUtils.h
+++ b/content/media/wmf/WMFUtils.h
@@ -1,16 +1,18 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WMF.h"
 #include "nsString.h"
+#include "nsRect.h"
+#include "VideoUtils.h"
 
 // Various utilities shared by WMF backend files.
 
 namespace mozilla {
 
 nsCString
 GetGUIDName(const GUID& guid);
 
@@ -59,9 +61,36 @@ HNsToUsecs(int64_t hNanoSecs) {
   return hNanoSecs / 10;
 }
 
 // Assigns aUnknown to *aInterface, and AddRef's it.
 // Helper for MSCOM QueryInterface implementations.
 HRESULT
 DoGetInterface(IUnknown* aUnknown, void** aInterface);
 
+HRESULT
+HNsToFrames(int64_t aHNs, uint32_t aRate, int64_t* aOutFrames);
+
+HRESULT
+FramesToUsecs(int64_t aSamples, uint32_t aRate, int64_t* aOutUsecs);
+
+HRESULT
+GetDefaultStride(IMFMediaType *aType, uint32_t* aOutStride);
+
+int32_t
+MFOffsetToInt32(const MFOffset& aOffset);
+
+// Gets the sub-region of the video frame that should be displayed.
+// See: http://msdn.microsoft.com/en-us/library/windows/desktop/bb530115(v=vs.85).aspx
+HRESULT
+GetPictureRegion(IMFMediaType* aMediaType, nsIntRect& aOutPictureRegion);
+
+// Returns the duration of a IMFSample in microseconds.
+// Returns -1 on failure.
+int64_t
+GetSampleDuration(IMFSample* aSample);
+
+// Returns the presentation time of a IMFSample in microseconds.
+// Returns -1 on failure.
+int64_t
+GetSampleTime(IMFSample* aSample);
+
 } // namespace mozilla
--- a/content/media/wmf/moz.build
+++ b/content/media/wmf/moz.build
@@ -5,16 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 MODULE = 'content'
 
 EXPORTS += [
     'WMF.h',
     'WMFDecoder.h',
     'WMFReader.h',
+    'WMFUtils.h',
 ]
 
 SOURCES += [
     'DXVA2Manager.cpp',
     'WMFByteStream.cpp',
     'WMFDecoder.cpp',
     'WMFReader.cpp',
     'WMFSourceReaderCallback.cpp',
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -111,16 +111,17 @@
 #include "nsITabChild.h"
 #include "nsISiteSecurityService.h"
 #include "nsStructuredCloneContainer.h"
 #include "nsIStructuredCloneContainer.h"
 #ifdef MOZ_PLACES
 #include "nsIFaviconService.h"
 #include "mozIAsyncFavicons.h"
 #endif
+#include "nsINetworkSeer.h"
 
 // Editor-related
 #include "nsIEditingSession.h"
 
 #include "nsPIDOMWindow.h"
 #include "nsGlobalWindow.h"
 #include "nsPIWindowRoot.h"
 #include "nsICachingChannel.h"
@@ -4582,25 +4583,19 @@ nsDocShell::LoadErrorPage(nsIURI *aURI, 
         errorPageUrl.AppendASCII(escapedCSSClass.get());
     }
     errorPageUrl.AppendLiteral("&c=");
     errorPageUrl.AppendASCII(escapedCharset.get());
     errorPageUrl.AppendLiteral("&d=");
     errorPageUrl.AppendASCII(escapedDescription.get());
 
     // Append the manifest URL if the error comes from an app.
-    uint32_t appId;
-    nsresult rv = GetAppId(&appId);
-    if (appId != nsIScriptSecurityManager::NO_APP_ID &&
-        appId != nsIScriptSecurityManager::UNKNOWN_APP_ID) {
-      nsCOMPtr<nsIAppsService> appsService =
-        do_GetService(APPS_SERVICE_CONTRACTID);
-      NS_ASSERTION(appsService, "No AppsService available");
-      nsAutoString manifestURL;
-      appsService->GetManifestURLByLocalId(appId, manifestURL);
+    nsString manifestURL;
+    nsresult rv = GetAppManifestURL(manifestURL);
+    if (manifestURL.Length() > 0) {
       nsCString manifestParam;
       SAFE_ESCAPE(manifestParam,
                   NS_ConvertUTF16toUTF8(manifestURL).get(),
                   url_Path);
       errorPageUrl.AppendLiteral("&m=");
       errorPageUrl.AppendASCII(manifestParam.get());
     }
 
@@ -7014,19 +7009,22 @@ nsDocShell::EndPageLoad(nsIWebProgress *
         else if (aStatus == NS_ERROR_DOCUMENT_NOT_CACHED) {
             // Non-caching channels will simply return NS_ERROR_OFFLINE.
             // Caching channels would have to look at their flags to work
             // out which error to return. Or we can fix up the error here.
             if (!(mLoadType & LOAD_CMD_HISTORY))
                 aStatus = NS_ERROR_OFFLINE;
             DisplayLoadError(aStatus, url, nullptr, aChannel);
         }
-  } // if we have a host
-
-  return NS_OK;
+    } // if we have a host
+    else if (url && NS_SUCCEEDED(aStatus)) {
+        mozilla::net::SeerLearnRedirect(url, aChannel, this);
+    }
+
+    return NS_OK;
 }
 
 
 //*****************************************************************************
 // nsDocShell: Content Viewer Management
 //*****************************************************************************   
 
 NS_IMETHODIMP
@@ -9403,16 +9401,19 @@ nsDocShell::InternalLoad(nsIURI * aURI,
     }
 
     nsAutoString srcdoc;
     if (aFlags & INTERNAL_LOAD_FLAGS_IS_SRCDOC)
       srcdoc = aSrcdoc;
     else
       srcdoc = NullString();
 
+    mozilla::net::SeerPredict(aURI, nullptr, nsINetworkSeer::PREDICT_LOAD,
+                              this, nullptr);
+
     nsCOMPtr<nsIRequest> req;
     rv = DoURILoad(aURI, aReferrer,
                    !(aFlags & INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER),
                    owner, aTypeHint, aFileName, aPostData, aHeadersData,
                    aFirstParty, aDocShell, getter_AddRefs(req),
                    (aFlags & INTERNAL_LOAD_FLAGS_FIRST_LOAD) != 0,
                    (aFlags & INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER) != 0,
                    (aFlags & INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES) != 0,
@@ -12459,16 +12460,19 @@ nsDocShell::OnOverLink(nsIContent* aCont
   nsAutoCString spec;
   rv = aURI->GetSpec(spec);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoString uStr;
   rv = textToSubURI->UnEscapeURIForUI(charset, spec, uStr);    
   NS_ENSURE_SUCCESS(rv, rv);
 
+  mozilla::net::SeerPredict(aURI, mCurrentURI, nsINetworkSeer::PREDICT_LINK,
+                            this, nullptr);
+
   if (browserChrome2) {
     nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aContent);
     rv = browserChrome2->SetStatusWithContext(nsIWebBrowserChrome::STATUS_LINK,
                                               uStr, element);
   } else {
     rv = browserChrome->SetStatus(nsIWebBrowserChrome::STATUS_LINK, uStr.get());
   }
   return rv;
@@ -12727,16 +12731,35 @@ nsDocShell::GetAppId(uint32_t* aAppId)
         *aAppId = nsIScriptSecurityManager::NO_APP_ID;
         return NS_OK;
     }
 
     return parent->GetAppId(aAppId);
 }
 
 NS_IMETHODIMP
+nsDocShell::GetAppManifestURL(nsAString& aAppManifestURL)
+{
+  uint32_t appId;
+  GetAppId(&appId);
+
+  if (appId != nsIScriptSecurityManager::NO_APP_ID &&
+      appId != nsIScriptSecurityManager::UNKNOWN_APP_ID) {
+    nsCOMPtr<nsIAppsService> appsService =
+      do_GetService(APPS_SERVICE_CONTRACTID);
+    NS_ASSERTION(appsService, "No AppsService available");
+    appsService->GetManifestURLByLocalId(appId, aAppManifestURL);
+  } else {
+    aAppManifestURL.SetLength(0);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDocShell::GetAsyncPanZoomEnabled(bool* aOut)
 {
     if (TabChild* tabChild = TabChild::GetFrom(this)) {
         *aOut = tabChild->IsAsyncPanZoomEnabled();
         return NS_OK;
     }
     *aOut = false;
     return NS_OK;
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -38,17 +38,17 @@ interface nsIDOMStorage;
 interface nsIPrincipal;
 interface nsIWebBrowserPrint;
 interface nsIVariant;
 interface nsIPrivacyTransitionObserver;
 interface nsIReflowObserver;
 
 typedef unsigned long nsLoadFlags;
 
-[scriptable, builtinclass, uuid(4c8cd9da-4e93-42ed-9901-3781e90458d9)]
+[scriptable, builtinclass, uuid(1470A132-99B2-44C3-B37D-D8093B2E29BF)]
 interface nsIDocShell : nsIDocShellTreeItem
 {
   /**
    * Loads a given URI.  This will give priority to loading the requested URI
    * in the object implementing	this interface.  If it can't be loaded here
    * however, the URL dispatcher will go through its normal process of content
    * loading.
    *
@@ -738,16 +738,23 @@ interface nsIDocShell : nsIDocShellTreeI
    * return NO_APP_ID.  We never return UNKNOWN_APP_ID.
    *
    * Notice that a docshell may have an associated app even if it returns true
    * for isBrowserElement!
    */
   [infallible] readonly attribute unsigned long appId;
 
   /**
+   * Return the manifest URL of the app associated with this docshell.
+   *
+   * If there is no associated app in our hierarchy, we return empty string.
+   */
+  readonly attribute DOMString appManifestURL;
+
+  /**
    * Like nsIDocShellTreeItem::GetSameTypeParent, except this ignores <iframe
    * mozbrowser> and <iframe mozapp> boundaries.
    */
   nsIDocShell getSameTypeParentIgnoreBrowserAndAppBoundaries();
 
   /** 
    * True iff asynchronous panning and zooming is enabled for this
    * docshell.
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -1,24 +1,27 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "base/basictypes.h"
 #include "nsCOMPtr.h"
 #include "nsDOMClassInfo.h"
+#include "nsHashPropertyBag.h"
 #include "jsapi.h"
 #include "nsThread.h"
 #include "DeviceStorage.h"
 #include "mozilla/dom/CameraControlBinding.h"
-#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/TabChild.h"
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
+#include "nsIAppsService.h"
 #include "nsIObserverService.h"
 #include "nsIDOMDeviceStorage.h"
+#include "nsIScriptSecurityManager.h"
 #include "nsXULAppAPI.h"
 #include "DOMCameraManager.h"
 #include "DOMCameraCapabilities.h"
 #include "DOMCameraControl.h"
 #include "CameraCommon.h"
 #include "mozilla/dom/CameraManagerBinding.h"
 #include "mozilla/dom/BindingUtils.h"
 
@@ -273,23 +276,26 @@ nsDOMCameraControl::StartRecording(JSCon
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   if (!obs) {
     NS_WARNING("Could not get the Observer service for CameraControl::StartRecording.");
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
-  obs->NotifyObservers(nullptr,
+  nsRefPtr<nsHashPropertyBag> props = CreateRecordingDeviceEventsSubject();
+  obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
                        "recording-device-events",
                        NS_LITERAL_STRING("starting").get());
   // Forward recording events to parent process.
   // The events are gathered in chrome process and used for recording indicator
   if (XRE_GetProcessType() != GeckoProcessType_Default) {
-    unused << ContentChild::GetSingleton()->SendRecordingDeviceEvents(NS_LITERAL_STRING("starting"));
+    unused << TabChild::GetFrom(mWindow)->SendRecordingDeviceEvents(NS_LITERAL_STRING("starting"),
+                                                                    true /* isAudio */,
+                                                                    true /* isVideo */);
   }
 
   #ifdef MOZ_B2G
   if (!mAudioChannelAgent) {
     mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1");
     if (mAudioChannelAgent) {
       // Camera app will stop recording when it falls to the background, so no callback is necessary.
       mAudioChannelAgent->Init(AUDIO_CHANNEL_CONTENT, nullptr);
@@ -313,23 +319,26 @@ void
 nsDOMCameraControl::StopRecording(ErrorResult& aRv)
 {
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   if (!obs) {
     NS_WARNING("Could not get the Observer service for CameraControl::StopRecording.");
     aRv.Throw(NS_ERROR_FAILURE);
   }
 
-  obs->NotifyObservers(nullptr,
+  nsRefPtr<nsHashPropertyBag> props = CreateRecordingDeviceEventsSubject();
+  obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props) ,
                        "recording-device-events",
                        NS_LITERAL_STRING("shutdown").get());
   // Forward recording events to parent process.
   // The events are gathered in chrome process and used for recording indicator
   if (XRE_GetProcessType() != GeckoProcessType_Default) {
-    unused << ContentChild::GetSingleton()->SendRecordingDeviceEvents(NS_LITERAL_STRING("shutdown"));
+    unused << TabChild::GetFrom(mWindow)->SendRecordingDeviceEvents(NS_LITERAL_STRING("shutdown"),
+                                                                    true /* isAudio */,
+                                                                    true /* isVideo */);
   }
 
   #ifdef MOZ_B2G
   if (mAudioChannelAgent) {
     mAudioChannelAgent->StopPlaying();
     mAudioChannelAgent = nullptr;
   }
   #endif
@@ -519,8 +528,44 @@ nsDOMCameraControl::Shutdown()
   mCameraControl->Shutdown();
 }
 
 nsRefPtr<ICameraControl>
 nsDOMCameraControl::GetNativeCameraControl()
 {
   return mCameraControl;
 }
+
+already_AddRefed<nsHashPropertyBag>
+nsDOMCameraControl::CreateRecordingDeviceEventsSubject()
+{
+  MOZ_ASSERT(mWindow);
+
+  nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
+  props->SetPropertyAsBool(NS_LITERAL_STRING("isAudio"), true);
+  props->SetPropertyAsBool(NS_LITERAL_STRING("isVideo"), true);
+
+  nsCOMPtr<nsIDocShell> docShell = mWindow->GetDocShell();
+  if (docShell) {
+    bool isApp;
+    DebugOnly<nsresult> rv = docShell->GetIsApp(&isApp);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+    nsString requestURL;
+    if (isApp) {
+      rv = docShell->GetAppManifestURL(requestURL);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+    } else {
+      nsCString pageURL;
+      nsCOMPtr<nsIURI> docURI = mWindow->GetDocumentURI();
+      MOZ_ASSERT(docURI);
+
+      rv = docURI->GetSpec(pageURL);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+      requestURL = NS_ConvertUTF8toUTF16(pageURL);
+    }
+    props->SetPropertyAsBool(NS_LITERAL_STRING("isApp"), isApp);
+    props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL);
+  }
+
+  return props.forget();
+}
--- a/dom/camera/DOMCameraControl.h
+++ b/dom/camera/DOMCameraControl.h
@@ -10,16 +10,17 @@
 #include "nsCycleCollectionParticipant.h"
 #include "DictionaryHelpers.h"
 #include "ICameraControl.h"
 #include "DOMCameraPreview.h"
 #include "nsIDOMCameraManager.h"
 #include "CameraCommon.h"
 #include "AudioChannelAgent.h"
 #include "nsProxyRelease.h"
+#include "nsHashPropertyBag.h"
 
 class nsDOMDeviceStorage;
 class nsPIDOMWindow;
 
 namespace mozilla {
 namespace dom {
 class CameraPictureOptions;
 template<typename T> class Optional;
@@ -94,16 +95,17 @@ public:
 protected:
   virtual ~nsDOMCameraControl();
 
 private:
   nsDOMCameraControl(const nsDOMCameraControl&) MOZ_DELETE;
   nsDOMCameraControl& operator=(const nsDOMCameraControl&) MOZ_DELETE;
 
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+  already_AddRefed<nsHashPropertyBag> CreateRecordingDeviceEventsSubject();
 
 protected:
   /* additional members */
   nsRefPtr<ICameraControl>        mCameraControl; // non-DOM camera control
   nsCOMPtr<nsICameraCapabilities> mDOMCapabilities;
   // An agent used to join audio channel service.
   nsCOMPtr<nsIAudioChannelAgent>  mAudioChannelAgent;
   nsCOMPtr<nsPIDOMWindow> mWindow;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1931,33 +1931,16 @@ ContentParent::RecvBroadcastVolume(const
     return true;
 #else
     NS_WARNING("ContentParent::RecvBroadcastVolume shouldn't be called when MOZ_WIDGET_GONK is not defined");
     return false;
 #endif
 }
 
 bool
-ContentParent::RecvRecordingDeviceEvents(const nsString& aRecordingStatus)
-{
-    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-    if (obs) {
-        // recording-device-ipc-events needs to gather more information from content process
-        nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
-        props->SetPropertyAsUint64(NS_LITERAL_STRING("childID"), mChildID);
-        obs->NotifyObservers((nsIPropertyBag2*) props,
-                             "recording-device-ipc-events",
-                             aRecordingStatus.get());
-    } else {
-        NS_WARNING("Could not get the Observer service for ContentParent::RecvRecordingDeviceEvents.");
-    }
-    return true;
-}
-
-bool
 ContentParent::SendNuwaFork()
 {
     if (this != sNuwaProcess) {
         return false;
     }
 
     CancelableTask* nuwaForkTimeoutTask = NewRunnableMethod(
         this, &ContentParent::OnNuwaForkTimeout);
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -159,16 +159,18 @@ public:
     /**
      * Kill our subprocess and make sure it dies.  Should only be used
      * in emergency situations since it bypasses the normal shutdown
      * process.
      */
     void KillHard();
 
     uint64_t ChildID() { return mChildID; }
+    const nsString& AppManifestURL() const { return mAppManifestURL; }
+
     bool IsPreallocated();
 
     /**
      * Get a user-friendly name for this ContentParent.  We make no guarantees
      * about this name: It might not be unique, apps can spoof special names,
      * etc.  So please don't use this name to make any decisions about the
      * ContentParent based on the value returned here.
      */
@@ -466,18 +468,16 @@ private:
 
     virtual bool RecvAudioChannelChangedNotification();
 
     virtual bool RecvAudioChannelChangeDefVolChannel(
       const AudioChannelType& aType, const bool& aHidden);
 
     virtual bool RecvBroadcastVolume(const nsString& aVolumeName);
 
-    virtual bool RecvRecordingDeviceEvents(const nsString& aRecordingStatus);
-
     virtual bool RecvSystemMessageHandled() MOZ_OVERRIDE;
 
     virtual bool RecvNuwaReady() MOZ_OVERRIDE;
 
     virtual bool RecvAddNewProcess(const uint32_t& aPid,
                                    const InfallibleTArray<ProtocolFdMapping>& aFds) MOZ_OVERRIDE;
 
     virtual bool RecvCreateFakeVolume(const nsString& fsName, const nsString& mountPoint) MOZ_OVERRIDE;
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -299,16 +299,24 @@ parent:
 
     /**
      * Notifies the parent about a scroll event. The pres shell ID and
      * view ID identify which scrollable (sub-)frame was scrolled, and
      * the new scroll offset for that frame is sent.
      */
     UpdateScrollOffset(uint32_t aPresShellId, ViewID aViewId, CSSIntPoint aScrollOffset);
 
+    /**
+     * Notifies the parent about a recording device is starting or shutdown.
+     * @param recordingStatus starting or shutdown
+     * @param isAudio recording start with microphone
+     * @param isVideo recording start with camera
+     */
+    async RecordingDeviceEvents(nsString recordingStatus, bool isAudio, bool isVideo);
+
     __delete__();
 
 child:
     /**
      * Notify the remote browser that it has been Show()n on this
      * side, with the given |visibleRect|.  This message is expected
      * to trigger creation of the remote browser's "widget".
      *
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -460,18 +460,16 @@ parent:
 
     async FilePathUpdateNotify(nsString aType,
                                nsString aStorageName,
                                nsString aFilepath,
                                nsCString aReason);
     // get nsIVolumeService to broadcast volume information
     async BroadcastVolume(nsString volumeName);
 
-    async RecordingDeviceEvents(nsString recordingStatus);
-
     // Notify the parent that the child has finished handling a system message.
     async SystemMessageHandled();
 
     NuwaReady();
 
     sync AddNewProcess(uint32_t pid, ProtocolFdMapping[] aFds);
 
     // called by the child (test code only) to propagate volume changes to the parent
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -25,16 +25,17 @@
 #include "mozilla/unused.h"
 #include "nsCOMPtr.h"
 #include "nsContentPermissionHelper.h"
 #include "nsContentUtils.h"
 #include "nsDebug.h"
 #include "nsEventStateManager.h"
 #include "nsFocusManager.h"
 #include "nsFrameLoader.h"
+#include "nsHashPropertyBag.h"
 #include "nsIContent.h"
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeOwner.h"
 #include "nsIDOMElement.h"
 #include "nsIDOMEvent.h"
 #include "nsIDOMWindow.h"
 #include "nsIDialogCreator.h"
 #include "nsIInterfaceRequestorUtils.h"
@@ -1620,10 +1621,44 @@ bool
 TabParent::RecvContentReceivedTouch(const bool& aPreventDefault)
 {
   if (RenderFrameParent* rfp = GetRenderFrame()) {
     rfp->ContentReceivedTouch(aPreventDefault);
   }
   return true;
 }
 
+bool
+TabParent::RecvRecordingDeviceEvents(const nsString& aRecordingStatus,
+                                     const bool& aIsAudio,
+                                     const bool& aIsVideo)
+{
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (obs) {
+        // recording-device-ipc-events needs to gather more information from content process
+        nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
+        props->SetPropertyAsUint64(NS_LITERAL_STRING("childID"), Manager()->ChildID());
+        props->SetPropertyAsBool(NS_LITERAL_STRING("isApp"), Manager()->IsForApp());
+        props->SetPropertyAsBool(NS_LITERAL_STRING("isAudio"), aIsAudio);
+        props->SetPropertyAsBool(NS_LITERAL_STRING("isVideo"), aIsVideo);
+
+        nsString requestURL;
+        if (Manager()->IsForApp()) {
+          requestURL = Manager()->AppManifestURL();
+        } else {
+          nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
+          NS_ENSURE_TRUE(frameLoader, true);
+
+          frameLoader->GetURL(requestURL);
+        }
+        props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL);
+
+        obs->NotifyObservers((nsIPropertyBag2*) props,
+                             "recording-device-ipc-events",
+                             aRecordingStatus.get());
+    } else {
+        NS_WARNING("Could not get the Observer service for ContentParent::RecvRecordingDeviceEvents.");
+    }
+    return true;
+}
+
 } // namespace tabs
 } // namespace mozilla
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -157,16 +157,19 @@ public:
     virtual bool RecvGetDefaultScale(double* aValue);
     virtual bool RecvGetWidgetNativeData(WindowsHandle* aValue);
     virtual bool RecvZoomToRect(const CSSRect& aRect);
     virtual bool RecvUpdateZoomConstraints(const bool& aAllowZoom,
                                            const CSSToScreenScale& aMinZoom,
                                            const CSSToScreenScale& aMaxZoom);
     virtual bool RecvUpdateScrollOffset(const uint32_t& aPresShellId, const ViewID& aViewId, const CSSIntPoint& aScrollOffset);
     virtual bool RecvContentReceivedTouch(const bool& aPreventDefault);
+    virtual bool RecvRecordingDeviceEvents(const nsString& aRecordingStatus,
+                                           const bool& aIsAudio,
+                                           const bool& aIsVideo);
     virtual PContentDialogParent* AllocPContentDialogParent(const uint32_t& aType,
                                                             const nsCString& aName,
                                                             const nsCString& aFeatures,
                                                             const InfallibleTArray<int>& aIntParams,
                                                             const InfallibleTArray<nsString>& aStringParams);
     virtual bool DeallocPContentDialogParent(PContentDialogParent* aDialog)
     {
       delete aDialog;
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -1,33 +1,38 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MediaManager.h"
 
 #include "MediaStreamGraph.h"
 #include "GetUserMediaRequest.h"
+#include "nsHashPropertyBag.h"
 #ifdef MOZ_WIDGET_GONK
 #include "nsIAudioManager.h"
 #endif
+#include "nsIAppsService.h"
 #include "nsIDOMFile.h"
 #include "nsIEventTarget.h"
 #include "nsIUUIDGenerator.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsIPopupWindowManager.h"
 #include "nsISupportsArray.h"
 #include "nsIDocShell.h"
 #include "nsIDocument.h"
 #include "nsISupportsPrimitives.h"
 #include "nsIInterfaceRequestorUtils.h"
-#include "mozilla/dom/ContentChild.h"
+#include "nsIScriptSecurityManager.h"
+#include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/MediaStreamTrackBinding.h"
 #include "mozilla/dom/GetUserMediaRequestBinding.h"
 
+#include "Latency.h"
+
 // For PR_snprintf
 #include "prprf.h"
 
 #include "nsJSUtils.h"
 #include "nsDOMFile.h"
 #include "nsGlobalWindow.h"
 
 #include "mozilla/Preferences.h"
@@ -133,16 +138,55 @@ static nsresult ValidateTrackConstraints
     nsresult rv = CompareDictionaries(aCx, track.mMandatory.Value(),
                                       aNormalized.mMandatory,
                                       aOutUnknownConstraint);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   return NS_OK;
 }
 
+static already_AddRefed<nsHashPropertyBag>
+CreateRecordingDeviceEventsSubject(nsPIDOMWindow* aWindow,
+                                   const bool aIsAudio,
+                                   const bool aIsVideo)
+{
+  MOZ_ASSERT(aWindow);
+
+  nsRefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
+  props->SetPropertyAsBool(NS_LITERAL_STRING("isAudio"), aIsAudio);
+  props->SetPropertyAsBool(NS_LITERAL_STRING("isVideo"), aIsVideo);
+
+  nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
+  if (docShell) {
+    bool isApp;
+    DebugOnly<nsresult> rv = docShell->GetIsApp(&isApp);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+    nsString requestURL;
+    if (isApp) {
+      rv = docShell->GetAppManifestURL(requestURL);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+    } else {
+      nsCString pageURL;
+      nsCOMPtr<nsIURI> docURI = aWindow->GetDocumentURI();
+      MOZ_ASSERT(docURI);
+
+      rv = docURI->GetSpec(pageURL);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+      requestURL = NS_ConvertUTF8toUTF16(pageURL);
+    }
+
+    props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL);
+    props->SetPropertyAsBool(NS_LITERAL_STRING("isApp"), isApp);
+  }
+
+  return props.forget();
+}
+
 /**
  * Send an error back to content. The error is the form a string.
  * Do this only on the main thread. The success callback is also passed here
  * so it can be released correctly.
  */
 class ErrorCallbackRunnable : public nsRunnable
 {
 public:
@@ -559,16 +603,22 @@ public:
     nsRefPtr<SourceMediaStream> stream = gm->CreateSourceStream(nullptr);
 
     // connect the source stream to the track union stream to avoid us blocking
     trackunion->GetStream()->AsProcessedStream()->SetAutofinish(true);
     nsRefPtr<MediaInputPort> port = trackunion->GetStream()->AsProcessedStream()->
       AllocateInputPort(stream, MediaInputPort::FLAG_BLOCK_OUTPUT);
     trackunion->mSourceStream = stream;
     trackunion->mPort = port.forget();
+    // Log the relationship between SourceMediaStream and TrackUnion stream
+    // Make sure logger starts before capture
+    AsyncLatencyLogger::Get(true);
+    LogLatency(AsyncLatencyLogger::MediaStreamCreate,
+               reinterpret_cast<uint64_t>(stream.get()),
+               reinterpret_cast<int64_t>(trackunion->GetStream()));
 
     trackunion->CombineWithPrincipal(window->GetExtantDoc()->NodePrincipal());
 
     // The listener was added at the begining in an inactive state.
     // Activate our listener. We'll call Start() on the source when get a callback
     // that the MediaStream has started consuming. The listener is freed
     // when the page is invalidated (on navigation or close).
     mListener->Activate(stream.forget(), mAudioSource, mVideoSource);
@@ -579,17 +629,17 @@ public:
     // Dispatch to the media thread to ask it to start the sources,
     // because that can take a while.
     // Pass ownership of trackunion to the MediaOperationRunnable
     // to ensure it's kept alive until the MediaOperationRunnable runs (at least).
     nsIThread *mediaThread = MediaManager::GetThread();
     nsRefPtr<MediaOperationRunnable> runnable(
       new MediaOperationRunnable(MEDIA_START, mListener, trackunion,
                                  tracksAvailableCallback,
-                                 mAudioSource, mVideoSource, false));
+                                 mAudioSource, mVideoSource, false, mWindowID));
     mediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
 
 #ifdef MOZ_WEBRTC
     // Right now these configs are only of use if webrtc is available
     nsresult rv;
     nsCOMPtr<nsIPrefService> prefs = do_GetService("@mozilla.org/preferences-service;1", &rv);
     if (NS_SUCCEEDED(rv)) {
       nsCOMPtr<nsIPrefBranch> branch = do_QueryInterface(prefs);
@@ -1725,17 +1775,17 @@ GetUserMediaCallbackMediaStreamListener:
   nsRefPtr<MediaOperationRunnable> runnable;
   // We can't take a chance on blocking here, so proxy this to another
   // thread.
   // Pass a ref to us (which is threadsafe) so it can query us for the
   // source stream info.
   runnable = new MediaOperationRunnable(MEDIA_STOP,
                                         this, nullptr, nullptr,
                                         mAudioSource, mVideoSource,
-                                        mFinished);
+                                        mFinished, mWindowID);
   mMediaThread->Dispatch(runnable, NS_DISPATCH_NORMAL);
 }
 
 // Called from the MediaStreamGraph thread
 void
 GetUserMediaCallbackMediaStreamListener::NotifyFinished(MediaStreamGraph* aGraph)
 {
   mFinished = true;
@@ -1782,20 +1832,27 @@ GetUserMediaNotificationEvent::Run()
     break;
   case STOPPING:
     msg = NS_LITERAL_STRING("shutdown");
     if (mListener) {
       mListener->SetStopped();
     }
     break;
   }
-  obs->NotifyObservers(nullptr,
+
+  nsCOMPtr<nsPIDOMWindow> window = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
+  MOZ_ASSERT(window);
+
+  nsRefPtr<nsHashPropertyBag> props = 
+    CreateRecordingDeviceEventsSubject(window, mIsAudio, mIsVideo);
+
+  obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
 		       "recording-device-events",
 		       msg.get());
   // Forward recording events to parent process.
   // The events are gathered in chrome process and used for recording indicator
   if (XRE_GetProcessType() != GeckoProcessType_Default) {
-    unused << dom::ContentChild::GetSingleton()->SendRecordingDeviceEvents(msg);
+    unused << dom::TabChild::GetFrom(window)->SendRecordingDeviceEvents(msg, mIsAudio, mIsVideo);
   }
   return NS_OK;
 }
 
 } // namespace mozilla
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -199,36 +199,42 @@ private:
 class GetUserMediaNotificationEvent: public nsRunnable
 {
   public:
     enum GetUserMediaStatus {
       STARTING,
       STOPPING
     };
     GetUserMediaNotificationEvent(GetUserMediaCallbackMediaStreamListener* aListener,
-                                  GetUserMediaStatus aStatus)
-    : mListener(aListener), mStatus(aStatus) {}
+                                  GetUserMediaStatus aStatus,
+                                  bool aIsAudio, bool aIsVideo, uint64_t aWindowID)
+    : mListener(aListener) , mStatus(aStatus) , mIsAudio(aIsAudio)
+    , mIsVideo(aIsVideo), mWindowID(aWindowID) {}
 
     GetUserMediaNotificationEvent(GetUserMediaStatus aStatus,
                                   already_AddRefed<DOMMediaStream> aStream,
-                                  DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback)
+                                  DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback,
+                                  bool aIsAudio, bool aIsVideo, uint64_t aWindowID)
     : mStream(aStream), mOnTracksAvailableCallback(aOnTracksAvailableCallback),
-      mStatus(aStatus) {}
+      mStatus(aStatus), mIsAudio(aIsAudio), mIsVideo(aIsVideo), mWindowID(aWindowID) {}
     virtual ~GetUserMediaNotificationEvent()
     {
 
     }
 
     NS_IMETHOD Run() MOZ_OVERRIDE;
 
   protected:
     nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
     nsRefPtr<DOMMediaStream> mStream;
     nsAutoPtr<DOMMediaStream::OnTracksAvailableCallback> mOnTracksAvailableCallback;
     GetUserMediaStatus mStatus;
+    bool mIsAudio;
+    bool mIsVideo;
+    uint64_t mWindowID;
 };
 
 typedef enum {
   MEDIA_START,
   MEDIA_STOP
 } MediaOperation;
 
 // Generic class for running long media operations like Start off the main
@@ -239,24 +245,26 @@ class MediaOperationRunnable : public ns
 public:
   // so we can send Stop without AddRef()ing from the MSG thread
   MediaOperationRunnable(MediaOperation aType,
     GetUserMediaCallbackMediaStreamListener* aListener,
     DOMMediaStream* aStream,
     DOMMediaStream::OnTracksAvailableCallback* aOnTracksAvailableCallback,
     MediaEngineSource* aAudioSource,
     MediaEngineSource* aVideoSource,
-    bool aNeedsFinish)
+    bool aNeedsFinish,
+    uint64_t aWindowID)
     : mType(aType)
     , mStream(aStream)
     , mOnTracksAvailableCallback(aOnTracksAvailableCallback)
     , mAudioSource(aAudioSource)
     , mVideoSource(aVideoSource)
     , mListener(aListener)
     , mFinish(aNeedsFinish)
+    , mWindowID(aWindowID)
     {}
 
   ~MediaOperationRunnable()
   {
     // MediaStreams can be released on any thread.
   }
 
   NS_IMETHOD
@@ -298,17 +306,20 @@ public:
 
           MM_LOG(("started all sources"));
           // Forward mOnTracksAvailableCallback to GetUserMediaNotificationEvent,
           // because mOnTracksAvailableCallback needs to be added to mStream
           // on the main thread.
           nsRefPtr<GetUserMediaNotificationEvent> event =
             new GetUserMediaNotificationEvent(GetUserMediaNotificationEvent::STARTING,
                                               mStream.forget(),
-                                              mOnTracksAvailableCallback.forget());
+                                              mOnTracksAvailableCallback.forget(),
+                                              mAudioSource != nullptr,
+                                              mVideoSource != nullptr,
+                                              mWindowID);
           NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
         }
         break;
 
       case MEDIA_STOP:
         {
           NS_ASSERTION(!NS_IsMainThread(), "Never call on main thread");
           if (mAudioSource) {
@@ -319,17 +330,21 @@ public:
             mVideoSource->Stop(source, kVideoTrack);
             mVideoSource->Deallocate();
           }
           // Do this after stopping all tracks with EndTrack()
           if (mFinish) {
             source->Finish();
           }
           nsRefPtr<GetUserMediaNotificationEvent> event =
-            new GetUserMediaNotificationEvent(mListener, GetUserMediaNotificationEvent::STOPPING);
+            new GetUserMediaNotificationEvent(mListener,
+                                              GetUserMediaNotificationEvent::STOPPING,
+                                              mAudioSource != nullptr,
+                                              mVideoSource != nullptr,
+                                              mWindowID);
 
           NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
         }
         break;
 
       default:
         MOZ_ASSERT(false,"invalid MediaManager operation");
         break;
@@ -340,16 +355,17 @@ public:
 private:
   MediaOperation mType;
   nsRefPtr<DOMMediaStream> mStream;
   nsAutoPtr<DOMMediaStream::OnTracksAvailableCallback> mOnTracksAvailableCallback;
   nsRefPtr<MediaEngineSource> mAudioSource; // threadsafe
   nsRefPtr<MediaEngineSource> mVideoSource; // threadsafe
   nsRefPtr<GetUserMediaCallbackMediaStreamListener> mListener; // threadsafe
   bool mFinish;
+  uint64_t mWindowID;
 };
 
 typedef nsTArray<nsRefPtr<GetUserMediaCallbackMediaStreamListener> > StreamListeners;
 typedef nsClassHashtable<nsUint64HashKey, StreamListeners> WindowTable;
 
 class MediaDevice : public nsIMediaDevice
 {
 public:
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/bugs/file_bug927901.html
@@ -0,0 +1,18 @@
+<html>
+  <head>
+    <title></title>
+    <script>
+      var ret = "pass";
+      try {
+        window.foo = window.crypto.getRandomValues;
+      } catch(ex) {
+        ret = "" + ex;
+      }
+      parent.postMessage(ret, "*");
+    </script>
+    <style>
+    </style>
+  </head>
+  <body onload="document.body.textContent = 'Crypto test file on ' + location">
+  </body>
+</html>
--- a/dom/tests/mochitest/bugs/mochitest.ini
+++ b/dom/tests/mochitest/bugs/mochitest.ini
@@ -27,16 +27,17 @@ support-files =
   iframe_bug430276-2.html
   iframe_bug430276.html
   iframe_bug440572.html
   iframe_bug49312.html
   iframe_domparser_after_blank.html
   utils_bug260264.js
   utils_bug743615.js
   worker_bug743615.js
+  file_bug927901.html
 
 [test_DOMWindowCreated_chromeonly.html]
 [test_bug132255.html]
 [test_bug159849.html]
 [test_bug260264.html]
 [test_bug260264_nested.html]
 [test_bug265203.html]
 [test_bug291377.html]
@@ -123,16 +124,17 @@ support-files =
 [test_bug809290.html]
 [test_bug817476.html]
 [test_bug823173.html]
 [test_bug848088.html]
 [test_bug850517.html]
 [test_bug857555.html]
 [test_bug862540.html]
 [test_bug876098.html]
+[test_bug927901.html]
 [test_devicemotion_multiple_listeners.html]
 [test_domparser_after_blank.html]
 [test_onerror_message.html]
 [test_protochains.html]
 [test_resize_move_windows.html]
 [test_sizetocontent_clamp.html]
 [test_toJSON.html]
 [test_window_bar.html]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/bugs/test_bug927901.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=927901
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 927901</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+  /** Test for Bug 927901 **/
+  SimpleTest.waitForExplicitFinish();
+
+  var counter = 0;
+  window.onmessage = function(e) {
+    ++counter;
+    is(e.data, "pass", "Accessing window.crypto.getRandomValues in the iframe should have succeeded!");
+    if (counter == 1) {
+      document.getElementById("testiframe").src =
+        "http://mochi.test:8888/tests/dom/tests/mochitest/bugs/file_bug927901.html "
+    } else if (counter == 2) {
+      SimpleTest.finish();
+    }
+  }
+
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=927901">Mozilla Bug 927901</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<iframe id="testiframe" src="http://test1.example.org:8000/tests/dom/tests/mochitest/bugs/file_bug927901.html"></iframe>
+</body>
+</html>
--- a/image/src/imgLoader.cpp
+++ b/image/src/imgLoader.cpp
@@ -24,16 +24,17 @@
 #include "nsICachingChannel.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIProgressEventSink.h"
 #include "nsIChannelEventSink.h"
 #include "nsIAsyncVerifyRedirectCallback.h"
 #include "nsIFileURL.h"
 #include "nsCRT.h"
 #include "nsIDocument.h"
+#include "nsINetworkSeer.h"
 
 #include "nsIApplicationCache.h"
 #include "nsIApplicationCacheContainer.h"
 
 #include "nsIMemoryReporter.h"
 #include "Image.h"
 #include "DiscardTracker.h"
 
@@ -1233,16 +1234,19 @@ bool imgLoader::ValidateRequestWithNewCh
     // In the mean time, we must defer notifications because we are added to
     // the imgRequest's proxy list, and we can get extra notifications
     // resulting from methods such as RequestDecode(). See bug 579122.
     proxy->SetNotificationsDeferred(true);
 
     // Add the proxy without notifying
     hvc->AddProxy(proxy);
 
+    mozilla::net::SeerLearn(aURI, aInitialDocumentURI,
+        nsINetworkSeer::LEARN_LOAD_SUBRESOURCE, aLoadGroup);
+
     rv = newChannel->AsyncOpen(listener, nullptr);
     if (NS_SUCCEEDED(rv))
       NS_ADDREF(*aProxyRequest = req.get());
 
     return NS_SUCCEEDED(rv);
   }
 }
 
@@ -1730,16 +1734,19 @@ nsresult imgLoader::LoadImage(nsIURI *aU
       }
 
       listener = corsproxy;
     }
 
     PR_LOG(GetImgLog(), PR_LOG_DEBUG,
            ("[this=%p] imgLoader::LoadImage -- Calling channel->AsyncOpen()\n", this));
 
+    mozilla::net::SeerLearn(aURI, aInitialDocumentURI,
+        nsINetworkSeer::LEARN_LOAD_SUBRESOURCE, aLoadGroup);
+
     nsresult openRes = newChannel->AsyncOpen(listener, nullptr);
 
     if (NS_FAILED(openRes)) {
       PR_LOG(GetImgLog(), PR_LOG_DEBUG,
              ("[this=%p] imgLoader::LoadImage -- AsyncOpen() failed: 0x%x\n",
               this, openRes));
       request->CancelAndAbort(openRes);
       return openRes;
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -2126,17 +2126,17 @@ jit::SetEnterJitData(JSContext *cx, Ente
                 data.calleeToken = CalleeToToken(iter.callee());
         }
     }
 
     return true;
 }
 
 IonExecStatus
-jit::Cannon(JSContext *cx, RunState &state)
+jit::IonCannon(JSContext *cx, RunState &state)
 {
     IonScript *ion = state.script()->ionScript();
 
     EnterJitData data(cx);
     data.jitcode = ion->method()->raw();
 
     AutoValueVector vals(cx);
     if (!SetEnterJitData(cx, data, state, vals))
--- a/js/src/jit/Ion.h
+++ b/js/src/jit/Ion.h
@@ -333,17 +333,17 @@ IsErrorStatus(IonExecStatus status)
 {
     return status == IonExec_Error || status == IonExec_Aborted;
 }
 
 struct EnterJitData;
 
 bool SetEnterJitData(JSContext *cx, EnterJitData &data, RunState &state, AutoValueVector &vals);
 
-IonExecStatus Cannon(JSContext *cx, RunState &state);
+IonExecStatus IonCannon(JSContext *cx, RunState &state);
 
 // Used to enter Ion from C++ natives like Array.map. Called from FastInvokeGuard.
 IonExecStatus FastInvoke(JSContext *cx, HandleFunction fun, CallArgs &args);
 
 // Walk the stack and invalidate active Ion frames for the invalid scripts.
 void Invalidate(types::TypeCompartment &types, FreeOp *fop,
                 const Vector<types::RecompileInfo> &invalid, bool resetUses = true);
 void Invalidate(JSContext *cx, const Vector<types::RecompileInfo> &invalid, bool resetUses = true);
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -275,17 +275,17 @@ IonBuilder::canInlineTarget(JSFunction *
 
     if (target->getParent() != &script()->global()) {
         IonSpew(IonSpew_Inlining, "Cannot inline due to scope mismatch");
         return false;
     }
 
     // Allow constructing lazy scripts when performing the definite properties
     // analysis, as baseline has not been used to warm the caller up yet.
-    if (target->isInterpretedLazy() && info().executionMode() == DefinitePropertiesAnalysis) {
+    if (target->isInterpreted() && info().executionMode() == DefinitePropertiesAnalysis) {
         if (!target->getOrCreateScript(context()))
             return false;
 
         RootedScript script(context(), target->nonLazyScript());
         if (!script->hasBaselineScript()) {
             MethodStatus status = BaselineCompile(context(), script);
             if (status != Method_Compiled)
                 return false;
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4542,16 +4542,21 @@ JS::CompileOffThread(JSContext *cx, Hand
 #endif
 }
 
 JS_PUBLIC_API(JSScript *)
 JS::FinishOffThreadScript(JSContext *maybecx, JSRuntime *rt, void *token)
 {
 #ifdef JS_WORKER_THREADS
     JS_ASSERT(CurrentThreadCanAccessRuntime(rt));
+
+    Maybe<AutoLastFrameCheck> lfc;
+    if (maybecx)
+        lfc.construct(maybecx);
+
     return rt->workerThreadState->finishParseTask(maybecx, rt, token);
 #else
     MOZ_ASSUME_UNREACHABLE("Off thread compilation is not available.");
 #endif
 }
 
 JS_PUBLIC_API(JSScript *)
 JS_CompileUCScriptForPrincipals(JSContext *cx, JSObject *objArg, JSPrincipals *principals,
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -391,17 +391,17 @@ js::RunScript(JSContext *cx, RunState &s
     SPSEntryMarker marker(cx->runtime());
 
 #ifdef JS_ION
     if (jit::IsIonEnabled(cx)) {
         jit::MethodStatus status = jit::CanEnter(cx, state);
         if (status == jit::Method_Error)
             return false;
         if (status == jit::Method_Compiled) {
-            jit::IonExecStatus status = jit::Cannon(cx, state);
+            jit::IonExecStatus status = jit::IonCannon(cx, state);
             return !IsErrorStatus(status);
         }
     }
 
     if (jit::IsBaselineEnabled(cx)) {
         jit::MethodStatus status = jit::CanEnterBaselineMethod(cx, state);
         if (status == jit::Method_Error)
             return false;
@@ -2484,17 +2484,17 @@ BEGIN_CASE(JSOP_FUNCALL)
         if (newType)
             state.setUseNewType();
 
         if (!newType && jit::IsIonEnabled(cx)) {
             jit::MethodStatus status = jit::CanEnter(cx, state);
             if (status == jit::Method_Error)
                 goto error;
             if (status == jit::Method_Compiled) {
-                jit::IonExecStatus exec = jit::Cannon(cx, state);
+                jit::IonExecStatus exec = jit::IonCannon(cx, state);
                 CHECK_BRANCH();
                 regs.sp = args.spAfterCall();
                 interpReturnOK = !IsErrorStatus(exec);
                 goto jit_return;
             }
         }
 
         if (jit::IsBaselineEnabled(cx)) {
--- a/js/xpconnect/tests/mochitest/mochitest.ini
+++ b/js/xpconnect/tests/mochitest/mochitest.ini
@@ -32,16 +32,18 @@ support-files =
   file_mozMatchesSelector.html
   file_nodelists.html
   file_wrappers-2.html
   inner.html
   test1_bug629331.html
   test2_bug629331.html
 
 [test_asmjs.html]
+# bug 929498
+skip-if = os == 'android'
 [test_bug384632.html]
 [test_bug390488.html]
 [test_bug393269.html]
 [test_bug396851.html]
 [test_bug428021.html]
 [test_bug446584.html]
 [test_bug462428.html]
 [test_bug478438.html]
--- a/layout/style/Loader.cpp
+++ b/layout/style/Loader.cpp
@@ -42,16 +42,17 @@
 #include "nsIStyleSheetLinkingElement.h"
 #include "nsICSSLoaderObserver.h"
 #include "nsCSSParser.h"
 #include "mozilla/css/ImportRule.h"
 #include "nsThreadUtils.h"
 #include "nsGkAtoms.h"
 #include "nsIThreadInternal.h"
 #include "nsCrossSiteListenerProxy.h"
+#include "nsINetworkSeer.h"
 
 #ifdef MOZ_XUL
 #include "nsXULPrototypeCache.h"
 #endif
 
 #include "nsIMediaList.h"
 #include "nsIDOMStyleSheet.h"
 #include "nsError.h"
@@ -1428,16 +1429,22 @@ Loader::LoadSheet(SheetLoadData* aLoadDa
     nsCOMPtr<nsIUnicharStreamLoader> streamLoader;
     rv = NS_NewUnicharStreamLoader(getter_AddRefs(streamLoader), aLoadData);
     if (NS_FAILED(rv)) {
       LOG_ERROR(("  Failed to create stream loader for sync load"));
       SheetComplete(aLoadData, rv);
       return rv;
     }
 
+    if (mDocument) {
+      mozilla::net::SeerLearn(aLoadData->mURI, mDocument->GetDocumentURI(),
+                              nsINetworkSeer::LEARN_LOAD_SUBRESOURCE,
+                              mDocument);
+    }
+
     // Just load it
     nsCOMPtr<nsIInputStream> stream;
     nsCOMPtr<nsIChannel> channel;
     rv = NS_OpenURI(getter_AddRefs(stream), aLoadData->mURI, nullptr,
                     nullptr, nullptr, nsIRequest::LOAD_NORMAL,
                     getter_AddRefs(channel));
     if (NS_FAILED(rv)) {
       LOG_ERROR(("  Failed to open URI synchronously"));
@@ -1601,16 +1608,21 @@ Loader::LoadSheet(SheetLoadData* aLoadDa
       SheetComplete(aLoadData, rv);
       return rv;
     }
     channelListener = corsListener;
   } else {
     channelListener = streamLoader;
   }
 
+  if (mDocument) {
+    mozilla::net::SeerLearn(aLoadData->mURI, mDocument->GetDocumentURI(),
+                            nsINetworkSeer::LEARN_LOAD_SUBRESOURCE, mDocument);
+  }
+
   rv = channel->AsyncOpen(channelListener, nullptr);
 
 #ifdef DEBUG
   mSyncCallback = false;
 #endif
 
   if (NS_FAILED(rv)) {
     LOG_ERROR(("  Failed to create stream loader"));
--- a/layout/style/nsFontFaceLoader.cpp
+++ b/layout/style/nsFontFaceLoader.cpp
@@ -24,16 +24,17 @@
 #include "nsIScriptSecurityManager.h"
 
 #include "nsIContentPolicy.h"
 #include "nsContentPolicyUtils.h"
 #include "nsCrossSiteListenerProxy.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsIDocShell.h"
 #include "nsIWebNavigation.h"
+#include "nsINetworkSeer.h"
 
 #include "nsIConsoleService.h"
 
 #include "nsStyleSet.h"
 #include "nsPrintfCString.h"
 #include "mozilla/gfx/2D.h"
 
 using namespace mozilla;
@@ -370,16 +371,20 @@ nsUserFontSet::StartLoad(gfxMixedFontFam
 #endif
 
   nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
   if (httpChannel)
     httpChannel->SetReferrer(aFontFaceSrc->mReferrer);
   rv = NS_NewStreamLoader(getter_AddRefs(streamLoader), fontLoader);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  nsIDocument *document = ps->GetDocument();
+  mozilla::net::SeerLearn(aFontFaceSrc->mURI, document->GetDocumentURI(),
+                          nsINetworkSeer::LEARN_LOAD_SUBRESOURCE, loadGroup);
+
   bool inherits = false;
   rv = NS_URIChainHasFlags(aFontFaceSrc->mURI,
                            nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT,
                            &inherits);
   if (NS_SUCCEEDED(rv) && inherits) {
     // allow data, javascript, etc URI's
     rv = channel->AsyncOpen(streamLoader, nullptr);
   } else {
--- a/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp
+++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.cpp
@@ -1,22 +1,31 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "CSFLog.h"
 #include "nspr.h"
 
+#ifdef HAVE_NETINET_IN_H
+#include <netinet/in.h>
+#elif defined XP_WIN
+#include <winsock2.h>
+#endif
+
 #include "AudioConduit.h"
 #include "nsCOMPtr.h"
 #include "mozilla/Services.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h"
 #include "nsThreadUtils.h"
+#ifdef MOZILLA_INTERNAL_API
+#include "Latency.h"
+#endif
 
 #include "webrtc/voice_engine/include/voe_errors.h"
 
 #ifdef MOZ_WIDGET_ANDROID
 #include "AndroidJNIWrapper.h"
 #endif
 
 namespace mozilla {
@@ -199,16 +208,22 @@ MediaConduitErrorCode WebrtcAudioConduit
   }
 
   if(!(mPtrVoEXmedia = VoEExternalMedia::GetInterface(mVoiceEngine)))
   {
     CSFLogError(logTag, "%s Unable to initialize VoEExternalMedia", __FUNCTION__);
     return kMediaConduitSessionNotInited;
   }
 
+  if(!(mPtrVoEVideoSync = VoEVideoSync::GetInterface(mVoiceEngine)))
+  {
+    CSFLogError(logTag, "%s Unable to initialize VoEVideoSync", __FUNCTION__);
+    return kMediaConduitSessionNotInited;
+  }
+
   if (other) {
     mChannel = other->mChannel;
   } else {
     // init the engine with our audio device layer
     if(mPtrVoEBase->Init() == -1)
     {
       CSFLogError(logTag, "%s VoiceEngine Base Not Initialized", __FUNCTION__);
       return kMediaConduitSessionNotInited;
@@ -505,16 +520,23 @@ WebrtcAudioConduit::SendAudioFrame(const
 
   // if transmission is not started .. conduit cannot insert frames
   if(!mEngineTransmitting)
   {
     CSFLogError(logTag, "%s Engine not transmitting ", __FUNCTION__);
     return kMediaConduitSessionNotInited;
   }
 
+#ifdef MOZILLA_INTERNAL_API
+    if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG)) {
+      struct Processing insert = { TimeStamp::Now(), 0 };
+      mProcessing.AppendElement(insert);
+    }
+#endif
+
   capture_delay = mCaptureDelay;
   //Insert the samples
   if(mPtrVoEXmedia->ExternalRecordingInsertData(audio_data,
                                                 lengthSamples,
                                                 samplingFreqHz,
                                                 capture_delay) == -1)
   {
     int error = mPtrVoEBase->LastError();
@@ -583,29 +605,62 @@ WebrtcAudioConduit::GetAudioFrame(int16_
     CSFLogError(logTag,  "%s Getting audio data Failed %d", __FUNCTION__, error);
     if(error == VE_RUNTIME_PLAY_ERROR)
     {
       return kMediaConduitPlayoutError;
     }
     return kMediaConduitUnknownError;
   }
 
+#ifdef MOZILLA_INTERNAL_API
+  if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG)) {
+    if (mProcessing.Length() > 0) {
+      unsigned int now;
+      mPtrVoEVideoSync->GetPlayoutTimestamp(mChannel, now);
+      if (static_cast<uint32_t>(now) != mLastTimestamp) {
+        mLastTimestamp = static_cast<uint32_t>(now);
+        // Find the block that includes this timestamp in the network input
+        while (mProcessing.Length() > 0) {
+          // FIX! assumes 20ms @ 48000Hz
+          // FIX handle wrap-around
+          if (mProcessing[0].mRTPTimeStamp + 20*(48000/1000) >= now) {
+            TimeDuration t = TimeStamp::Now() - mProcessing[0].mTimeStamp;
+            // Wrap-around?
+            int64_t delta = t.ToMilliseconds() + (now - mProcessing[0].mRTPTimeStamp)/(48000/1000);
+            LogTime(AsyncLatencyLogger::AudioRecvRTP, ((uint64_t) this), delta);
+            break;
+          }
+          mProcessing.RemoveElementAt(0);
+        }
+      }
+    }
+  }
+#endif
   CSFLogDebug(logTag,"%s GetAudioFrame:Got samples: length %d ",__FUNCTION__,
                                                                lengthSamples);
   return kMediaConduitNoError;
 }
 
 // Transport Layer Callbacks
 MediaConduitErrorCode
 WebrtcAudioConduit::ReceivedRTPPacket(const void *data, int len)
 {
   CSFLogDebug(logTag,  "%s : channel %d", __FUNCTION__, mChannel);
 
   if(mEngineReceiving)
   {
+#ifdef MOZILLA_INTERNAL_API
+    if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG)) {
+      // timestamp is at 32 bits in ([1])
+      struct Processing insert = { TimeStamp::Now(),
+                                   ntohl(static_cast<const uint32_t *>(data)[1]) };
+      mProcessing.AppendElement(insert);
+    }
+#endif
+
     if(mPtrVoENetwork->ReceivedRTPPacket(mChannel,data,len) == -1)
     {
       int error = mPtrVoEBase->LastError();
       CSFLogError(logTag, "%s RTP Processing Error %d", __FUNCTION__, error);
       if(error == VE_RTP_RTCP_MODULE_ERROR)
       {
         return kMediaConduitRTPRTCPModuleError;
       }
@@ -654,16 +709,28 @@ int WebrtcAudioConduit::SendPacket(int c
     if (mOtherDirection)
     {
       return mOtherDirection->SendPacket(channel, data, len);
     }
     CSFLogDebug(logTag,  "%s : Asked to send RTP without an RTP sender on channel %d",
                 __FUNCTION__, channel);
     return -1;
   } else {
+#ifdef MOZILLA_INTERNAL_API
+    if (PR_LOG_TEST(GetLatencyLog(), PR_LOG_DEBUG)) {
+      if (mProcessing.Length() > 0) {
+        TimeStamp started = mProcessing[0].mTimeStamp;
+        mProcessing.RemoveElementAt(0);
+        mProcessing.RemoveElementAt(0); // 20ms packetization!  Could automate this by watching sizes
+        TimeDuration t = TimeStamp::Now() - started;
+        int64_t delta = t.ToMilliseconds();
+        LogTime(AsyncLatencyLogger::AudioSendRTP, ((uint64_t) this), delta);
+      }
+    }
+#endif
     if(mTransport && (mTransport->SendRtpPacket(data, len) == NS_OK))
     {
       CSFLogDebug(logTag, "%s Sent RTP Packet ", __FUNCTION__);
       return len;
     } else {
       CSFLogError(logTag, "%s RTP Packet Send Failed ", __FUNCTION__);
       return -1;
     }
--- a/media/webrtc/signaling/src/media-conduit/AudioConduit.h
+++ b/media/webrtc/signaling/src/media-conduit/AudioConduit.h
@@ -2,35 +2,39 @@
  * 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 AUDIO_SESSION_H_
 #define AUDIO_SESSION_H_
 
 #include "mozilla/Attributes.h"
+#include "mozilla/TimeStamp.h"
+#include "nsTArray.h"
 
 #include "MediaConduitInterface.h"
 
 // Audio Engine Includes
 #include "webrtc/common_types.h"
 #include "webrtc/voice_engine/include/voe_base.h"
 #include "webrtc/voice_engine/include/voe_volume_control.h"
 #include "webrtc/voice_engine/include/voe_codec.h"
 #include "webrtc/voice_engine/include/voe_file.h"
 #include "webrtc/voice_engine/include/voe_network.h"
 #include "webrtc/voice_engine/include/voe_external_media.h"
 #include "webrtc/voice_engine/include/voe_audio_processing.h"
+#include "webrtc/voice_engine/include/voe_video_sync.h"
 
 //Some WebRTC types for short notations
  using webrtc::VoEBase;
  using webrtc::VoENetwork;
  using webrtc::VoECodec;
  using webrtc::VoEExternalMedia;
  using webrtc::VoEAudioProcessing;
+ using webrtc::VoEVideoSync;
 
 /** This file hosts several structures identifying different aspects
  * of a RTP Session.
  */
 
 namespace mozilla {
 
 /**
@@ -142,16 +146,17 @@ public:
 
   WebrtcAudioConduit():
                       mOtherDirection(nullptr),
                       mShutDown(false),
                       mVoiceEngine(nullptr),
                       mTransport(nullptr),
                       mEngineTransmitting(false),
                       mEngineReceiving(false),
+                      mLastTimestamp(0),
                       mChannel(-1),
                       mCurSendCodecConfig(nullptr),
                       mCaptureDelay(150),
                       mEchoOn(true),
                       mEchoCancel(webrtc::kEcAec)
   {
   }
 
@@ -204,22 +209,33 @@ private:
   // conduit to die
   webrtc::VoiceEngine* mVoiceEngine;
   mozilla::RefPtr<TransportInterface> mTransport;
   webrtc::VoENetwork*  mPtrVoENetwork;
   webrtc::VoEBase*     mPtrVoEBase;
   webrtc::VoECodec*    mPtrVoECodec;
   webrtc::VoEExternalMedia* mPtrVoEXmedia;
   webrtc::VoEAudioProcessing* mPtrVoEProcessing;
+  webrtc::VoEVideoSync* mPtrVoEVideoSync;
 
   //engine states of our interets
   bool mEngineTransmitting; // If true => VoiceEngine Send-subsystem is up
   bool mEngineReceiving;    // If true => VoiceEngine Receive-subsystem is up
                             // and playout is enabled
 
+  // Keep track of each inserted RTP block and the time it was inserted
+  // so we can estimate the clock time for a specific TimeStamp coming out
+  // (for when we send data to MediaStreamTracks).  Blocks are aged out as needed.
+  struct Processing {
+    TimeStamp mTimeStamp;
+    uint32_t mRTPTimeStamp; // RTP timestamps received
+  };
+  nsAutoTArray<Processing,8> mProcessing;
+  uint32_t mLastTimestamp;
+
   int mChannel;
   RecvCodecList    mRecvCodecList;
   AudioCodecConfig* mCurSendCodecConfig;
 
   // Current "capture" delay (really output plus input delay)
   int32_t mCaptureDelay;
 
   bool mEchoOn;
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.cpp
@@ -19,16 +19,20 @@
 #include "PeerConnectionCtx.h"
 #include "runnable_utils.h"
 #include "cpr_socket.h"
 #include "debug-psipcc-types.h"
 #include "prcvar.h"
 
 #include "mozilla/Telemetry.h"
 
+#ifdef MOZILLA_INTERNAL_API
+#include "mozilla/dom/RTCPeerConnectionBinding.h"
+#endif
+
 #include "nsIObserverService.h"
 #include "nsIObserver.h"
 #include "mozilla/Services.h"
 #include "StaticPtr.h"
 extern "C" {
 #include "../sipcc/core/common/thread_monitor.h"
 }
 
@@ -36,16 +40,67 @@ static const char* logTag = "PeerConnect
 
 extern "C" {
 extern PRCondVar *ccAppReadyToStartCond;
 extern PRLock *ccAppReadyToStartLock;
 extern char ccAppReadyToStart;
 }
 
 namespace mozilla {
+
+using namespace dom;
+
+// Convert constraints to C structures
+
+#ifdef MOZILLA_INTERNAL_API
+static void
+Apply(const Optional<bool> &aSrc, cc_boolean_constraint_t *aDst,
+      bool mandatory = false) {
+  if (aSrc.WasPassed() && (mandatory || !aDst->was_passed)) {
+    aDst->was_passed = true;
+    aDst->value = aSrc.Value();
+    aDst->mandatory = mandatory;
+  }
+}
+#endif
+
+MediaConstraintsExternal::MediaConstraintsExternal() {
+  memset(&mConstraints, 0, sizeof(mConstraints));
+}
+
+MediaConstraintsExternal::MediaConstraintsExternal(
+    const MediaConstraintsInternal &aSrc) {
+  cc_media_constraints_t* c = &mConstraints;
+  memset(c, 0, sizeof(*c));
+#ifdef MOZILLA_INTERNAL_API
+  Apply(aSrc.mMandatory.mOfferToReceiveAudio, &c->offer_to_receive_audio, true);
+  Apply(aSrc.mMandatory.mOfferToReceiveVideo, &c->offer_to_receive_video, true);
+  Apply(aSrc.mMandatory.mMozDontOfferDataChannel, &c->moz_dont_offer_datachannel,
+        true);
+  if (aSrc.mOptional.WasPassed()) {
+    const Sequence<MediaConstraintSet> &array = aSrc.mOptional.Value();
+    for (uint32_t i = 0; i < array.Length(); i++) {
+      Apply(array[i].mOfferToReceiveAudio, &c->offer_to_receive_audio);
+      Apply(array[i].mOfferToReceiveVideo, &c->offer_to_receive_video);
+      Apply(array[i].mMozDontOfferDataChannel, &c->moz_dont_offer_datachannel);
+    }
+  }
+#endif
+}
+
+cc_media_constraints_t*
+MediaConstraintsExternal::build() const {
+  cc_media_constraints_t* cc  = (cc_media_constraints_t*)
+    cpr_malloc(sizeof(cc_media_constraints_t));
+  if (cc) {
+    *cc = mConstraints;
+  }
+  return cc;
+}
+
 class PeerConnectionCtxShutdown : public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
 
   PeerConnectionCtxShutdown() {}
 
   void Init()
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionCtx.h
@@ -12,37 +12,40 @@
 #include "CC_Device.h"
 #include "CC_DeviceInfo.h"
 #include "CC_Call.h"
 #include "CC_CallInfo.h"
 #include "CC_Line.h"
 #include "CC_LineInfo.h"
 #include "CC_Observer.h"
 #include "CC_FeatureInfo.h"
+#include "cpr_stdlib.h"
 
 #include "StaticPtr.h"
 #include "PeerConnectionImpl.h"
 
 namespace mozilla {
 class PeerConnectionCtxShutdown;
+
+// Unit-test helper, because cc_media_constraints_t is hard to forward-declare
+
+class MediaConstraintsExternal {
+public:
+  MediaConstraintsExternal();
+  MediaConstraintsExternal(const dom::MediaConstraintsInternal &aOther);
+  cc_media_constraints_t* build() const;
+protected:
+  cc_media_constraints_t mConstraints;
+};
 }
 
 namespace sipcc {
 
 using namespace mozilla;
 
-// Unit-test helper, because cc_media_constraints_t is hard to forward-declare
-
-class MediaConstraintsExternal {
-public:
-  MediaConstraintsExternal(cc_media_constraints_t *aConstraints)
-  : mConstraints(aConstraints) {}
-  cc_media_constraints_t *mConstraints;
-};
-
 class OnCallEventArgs {
 public:
   OnCallEventArgs(ccapi_call_event_e aCallEvent, CSF::CC_CallInfoPtr aInfo)
   : mCallEvent(aCallEvent), mInfo(aInfo) {}
 
   ccapi_call_event_e mCallEvent;
   CSF::CC_CallInfoPtr mInfo;
 };
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -94,53 +94,16 @@ PRLogModuleInfo *signalingLogInfo() {
     logModuleInfo = PR_NewLogModule("signaling");
   }
   return logModuleInfo;
 }
 
 
 namespace sipcc {
 
-// Convert constraints to C structures
-
-#ifdef MOZILLA_INTERNAL_API
-static void
-Apply(const Optional<bool> &aSrc, cc_boolean_constraint_t *aDst,
-      bool mandatory = false) {
-  if (aSrc.WasPassed() && (mandatory || !aDst->was_passed)) {
-    aDst->was_passed = true;
-    aDst->value = aSrc.Value();
-    aDst->mandatory = mandatory;
-  }
-}
-#endif
-
-static cc_media_constraints_t*
-ConvertConstraints(const MediaConstraintsInternal& aSrc) {
-  cc_media_constraints_t* c = (cc_media_constraints_t*)
-      cpr_malloc(sizeof(cc_media_constraints_t));
-  NS_ENSURE_TRUE(c,c);
-  memset(c, 0, sizeof(cc_media_constraints_t));
-#ifdef MOZILLA_INTERNAL_API
-  Apply(aSrc.mMandatory.mOfferToReceiveAudio, &c->offer_to_receive_audio, true);
-  Apply(aSrc.mMandatory.mOfferToReceiveVideo, &c->offer_to_receive_video, true);
-  Apply(aSrc.mMandatory.mMozDontOfferDataChannel, &c->moz_dont_offer_datachannel,
-        true);
-  if (aSrc.mOptional.WasPassed()) {
-    const Sequence<MediaConstraintSet> &array = aSrc.mOptional.Value();
-    for (uint32_t i = 0; i < array.Length(); i++) {
-      Apply(array[i].mOfferToReceiveAudio, &c->offer_to_receive_audio);
-      Apply(array[i].mOfferToReceiveVideo, &c->offer_to_receive_video);
-      Apply(array[i].mMozDontOfferDataChannel, &c->moz_dont_offer_datachannel);
-    }
-  }
-#endif
-  return c;
-}
-
 // Getting exceptions back down from PCObserver is generally not harmful.
 namespace {
 class JSErrorResult : public ErrorResult
 {
 public:
   ~JSErrorResult()
   {
 #ifdef MOZILLA_INTERNAL_API
@@ -1049,51 +1012,53 @@ PeerConnectionImpl::NotifyDataChannel(al
                                pco),
                 NS_DISPATCH_NORMAL);
 #endif
 }
 
 NS_IMETHODIMP
 PeerConnectionImpl::CreateOffer(const MediaConstraintsInternal& aConstraints)
 {
-  return CreateOffer(MediaConstraintsExternal(ConvertConstraints(aConstraints)));
+  return CreateOffer(MediaConstraintsExternal (aConstraints));
 }
 
 // Used by unit tests and the IDL CreateOffer.
 NS_IMETHODIMP
 PeerConnectionImpl::CreateOffer(const MediaConstraintsExternal& aConstraints)
 {
   PC_AUTO_ENTER_API_CALL(true);
 
   Timecard *tc = mTimeCard;
   mTimeCard = nullptr;
   STAMP_TIMECARD(tc, "Create Offer");
 
-  NS_ENSURE_TRUE(aConstraints.mConstraints, NS_ERROR_UNEXPECTED);
-  mInternal->mCall->createOffer(aConstraints.mConstraints, tc);
+  cc_media_constraints_t* cc_constraints = aConstraints.build();
+  NS_ENSURE_TRUE(cc_constraints, NS_ERROR_UNEXPECTED);
+  mInternal->mCall->createOffer(cc_constraints, tc);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PeerConnectionImpl::CreateAnswer(const MediaConstraintsInternal& aConstraints)
 {
-  return CreateAnswer(MediaConstraintsExternal(ConvertConstraints(aConstraints)));
+  return CreateAnswer(MediaConstraintsExternal (aConstraints));
 }
 
 NS_IMETHODIMP
 PeerConnectionImpl::CreateAnswer(const MediaConstraintsExternal& aConstraints)
 {
   PC_AUTO_ENTER_API_CALL(true);
 
   Timecard *tc = mTimeCard;
   mTimeCard = nullptr;
   STAMP_TIMECARD(tc, "Create Answer");
 
-  NS_ENSURE_TRUE(aConstraints.mConstraints, NS_ERROR_UNEXPECTED);
-  mInternal->mCall->createAnswer(aConstraints.mConstraints, tc);
+  cc_media_constraints_t* cc_constraints = aConstraints.build();
+  NS_ENSURE_TRUE(cc_constraints, NS_ERROR_UNEXPECTED);
+  mInternal->mCall->createAnswer(cc_constraints, tc);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PeerConnectionImpl::SetLocalDescription(int32_t aAction, const char* aSDP)
 {
   PC_AUTO_ENTER_API_CALL(true);
 
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -69,16 +69,17 @@ class RTCStatsReportInternal;
 #ifdef USE_FAKE_PCOBSERVER
 typedef test::AFakePCObserver PeerConnectionObserver;
 typedef const char *PCObserverString;
 #else
 class PeerConnectionObserver;
 typedef NS_ConvertUTF8toUTF16 PCObserverString;
 #endif
 }
+class MediaConstraintsExternal;
 }
 
 #if defined(__cplusplus) && __cplusplus >= 201103L
 typedef struct Timecard Timecard;
 #else
 #include "timecard.h"
 #endif
 
@@ -93,28 +94,28 @@ void func (__VA_ARGS__, rv)
 NS_IMETHODIMP func(__VA_ARGS__, resulttype **result);                  \
 already_AddRefed<resulttype> func (__VA_ARGS__, rv)
 
 namespace sipcc {
 
 using mozilla::dom::PeerConnectionObserver;
 using mozilla::dom::RTCConfiguration;
 using mozilla::dom::MediaConstraintsInternal;
+using mozilla::MediaConstraintsExternal;
 using mozilla::DOMMediaStream;
 using mozilla::NrIceCtx;
 using mozilla::NrIceMediaStream;
 using mozilla::DtlsIdentity;
 using mozilla::ErrorResult;
 using mozilla::NrIceStunServer;
 using mozilla::NrIceTurnServer;
 
 class PeerConnectionWrapper;
 class PeerConnectionMedia;
 class RemoteSourceStreamInfo;
-class MediaConstraintsExternal;
 class OnCallEventArgs;
 
 class IceConfiguration
 {
 public:
   bool addStunServer(const std::string& addr, uint16_t port)
   {
     NrIceStunServer* server(NrIceStunServer::Create(addr, port));
--- a/media/webrtc/signaling/test/FakeMediaStreamsImpl.h
+++ b/media/webrtc/signaling/test/FakeMediaStreamsImpl.h
@@ -5,16 +5,19 @@
 #ifndef FAKE_MEDIA_STREAMIMPL_H_
 #define FAKE_MEDIA_STREAMIMPL_H_
 
 #include "FakeMediaStreams.h"
 
 #include "nspr.h"
 #include "nsError.h"
 
+void LogTime(AsyncLatencyLogger::LatencyLogIndex index, uint64_t b, int64_t c) {}
+void LogLatency(AsyncLatencyLogger::LatencyLogIndex index, uint64_t b, int64_t c) {}
+
 static const int AUDIO_BUFFER_SIZE = 1600;
 static const int NUM_CHANNELS      = 2;
 
 NS_IMPL_ISUPPORTS1(Fake_DOMMediaStream, nsIDOMMediaStream)
 
 // Fake_SourceMediaStream
 nsresult Fake_SourceMediaStream::Start() {
   mTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
--- a/media/webrtc/signaling/test/signaling_unittests.cpp
+++ b/media/webrtc/signaling/test/signaling_unittests.cpp
@@ -73,40 +73,32 @@ uint16_t kBogusSrflxPort(1001);
 
 namespace sipcc {
 
 // We can't use mozilla/dom/MediaConstraintsBinding.h here because it uses
 // nsString, so we pass constraints in using MediaConstraintsExternal instead
 
 class MediaConstraints : public MediaConstraintsExternal {
 public:
-  MediaConstraints()
-  : MediaConstraintsExternal((cc_media_constraints_t*)
-                             cpr_malloc(sizeof(*mConstraints))) {
-    MOZ_ASSERT(mConstraints);
-    memset(mConstraints, 0, sizeof(*mConstraints));
-  }
-
   void setBooleanConstraint(const char *namePtr, bool value, bool mandatory) {
     cc_boolean_constraint_t &member (getMember(namePtr));
     member.was_passed = true;
     member.value = value;
     member.mandatory = mandatory;
   }
 private:
   cc_boolean_constraint_t &getMember(const char *namePtr) {
-    MOZ_ASSERT(mConstraints);
     if (strcmp(namePtr, "OfferToReceiveAudio") == 0) {
-        return mConstraints->offer_to_receive_audio;
+        return mConstraints.offer_to_receive_audio;
     }
     if (strcmp(namePtr, "OfferToReceiveVideo") == 0) {
-        return mConstraints->offer_to_receive_video;
+        return mConstraints.offer_to_receive_video;
     }
     MOZ_ASSERT(false);
-    return mConstraints->moz_dont_offer_datachannel;
+    return mConstraints.moz_dont_offer_datachannel;
   }
 };
 }
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 namespace test {
--- a/media/webrtc/trunk/webrtc/modules/audio_device/audio_device.gypi
+++ b/media/webrtc/trunk/webrtc/modules/audio_device/audio_device.gypi
@@ -39,16 +39,22 @@
         'audio_device_utility.h',
         'audio_device_impl.cc',
         'audio_device_impl.h',
         'audio_device_config.h',
         'dummy/audio_device_dummy.h',
         'dummy/audio_device_utility_dummy.h',
       ],
       'conditions': [
+        ['build_with_mozilla==1', {
+          'include_dirs': [
+            '$(DIST)/include',
+            '$(DIST)/include/nspr',
+          ],
+        }],
         ['OS=="linux" or include_alsa_audio==1 or include_pulse_audio==1', {
           'include_dirs': [
             'linux',
           ],
         }], # OS=="linux" or include_alsa_audio==1 or include_pulse_audio==1
         ['OS=="ios"', {
           'include_dirs': [
             'ios',
--- a/media/webrtc/trunk/webrtc/modules/audio_device/linux/audio_device_alsa_linux.cc
+++ b/media/webrtc/trunk/webrtc/modules/audio_device/linux/audio_device_alsa_linux.cc
@@ -14,16 +14,23 @@
 #include "audio_device_alsa_linux.h"
 #include "audio_device_config.h"
 
 #include "event_wrapper.h"
 #include "system_wrappers/interface/sleep.h"
 #include "trace.h"
 #include "thread_wrapper.h"
 
+#include "Latency.h"
+
+#define LOG_FIRST_CAPTURE(x) LogTime(AsyncLatencyLogger::AudioCaptureBase, \
+                                     reinterpret_cast<uint64_t>(x), 0)
+#define LOG_CAPTURE_FRAMES(x, frames) LogLatency(AsyncLatencyLogger::AudioCapture, \
+                                                 reinterpret_cast<uint64_t>(x), frames)
+
 webrtc_adm_linux_alsa::AlsaSymbolTable AlsaSymbolTable;
 
 // Accesses ALSA functions through our late-binding symbol table instead of
 // directly. This way we don't have to link to libasound, which means our binary
 // will work on systems that don't have it.
 #define LATE(sym) \
   LATESYM_GET(webrtc_adm_linux_alsa::AlsaSymbolTable, &AlsaSymbolTable, sym)
 
@@ -91,16 +98,17 @@ AudioDeviceLinuxALSA::AudioDeviceLinuxAL
     _playChannels(ALSA_PLAYOUT_CH),
     _recordingBuffer(NULL),
     _playoutBuffer(NULL),
     _recordingFramesLeft(0),
     _playoutFramesLeft(0),
     _playBufType(AudioDeviceModule::kFixedBufferSize),
     _initialized(false),
     _recording(false),
+    _firstRecord(true),
     _playing(false),
     _recIsInitialized(false),
     _playIsInitialized(false),
     _AGC(false),
     _recordingDelay(0),
     _playoutDelay(0),
     _playWarning(0),
     _playError(0),
@@ -1444,16 +1452,17 @@ int32_t AudioDeviceLinuxALSA::StartRecor
     {
         WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id,
                      "   failed to alloc recording buffer");
         _recording = false;
         return -1;
     }
     // RECORDING
     const char* threadName = "webrtc_audio_module_capture_thread";
+    _firstRecord = true;
     _ptrThreadRec = ThreadWrapper::CreateThread(RecThreadFunc,
                                                 this,
                                                 kRealtimePriority,
                                                 threadName);
     if (_ptrThreadRec == NULL)
     {
         WEBRTC_TRACE(kTraceCritical, kTraceAudioDevice, _id,
                      "  failed to create the rec audio thread");
@@ -2262,16 +2271,21 @@ bool AudioDeviceLinuxALSA::RecThreadProc
         memcpy(&_recordingBuffer[_recordingBufferSizeIn10MS - left_size],
                buffer, size);
         _recordingFramesLeft -= frames;
 
         if (!_recordingFramesLeft)
         { // buf is full
             _recordingFramesLeft = _recordingFramesIn10MS;
 
+            if (_firstRecord) {
+              LOG_FIRST_CAPTURE(this);
+              _firstRecord = false;
+            }
+            LOG_CAPTURE_FRAMES(this, _recordingFramesIn10MS);
             // store the recorded buffer (no action will be taken if the
             // #recorded samples is not a full buffer)
             _ptrAudioBuffer->SetRecordedBuffer(_recordingBuffer,
                                                _recordingFramesIn10MS);
 
             uint32_t currentMicLevel = 0;
             uint32_t newMicLevel = 0;
 
--- a/media/webrtc/trunk/webrtc/modules/audio_device/linux/audio_device_alsa_linux.h
+++ b/media/webrtc/trunk/webrtc/modules/audio_device/linux/audio_device_alsa_linux.h
@@ -229,16 +229,17 @@ private:
     uint32_t _recordingFramesLeft;
     uint32_t _playoutFramesLeft;
 
     AudioDeviceModule::BufferType _playBufType;
 
 private:
     bool _initialized;
     bool _recording;
+    bool _firstRecord;
     bool _playing;
     bool _recIsInitialized;
     bool _playIsInitialized;
     bool _AGC;
 
     snd_pcm_sframes_t _recordingDelay;
     snd_pcm_sframes_t _playoutDelay;
 
--- a/media/webrtc/trunk/webrtc/modules/audio_processing/aec/aec_core.c
+++ b/media/webrtc/trunk/webrtc/modules/audio_processing/aec/aec_core.c
@@ -104,17 +104,27 @@ const float WebRtcAec_overDriveCurve[65]
     1.8660f, 1.8750f, 1.8839f, 1.8927f, 1.9014f, 1.9100f,
     1.9186f, 1.9270f, 1.9354f, 1.9437f, 1.9520f, 1.9601f,
     1.9682f, 1.9763f, 1.9843f, 1.9922f, 2.0000f
 };
 
 // Target suppression levels for nlp modes.
 // log{0.001, 0.00001, 0.00000001}
 static const float kTargetSupp[3] = { -6.9f, -11.5f, -18.4f };
-static const float kMinOverDrive[3] = { 1.0f, 2.0f, 5.0f };
+
+// Two sets of parameters, one for the extended filter mode.
+static const float kExtendedMinOverDrive[3] = { 3.0f, 6.0f, 15.0f };
+static const float kNormalMinOverDrive[3] = { 1.0f, 2.0f, 5.0f };
+static const float kExtendedSmoothingCoefficients[2][2] =
+    { { 0.9f, 0.1f }, { 0.92f, 0.08f } };
+static const float kNormalSmoothingCoefficients[2][2] =
+    { { 0.9f, 0.1f }, { 0.93f, 0.07f } };
+
+// Number of partitions forming the NLP's "preferred" bands.
+enum { kPrefBandSize = 24 };
 
 #ifdef WEBRTC_AEC_DEBUG_DUMP
 extern int webrtc_aec_instance_count;
 #endif
 
 // "Private" function prototypes.
 static void ProcessBlock(AecCore* aec);
 
@@ -276,89 +286,92 @@ int WebRtcAec_FreeAec(AecCore* aec)
 
     free(aec);
     return 0;
 }
 
 static void FilterFar(AecCore* aec, float yf[2][PART_LEN1])
 {
   int i;
-  for (i = 0; i < NR_PART; i++) {
+  for (i = 0; i < aec->num_partitions; i++) {
     int j;
     int xPos = (i + aec->xfBufBlockPos) * PART_LEN1;
     int pos = i * PART_LEN1;
     // Check for wrap
-    if (i + aec->xfBufBlockPos >= NR_PART) {
-      xPos -= NR_PART*(PART_LEN1);
+    if (i + aec->xfBufBlockPos >= aec->num_partitions) {
+      xPos -= aec->num_partitions*(PART_LEN1);
     }
 
     for (j = 0; j < PART_LEN1; j++) {
       yf[0][j] += MulRe(aec->xfBuf[0][xPos + j], aec->xfBuf[1][xPos + j],
                         aec->wfBuf[0][ pos + j], aec->wfBuf[1][ pos + j]);
       yf[1][j] += MulIm(aec->xfBuf[0][xPos + j], aec->xfBuf[1][xPos + j],
                         aec->wfBuf[0][ pos + j], aec->wfBuf[1][ pos + j]);
     }
   }
 }
 
 static void ScaleErrorSignal(AecCore* aec, float ef[2][PART_LEN1])
 {
+  const float mu = aec->extended_filter_enabled ? kExtendedMu : aec->normal_mu;
+  const float error_threshold = aec->extended_filter_enabled ?
+      kExtendedErrorThreshold : aec->normal_error_threshold;
   int i;
-  float absEf;
+  float abs_ef;
   for (i = 0; i < (PART_LEN1); i++) {
     ef[0][i] /= (aec->xPow[i] + 1e-10f);
     ef[1][i] /= (aec->xPow[i] + 1e-10f);
-    absEf = sqrtf(ef[0][i] * ef[0][i] + ef[1][i] * ef[1][i]);
+    abs_ef = sqrtf(ef[0][i] * ef[0][i] + ef[1][i] * ef[1][i]);
 
-    if (absEf > aec->errThresh) {
-      absEf = aec->errThresh / (absEf + 1e-10f);
-      ef[0][i] *= absEf;
-      ef[1][i] *= absEf;
+    if (abs_ef > error_threshold) {
+      abs_ef = error_threshold / (abs_ef + 1e-10f);
+      ef[0][i] *= abs_ef;
+      ef[1][i] *= abs_ef;
     }
 
     // Stepsize factor
-    ef[0][i] *= aec->mu;
-    ef[1][i] *= aec->mu;
+    ef[0][i] *= mu;
+    ef[1][i] *= mu;
   }
 }
 
 // Time-unconstrined filter adaptation.
 // TODO(andrew): consider for a low-complexity mode.
 //static void FilterAdaptationUnconstrained(AecCore* aec, float *fft,
 //                                          float ef[2][PART_LEN1]) {
 //  int i, j;
-//  for (i = 0; i < NR_PART; i++) {
+//  for (i = 0; i < aec->num_partitions; i++) {
 //    int xPos = (i + aec->xfBufBlockPos)*(PART_LEN1);
 //    int pos;
 //    // Check for wrap
-//    if (i + aec->xfBufBlockPos >= NR_PART) {
-//      xPos -= NR_PART * PART_LEN1;
+//    if (i + aec->xfBufBlockPos >= aec->num_partitions) {
+//      xPos -= aec->num_partitions * PART_LEN1;
 //    }
 //
 //    pos = i * PART_LEN1;
 //
 //    for (j = 0; j < PART_LEN1; j++) {
-//      aec->wfBuf[pos + j][0] += MulRe(aec->xfBuf[xPos + j][0],
-//                                      -aec->xfBuf[xPos + j][1],
-//                                      ef[j][0], ef[j][1]);
-//      aec->wfBuf[pos + j][1] += MulIm(aec->xfBuf[xPos + j][0],
-//                                      -aec->xfBuf[xPos + j][1],
-//                                      ef[j][0], ef[j][1]);
+//      aec->wfBuf[0][pos + j] += MulRe(aec->xfBuf[0][xPos + j],
+//                                      -aec->xfBuf[1][xPos + j],
+//                                      ef[0][j], ef[1][j]);
+//      aec->wfBuf[1][pos + j] += MulIm(aec->xfBuf[0][xPos + j],
+//                                      -aec->xfBuf[1][xPos + j],
+//                                      ef[0][j], ef[1][j]);
 //    }
 //  }
 //}
 
 static void FilterAdaptation(AecCore* aec, float *fft, float ef[2][PART_LEN1]) {
   int i, j;
-  for (i = 0; i < NR_PART; i++) {
+  for (i = 0; i < aec->num_partitions; i++) {
     int xPos = (i + aec->xfBufBlockPos)*(PART_LEN1);
     int pos;
     // Check for wrap
-    if (i + aec->xfBufBlockPos >= NR_PART) {
-      xPos -= NR_PART * PART_LEN1;
+    if (i + aec->xfBufBlockPos >= aec->num_partitions) {
+      xPos -= aec->num_partitions * PART_LEN1;
     }
 
     pos = i * PART_LEN1;
 
     for (j = 0; j < PART_LEN; j++) {
 
       fft[2 * j] = MulRe(aec->xfBuf[0][xPos + j],
                          -aec->xfBuf[1][xPos + j],
@@ -422,22 +435,22 @@ WebRtcAec_OverdriveAndSuppress_t WebRtcA
 
 int WebRtcAec_InitAec(AecCore* aec, int sampFreq)
 {
     int i;
 
     aec->sampFreq = sampFreq;
 
     if (sampFreq == 8000) {
-        aec->mu = 0.6f;
-        aec->errThresh = 2e-6f;
+        aec->normal_mu = 0.6f;
+        aec->normal_error_threshold = 2e-6f;
     }
     else {
-        aec->mu = 0.5f;
-        aec->errThresh = 1.5e-6f;
+        aec->normal_mu = 0.5f;
+        aec->normal_error_threshold = 1.5e-6f;
     }
 
     if (WebRtc_InitBuffer(aec->nearFrBuf) == -1) {
         return -1;
     }
 
     if (WebRtc_InitBuffer(aec->outFrBuf) == -1) {
         return -1;
@@ -469,26 +482,29 @@ int WebRtcAec_InitAec(AecCore* aec, int 
       return -1;
     }
     if (WebRtc_InitDelayEstimator(aec->delay_estimator) != 0) {
       return -1;
     }
     aec->delay_logging_enabled = 0;
     memset(aec->delay_histogram, 0, sizeof(aec->delay_histogram));
 
+    aec->extended_filter_enabled = 0;
+    aec->num_partitions = kNormalNumPartitions;
+
     // Default target suppression mode.
     aec->nlp_mode = 1;
 
     // Sampling frequency multiplier
     // SWB is processed as 160 frame size
     if (aec->sampFreq == 32000) {
       aec->mult = (short)aec->sampFreq / 16000;
     }
     else {
-        aec->mult = (short)aec->sampFreq / 8000;
+      aec->mult = (short)aec->sampFreq / 8000;
     }
 
     aec->farBufWritePos = 0;
     aec->farBufReadPos = 0;
 
     aec->inSamples = 0;
     aec->outSamples = 0;
     aec->knownDelay = 0;
@@ -509,21 +525,24 @@ int WebRtcAec_InitAec(AecCore* aec, int 
     for (i = 0; i < PART_LEN1; i++) {
         aec->dMinPow[i] = 1.0e6f;
     }
 
     // Holds the last block written to
     aec->xfBufBlockPos = 0;
     // TODO: Investigate need for these initializations. Deleting them doesn't
     //       change the output at all and yields 0.4% overall speedup.
-    memset(aec->xfBuf, 0, sizeof(complex_t) * NR_PART * PART_LEN1);
-    memset(aec->wfBuf, 0, sizeof(complex_t) * NR_PART * PART_LEN1);
+    memset(aec->xfBuf, 0, sizeof(complex_t) * kExtendedNumPartitions *
+        PART_LEN1);
+    memset(aec->wfBuf, 0, sizeof(complex_t) * kExtendedNumPartitions *
+        PART_LEN1);
     memset(aec->sde, 0, sizeof(complex_t) * PART_LEN1);
     memset(aec->sxd, 0, sizeof(complex_t) * PART_LEN1);
-    memset(aec->xfwBuf, 0, sizeof(complex_t) * NR_PART * PART_LEN1);
+    memset(aec->xfwBuf, 0, sizeof(complex_t) * kExtendedNumPartitions *
+        PART_LEN1);
     memset(aec->se, 0, sizeof(float) * PART_LEN1);
 
     // To prevent numerical instability in the first block.
     for (i = 0; i < PART_LEN1; i++) {
         aec->sd[i] = 1;
     }
     for (i = 0; i < PART_LEN1; i++) {
         aec->sx[i] = 1;
@@ -729,60 +748,63 @@ int WebRtcAec_GetDelayMetricsCore(AecCor
 
   // Reset histogram.
   memset(self->delay_histogram, 0, sizeof(self->delay_histogram));
 
   return 0;
 }
 
 int WebRtcAec_echo_state(AecCore* self) {
-  assert(self != NULL);
   return self->echoState;
 }
 
 void WebRtcAec_GetEchoStats(AecCore* self, Stats* erl, Stats* erle,
                             Stats* a_nlp) {
-  assert(self != NULL);
   assert(erl != NULL);
   assert(erle != NULL);
   assert(a_nlp != NULL);
   *erl = self->erl;
   *erle = self->erle;
   *a_nlp = self->aNlp;
 }
 
 #ifdef WEBRTC_AEC_DEBUG_DUMP
 void* WebRtcAec_far_time_buf(AecCore* self) {
-  assert(self != NULL);
   return self->far_time_buf;
 }
 #endif
 
 void WebRtcAec_SetConfigCore(AecCore* self, int nlp_mode, int metrics_mode,
                              int delay_logging) {
-  assert(self != NULL);
   assert(nlp_mode >= 0 && nlp_mode < 3);
   self->nlp_mode = nlp_mode;
   self->metricsMode = metrics_mode;
   if (self->metricsMode) {
     InitMetrics(self);
   }
   self->delay_logging_enabled = delay_logging;
   if (self->delay_logging_enabled) {
     memset(self->delay_histogram, 0, sizeof(self->delay_histogram));
   }
 }
 
+void WebRtcAec_enable_delay_correction(AecCore* self, int enable) {
+  self->extended_filter_enabled = enable;
+  self->num_partitions = enable ? kExtendedNumPartitions : kNormalNumPartitions;
+}
+
+int WebRtcAec_delay_correction_enabled(AecCore* self) {
+  return self->extended_filter_enabled;
+}
+
 int WebRtcAec_system_delay(AecCore* self) {
-  assert(self != NULL);
   return self->system_delay;
 }
 
 void WebRtcAec_SetSystemDelay(AecCore* self, int delay) {
-  assert(self != NULL);
   assert(delay >= 0);
   self->system_delay = delay;
 }
 
 static void ProcessBlock(AecCore* aec) {
     int i;
     float d[PART_LEN], y[PART_LEN], e[PART_LEN], dH[PART_LEN];
     float scale;
@@ -848,17 +870,18 @@ static void ProcessBlock(AecCore* aec) {
     // Near fft
     memcpy(fft, aec->dBuf, sizeof(float) * PART_LEN2);
     TimeToFrequency(fft, df, 0);
 
     // Power smoothing
     for (i = 0; i < PART_LEN1; i++) {
       far_spectrum = (xf_ptr[i] * xf_ptr[i]) +
           (xf_ptr[PART_LEN1 + i] * xf_ptr[PART_LEN1 + i]);
-      aec->xPow[i] = gPow[0] * aec->xPow[i] + gPow[1] * NR_PART * far_spectrum;
+      aec->xPow[i] = gPow[0] * aec->xPow[i] + gPow[1] * aec->num_partitions *
+          far_spectrum;
       // Calculate absolute spectra
       abs_far_spectrum[i] = sqrtf(far_spectrum);
 
       near_spectrum = df[0][i] * df[0][i] + df[1][i] * df[1][i];
       aec->dPow[i] = gPow[0] * aec->dPow[i] + gPow[1] * near_spectrum;
       // Calculate absolute spectra
       abs_near_spectrum[i] = sqrtf(near_spectrum);
     }
@@ -908,17 +931,17 @@ static void ProcessBlock(AecCore* aec) {
           aec->delay_histogram[delay_estimate]++;
         }
       }
     }
 
     // Update the xfBuf block position.
     aec->xfBufBlockPos--;
     if (aec->xfBufBlockPos == -1) {
-        aec->xfBufBlockPos = NR_PART - 1;
+        aec->xfBufBlockPos = aec->num_partitions - 1;
     }
 
     // Buffer xf
     memcpy(aec->xfBuf[0] + aec->xfBufBlockPos * PART_LEN1, xf_ptr,
            sizeof(float) * PART_LEN1);
     memcpy(aec->xfBuf[1] + aec->xfBufBlockPos * PART_LEN1, &xf_ptr[PART_LEN1],
            sizeof(float) * PART_LEN1);
 
@@ -1009,28 +1032,31 @@ static void NonLinearProcessing(AecCore*
     float scale, dtmp;
     float nlpGainHband;
     int i, j, pos;
 
     // Coherence and non-linear filter
     float cohde[PART_LEN1], cohxd[PART_LEN1];
     float hNlDeAvg, hNlXdAvg;
     float hNl[PART_LEN1];
-    float hNlPref[PREF_BAND_SIZE];
+    float hNlPref[kPrefBandSize];
     float hNlFb = 0, hNlFbLow = 0;
     const float prefBandQuant = 0.75f, prefBandQuantLow = 0.5f;
-    const int prefBandSize = PREF_BAND_SIZE / aec->mult;
+    const int prefBandSize = kPrefBandSize / aec->mult;
     const int minPrefBand = 4 / aec->mult;
 
     // Near and error power sums
     float sdSum = 0, seSum = 0;
 
-    // Power estimate smoothing coefficients
-    const float gCoh[2][2] = {{0.9f, 0.1f}, {0.93f, 0.07f}};
-    const float *ptrGCoh = gCoh[aec->mult - 1];
+    // Power estimate smoothing coefficients.
+    const float *ptrGCoh = aec->extended_filter_enabled ?
+        kExtendedSmoothingCoefficients[aec->mult - 1] :
+        kNormalSmoothingCoefficients[aec->mult - 1];
+    const float* min_overdrive = aec->extended_filter_enabled ?
+        kExtendedMinOverDrive : kNormalMinOverDrive;
 
     // Filter energy
     float wfEnMax = 0, wfEn = 0;
     const int delayEstInterval = 10 * aec->mult;
 
     float* xfw_ptr = NULL;
 
     aec->delayEstCtr++;
@@ -1043,17 +1069,17 @@ static void NonLinearProcessing(AecCore*
     nlpGainHband = (float)0.0;
     dtmp = (float)0.0;
 
     // Measure energy in each filter partition to determine delay.
     // TODO: Spread by computing one partition per block?
     if (aec->delayEstCtr == 0) {
         wfEnMax = 0;
         aec->delayIdx = 0;
-        for (i = 0; i < NR_PART; i++) {
+        for (i = 0; i < aec->num_partitions; i++) {
             pos = i * PART_LEN1;
             wfEn = 0;
             for (j = 0; j < PART_LEN1; j++) {
                 wfEn += aec->wfBuf[0][pos + j] * aec->wfBuf[0][pos + j] +
                     aec->wfBuf[1][pos + j] * aec->wfBuf[1][pos + j];
             }
 
             if (wfEn > wfEnMax) {
@@ -1184,17 +1210,17 @@ static void NonLinearProcessing(AecCore*
         aec->stNearState = 1;
     }
     else if (hNlDeAvg < 0.95f || hNlXdAvg < 0.8f) {
         aec->stNearState = 0;
     }
 
     if (aec->hNlXdAvgMin == 1) {
         aec->echoState = 0;
-        aec->overDrive = kMinOverDrive[aec->nlp_mode];
+        aec->overDrive = min_overdrive[aec->nlp_mode];
 
         if (aec->stNearState == 1) {
             memcpy(hNl, cohde, sizeof(hNl));
             hNlFb = hNlDeAvg;
             hNlFbLow = hNlDeAvg;
         }
         else {
             for (i = 0; i < PART_LEN1; i++) {
@@ -1240,17 +1266,17 @@ static void NonLinearProcessing(AecCore*
     if (aec->hNlNewMin == 1) {
         aec->hNlMinCtr++;
     }
     if (aec->hNlMinCtr == 2) {
         aec->hNlNewMin = 0;
         aec->hNlMinCtr = 0;
         aec->overDrive = WEBRTC_SPL_MAX(kTargetSupp[aec->nlp_mode] /
             ((float)log(aec->hNlFbMin + 1e-10f) + 1e-10f),
-            kMinOverDrive[aec->nlp_mode]);
+            min_overdrive[aec->nlp_mode]);
     }
 
     // Smooth the overdrive.
     if (aec->overDrive < aec->overDriveSm) {
       aec->overDriveSm = 0.99f * aec->overDriveSm + 0.01f * aec->overDrive;
     }
     else {
       aec->overDriveSm = 0.9f * aec->overDriveSm + 0.1f * aec->overDrive;
@@ -1460,17 +1486,16 @@ static void InitStats(Stats* stats) {
   stats->sum = 0;
   stats->hisum = 0;
   stats->himean = kOffsetLevel;
   stats->counter = 0;
   stats->hicounter = 0;
 }
 
 static void InitMetrics(AecCore* self) {
-  assert(self != NULL);
   self->stateCounter = 0;
   InitLevel(&self->farlevel);
   InitLevel(&self->nearlevel);
   InitLevel(&self->linoutlevel);
   InitLevel(&self->nlpoutlevel);
 
   InitStats(&self->erl);
   InitStats(&self->erle);
@@ -1682,8 +1707,9 @@ static void TimeToFrequency(float time_d
   freq_data[1][PART_LEN] = 0;
   freq_data[0][0] = time_data[0];
   freq_data[0][PART_LEN] = time_data[1];
   for (i = 1; i < PART_LEN; i++) {
     freq_data[0][i] = time_data[2 * i];
     freq_data[1][i] = time_data[2 * i + 1];
   }
 }
+
--- a/media/webrtc/trunk/webrtc/modules/audio_processing/aec/aec_core.h
+++ b/media/webrtc/trunk/webrtc/modules/audio_processing/aec/aec_core.h
@@ -16,18 +16,16 @@
 #define WEBRTC_MODULES_AUDIO_PROCESSING_AEC_AEC_CORE_H_
 
 #include "webrtc/typedefs.h"
 
 #define FRAME_LEN 80
 #define PART_LEN 64  // Length of partition
 #define PART_LEN1 (PART_LEN + 1)  // Unique fft coefficients
 #define PART_LEN2 (PART_LEN * 2)  // Length of partition * 2
-#define NR_PART 12  // Number of partitions in filter.
-#define PREF_BAND_SIZE 24
 
 // Delay estimator constants, used for logging.
 enum { kMaxDelayBlocks = 60 };
 enum { kLookaheadBlocks = 15 };
 enum { kHistorySizeBlocks = kMaxDelayBlocks + kLookaheadBlocks };
 
 typedef float complex_t[2];
 // For performance reasons, some arrays of complex numbers are replaced by twice
@@ -67,31 +65,46 @@ void WebRtcAec_ProcessFrame(AecCore* aec
                             int knownDelay,
                             int16_t* out,
                             int16_t* outH);
 
 // A helper function to call WebRtc_MoveReadPtr() for all far-end buffers.
 // Returns the number of elements moved, and adjusts |system_delay| by the
 // corresponding amount in ms.
 int WebRtcAec_MoveFarReadPtr(AecCore* aec, int elements);
+
 // Calculates the median and standard deviation among the delay estimates
 // collected since the last call to this function.
 int WebRtcAec_GetDelayMetricsCore(AecCore* self, int* median, int* std);
+
 // Returns the echo state (1: echo, 0: no echo).
 int WebRtcAec_echo_state(AecCore* self);
+
 // Gets statistics of the echo metrics ERL, ERLE, A_NLP.
 void WebRtcAec_GetEchoStats(AecCore* self, Stats* erl, Stats* erle,
                             Stats* a_nlp);
 #ifdef WEBRTC_AEC_DEBUG_DUMP
 void* WebRtcAec_far_time_buf(AecCore* self);
 #endif
+
 // Sets local configuration modes.
 void WebRtcAec_SetConfigCore(AecCore* self, int nlp_mode, int metrics_mode,
                              int delay_logging);
+
+// We now interpret delay correction to mean an extended filter length feature.
+// We reuse the delay correction infrastructure to avoid changes through to
+// libjingle. See details along with |DelayCorrection| in
+// echo_cancellation_impl.h. Non-zero enables, zero disables.
+void WebRtcAec_enable_delay_correction(AecCore* self, int enable);
+
+// Returns non-zero if delay correction is enabled and zero if disabled.
+int WebRtcAec_delay_correction_enabled(AecCore* self);
+
 // Returns the current |system_delay|, i.e., the buffered difference between
 // far-end and near-end.
 int WebRtcAec_system_delay(AecCore* self);
+
 // Sets the |system_delay| to |value|.  Note that if the value is changed
 // improperly, there can be a performance regression.  So it should be used with
 // care.
 void WebRtcAec_SetSystemDelay(AecCore* self, int delay);
 
 #endif  // WEBRTC_MODULES_AUDIO_PROCESSING_AEC_AEC_CORE_H_
--- a/media/webrtc/trunk/webrtc/modules/audio_processing/aec/aec_core_internal.h
+++ b/media/webrtc/trunk/webrtc/modules/audio_processing/aec/aec_core_internal.h
@@ -14,16 +14,26 @@
 #ifdef WEBRTC_AEC_DEBUG_DUMP
 #include <stdio.h>
 #endif
 
 #include "webrtc/modules/audio_processing/aec/aec_core.h"
 #include "webrtc/modules/audio_processing/utility/ring_buffer.h"
 #include "webrtc/typedefs.h"
 
+// Number of partitions for the extended filter mode. The first one is an enum
+// to be used in array declarations, as it represents the maximum filter length.
+enum { kExtendedNumPartitions = 32 };
+static const int kNormalNumPartitions = 12;
+
+// Extended filter adaptation parameters.
+// TODO(ajm): No narrowband tuning yet.
+static const float kExtendedMu = 0.4f;
+static const float kExtendedErrorThreshold = 1.0e-6f;
+
 typedef struct PowerLevel {
   float sfrsum;
   int sfrcounter;
   float framelevel;
   float frsum;
   int frcounter;
   float minlevel;
   float averagelevel;
@@ -48,21 +58,22 @@ struct AecCore {
   float dBufH[PART_LEN2];  // nearend
 
   float xPow[PART_LEN1];
   float dPow[PART_LEN1];
   float dMinPow[PART_LEN1];
   float dInitMinPow[PART_LEN1];
   float *noisePow;
 
-  float xfBuf[2][NR_PART * PART_LEN1];  // farend fft buffer
-  float wfBuf[2][NR_PART * PART_LEN1];  // filter fft
+  float xfBuf[2][kExtendedNumPartitions * PART_LEN1];  // farend fft buffer
+  float wfBuf[2][kExtendedNumPartitions * PART_LEN1];  // filter fft
   complex_t sde[PART_LEN1];  // cross-psd of nearend and error
   complex_t sxd[PART_LEN1];  // cross-psd of farend and nearend
-  complex_t xfwBuf[NR_PART * PART_LEN1];  // farend windowed fft buffer
+  // Farend windowed fft buffer.
+  complex_t xfwBuf[kExtendedNumPartitions * PART_LEN1];
 
   float sx[PART_LEN1], sd[PART_LEN1], se[PART_LEN1];  // far, near, error psd
   float hNs[PART_LEN1];
   float hNlFbMin, hNlFbLocalMin;
   float hNlXdAvgMin;
   int hNlNewMin, hNlMinCtr;
   float overDrive, overDriveSm;
   int nlp_mode;
@@ -77,18 +88,18 @@ struct AecCore {
   RingBuffer* far_buf;
   RingBuffer* far_buf_windowed;
   int system_delay;  // Current system delay buffered in AEC.
 
   int mult;  // sampling frequency multiple
   int sampFreq;
   uint32_t seed;
 
-  float mu;  // stepsize
-  float errThresh;  // error threshold
+  float normal_mu;  // stepsize
+  float normal_error_threshold;  // error threshold
 
   int noiseEstCtr;
 
   PowerLevel farlevel;
   PowerLevel nearlevel;
   PowerLevel linoutlevel;
   PowerLevel nlpoutlevel;
 
@@ -104,16 +115,21 @@ struct AecCore {
   int flag_Hband_cn;  // for comfort noise
   float cn_scale_Hband;  // scale for comfort noise in H band
 
   int delay_histogram[kHistorySizeBlocks];
   int delay_logging_enabled;
   void* delay_estimator_farend;
   void* delay_estimator;
 
+  // 1 = extended filter mode enabled, 0 = disabled.
+  int extended_filter_enabled;
+  // Runtime selection of number of filter partitions.
+  int num_partitions;
+
 #ifdef WEBRTC_AEC_DEBUG_DUMP
   RingBuffer* far_time_buf;
   FILE *farFile;
   FILE *nearFile;
   FILE *outFile;
   FILE *outLinearFile;
 #endif
 };
--- a/media/webrtc/trunk/webrtc/modules/audio_processing/aec/aec_core_sse2.c
+++ b/media/webrtc/trunk/webrtc/modules/audio_processing/aec/aec_core_sse2.c
@@ -29,23 +29,24 @@
 __inline static float MulIm(float aRe, float aIm, float bRe, float bIm)
 {
   return aRe * bIm + aIm * bRe;
 }
 
 static void FilterFarSSE2(AecCore* aec, float yf[2][PART_LEN1])
 {
   int i;
-  for (i = 0; i < NR_PART; i++) {
+  const int num_partitions = aec->num_partitions;
+  for (i = 0; i < num_partitions; i++) {
     int j;
     int xPos = (i + aec->xfBufBlockPos) * PART_LEN1;
     int pos = i * PART_LEN1;
     // Check for wrap
-    if (i + aec->xfBufBlockPos >= NR_PART) {
-      xPos -= NR_PART*(PART_LEN1);
+    if (i + aec->xfBufBlockPos >= num_partitions) {
+      xPos -= num_partitions*(PART_LEN1);
     }
 
     // vectorized code (four at once)
     for (j = 0; j + 3 < PART_LEN1; j += 4) {
       const __m128 xfBuf_re = _mm_loadu_ps(&aec->xfBuf[0][xPos + j]);
       const __m128 xfBuf_im = _mm_loadu_ps(&aec->xfBuf[1][xPos + j]);
       const __m128 wfBuf_re = _mm_loadu_ps(&aec->wfBuf[0][pos + j]);
       const __m128 wfBuf_im = _mm_loadu_ps(&aec->wfBuf[1][pos + j]);
@@ -70,18 +71,21 @@ static void FilterFarSSE2(AecCore* aec, 
                         aec->wfBuf[0][ pos + j], aec->wfBuf[1][ pos + j]);
     }
   }
 }
 
 static void ScaleErrorSignalSSE2(AecCore* aec, float ef[2][PART_LEN1])
 {
   const __m128 k1e_10f = _mm_set1_ps(1e-10f);
-  const __m128 kThresh = _mm_set1_ps(aec->errThresh);
-  const __m128 kMu = _mm_set1_ps(aec->mu);
+  const __m128 kMu = aec->extended_filter_enabled ?
+      _mm_set1_ps(kExtendedMu) : _mm_set1_ps(aec->normal_mu);
+  const __m128 kThresh = aec->extended_filter_enabled ?
+      _mm_set1_ps(kExtendedErrorThreshold) :
+      _mm_set1_ps(aec->normal_error_threshold);
 
   int i;
   // vectorized code (four at once)
   for (i = 0; i + 3 < PART_LEN1; i += 4) {
     const __m128 xPow = _mm_loadu_ps(&aec->xPow[i]);
     const __m128 ef_re_base = _mm_loadu_ps(&ef[0][i]);
     const __m128 ef_im_base = _mm_loadu_ps(&ef[1][i]);
 
@@ -105,42 +109,49 @@ static void ScaleErrorSignalSSE2(AecCore
     ef_im = _mm_or_ps(ef_im, ef_im_if);
     ef_re = _mm_mul_ps(ef_re, kMu);
     ef_im = _mm_mul_ps(ef_im, kMu);
 
     _mm_storeu_ps(&ef[0][i], ef_re);
     _mm_storeu_ps(&ef[1][i], ef_im);
   }
   // scalar code for the remaining items.
-  for (; i < (PART_LEN1); i++) {
-    float absEf;
-    ef[0][i] /= (aec->xPow[i] + 1e-10f);
-    ef[1][i] /= (aec->xPow[i] + 1e-10f);
-    absEf = sqrtf(ef[0][i] * ef[0][i] + ef[1][i] * ef[1][i]);
+  {
+    const float mu = aec->extended_filter_enabled ?
+        kExtendedMu : aec->normal_mu;
+    const float error_threshold = aec->extended_filter_enabled ?
+        kExtendedErrorThreshold : aec->normal_error_threshold;
+    for (; i < (PART_LEN1); i++) {
+    float abs_ef;
+      ef[0][i] /= (aec->xPow[i] + 1e-10f);
+      ef[1][i] /= (aec->xPow[i] + 1e-10f);
+      abs_ef = sqrtf(ef[0][i] * ef[0][i] + ef[1][i] * ef[1][i]);
 
-    if (absEf > aec->errThresh) {
-      absEf = aec->errThresh / (absEf + 1e-10f);
-      ef[0][i] *= absEf;
-      ef[1][i] *= absEf;
+      if (abs_ef > error_threshold) {
+        abs_ef = error_threshold / (abs_ef + 1e-10f);
+        ef[0][i] *= abs_ef;
+        ef[1][i] *= abs_ef;
+      }
+
+      // Stepsize factor
+      ef[0][i] *= mu;
+      ef[1][i] *= mu;
     }
-
-    // Stepsize factor
-    ef[0][i] *= aec->mu;
-    ef[1][i] *= aec->mu;
   }
 }
 
 static void FilterAdaptationSSE2(AecCore* aec, float *fft, float ef[2][PART_LEN1]) {
   int i, j;
-  for (i = 0; i < NR_PART; i++) {
+  const int num_partitions = aec->num_partitions;
+  for (i = 0; i < num_partitions; i++) {
     int xPos = (i + aec->xfBufBlockPos)*(PART_LEN1);
     int pos = i * PART_LEN1;
     // Check for wrap
-    if (i + aec->xfBufBlockPos >= NR_PART) {
-      xPos -= NR_PART * PART_LEN1;
+    if (i + aec->xfBufBlockPos >= num_partitions) {
+      xPos -= num_partitions * PART_LEN1;
     }
 
     // Process the whole array...
     for (j = 0; j < PART_LEN; j+= 4) {
       // Load xfBuf and ef.
       const __m128 xfBuf_re = _mm_loadu_ps(&aec->xfBuf[0][xPos + j]);
       const __m128 xfBuf_im = _mm_loadu_ps(&aec->xfBuf[1][xPos + j]);
       const __m128 ef_re = _mm_loadu_ps(&ef[0][j]);
@@ -408,8 +419,9 @@ static void OverdriveAndSuppressSSE2(Aec
 }
 
 void WebRtcAec_InitAec_SSE2(void) {
   WebRtcAec_FilterFar = FilterFarSSE2;
   WebRtcAec_ScaleErrorSignal = ScaleErrorSignalSSE2;
   WebRtcAec_FilterAdaptation = FilterAdaptationSSE2;
   WebRtcAec_OverdriveAndSuppress = OverdriveAndSuppressSSE2;
 }
+
--- a/media/webrtc/trunk/webrtc/modules/audio_processing/aec/echo_cancellation.c
+++ b/media/webrtc/trunk/webrtc/modules/audio_processing/aec/echo_cancellation.c
@@ -22,33 +22,95 @@
 
 #include "webrtc/common_audio/signal_processing/include/signal_processing_library.h"
 #include "webrtc/modules/audio_processing/aec/aec_core.h"
 #include "webrtc/modules/audio_processing/aec/aec_resampler.h"
 #include "webrtc/modules/audio_processing/aec/echo_cancellation_internal.h"
 #include "webrtc/modules/audio_processing/utility/ring_buffer.h"
 #include "webrtc/typedefs.h"
 
+// Measured delays [ms]
+// Device                Chrome  GTP
+// MacBook Air           10
+// MacBook Retina        10      100
+// MacPro                30?
+//
+// Win7 Desktop          70      80?
+// Win7 T430s            110
+// Win8 T420s            70
+//
+// Daisy                 50
+// Pixel (w/ preproc?)           240
+// Pixel (w/o preproc?)  110     110
+
+// The extended filter mode gives us the flexibility to ignore the system's
+// reported delays. We do this for platforms which we believe provide results
+// which are incompatible with the AEC's expectations. Based on measurements
+// (some provided above) we set a conservative (i.e. lower than measured)
+// fixed delay.
+//
+// WEBRTC_UNTRUSTED_DELAY will only have an impact when |extended_filter_mode|
+// is enabled. See the note along with |DelayCorrection| in
+// echo_cancellation_impl.h for more details on the mode.
+//
+// Justification:
+// Chromium/Mac: Here, the true latency is so low (~10-20 ms), that it plays
+// havoc with the AEC's buffering. To avoid this, we set a fixed delay of 20 ms
+// and then compensate by rewinding by 10 ms (in wideband) through
+// kDelayDiffOffsetSamples. This trick does not seem to work for larger rewind
+// values, but fortunately this is sufficient.
+//
+// Chromium/Linux(ChromeOS): The values we get on this platform don't correspond
+// well to reality. The variance doesn't match the AEC's buffer changes, and the
+// bulk values tend to be too low. However, the range across different hardware
+// appears to be too large to choose a single value.
+//
+// GTP/Linux(ChromeOS): TBD, but for the moment we will trust the values.
+#if defined(WEBRTC_CHROMIUM_BUILD) && defined(WEBRTC_MAC)
+#define WEBRTC_UNTRUSTED_DELAY
+#endif
+
+#if defined(WEBRTC_MAC)
+static const int kFixedDelayMs = 20;
+static const int kDelayDiffOffsetSamples = -160;
+#elif defined(WEBRTC_WIN)
+static const int kFixedDelayMs = 50;
+static const int kDelayDiffOffsetSamples = 0;
+#else
+// Essentially ChromeOS.
+static const int kFixedDelayMs = 50;
+static const int kDelayDiffOffsetSamples = 0;
+#endif
+static const int kMinTrustedDelayMs = 20;
+static const int kMaxTrustedDelayMs = 500;
+
 // Maximum length of resampled signal. Must be an integer multiple of frames
 // (ceil(1/(1 + MIN_SKEW)*2) + 1)*FRAME_LEN
 // The factor of 2 handles wb, and the + 1 is as a safety margin
 // TODO(bjornv): Replace with kResamplerBufferSize
 #define MAX_RESAMP_LEN (5 * FRAME_LEN)
 
 static const int kMaxBufSizeStart = 62;  // In partitions
 static const int sampMsNb = 8; // samples per ms in nb
 static const int initCheck = 42;
 
 #ifdef WEBRTC_AEC_DEBUG_DUMP
 int webrtc_aec_instance_count = 0;
 #endif
 
 // Estimates delay to set the position of the far-end buffer read pointer
 // (controlled by knownDelay)
-static int EstBufDelay(aecpc_t *aecInst);
+static void EstBufDelayNormal(aecpc_t *aecInst);
+static void EstBufDelayExtended(aecpc_t *aecInst);
+static int ProcessNormal(aecpc_t* self, const int16_t* near,
+    const int16_t* near_high, int16_t* out, int16_t* out_high,
+    int16_t num_samples, int16_t reported_delay_ms, int32_t skew);
+static void ProcessExtended(aecpc_t* self, const int16_t* near,
+    const int16_t* near_high, int16_t* out, int16_t* out_high,
+    int16_t num_samples, int16_t reported_delay_ms, int32_t skew);
 
 int32_t WebRtcAec_Create(void **aecInst)
 {
     aecpc_t *aecpc;
     if (aecInst == NULL) {
         return -1;
     }
 
@@ -130,20 +192,16 @@ int32_t WebRtcAec_Free(void *aecInst)
     return 0;
 }
 
 int32_t WebRtcAec_Init(void *aecInst, int32_t sampFreq, int32_t scSampFreq)
 {
     aecpc_t *aecpc = aecInst;
     AecConfig aecConfig;
 
-    if (aecpc == NULL) {
-        return -1;
-    }
-
     if (sampFreq != 8000 && sampFreq != 16000  && sampFreq != 32000) {
         aecpc->lastError = AEC_BAD_PARAMETER_ERROR;
         return -1;
     }
     aecpc->sampFreq = sampFreq;
 
     if (scSampFreq < 1 || scSampFreq > 96000) {
         aecpc->lastError = AEC_BAD_PARAMETER_ERROR;
@@ -172,41 +230,41 @@ int32_t WebRtcAec_Init(void *aecInst, in
 
     if (aecpc->sampFreq == 32000) {
         aecpc->splitSampFreq = 16000;
     }
     else {
         aecpc->splitSampFreq = sampFreq;
     }
 
-    aecpc->skewFrCtr = 0;
-    aecpc->activity = 0;
-
     aecpc->delayCtr = 0;
+    aecpc->sampFactor = (aecpc->scSampFreq * 1.0f) / aecpc->splitSampFreq;
+    // Sampling frequency multiplier (SWB is processed as 160 frame size).
+    aecpc->rate_factor = aecpc->splitSampFreq / 8000;
 
     aecpc->sum = 0;
     aecpc->counter = 0;
     aecpc->checkBuffSize = 1;
     aecpc->firstVal = 0;
 
-    aecpc->ECstartup = 1;
+    aecpc->startup_phase = 1;
     aecpc->bufSizeStart = 0;
     aecpc->checkBufSizeCtr = 0;
-    aecpc->filtDelay = 0;
+    aecpc->msInSndCardBuf = 0;
+    aecpc->filtDelay = -1;  // -1 indicates an initialized state.
     aecpc->timeForDelayChange = 0;
     aecpc->knownDelay = 0;
     aecpc->lastDelayDiff = 0;
 
-    aecpc->skew = 0;
+    aecpc->skewFrCtr = 0;
     aecpc->resample = kAecFalse;
     aecpc->highSkewCtr = 0;
-    aecpc->sampFactor = (aecpc->scSampFreq * 1.0f) / aecpc->splitSampFreq;
+    aecpc->skew = 0;
 
-    // Sampling frequency multiplier (SWB is processed as 160 frame size).
-    aecpc->rate_factor = aecpc->splitSampFreq / 8000;
+    aecpc->farend_started = 0;
 
     // Default settings.
     aecConfig.nlpMode = kAecNlpModerate;
     aecConfig.skewMode = kAecFalse;
     aecConfig.metricsMode = kAecFalse;
     aecConfig.delay_logging = kAecFalse;
 
     if (WebRtcAec_set_config(aecpc, aecConfig) == -1) {
@@ -234,20 +292,16 @@ int32_t WebRtcAec_BufferFarend(void *aec
     int newNrOfSamples = (int) nrOfSamples;
     short newFarend[MAX_RESAMP_LEN];
     const int16_t* farend_ptr = farend;
     float tmp_farend[MAX_RESAMP_LEN];
     const float* farend_float = tmp_farend;
     float skew;
     int i = 0;
 
-    if (aecpc == NULL) {
-        return -1;
-    }
-
     if (farend == NULL) {
         aecpc->lastError = AEC_NULL_POINTER_ERROR;
         return -1;
     }
 
     if (aecpc->initFlag != initCheck) {
         aecpc->lastError = AEC_UNINITIALIZED_ERROR;
         return -1;
@@ -263,16 +317,17 @@ int32_t WebRtcAec_BufferFarend(void *aec
 
     if (aecpc->skewMode == kAecTrue && aecpc->resample == kAecTrue) {
         // Resample and get a new number of samples
         WebRtcAec_ResampleLinear(aecpc->resampler, farend, nrOfSamples, skew,
                                  newFarend, &newNrOfSamples);
         farend_ptr = (const int16_t*) newFarend;
     }
 
+    aecpc->farend_started = 1;
     WebRtcAec_SetSystemDelay(aecpc->aec, WebRtcAec_system_delay(aecpc->aec) +
                              newNrOfSamples);
 
 #ifdef WEBRTC_AEC_DEBUG_DUMP
     WebRtc_WriteBuffer(aecpc->far_pre_buf_s16, farend_ptr,
                        (size_t) newNrOfSamples);
 #endif
     // Cast to float and write the time-domain data to |far_pre_buf|.
@@ -306,27 +361,16 @@ int32_t WebRtcAec_BufferFarend(void *aec
 
 int32_t WebRtcAec_Process(void *aecInst, const int16_t *nearend,
                           const int16_t *nearendH, int16_t *out, int16_t *outH,
                           int16_t nrOfSamples, int16_t msInSndCardBuf,
                           int32_t skew)
 {
     aecpc_t *aecpc = aecInst;
     int32_t retVal = 0;
-    short i;
-    short nBlocks10ms;
-    short nFrames;
-    // Limit resampling to doubling/halving of signal
-    const float minSkewEst = -0.5f;
-    const float maxSkewEst = 1.0f;
-
-    if (aecpc == NULL) {
-        return -1;
-    }
-
     if (nearend == NULL) {
         aecpc->lastError = AEC_NULL_POINTER_ERROR;
         return -1;
     }
 
     if (out == NULL) {
         aecpc->lastError = AEC_NULL_POINTER_ERROR;
         return -1;
@@ -349,154 +393,31 @@ int32_t WebRtcAec_Process(void *aecInst,
        return -1;
     }
 
     if (msInSndCardBuf < 0) {
         msInSndCardBuf = 0;
         aecpc->lastError = AEC_BAD_PARAMETER_WARNING;
         retVal = -1;
     }
-    else if (msInSndCardBuf > 500) {
-        msInSndCardBuf = 500;
+    else if (msInSndCardBuf > kMaxTrustedDelayMs) {
+        // The clamping is now done in ProcessExtended/Normal().
         aecpc->lastError = AEC_BAD_PARAMETER_WARNING;
         retVal = -1;
     }
-    // TODO(andrew): we need to investigate if this +10 is really wanted.
-    msInSndCardBuf += 10;
-    aecpc->msInSndCardBuf = msInSndCardBuf;
 
-    if (aecpc->skewMode == kAecTrue) {
-        if (aecpc->skewFrCtr < 25) {
-            aecpc->skewFrCtr++;
-        }
-        else {
-            retVal = WebRtcAec_GetSkew(aecpc->resampler, skew, &aecpc->skew);
-            if (retVal == -1) {
-                aecpc->skew = 0;
-                aecpc->lastError = AEC_BAD_PARAMETER_WARNING;
-            }
-
-            aecpc->skew /= aecpc->sampFactor*nrOfSamples;
-
-            if (aecpc->skew < 1.0e-3 && aecpc->skew > -1.0e-3) {
-                aecpc->resample = kAecFalse;
-            }
-            else {
-                aecpc->resample = kAecTrue;
-            }
-
-            if (aecpc->skew < minSkewEst) {
-                aecpc->skew = minSkewEst;
-            }
-            else if (aecpc->skew > maxSkewEst) {
-                aecpc->skew = maxSkewEst;
-            }
-
-#ifdef WEBRTC_AEC_DEBUG_DUMP
-            (void)fwrite(&aecpc->skew, sizeof(aecpc->skew), 1, aecpc->skewFile);
-#endif
-        }
-    }
-
-    nFrames = nrOfSamples / FRAME_LEN;
-    nBlocks10ms = nFrames / aecpc->rate_factor;
-
-    if (aecpc->ECstartup) {
-        if (nearend != out) {
-            // Only needed if they don't already point to the same place.
-            memcpy(out, nearend, sizeof(short) * nrOfSamples);
-        }
-
-        // The AEC is in the start up mode
-        // AEC is disabled until the system delay is OK
-
-        // Mechanism to ensure that the system delay is reasonably stable.
-        if (aecpc->checkBuffSize) {
-            aecpc->checkBufSizeCtr++;
-            // Before we fill up the far-end buffer we require the system delay
-            // to be stable (+/-8 ms) compared to the first value. This
-            // comparison is made during the following 6 consecutive 10 ms
-            // blocks. If it seems to be stable then we start to fill up the
-            // far-end buffer.
-            if (aecpc->counter == 0) {
-                aecpc->firstVal = aecpc->msInSndCardBuf;
-                aecpc->sum = 0;
-            }
-
-            if (abs(aecpc->firstVal - aecpc->msInSndCardBuf) <
-                WEBRTC_SPL_MAX(0.2 * aecpc->msInSndCardBuf, sampMsNb)) {
-                aecpc->sum += aecpc->msInSndCardBuf;
-                aecpc->counter++;
-            }
-            else {
-                aecpc->counter = 0;
-            }
-
-            if (aecpc->counter * nBlocks10ms >= 6) {
-                // The far-end buffer size is determined in partitions of
-                // PART_LEN samples. Use 75% of the average value of the system
-                // delay as buffer size to start with.
-                aecpc->bufSizeStart = WEBRTC_SPL_MIN((3 * aecpc->sum *
-                  aecpc->rate_factor * 8) / (4 * aecpc->counter * PART_LEN),
-                  kMaxBufSizeStart);
-                // Buffer size has now been determined.
-                aecpc->checkBuffSize = 0;
-            }
-
-            if (aecpc->checkBufSizeCtr * nBlocks10ms > 50) {
-                // For really bad systems, don't disable the echo canceller for
-                // more than 0.5 sec.
-                aecpc->bufSizeStart = WEBRTC_SPL_MIN((aecpc->msInSndCardBuf *
-                    aecpc->rate_factor * 3) / 40, kMaxBufSizeStart);
-                aecpc->checkBuffSize = 0;
-            }
-        }
-
-        // If |checkBuffSize| changed in the if-statement above.
-        if (!aecpc->checkBuffSize) {
-            // The system delay is now reasonably stable (or has been unstable
-            // for too long). When the far-end buffer is filled with
-            // approximately the same amount of data as reported by the system
-            // we end the startup phase.
-            int overhead_elements =
-                WebRtcAec_system_delay(aecpc->aec) / PART_LEN -
-                aecpc->bufSizeStart;
-            if (overhead_elements == 0) {
-                // Enable the AEC
-                aecpc->ECstartup = 0;
-            } else if (overhead_elements > 0) {
-                // TODO(bjornv): Do we need a check on how much we actually
-                // moved the read pointer? It should always be possible to move
-                // the pointer |overhead_elements| since we have only added data
-                // to the buffer and no delay compensation nor AEC processing
-                // has been done.
-                WebRtcAec_MoveFarReadPtr(aecpc->aec, overhead_elements);
-
-                // Enable the AEC
-                aecpc->ECstartup = 0;
-            }
-        }
+    // This returns the value of aec->extended_filter_enabled.
+    if (WebRtcAec_delay_correction_enabled(aecpc->aec)) {
+      ProcessExtended(aecpc, nearend, nearendH, out, outH, nrOfSamples,
+                      msInSndCardBuf, skew);
     } else {
-        // AEC is enabled.
-
-        EstBufDelay(aecpc);
-
-        // Note that 1 frame is supported for NB and 2 frames for WB.
-        for (i = 0; i < nFrames; i++) {
-            // Call the AEC.
-            WebRtcAec_ProcessFrame(aecpc->aec,
-                                   &nearend[FRAME_LEN * i],
-                                   &nearendH[FRAME_LEN * i],
-                                   aecpc->knownDelay,
-                                   &out[FRAME_LEN * i],
-                                   &outH[FRAME_LEN * i]);
-            // TODO(bjornv): Re-structure such that we don't have to pass
-            // |aecpc->knownDelay| as input. Change name to something like
-            // |system_buffer_diff|.
-        }
+      if (ProcessNormal(aecpc, nearend, nearendH, out, outH, nrOfSamples,
+                        msInSndCardBuf, skew) != 0) {
+        retVal = -1;
+      }
     }
 
 #ifdef WEBRTC_AEC_DEBUG_DUMP
     {
         int16_t far_buf_size_ms = (int16_t)(WebRtcAec_system_delay(aecpc->aec) /
             (sampMsNb * aecpc->rate_factor));
         (void)fwrite(&far_buf_size_ms, 2, 1, aecpc->bufFile);
         (void)fwrite(&aecpc->knownDelay, sizeof(aecpc->knownDelay), 1,
@@ -504,21 +425,16 @@ int32_t WebRtcAec_Process(void *aecInst,
     }
 #endif
 
     return retVal;
 }
 
 int WebRtcAec_set_config(void* handle, AecConfig config) {
   aecpc_t* self = (aecpc_t*)handle;
-
-  if (handle == NULL ) {
-    return -1;
-  }
-
   if (self->initFlag != initCheck) {
     self->lastError = AEC_UNINITIALIZED_ERROR;
     return -1;
   }
 
   if (config.skewMode != kAecFalse && config.skewMode != kAecTrue) {
     self->lastError = AEC_BAD_PARAMETER_ERROR;
     return -1;
@@ -543,20 +459,16 @@ int WebRtcAec_set_config(void* handle, A
 
   WebRtcAec_SetConfigCore(self->aec, config.nlpMode, config.metricsMode,
                           config.delay_logging);
   return 0;
 }
 
 int WebRtcAec_get_echo_status(void* handle, int* status) {
   aecpc_t* self = (aecpc_t*)handle;
-
-  if (handle == NULL ) {
-    return -1;
-  }
   if (status == NULL ) {
     self->lastError = AEC_NULL_POINTER_ERROR;
     return -1;
   }
   if (self->initFlag != initCheck) {
     self->lastError = AEC_UNINITIALIZED_ERROR;
     return -1;
   }
@@ -660,20 +572,16 @@ int WebRtcAec_GetMetrics(void* handle, A
     metrics->aNlp.min = kOffsetLevel;
   }
 
   return 0;
 }
 
 int WebRtcAec_GetDelayMetrics(void* handle, int* median, int* std) {
   aecpc_t* self = handle;
-
-  if (handle == NULL) {
-    return -1;
-  }
   if (median == NULL) {
     self->lastError = AEC_NULL_POINTER_ERROR;
     return -1;
   }
   if (std == NULL) {
     self->lastError = AEC_NULL_POINTER_ERROR;
     return -1;
   }
@@ -688,32 +596,245 @@ int WebRtcAec_GetDelayMetrics(void* hand
   }
 
   return 0;
 }
 
 int32_t WebRtcAec_get_error_code(void *aecInst)
 {
     aecpc_t *aecpc = aecInst;
-
-    if (aecpc == NULL) {
-        return -1;
-    }
-
     return aecpc->lastError;
 }
 
 AecCore* WebRtcAec_aec_core(void* handle) {
   if (!handle) {
     return NULL;
   }
   return ((aecpc_t*) handle)->aec;
 }
 
-static int EstBufDelay(aecpc_t* aecpc) {
+static int ProcessNormal(aecpc_t *aecpc, const int16_t *nearend,
+                         const int16_t *nearendH, int16_t *out, int16_t *outH,
+                         int16_t nrOfSamples, int16_t msInSndCardBuf,
+                         int32_t skew) {
+  int retVal = 0;
+  short i;
+  short nBlocks10ms;
+  short nFrames;
+  // Limit resampling to doubling/halving of signal
+  const float minSkewEst = -0.5f;
+  const float maxSkewEst = 1.0f;
+
+  msInSndCardBuf = msInSndCardBuf > kMaxTrustedDelayMs ?
+      kMaxTrustedDelayMs : msInSndCardBuf;
+  // TODO(andrew): we need to investigate if this +10 is really wanted.
+  msInSndCardBuf += 10;
+  aecpc->msInSndCardBuf = msInSndCardBuf;
+
+  if (aecpc->skewMode == kAecTrue) {
+    if (aecpc->skewFrCtr < 25) {
+      aecpc->skewFrCtr++;
+    }
+    else {
+      retVal = WebRtcAec_GetSkew(aecpc->resampler, skew, &aecpc->skew);
+      if (retVal == -1) {
+        aecpc->skew = 0;
+        aecpc->lastError = AEC_BAD_PARAMETER_WARNING;
+      }
+
+      aecpc->skew /= aecpc->sampFactor*nrOfSamples;
+
+      if (aecpc->skew < 1.0e-3 && aecpc->skew > -1.0e-3) {
+        aecpc->resample = kAecFalse;
+      }
+      else {
+        aecpc->resample = kAecTrue;
+      }
+
+      if (aecpc->skew < minSkewEst) {
+        aecpc->skew = minSkewEst;
+      }
+      else if (aecpc->skew > maxSkewEst) {
+        aecpc->skew = maxSkewEst;
+      }
+
+#ifdef WEBRTC_AEC_DEBUG_DUMP
+      (void)fwrite(&aecpc->skew, sizeof(aecpc->skew), 1, aecpc->skewFile);
+#endif
+    }
+  }
+
+  nFrames = nrOfSamples / FRAME_LEN;
+  nBlocks10ms = nFrames / aecpc->rate_factor;
+
+  if (aecpc->startup_phase) {
+    // Only needed if they don't already point to the same place.
+    if (nearend != out) {
+      memcpy(out, nearend, sizeof(short) * nrOfSamples);
+    }
+    if (nearendH != outH) {
+      memcpy(outH, nearendH, sizeof(short) * nrOfSamples);
+    }
+
+    // The AEC is in the start up mode
+    // AEC is disabled until the system delay is OK
+
+    // Mechanism to ensure that the system delay is reasonably stable.
+    if (aecpc->checkBuffSize) {
+      aecpc->checkBufSizeCtr++;
+      // Before we fill up the far-end buffer we require the system delay
+      // to be stable (+/-8 ms) compared to the first value. This
+      // comparison is made during the following 6 consecutive 10 ms
+      // blocks. If it seems to be stable then we start to fill up the
+      // far-end buffer.
+      if (aecpc->counter == 0) {
+        aecpc->firstVal = aecpc->msInSndCardBuf;
+        aecpc->sum = 0;
+      }
+
+      if (abs(aecpc->firstVal - aecpc->msInSndCardBuf) <
+        WEBRTC_SPL_MAX(0.2 * aecpc->msInSndCardBuf, sampMsNb)) {
+        aecpc->sum += aecpc->msInSndCardBuf;
+        aecpc->counter++;
+      }
+      else {
+        aecpc->counter = 0;
+      }
+
+      if (aecpc->counter * nBlocks10ms >= 6) {
+        // The far-end buffer size is determined in partitions of
+        // PART_LEN samples. Use 75% of the average value of the system
+        // delay as buffer size to start with.
+        aecpc->bufSizeStart = WEBRTC_SPL_MIN((3 * aecpc->sum *
+            aecpc->rate_factor * 8) / (4 * aecpc->counter * PART_LEN),
+            kMaxBufSizeStart);
+        // Buffer size has now been determined.
+        aecpc->checkBuffSize = 0;
+      }
+
+      if (aecpc->checkBufSizeCtr * nBlocks10ms > 50) {
+        // For really bad systems, don't disable the echo canceller for
+        // more than 0.5 sec.
+        aecpc->bufSizeStart = WEBRTC_SPL_MIN((aecpc->msInSndCardBuf *
+            aecpc->rate_factor * 3) / 40, kMaxBufSizeStart);
+        aecpc->checkBuffSize = 0;
+      }
+    }
+
+    // If |checkBuffSize| changed in the if-statement above.
+    if (!aecpc->checkBuffSize) {
+      // The system delay is now reasonably stable (or has been unstable
+      // for too long). When the far-end buffer is filled with
+      // approximately the same amount of data as reported by the system
+      // we end the startup phase.
+      int overhead_elements =
+          WebRtcAec_system_delay(aecpc->aec) / PART_LEN - aecpc->bufSizeStart;
+      if (overhead_elements == 0) {
+        // Enable the AEC
+        aecpc->startup_phase = 0;
+      } else if (overhead_elements > 0) {
+        // TODO(bjornv): Do we need a check on how much we actually
+        // moved the read pointer? It should always be possible to move
+        // the pointer |overhead_elements| since we have only added data
+        // to the buffer and no delay compensation nor AEC processing
+        // has been done.
+        WebRtcAec_MoveFarReadPtr(aecpc->aec, overhead_elements);
+
+        // Enable the AEC
+        aecpc->startup_phase = 0;
+      }
+    }
+  } else {
+    // AEC is enabled.
+    EstBufDelayNormal(aecpc);
+
+    // Note that 1 frame is supported for NB and 2 frames for WB.
+    for (i = 0; i < nFrames; i++) {
+      // Call the AEC.
+      WebRtcAec_ProcessFrame(aecpc->aec,
+                             &nearend[FRAME_LEN * i],
+                             &nearendH[FRAME_LEN * i],
+                             aecpc->knownDelay,
+                             &out[FRAME_LEN * i],
+                             &outH[FRAME_LEN * i]);
+      // TODO(bjornv): Re-structure such that we don't have to pass
+      // |aecpc->knownDelay| as input. Change name to something like
+      // |system_buffer_diff|.
+    }
+  }
+
+  return retVal;
+}
+
+static void ProcessExtended(aecpc_t* self, const int16_t* near,
+    const int16_t* near_high, int16_t* out, int16_t* out_high,
+    int16_t num_samples, int16_t reported_delay_ms, int32_t skew) {
+  int i;
+  const int num_frames = num_samples / FRAME_LEN;
+#if defined(WEBRTC_UNTRUSTED_DELAY)
+  const int delay_diff_offset = kDelayDiffOffsetSamples;
+  reported_delay_ms = kFixedDelayMs;
+#else
+  // This is the usual mode where we trust the reported system delay values.
+  const int delay_diff_offset = 0;
+  // Due to the longer filter, we no longer add 10 ms to the reported delay
+  // to reduce chance of non-causality. Instead we apply a minimum here to avoid
+  // issues with the read pointer jumping around needlessly.
+  reported_delay_ms = reported_delay_ms < kMinTrustedDelayMs ?
+      kMinTrustedDelayMs : reported_delay_ms;
+  // If the reported delay appears to be bogus, we attempt to recover by using
+  // the measured fixed delay values. We use >= here because higher layers
+  // may already clamp to this maximum value, and we would otherwise not
+  // detect it here.
+  reported_delay_ms = reported_delay_ms >= kMaxTrustedDelayMs ?
+      kFixedDelayMs : reported_delay_ms;
+#endif
+  self->msInSndCardBuf = reported_delay_ms;
+
+  if (!self->farend_started) {
+    // Only needed if they don't already point to the same place.
+    if (near != out) {
+      memcpy(out, near, sizeof(short) * num_samples);
+    }
+    if (near_high != out_high) {
+      memcpy(out_high, near_high, sizeof(short) * num_samples);
+    }
+    return;
+  }
+  if (self->startup_phase) {
+    // In the extended mode, there isn't a startup "phase", just a special
+    // action on the first frame. In the trusted delay case, we'll take the
+    // current reported delay, unless it's less then our conservative
+    // measurement.
+    int startup_size_ms = reported_delay_ms < kFixedDelayMs ?
+        kFixedDelayMs : reported_delay_ms;
+    int overhead_elements = (WebRtcAec_system_delay(self->aec) -
+        startup_size_ms / 2 * self->rate_factor * 8) / PART_LEN;
+    WebRtcAec_MoveFarReadPtr(self->aec, overhead_elements);
+    self->startup_phase = 0;
+  }
+
+  EstBufDelayExtended(self);
+
+  {
+    // |delay_diff_offset| gives us the option to manually rewind the delay on
+    // very low delay platforms which can't be expressed purely through
+    // |reported_delay_ms|.
+    const int adjusted_known_delay =
+        WEBRTC_SPL_MAX(0, self->knownDelay + delay_diff_offset);
+
+    for (i = 0; i < num_frames; ++i) {
+      WebRtcAec_ProcessFrame(self->aec, &near[FRAME_LEN * i],
+          &near_high[FRAME_LEN * i], adjusted_known_delay,
+          &out[FRAME_LEN * i], &out_high[FRAME_LEN * i]);
+    }
+  }
+}
+
+static void EstBufDelayNormal(aecpc_t* aecpc) {
   int nSampSndCard = aecpc->msInSndCardBuf * sampMsNb * aecpc->rate_factor;
   int current_delay = nSampSndCard - WebRtcAec_system_delay(aecpc->aec);
   int delay_difference = 0;
 
   // Before we proceed with the delay estimate filtering we:
   // 1) Compensate for the frame that will be read.
   // 2) Compensate for drift resampling.
   // 3) Compensate for non-causality if needed, since the estimated delay can't
@@ -727,18 +848,21 @@ static int EstBufDelay(aecpc_t* aecpc) {
     current_delay -= kResamplingDelay;
   }
 
   // 3) Compensate for non-causality, if needed, by flushing one block.
   if (current_delay < PART_LEN) {
     current_delay += WebRtcAec_MoveFarReadPtr(aecpc->aec, 1) * PART_LEN;
   }
 
+  // We use -1 to signal an initialized state in the "extended" implementation;
+  // compensate for that.
+  aecpc->filtDelay = aecpc->filtDelay < 0 ? 0 : aecpc->filtDelay;
   aecpc->filtDelay = WEBRTC_SPL_MAX(0, (short) (0.8 * aecpc->filtDelay +
-          0.2 * current_delay));
+      0.2 * current_delay));
 
   delay_difference = aecpc->filtDelay - aecpc->knownDelay;
   if (delay_difference > 224) {
     if (aecpc->lastDelayDiff < 96) {
       aecpc->timeForDelayChange = 0;
     } else {
       aecpc->timeForDelayChange++;
     }
@@ -751,11 +875,63 @@ static int EstBufDelay(aecpc_t* aecpc) {
   } else {
     aecpc->timeForDelayChange = 0;
   }
   aecpc->lastDelayDiff = delay_difference;
 
   if (aecpc->timeForDelayChange > 25) {
     aecpc->knownDelay = WEBRTC_SPL_MAX((int) aecpc->filtDelay - 160, 0);
   }
+}
 
-  return 0;
+static void EstBufDelayExtended(aecpc_t* self) {
+  int reported_delay = self->msInSndCardBuf * sampMsNb * self->rate_factor;
+  int current_delay = reported_delay - WebRtcAec_system_delay(self->aec);
+  int delay_difference = 0;
+
+  // Before we proceed with the delay estimate filtering we:
+  // 1) Compensate for the frame that will be read.
+  // 2) Compensate for drift resampling.
+  // 3) Compensate for non-causality if needed, since the estimated delay can't
+  //    be negative.
+
+  // 1) Compensating for the frame(s) that will be read/processed.
+  current_delay += FRAME_LEN * self->rate_factor;
+
+  // 2) Account for resampling frame delay.
+  if (self->skewMode == kAecTrue && self->resample == kAecTrue) {
+    current_delay -= kResamplingDelay;
+  }
+
+  // 3) Compensate for non-causality, if needed, by flushing two blocks.
+  if (current_delay < PART_LEN) {
+    current_delay += WebRtcAec_MoveFarReadPtr(self->aec, 2) * PART_LEN;
+  }
+
+  if (self->filtDelay == -1) {
+    self->filtDelay = WEBRTC_SPL_MAX(0, 0.5 * current_delay);
+  } else {
+    self->filtDelay = WEBRTC_SPL_MAX(0, (short) (0.95 * self->filtDelay +
+        0.05 * current_delay));
+  }
+
+  delay_difference = self->filtDelay - self->knownDelay;
+  if (delay_difference > 384) {
+    if (self->lastDelayDiff < 128) {
+      self->timeForDelayChange = 0;
+    } else {
+      self->timeForDelayChange++;
+    }
+  } else if (delay_difference < 128 && self->knownDelay > 0) {
+    if (self->lastDelayDiff > 384) {
+      self->timeForDelayChange = 0;
+    } else {
+      self->timeForDelayChange++;
+    }
+  } else {
+    self->timeForDelayChange = 0;
+  }
+  self->lastDelayDiff = delay_difference;
+
+  if (self->timeForDelayChange > 25) {
+    self->knownDelay = WEBRTC_SPL_MAX((int) self->filtDelay - 256, 0);
+  }
 }
--- a/media/webrtc/trunk/webrtc/modules/audio_processing/aec/echo_cancellation_internal.h
+++ b/media/webrtc/trunk/webrtc/modules/audio_processing/aec/echo_cancellation_internal.h
@@ -15,18 +15,16 @@
 #include "webrtc/modules/audio_processing/utility/ring_buffer.h"
 
 typedef struct {
   int delayCtr;
   int sampFreq;
   int splitSampFreq;
   int scSampFreq;
   float sampFactor;  // scSampRate / sampFreq
-  short autoOnOff;
-  short activity;
   short skewMode;
   int bufSizeStart;
   int knownDelay;
   int rate_factor;
 
   short initFlag;  // indicates if AEC has been initialized
 
   // Variables used for averaging far end buffer size
@@ -34,17 +32,17 @@ typedef struct {
   int sum;
   short firstVal;
   short checkBufSizeCtr;
 
   // Variables used for delay shifts
   short msInSndCardBuf;
   short filtDelay;  // Filtered delay estimate.
   int timeForDelayChange;
-  int ECstartup;
+  int startup_phase;
   int checkBuffSize;
   short lastDelayDiff;
 
 #ifdef WEBRTC_AEC_DEBUG_DUMP
   RingBuffer* far_pre_buf_s16;  // Time domain far-end pre-buffer in int16_t.
   FILE* bufFile;
   FILE* delayFile;
   FILE* skewFile;
@@ -57,12 +55,14 @@ typedef struct {
   int resample;  // if the skew is small enough we don't resample
   int highSkewCtr;
   float skew;
 
   RingBuffer* far_pre_buf;  // Time domain far-end pre-buffer.
 
   int lastError;
 
+  int farend_started;
+
   AecCore* aec;
 } aecpc_t;
 
 #endif  // WEBRTC_MODULES_AUDIO_PROCESSING_AEC_ECHO_CANCELLATION_INTERNAL_H_
--- a/media/webrtc/trunk/webrtc/modules/audio_processing/aec/system_delay_unittest.cc
+++ b/media/webrtc/trunk/webrtc/modules/audio_processing/aec/system_delay_unittest.cc
@@ -123,17 +123,17 @@ void SystemDelayTest::RunStableStartup()
   // Process().
   int buffer_size = BufferFillUp();
   // A stable device should be accepted and put in a regular process mode within
   // |kStableConvergenceMs|.
   int process_time_ms = 0;
   for (; process_time_ms < kStableConvergenceMs; process_time_ms += 10) {
     RenderAndCapture(kDeviceBufMs);
     buffer_size += samples_per_frame_;
-    if (self_->ECstartup == 0) {
+    if (self_->startup_phase == 0) {
       // We have left the startup phase.
       break;
     }
   }
   // Verify convergence time.
   EXPECT_GT(kStableConvergenceMs, process_time_ms);
   // Verify that the buffer has been flushed.
   EXPECT_GE(buffer_size, WebRtcAec_system_delay(self_->aec));
@@ -217,17 +217,17 @@ TEST_F(SystemDelayTest, CorrectDelayAfte
     int buffer_offset_ms = 25;
     int reported_delay_ms = 0;
     int process_time_ms = 0;
     for (; process_time_ms <= kMaxConvergenceMs; process_time_ms += 10) {
       reported_delay_ms = kDeviceBufMs + buffer_offset_ms;
       RenderAndCapture(reported_delay_ms);
       buffer_size += samples_per_frame_;
       buffer_offset_ms = -buffer_offset_ms;
-      if (self_->ECstartup == 0) {
+      if (self_->startup_phase == 0) {
         // We have left the startup phase.
         break;
       }
     }
     // Verify convergence time.
     EXPECT_GE(kMaxConvergenceMs, process_time_ms);
     // Verify that the buffer has been flushed.
     EXPECT_GE(buffer_size, WebRtcAec_system_delay(self_->aec));
@@ -263,17 +263,17 @@ TEST_F(SystemDelayTest, CorrectDelayAfte
     // We now have established the required buffer size. Let us verify that we
     // fill up before leaving the startup phase for normal processing.
     int buffer_size = 0;
     int target_buffer_size = kDeviceBufMs * samples_per_frame_ / 10 * 3 / 4;
     process_time_ms = 0;
     for (; process_time_ms <= kMaxConvergenceMs; process_time_ms += 10) {
       RenderAndCapture(kDeviceBufMs);
       buffer_size += samples_per_frame_;
-      if (self_->ECstartup == 0) {
+      if (self_->startup_phase == 0) {
         // We have left the startup phase.
         break;
       }
     }
     // Verify convergence time.
     EXPECT_GT(kMaxConvergenceMs, process_time_ms);
     // Verify that the buffer has reached the desired size.
     EXPECT_LE(target_buffer_size, WebRtcAec_system_delay(self_->aec));
--- a/media/webrtc/trunk/webrtc/modules/audio_processing/echo_cancellation_impl.cc
+++ b/media/webrtc/trunk/webrtc/modules/audio_processing/echo_cancellation_impl.cc
@@ -8,22 +8,24 @@
  *  be found in the AUTHORS file in the root of the source tree.
  */
 
 #include "webrtc/modules/audio_processing/echo_cancellation_impl.h"
 
 #include <assert.h>
 #include <string.h>
 
+extern "C" {
+#include "webrtc/modules/audio_processing/aec/aec_core.h"
+}
+#include "webrtc/modules/audio_processing/aec/include/echo_cancellation.h"
 #include "webrtc/modules/audio_processing/audio_buffer.h"
 #include "webrtc/modules/audio_processing/audio_processing_impl.h"
 #include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
 
-#include "webrtc/modules/audio_processing/aec/include/echo_cancellation.h"
-
 namespace webrtc {
 
 typedef void Handle;
 
 namespace {
 int16_t MapSetting(EchoCancellation::SuppressionLevel level) {
   switch (level) {
     case EchoCancellation::kLowSuppression:
@@ -59,17 +61,18 @@ EchoCancellationImpl::EchoCancellationIm
     apm_(apm),
     drift_compensation_enabled_(false),
     metrics_enabled_(false),
     suppression_level_(kModerateSuppression),
     device_sample_rate_hz_(48000),
     stream_drift_samples_(0),
     was_stream_drift_set_(false),
     stream_has_echo_(false),
-    delay_logging_enabled_(false) {}
+    delay_logging_enabled_(false),
+    delay_correction_enabled_(true) {}
 
 EchoCancellationImpl::~EchoCancellationImpl() {}
 
 int EchoCancellationImpl::ProcessRenderAudio(const AudioBuffer* audio) {
   if (!is_component_enabled()) {
     return apm_->kNoError;
   }
 
@@ -328,16 +331,23 @@ int EchoCancellationImpl::Initialize() {
     return err;
   }
 
   was_stream_drift_set_ = false;
 
   return apm_->kNoError;
 }
 
+#if 0
+void EchoCancellationImpl::SetExtraOptions(const Config& config) {
+  delay_correction_enabled_ = config.Get<DelayCorrection>().enabled;
+  Configure();
+}
+#endif
+
 void* EchoCancellationImpl::CreateHandle() const {
   Handle* handle = NULL;
   if (WebRtcAec_Create(&handle) != apm_->kNoError) {
     handle = NULL;
   } else {
     assert(handle != NULL);
   }
 
@@ -359,16 +369,18 @@ int EchoCancellationImpl::InitializeHand
 int EchoCancellationImpl::ConfigureHandle(void* handle) const {
   assert(handle != NULL);
   AecConfig config;
   config.metricsMode = metrics_enabled_;
   config.nlpMode = MapSetting(suppression_level_);
   config.skewMode = drift_compensation_enabled_;
   config.delay_logging = delay_logging_enabled_;
 
+  WebRtcAec_enable_delay_correction(WebRtcAec_aec_core(
+      static_cast<Handle*>(handle)), delay_correction_enabled_ ? 1 : 0);
   return WebRtcAec_set_config(static_cast<Handle*>(handle), config);
 }
 
 int EchoCancellationImpl::num_handles_required() const {
   return apm_->num_output_channels() *
          apm_->num_reverse_channels();
 }
 
--- a/media/webrtc/trunk/webrtc/modules/audio_processing/echo_cancellation_impl.h
+++ b/media/webrtc/trunk/webrtc/modules/audio_processing/echo_cancellation_impl.h
@@ -10,16 +10,40 @@
 
 #ifndef WEBRTC_MODULES_AUDIO_PROCESSING_ECHO_CANCELLATION_IMPL_H_
 #define WEBRTC_MODULES_AUDIO_PROCESSING_ECHO_CANCELLATION_IMPL_H_
 
 #include "webrtc/modules/audio_processing/include/audio_processing.h"
 #include "webrtc/modules/audio_processing/processing_component.h"
 
 namespace webrtc {
+// Use to enable the delay correction feature. This now engages an extended
+// filter mode in the AEC, along with robustness measures around the reported
+// system delays. It comes with a significant increase in AEC complexity, but is
+// much more robust to unreliable reported delays.
+//
+// Detailed changes to the algorithm:
+// - The filter length is changed from 48 to 128 ms. This comes with tuning of
+//   several parameters: i) filter adaptation stepsize and error threshold;
+//   ii) non-linear processing smoothing and overdrive.
+// - Option to ignore the reported delays on platforms which we deem
+//   sufficiently unreliable. See WEBRTC_UNTRUSTED_DELAY in echo_cancellation.c.
+// - Faster startup times by removing the excessive "startup phase" processing
+//   of reported delays.
+// - Much more conservative adjustments to the far-end read pointer. We smooth
+//   the delay difference more heavily, and back off from the difference more.
+//   Adjustments force a readaptation of the filter, so they should be avoided
+//   except when really necessary.
+struct DelayCorrection {
+  DelayCorrection() : enabled(false) {}
+  DelayCorrection(bool enabled) : enabled(enabled) {}
+
+  bool enabled;
+};
+
 class AudioProcessingImpl;
 class AudioBuffer;
 
 class EchoCancellationImpl : public EchoCancellation,
                              public ProcessingComponent {
  public:
   explicit EchoCancellationImpl(const AudioProcessingImpl* apm);
   virtual ~EchoCancellationImpl();
@@ -29,16 +53,17 @@ class EchoCancellationImpl : public Echo
 
   // EchoCancellation implementation.
   virtual bool is_enabled() const;
   virtual int device_sample_rate_hz() const;
   virtual int stream_drift_samples() const;
 
   // ProcessingComponent implementation.
   virtual int Initialize();
+  //  virtual void SetExtraOptions(const Config& config) OVERRIDE;
 
  private:
   // EchoCancellation implementation.
   virtual int Enable(bool enable);
   virtual int enable_drift_compensation(bool enable);
   virtual bool is_drift_compensation_enabled() const;
   virtual int set_device_sample_rate_hz(int rate);
   virtual void set_stream_drift_samples(int drift);
@@ -65,12 +90,13 @@ class EchoCancellationImpl : public Echo
   bool drift_compensation_enabled_;
   bool metrics_enabled_;
   SuppressionLevel suppression_level_;
   int device_sample_rate_hz_;
   int stream_drift_samples_;
   bool was_stream_drift_set_;
   bool stream_has_echo_;
   bool delay_logging_enabled_;
+  bool delay_correction_enabled_;
 };
 }  // namespace webrtc
 
 #endif  // WEBRTC_MODULES_AUDIO_PROCESSING_ECHO_CANCELLATION_IMPL_H_
new file mode 100644
--- /dev/null
+++ b/media/webrtc/trunk/webrtc/modules/audio_processing/echo_cancellation_impl_unittest.cc
@@ -0,0 +1,51 @@
+/*
+ *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
+ *
+ *  Use of this source code is governed by a BSD-style license
+ *  that can be found in the LICENSE file in the root of the source
+ *  tree. An additional intellectual property rights grant can be found
+ *  in the file PATENTS.  All contributing project authors may
+ *  be found in the AUTHORS file in the root of the source tree.
+ */
+
+#include "testing/gtest/include/gtest/gtest.h"
+extern "C" {
+#include "webrtc/modules/audio_processing/aec/aec_core.h"
+}
+#include "webrtc/modules/audio_processing/echo_cancellation_impl.h"
+#include "webrtc/modules/audio_processing/include/audio_processing.h"
+#include "webrtc/system_wrappers/interface/scoped_ptr.h"
+
+namespace webrtc {
+
+TEST(EchoCancellationInternalTest, DelayCorrection) {
+  scoped_ptr<AudioProcessing> ap(AudioProcessing::Create(0));
+  EXPECT_TRUE(ap->echo_cancellation()->aec_core() == NULL);
+
+  EXPECT_EQ(ap->kNoError, ap->echo_cancellation()->Enable(true));
+  EXPECT_TRUE(ap->echo_cancellation()->is_enabled());
+
+  AecCore* aec_core = ap->echo_cancellation()->aec_core();
+  ASSERT_TRUE(aec_core != NULL);
+  // Disabled by default.
+  EXPECT_EQ(0, WebRtcAec_delay_correction_enabled(aec_core));
+
+  Config config;
+  config.Set<DelayCorrection>(new DelayCorrection(true));
+  ap->SetExtraOptions(config);
+  EXPECT_EQ(1, WebRtcAec_delay_correction_enabled(aec_core));
+
+  // Retains setting after initialization.
+  EXPECT_EQ(ap->kNoError, ap->Initialize());
+  EXPECT_EQ(1, WebRtcAec_delay_correction_enabled(aec_core));
+
+  config.Set<DelayCorrection>(new DelayCorrection(false));
+  ap->SetExtraOptions(config);
+  EXPECT_EQ(0, WebRtcAec_delay_correction_enabled(aec_core));
+
+  // Retains setting after initialization.
+  EXPECT_EQ(ap->kNoError, ap->Initialize());
+  EXPECT_EQ(0, WebRtcAec_delay_correction_enabled(aec_core));
+}
+
+}  // namespace webrtc
--- a/media/webrtc/trunk/webrtc/modules/modules.gyp
+++ b/media/webrtc/trunk/webrtc/modules/modules.gyp
@@ -138,16 +138,17 @@
             'audio_coding/neteq4/mock/mock_delay_peak_detector.h',
             'audio_coding/neteq4/mock/mock_dtmf_buffer.h',
             'audio_coding/neteq4/mock/mock_dtmf_tone_generator.h',
             'audio_coding/neteq4/mock/mock_external_decoder_pcm16b.h',
             'audio_coding/neteq4/mock/mock_packet_buffer.h',
             'audio_coding/neteq4/mock/mock_payload_splitter.h',
             'audio_processing/aec/system_delay_unittest.cc',
             'audio_processing/aec/echo_cancellation_unittest.cc',
+            'audio_processing/echo_cancellation_impl_unittest.cc',
             'audio_processing/test/unit_test.cc',
             'audio_processing/utility/delay_estimator_unittest.cc',
             'audio_processing/utility/ring_buffer_unittest.cc',
             'bitrate_controller/bitrate_controller_unittest.cc',
             'desktop_capture/desktop_region_unittest.cc',
             'desktop_capture/differ_block_unittest.cc',
             'desktop_capture/differ_unittest.cc',
             'desktop_capture/screen_capturer_helper_unittest.cc',
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -1369,16 +1369,34 @@ pref("network.ftp.idleConnectionTimeout"
 // 2: HTML
 // 3: XUL directory viewer
 // all other values are treated like 2
 pref("network.dir.format", 2);
 
 // enables the prefetch service (i.e., prefetching of <link rel="next"> URLs).
 pref("network.prefetch-next", true);
 
+// enables the predictive service
+pref("network.seer.enabled", true);
+pref("network.seer.enable-hover-on-ssl", false);
+pref("network.seer.page-degradation.day", 0);
+pref("network.seer.page-degradation.week", 5);
+pref("network.seer.page-degradation.month", 10);
+pref("network.seer.page-degradation.year", 25);
+pref("network.seer.page-degradation.max", 50);
+pref("network.seer.subresource-degradation.day", 1);
+pref("network.seer.subresource-degradation.week", 10);
+pref("network.seer.subresource-degradation.month", 25);
+pref("network.seer.subresource-degradation.year", 50);
+pref("network.seer.subresource-degradation.max", 100);
+pref("network.seer.preconnect-min-confidence", 90);
+pref("network.seer.preresolve-min-confidence", 60);
+pref("network.seer.redirect-likely-confidence", 75);
+pref("network.seer.max-queue-size", 50);
+
 
 // The following prefs pertain to the negotiate-auth extension (see bug 17578),
 // which provides transparent Kerberos or NTLM authentication using the SPNEGO
 // protocol.  Each pref is a comma-separated list of keys, where each key has
 // the format:
 //   [scheme "://"] [host [":" port]]
 // For example, "foo.com" would match "http://www.foo.com/bar", etc.
 
--- a/netwerk/base/public/moz.build
+++ b/netwerk/base/public/moz.build
@@ -51,16 +51,18 @@ XPIDL_SOURCES += [
     'nsILoadGroupChild.idl',
     'nsIMIMEInputStream.idl',
     'nsIMultiPartChannel.idl',
     'nsINestedURI.idl',
     'nsINetAddr.idl',
     'nsINetUtil.idl',
     'nsINetworkLinkService.idl',
     'nsINetworkProperties.idl',
+    'nsINetworkSeer.idl',
+    'nsINetworkSeerVerifier.idl',
     'nsINSSErrorsService.idl',
     'nsIParentChannel.idl',
     'nsIParentRedirectingChannel.idl',
     'nsIPermission.idl',
     'nsIPermissionManager.idl',
     'nsIPrivateBrowsingChannel.idl',
     'nsIProgressEventSink.idl',
     'nsIPrompt.idl',
new file mode 100644
--- /dev/null
+++ b/netwerk/base/public/nsINetworkSeer.idl
@@ -0,0 +1,164 @@
+/* vim: set ts=2 sts=2 et sw=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+interface nsILoadContext;
+interface nsINetworkSeerVerifier;
+
+typedef unsigned long SeerPredictReason;
+typedef unsigned long SeerLearnReason;
+
+/**
+ * nsINetworkSeer - learn about pages users visit, and allow us to take
+ *                  predictive actions upon future visits.
+ *                  NOTE: nsINetworkSeer should only be used on the main thread
+ */
+[scriptable, uuid(884a39a0-a3ed-4855-826a-fabb73ae878d)]
+interface nsINetworkSeer : nsISupports
+{
+  /**
+   * Prediction reasons
+   *
+   * PREDICT_LINK - we are being asked to take predictive action because
+   * the user is hovering over a link.
+   *
+   * PREDICT_LOAD - we are being asked to take predictive action because
+   * the user has initiated a pageload.
+   *
+   * PREDICT_STARTUP - we are being asked to take predictive action
+   * because the browser is starting up.
+   */
+  const SeerPredictReason PREDICT_LINK = 0;
+  const SeerPredictReason PREDICT_LOAD = 1;
+  const SeerPredictReason PREDICT_STARTUP = 2;
+
+  /**
+   * Start taking predictive actions
+   *
+   * Calling this will cause the seer to (possibly) start
+   * taking actions such as DNS prefetch and/or TCP preconnect based on
+   * (1) the host name that we are given, and (2) the reason we are being
+   * asked to take actions.
+   *
+   * @param targetURI - The URI we are being asked to take actions based on.
+   * @param sourceURI - The URI that is currently loaded. This is so we can
+   *   avoid doing predictive actions for link hover on an HTTPS page (for
+   *   example).
+   * @param reason - The reason we are being asked to take actions. Can be
+   *   any of the PREDICT_* values above.
+   *   In the case of PREDICT_LINK, targetURI should be the URI of the link
+   *   that is being hovered over, and sourceURI should be the URI of the page
+   *   on which the link appears.
+   *   In the case of PREDICT_LOAD, targetURI should be the URI of the page that
+   *   is being loaded and sourceURI should be null.
+   *   In the case of PREDICT_STARTUP, both targetURI and sourceURI should be
+   *   null.
+   * @param loadContext - The nsILoadContext of the page load we are predicting
+   *   about.
+   * @param verifier - An nsINetworkSeerVerifier used in testing to ensure we're
+   *   predicting the way we expect to. Not necessary (or desired) for normal
+   *   operation.
+   */
+  void predict(in nsIURI targetURI,
+               in nsIURI sourceURI,
+               in SeerPredictReason reason,
+               in nsILoadContext loadContext,
+               in nsINetworkSeerVerifier verifier);
+
+
+  /*
+   * Reasons we are learning something
+   *
+   * LEARN_LOAD_TOPLEVEL - we are learning about the toplevel resource of a
+   *                       pageload (NOTE: this should ONLY be used by tests)
+   *
+   * LEARN_LOAD_SUBRESOURCE - we are learning a subresource from a pageload
+   *
+   * LEARN_LOAD_REDIRECT - we are learning about the re-direct of a URI
+   *
+   * LEARN_STARTUP - we are learning about a page loaded during startup
+   */
+  const SeerLearnReason LEARN_LOAD_TOPLEVEL = 0;
+  const SeerLearnReason LEARN_LOAD_SUBRESOURCE = 1;
+  const SeerLearnReason LEARN_LOAD_REDIRECT = 2;
+  const SeerLearnReason LEARN_STARTUP = 3;
+
+  /**
+   * Add to our compendium of knowledge
+   *
+   * This adds to our prediction database to make things (hopefully)
+   * smarter next time we predict something.
+   *
+   * @param targetURI - The URI that was loaded that we are keeping track of.
+   * @param sourceURI - The URI that caused targetURI to be loaded (for page
+   *   loads). This means the DOCUMENT URI.
+   * @param reason - The reason we are learning this bit of knowledge.
+   *   Reason can be any of the LEARN_* values.
+   *   In the case of LEARN_LOAD_SUBRESOURCE, targetURI should be the URI of a
+   *   subresource of a page, and sourceURI should be the top-level URI.
+   *   In the case of LEARN_LOAD_REDIRECT, targetURI is the NEW URI of a
+   *   top-level resource that was redirected to, and sourceURI is the
+   *   ORIGINAL URI of said top-level resource.
+   *   In the case of LEARN_STARTUP, targetURI should be the URI of a page
+   *   that was loaded immediately after browser startup, and sourceURI should
+   *   be null.
+   * @param loadContext - The nsILoadContext for the page load that we are
+   *   learning about.
+   */
+  void learn(in nsIURI targetURI,
+             in nsIURI sourceURI,
+             in SeerLearnReason reason,
+             in nsILoadContext loadContext);
+
+  /**
+   * Clear out all our learned knowledge
+   *
+   * This removes everything from our database so that any predictions begun
+   * after this completes will start from a blank slate.
+   */
+  void reset();
+};
+
+%{C++
+// Wrapper functions to make use of the seer easier and less invasive
+class nsIChannel;
+class nsIDocument;
+class nsILoadContext;
+class nsILoadGroup;
+class nsINetworkSeerVerifier;
+
+namespace mozilla {
+namespace net {
+
+nsresult SeerPredict(nsIURI *targetURI,
+                     nsIURI *sourceURI,
+                     SeerPredictReason reason,
+                     nsILoadContext *loadContext,
+                     nsINetworkSeerVerifier *verifier);
+
+nsresult SeerLearn(nsIURI *targetURI,
+                   nsIURI *sourceURI,
+                   SeerLearnReason reason,
+                   nsILoadContext *loadContext);
+
+nsresult SeerLearn(nsIURI *targetURI,
+                   nsIURI *sourceURI,
+                   SeerLearnReason reason,
+                   nsILoadGroup *loadGroup);
+
+nsresult SeerLearn(nsIURI *targetURI,
+                   nsIURI *sourceURI,
+                   SeerLearnReason reason,
+                   nsIDocument *document);
+
+nsresult SeerLearnRedirect(nsIURI *targetURI,
+                           nsIChannel *channel,
+                           nsILoadContext *loadContext);
+
+} // mozilla::net
+} // mozilla
+%}
new file mode 100644
--- /dev/null
+++ b/netwerk/base/public/nsINetworkSeerVerifier.idl
@@ -0,0 +1,31 @@
+/* vim: set ts=2 sts=2 et sw=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * nsINetworkSeerVerifier - used for testing the network seer to ensure it
+ *                          does what we expect it to do.
+ */
+
+#include "nsISupports.idl"
+
+interface nsIURI;
+
+[scriptable, uuid(ea273653-43a8-4632-8b30-4032e0918e8b)]
+interface nsINetworkSeerVerifier : nsISupports
+{
+    /**
+     * Callback for when we do a predictive preconnect
+     *
+     * @param uri - The URI that was preconnected to
+     */
+    void onPredictPreconnect(in nsIURI uri);
+
+    /**
+     * Callback for when we do a predictive DNS lookup
+     *
+     * @param uri - The URI that was looked up
+     */
+    void onPredictDNS(in nsIURI uri);
+};
--- a/netwerk/base/public/nsISpeculativeConnect.idl
+++ b/netwerk/base/public/nsISpeculativeConnect.idl
@@ -25,8 +25,36 @@ interface nsISpeculativeConnect : nsISup
      *        such as nsIBadCertListener. May be null.
      *
      */
     void speculativeConnect(in nsIURI aURI,
                             in nsIInterfaceRequestor aCallbacks);
 
 };
 
+/**
+ * This is used to override the default values for various values (documented
+ * inline) to determine whether or not to actually make a speculative
+ * connection.
+ */
+[builtinclass, uuid(2b6d6fb6-ab28-4f4c-af84-bfdbb7866d72)]
+interface nsISpeculativeConnectionOverrider : nsISupports
+{
+    /**
+     * Used to determine the maximum number of unused speculative connections
+     * we will have open for a host at any one time
+     */
+    [infallible] readonly attribute unsigned long parallelSpeculativeConnectLimit;
+
+    /**
+     * Used to loosen the restrictions nsHttpConnectionMgr::RestrictConnections
+     * to allow more speculative connections when we're unsure if a host will
+     * connect via SPDY or not.
+     */
+    [infallible] readonly attribute boolean ignorePossibleSpdyConnections;
+
+    /**
+     * Used to determine if we will ignore the existence of any currently idle
+     * connections when we decide whether or not to make a speculative
+     * connection.
+     */
+    [infallible] readonly attribute boolean ignoreIdle;
+};
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/Seer.cpp
@@ -0,0 +1,2189 @@
+/* vim: set ts=2 sts=2 et sw=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <algorithm>
+
+#include "Seer.h"
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsICancelable.h"
+#include "nsIChannel.h"
+#include "nsIDNSListener.h"
+#include "nsIDNSService.h"
+#include "nsIDocument.h"
+#include "nsIFile.h"
+#include "nsILoadContext.h"
+#include "nsILoadGroup.h"
+#include "nsINetworkSeerVerifier.h"
+#include "nsIObserverService.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefService.h"
+#include "nsISpeculativeConnect.h"
+#include "nsIURI.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "prlog.h"
+
+#include "mozIStorageConnection.h"
+#include "mozIStorageService.h"
+#include "mozIStorageStatement.h"
+#include "mozStorageHelper.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/storage.h"
+#include "mozilla/Telemetry.h"
+
+#if defined(ANDROID) && !defined(MOZ_WIDGET_GONK)
+#include "nsIPropertyBag2.h"
+static const int32_t ANDROID_23_VERSION = 10;
+#endif
+
+using namespace mozilla;
+
+namespace mozilla {
+namespace net {
+
+#define RETURN_IF_FAILED(_rv) \
+  do { \
+    if (NS_FAILED(_rv)) { \
+      return; \
+    } \
+  } while (0)
+
+const char SEER_ENABLED_PREF[] = "network.seer.enabled";
+const char SEER_SSL_HOVER_PREF[] = "network.seer.enable-hover-on-ssl";
+
+const char SEER_PAGE_DELTA_DAY_PREF[] = "network.seer.page-degradation.day";
+const int SEER_PAGE_DELTA_DAY_DEFAULT = 0;
+const char SEER_PAGE_DELTA_WEEK_PREF[] = "network.seer.page-degradation.week";
+const int SEER_PAGE_DELTA_WEEK_DEFAULT = 5;
+const char SEER_PAGE_DELTA_MONTH_PREF[] = "network.seer.page-degradation.month";
+const int SEER_PAGE_DELTA_MONTH_DEFAULT = 10;
+const char SEER_PAGE_DELTA_YEAR_PREF[] = "network.seer.page-degradation.year";
+const int SEER_PAGE_DELTA_YEAR_DEFAULT = 25;
+const char SEER_PAGE_DELTA_MAX_PREF[] = "network.seer.page-degradation.max";
+const int SEER_PAGE_DELTA_MAX_DEFAULT = 50;
+const char SEER_SUB_DELTA_DAY_PREF[] =
+  "network.seer.subresource-degradation.day";
+const int SEER_SUB_DELTA_DAY_DEFAULT = 1;
+const char SEER_SUB_DELTA_WEEK_PREF[] =
+  "network.seer.subresource-degradation.week";
+const int SEER_SUB_DELTA_WEEK_DEFAULT = 10;
+const char SEER_SUB_DELTA_MONTH_PREF[] =
+  "network.seer.subresource-degradation.month";
+const int SEER_SUB_DELTA_MONTH_DEFAULT = 25;
+const char SEER_SUB_DELTA_YEAR_PREF[] =
+  "network.seer.subresource-degradation.year";
+const int SEER_SUB_DELTA_YEAR_DEFAULT = 50;
+const char SEER_SUB_DELTA_MAX_PREF[] =
+  "network.seer.subresource-degradation.max";
+const int SEER_SUB_DELTA_MAX_DEFAULT = 100;
+
+const char SEER_PRECONNECT_MIN_PREF[] =
+  "network.seer.preconnect-min-confidence";
+const int PRECONNECT_MIN_DEFAULT = 90;
+const char SEER_PRERESOLVE_MIN_PREF[] =
+  "network.seer.preresolve-min-confidence";
+const int PRERESOLVE_MIN_DEFAULT = 60;
+const char SEER_REDIRECT_LIKELY_PREF[] =
+  "network.seer.redirect-likely-confidence";
+const int REDIRECT_LIKELY_DEFAULT = 75;
+
+const char SEER_MAX_QUEUE_SIZE_PREF[] = "network.seer.max-queue-size";
+const uint32_t SEER_MAX_QUEUE_SIZE_DEFAULT = 50;
+
+// All these time values are in usec
+const long long ONE_DAY = 86400LL * 1000000LL;
+const long long ONE_WEEK = 7LL * ONE_DAY;
+const long long ONE_MONTH = 30LL * ONE_DAY;
+const long long ONE_YEAR = 365LL * ONE_DAY;
+
+const long STARTUP_WINDOW = 5L * 60L * 1000000L; // 5min
+
+// Version for the database schema
+static const int32_t SEER_SCHEMA_VERSION = 1;
+
+struct SeerTelemetryAccumulators {
+  Telemetry::AutoCounter<Telemetry::SEER_PREDICT_ATTEMPTS> mPredictAttempts;
+  Telemetry::AutoCounter<Telemetry::SEER_LEARN_ATTEMPTS> mLearnAttempts;
+  Telemetry::AutoCounter<Telemetry::SEER_PREDICT_FULL_QUEUE> mPredictFullQueue;
+  Telemetry::AutoCounter<Telemetry::SEER_LEARN_FULL_QUEUE> mLearnFullQueue;
+  Telemetry::AutoCounter<Telemetry::SEER_TOTAL_PREDICTIONS> mTotalPredictions;
+  Telemetry::AutoCounter<Telemetry::SEER_TOTAL_PRECONNECTS> mTotalPreconnects;
+  Telemetry::AutoCounter<Telemetry::SEER_TOTAL_PRERESOLVES> mTotalPreresolves;
+  Telemetry::AutoCounter<Telemetry::SEER_PREDICTIONS_CALCULATED> mPredictionsCalculated;
+};
+
+// Listener for the speculative DNS requests we'll fire off, which just ignores
+// the result (since we're just trying to warm the cache). This also exists to
+// reduce round-trips to the main thread, by being something threadsafe the Seer
+// can use.
+
+class SeerDNSListener : public nsIDNSListener
+{
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+  NS_DECL_NSIDNSLISTENER
+
+  SeerDNSListener()
+  { }
+
+  virtual ~SeerDNSListener()
+  { }
+};
+
+NS_IMPL_ISUPPORTS1(SeerDNSListener, nsIDNSListener);
+
+NS_IMETHODIMP
+SeerDNSListener::OnLookupComplete(nsICancelable *request,
+                                  nsIDNSRecord *rec,
+                                  nsresult status)
+{
+  return NS_OK;
+}
+
+// Are you ready for the fun part? Because here comes the fun part. The seer,
+// which will do awesome stuff as you browse to make your browsing experience
+// faster.
+
+static Seer *gSeer = nullptr;
+
+#if defined(PR_LOGGING)
+static PRLogModuleInfo *gSeerLog = nullptr;
+#define SEER_LOG(args) PR_LOG(gSeerLog, 4, args)
+#else
+#define SEER_LOG(args)
+#endif
+
+NS_IMPL_ISUPPORTS4(Seer,
+                   nsINetworkSeer,
+                   nsIObserver,
+                   nsISpeculativeConnectionOverrider,
+                   nsIInterfaceRequestor)
+
+Seer::Seer()
+  :mInitialized(false)
+  ,mEnabled(true)
+  ,mEnableHoverOnSSL(false)
+  ,mPageDegradationDay(SEER_PAGE_DELTA_DAY_DEFAULT)
+  ,mPageDegradationWeek(SEER_PAGE_DELTA_WEEK_DEFAULT)
+  ,mPageDegradationMonth(SEER_PAGE_DELTA_MONTH_DEFAULT)
+  ,mPageDegradationYear(SEER_PAGE_DELTA_YEAR_DEFAULT)
+  ,mPageDegradationMax(SEER_PAGE_DELTA_MAX_DEFAULT)
+  ,mSubresourceDegradationDay(SEER_SUB_DELTA_DAY_DEFAULT)
+  ,mSubresourceDegradationWeek(SEER_SUB_DELTA_WEEK_DEFAULT)
+  ,mSubresourceDegradationMonth(SEER_SUB_DELTA_MONTH_DEFAULT)
+  ,mSubresourceDegradationYear(SEER_SUB_DELTA_YEAR_DEFAULT)
+  ,mSubresourceDegradationMax(SEER_SUB_DELTA_MAX_DEFAULT)
+  ,mPreconnectMinConfidence(PRECONNECT_MIN_DEFAULT)
+  ,mPreresolveMinConfidence(PRERESOLVE_MIN_DEFAULT)
+  ,mRedirectLikelyConfidence(REDIRECT_LIKELY_DEFAULT)
+  ,mMaxQueueSize(SEER_MAX_QUEUE_SIZE_DEFAULT)
+  ,mStatements(mDB)
+  ,mLastStartupTime(0)
+  ,mStartupCount(0)
+  ,mQueueSize(0)
+  ,mQueueSizeLock("Seer.mQueueSizeLock")
+{
+#if defined(PR_LOGGING)
+  gSeerLog = PR_NewLogModule("NetworkSeer");
+#endif
+
+  MOZ_ASSERT(!gSeer, "multiple Seer instances!");
+  gSeer = this;
+}
+
+Seer::~Seer()
+{
+  if (mInitialized)
+    Shutdown();
+
+  RemoveObserver();
+
+  gSeer = nullptr;
+}
+
+// Seer::nsIObserver
+
+nsresult
+Seer::InstallObserver()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Installing observer off main thread");
+
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsIObserverService> obs =
+    mozilla::services::GetObserverService();
+  if (!obs) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIPrefBranch> prefs = do_GetService(NS_PREFSERVICE_CONTRACTID);
+  if (!prefs) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  Preferences::AddBoolVarCache(&mEnabled, SEER_ENABLED_PREF, true);
+  Preferences::AddBoolVarCache(&mEnableHoverOnSSL, SEER_SSL_HOVER_PREF, false);
+  Preferences::AddIntVarCache(&mPageDegradationDay, SEER_PAGE_DELTA_DAY_PREF,
+                              SEER_PAGE_DELTA_DAY_DEFAULT);
+  Preferences::AddIntVarCache(&mPageDegradationWeek, SEER_PAGE_DELTA_WEEK_PREF,
+                              SEER_PAGE_DELTA_WEEK_DEFAULT);
+  Preferences::AddIntVarCache(&mPageDegradationMonth,
+                              SEER_PAGE_DELTA_MONTH_PREF,
+                              SEER_PAGE_DELTA_MONTH_DEFAULT);
+  Preferences::AddIntVarCache(&mPageDegradationYear, SEER_PAGE_DELTA_YEAR_PREF,
+                              SEER_PAGE_DELTA_YEAR_DEFAULT);
+  Preferences::AddIntVarCache(&mPageDegradationMax, SEER_PAGE_DELTA_MAX_PREF,
+                              SEER_PAGE_DELTA_MAX_DEFAULT);
+
+  Preferences::AddIntVarCache(&mSubresourceDegradationDay,
+                              SEER_SUB_DELTA_DAY_PREF,
+                              SEER_SUB_DELTA_DAY_DEFAULT);
+  Preferences::AddIntVarCache(&mSubresourceDegradationWeek,
+                              SEER_SUB_DELTA_WEEK_PREF,
+                              SEER_SUB_DELTA_WEEK_DEFAULT);
+  Preferences::AddIntVarCache(&mSubresourceDegradationMonth,
+                              SEER_SUB_DELTA_MONTH_PREF,
+                              SEER_SUB_DELTA_MONTH_DEFAULT);
+  Preferences::AddIntVarCache(&mSubresourceDegradationYear,
+                              SEER_SUB_DELTA_YEAR_PREF,
+                              SEER_SUB_DELTA_YEAR_DEFAULT);
+  Preferences::AddIntVarCache(&mSubresourceDegradationMax,
+                              SEER_SUB_DELTA_MAX_PREF,
+                              SEER_SUB_DELTA_MAX_DEFAULT);
+
+  Preferences::AddIntVarCache(&mPreconnectMinConfidence,
+                              SEER_PRECONNECT_MIN_PREF,
+                              PRECONNECT_MIN_DEFAULT);
+  Preferences::AddIntVarCache(&mPreresolveMinConfidence,
+                              SEER_PRERESOLVE_MIN_PREF,
+                              PRERESOLVE_MIN_DEFAULT);
+  Preferences::AddIntVarCache(&mRedirectLikelyConfidence,
+                              SEER_REDIRECT_LIKELY_PREF,
+                              REDIRECT_LIKELY_DEFAULT);
+
+  Preferences::AddIntVarCache(&mMaxQueueSize, SEER_MAX_QUEUE_SIZE_PREF,
+                              SEER_MAX_QUEUE_SIZE_DEFAULT);
+
+  return rv;
+}
+
+void
+Seer::RemoveObserver()
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Removing observer off main thread");
+
+  nsCOMPtr<nsIObserverService> obs =
+    mozilla::services::GetObserverService();
+  if (obs) {
+    obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
+  }
+}
+
+NS_IMETHODIMP
+Seer::Observe(nsISupports *subject, const char *topic,
+              const PRUnichar *data_unicode)
+{
+  nsresult rv = NS_OK;
+
+  if (!strcmp(NS_XPCOM_SHUTDOWN_OBSERVER_ID, topic)) {
+    gSeer->Shutdown();
+  }
+
+  return rv;
+}
+
+// Seer::nsISpeculativeConnectionOverrider
+
+NS_IMETHODIMP
+Seer::GetIgnoreIdle(bool *ignoreIdle)
+{
+  *ignoreIdle = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+Seer::GetIgnorePossibleSpdyConnections(bool *ignorePossibleSpdyConnections)
+{
+  *ignorePossibleSpdyConnections = true;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+Seer::GetParallelSpeculativeConnectLimit(
+    uint32_t *parallelSpeculativeConnectLimit)
+{
+  *parallelSpeculativeConnectLimit = 6;
+  return NS_OK;
+}
+
+// Seer::nsIInterfaceRequestor
+
+NS_IMETHODIMP
+Seer::GetInterface(const nsIID &iid, void **result)
+{
+  return QueryInterface(iid, result);
+}
+
+// Seer::nsINetworkSeer
+
+nsresult
+Seer::Init()
+{
+  if (!NS_IsMainThread()) {
+    MOZ_ASSERT(false, "Seer::Init called off the main thread!");
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  nsresult rv = NS_OK;
+
+#if defined(ANDROID) && !defined(MOZ_WIDGET_GONK)
+  // This is an ugly hack to disable the seer on android < 2.3, as it doesn't
+  // play nicely with those android versions, at least on our infra. Causes
+  // timeouts in reftests. See bug 881804 comment 86.
+  nsCOMPtr<nsIPropertyBag2> infoService =
+    do_GetService("@mozilla.org/system-info;1");
+  if (infoService) {
+    int32_t androidVersion = -1;
+    rv = infoService->GetPropertyAsInt32(NS_LITERAL_STRING("version"),
+                                         &androidVersion);
+    if (NS_SUCCEEDED(rv) && (androidVersion < ANDROID_23_VERSION)) {
+      return NS_ERROR_NOT_AVAILABLE;
+    }
+  }
+#endif
+
+  mStartupTime = PR_Now();
+
+  mAccumulators = new SeerTelemetryAccumulators();
+
+  rv = InstallObserver();
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!mDNSListener) {
+    mDNSListener = new SeerDNSListener();
+  }
+
+  rv = NS_NewNamedThread("Network Seer", getter_AddRefs(mIOThread));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mSpeculativeService = do_GetService("@mozilla.org/network/io-service;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mDnsService = do_GetService("@mozilla.org/network/dns-service;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
+                              getter_AddRefs(mDBFile));
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = mDBFile->AppendNative(NS_LITERAL_CSTRING("seer.sqlite"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mInitialized = true;
+
+  return rv;
+}
+
+// Make sure that our sqlite storage is all set up with all the tables we need
+// to do the work. It isn't the end of the world if this fails, since this is
+// all an optimization, anyway.
+
+nsresult
+Seer::EnsureInitStorage()
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "Initializing seer storage on main thread");
+
+  if (mDB) {
+    return NS_OK;
+  }
+
+  nsresult rv;
+
+  rv = mStorageService->OpenDatabase(mDBFile, getter_AddRefs(mDB));
+  if (NS_FAILED(rv)) {
+    // Retry once by trashing the file and trying to open again. If this fails,
+    // we can just bail, and hope for better luck next time.
+    rv = mDBFile->Remove(false);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mStorageService->OpenDatabase(mDBFile, getter_AddRefs(mDB));
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF;"));
+  mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA foreign_keys = ON;"));
+
+  // A table to make sure we're working with the database layout we expect
+  rv = mDB->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_seer_version (\n"
+                         "  version INTEGER NOT NULL\n"
+                         ");\n"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<mozIStorageStatement> stmt;
+  rv = mDB->CreateStatement(
+      NS_LITERAL_CSTRING("SELECT version FROM moz_seer_version;\n"),
+      getter_AddRefs(stmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool hasRows;
+  rv = stmt->ExecuteStep(&hasRows);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (hasRows) {
+    int32_t currentVersion;
+    rv = stmt->GetInt32(0, &currentVersion);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // This is what we do while we only have one schema version. Later, we'll
+    // have to change this to actually upgrade things as appropriate.
+    MOZ_ASSERT(currentVersion == SEER_SCHEMA_VERSION,
+               "Invalid seer schema version!");
+    if (currentVersion != SEER_SCHEMA_VERSION) {
+      return NS_ERROR_UNEXPECTED;
+    }
+  } else {
+    stmt = nullptr;
+    rv = mDB->CreateStatement(
+        NS_LITERAL_CSTRING("INSERT INTO moz_seer_version (version) VALUES "
+                           "(:seer_version);"),
+        getter_AddRefs(stmt));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("seer_version"),
+                               SEER_SCHEMA_VERSION);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    stmt->Execute();
+  }
+
+  stmt = nullptr;
+
+  // This table keeps track of the hosts we've seen at the top level of a
+  // pageload so we can map them to hosts used for subresources of a pageload.
+  rv = mDB->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_hosts (\n"
+                         "  id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
+                         "  origin TEXT NOT NULL,\n"
+                         "  loads INTEGER DEFAULT 0,\n"
+                         "  last_load INTEGER DEFAULT 0\n"
+                         ");\n"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // And this is the table that keeps track of the hosts for subresources of a
+  // pageload.
+  rv = mDB->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_subhosts (\n"
+                         "  id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
+                         "  hid INTEGER NOT NULL,\n"
+                         "  origin TEXT NOT NULL,\n"
+                         "  hits INTEGER DEFAULT 0,\n"
+                         "  last_hit INTEGER DEFAULT 0,\n"
+                         "  FOREIGN KEY(hid) REFERENCES moz_hosts(id) ON DELETE CASCADE\n"
+                         ");\n"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mDB->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS subhost_hid_origin_index "
+                         "ON moz_subhosts (hid, origin);"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // Table to keep track of how many times we've started up, and when the last
+  // time was.
+  rv = mDB->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_startups (\n"
+                         "  startups INTEGER,\n"
+                         "  last_startup INTEGER\n"
+                         ");\n"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mDB->CreateStatement(
+      NS_LITERAL_CSTRING("SELECT startups, last_startup FROM moz_startups;\n"),
+      getter_AddRefs(stmt));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // We'll go ahead and keep track of our startup count here, since we can
+  // (mostly) equate "the service was created and asked to do stuff" with
+  // "the browser was started up".
+  rv = stmt->ExecuteStep(&hasRows);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (hasRows) {
+    // We've started up before. Update our startup statistics
+    stmt->GetInt32(0, &mStartupCount);
+    stmt->GetInt64(1, &mLastStartupTime);
+
+    // This finalizes the statement
+    stmt = nullptr;
+
+    rv = mDB->CreateStatement(
+        NS_LITERAL_CSTRING("UPDATE moz_startups SET startups = :startup_count "
+                           "last_startup = :startup_time;\n"),
+        getter_AddRefs(stmt));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("startup_count"),
+                               mStartupCount + 1);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"),
+                               mStartupTime);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    stmt->Execute();
+  } else {
+    // This is our first startup, so let's go ahead and mark it as such
+    mStartupCount = 1;
+
+    rv = mDB->CreateStatement(
+        NS_LITERAL_CSTRING("INSERT INTO moz_startups (startups, last_startup) "
+                           "VALUES (1, :startup_time);\n"),
+        getter_AddRefs(stmt));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"),
+                               mStartupTime);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    stmt->Execute();
+  }
+
+  // This finalizes the statement
+  stmt = nullptr;
+
+  // This table lists URIs loaded at startup, along with how many startups
+  // they've been loaded during, and when the last time was.
+  rv = mDB->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_startup_pages (\n"
+                         "  id INTEGER PRIMARY KEY AUTOINCREMENT,\n"
+                         "  uri TEXT NOT NULL,\n"
+                         "  hits INTEGER DEFAULT 0,\n"
+                         "  last_hit INTEGER DEFAULT 0\n"
+                         ");\n"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // This table is similar to moz_hosts above, but uses full URIs instead of
+  // hosts so that we can get more specific predictions for URIs that people
+  // visit often (such as their email or social network home pages).
+  rv = mDB->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_pages (\n"
+                         "  id integer PRIMARY KEY AUTOINCREMENT,\n"
+                         "  uri TEXT NOT NULL,\n"
+                         "  loads INTEGER DEFAULT 0,\n"
+                         "  last_load INTEGER DEFAULT 0\n"
+                         ");\n"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // This table is similar to moz_subhosts above, but is instead related to
+  // moz_pages for finer granularity.
+  rv = mDB->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_subresources (\n"
+                         "  id integer PRIMARY KEY AUTOINCREMENT,\n"
+                         "  pid INTEGER NOT NULL,\n"
+                         "  uri TEXT NOT NULL,\n"
+                         "  hits INTEGER DEFAULT 0,\n"
+                         "  last_hit INTEGER DEFAULT 0,\n"
+                         "  FOREIGN KEY(pid) REFERENCES moz_pages(id) ON DELETE CASCADE\n"
+                         ");\n"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = mDB->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("CREATE INDEX IF NOT EXISTS subresource_pid_uri_index "
+                         "ON moz_subresources (pid, uri);"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // This table keeps track of URIs and what they end up finally redirecting to
+  // so we can handle redirects in a sane fashion, as well.
+  rv = mDB->ExecuteSimpleSQL(
+      NS_LITERAL_CSTRING("CREATE TABLE IF NOT EXISTS moz_redirects (\n"
+                         "  id integer PRIMARY KEY AUTOINCREMENT,\n"
+                         "  pid integer NOT NULL,\n"
+                         "  uri TEXT NOT NULL,\n"
+                         "  origin TEXT NOT NULL,\n"
+                         "  hits INTEGER DEFAULT 0,\n"
+                         "  last_hit INTEGER DEFAULT 0,\n"
+                         "  FOREIGN KEY(pid) REFERENCES moz_pages(id)\n"
+                         ");\n"));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return NS_OK;
+}
+
+class SeerThreadShutdownRunner : public nsRunnable
+{
+public:
+  SeerThreadShutdownRunner(nsIThread *ioThread)
+    :mIOThread(ioThread)
+  { }
+
+  NS_IMETHODIMP Run() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(NS_IsMainThread(), "Shut down seer io thread off main thread");
+    mIOThread->Shutdown();
+    return NS_OK;
+  }
+
+private:
+  nsCOMPtr<nsIThread> mIOThread;
+};
+
+class SeerDBShutdownRunner : public nsRunnable
+{
+public:
+  SeerDBShutdownRunner(nsIThread *ioThread, nsINetworkSeer *seer)
+    :mIOThread(ioThread)
+  {
+    mSeer = new nsMainThreadPtrHolder<nsINetworkSeer>(seer);
+  }
+
+  NS_IMETHODIMP Run() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(!NS_IsMainThread(), "Shutting down DB on main thread");
+
+    gSeer->mStatements.FinalizeStatements();
+    gSeer->mDB->Close();
+    gSeer->mDB = nullptr;
+
+    nsRefPtr<SeerThreadShutdownRunner> runner =
+      new SeerThreadShutdownRunner(mIOThread);
+    NS_DispatchToMainThread(runner);
+
+    return NS_OK;
+  }
+
+private:
+  nsCOMPtr<nsIThread> mIOThread;
+
+  // Death grip to keep seer alive while we cleanly close its DB connection
+  nsMainThreadPtrHandle<nsINetworkSeer> mSeer;
+};
+
+void
+Seer::Shutdown()
+{
+  if (!NS_IsMainThread()) {
+    MOZ_ASSERT(false, "Seer::Shutdown called off the main thread!");
+    return;
+  }
+
+  mInitialized = false;
+
+  if (mIOThread) {
+    nsCOMPtr<nsIThread> ioThread;
+    mIOThread.swap(ioThread);
+
+    if (mDB) {
+      nsRefPtr<SeerDBShutdownRunner> runner =
+        new SeerDBShutdownRunner(ioThread, this);
+      ioThread->Dispatch(runner, NS_DISPATCH_NORMAL);
+    } else {
+      nsRefPtr<SeerThreadShutdownRunner> runner =
+        new SeerThreadShutdownRunner(ioThread);
+      NS_DispatchToMainThread(runner);
+    }
+  }
+}
+
+nsresult
+Seer::Create(nsISupports *aOuter, const nsIID& aIID,
+             void **aResult)
+{
+  nsresult rv;
+
+  if (aOuter != nullptr) {
+    return NS_ERROR_NO_AGGREGATION;
+  }
+
+  nsRefPtr<Seer> svc = new Seer();
+
+  rv = svc->Init();
+  if (NS_FAILED(rv)) {
+    SEER_LOG(("Failed to initialize seer, seer will be a noop"));
+  }
+
+  // We treat init failure the same as the service being disabled, since this
+  // is all an optimization anyway. No need to freak people out. That's why we
+  // gladly continue on QI'ing here.
+  rv = svc->QueryInterface(aIID, aResult);
+
+  return rv;
+}
+
+// Get the full origin (scheme, host, port) out of a URI (maybe should be part
+// of nsIURI instead?)
+static void
+ExtractOrigin(nsIURI *uri, nsAutoCString &s)
+{
+  s.Truncate();
+
+  nsAutoCString scheme;
+  nsresult rv = uri->GetScheme(scheme);
+  RETURN_IF_FAILED(rv);
+
+  nsAutoCString host;
+  rv = uri->GetAsciiHost(host);
+  RETURN_IF_FAILED(rv);
+
+  int32_t port;
+  rv = uri->GetPort(&port);
+  RETURN_IF_FAILED(rv);
+
+  s.Assign(scheme);
+  s.AppendLiteral("://");
+  s.Append(host);
+  if (port != -1) {
+    s.AppendLiteral(":");
+    s.AppendInt(port);
+  }
+}
+
+// An event to do the work for a prediction that needs to hit the sqlite
+// database. These events should be created on the main thread, and run on
+// the seer thread.
+class SeerPredictionEvent : public nsRunnable
+{
+public:
+  SeerPredictionEvent(nsIURI *targetURI, nsIURI *sourceURI,
+                      SeerPredictReason reason,
+                      nsINetworkSeerVerifier *verifier)
+    :mReason(reason)
+  {
+    MOZ_ASSERT(NS_IsMainThread(), "Creating prediction event off main thread");
+
+    mEnqueueTime = TimeStamp::Now();
+
+    if (verifier) {
+      mVerifier = new nsMainThreadPtrHolder<nsINetworkSeerVerifier>(verifier);
+    }
+    if (targetURI) {
+      targetURI->GetAsciiSpec(mTargetURI.spec);
+      ExtractOrigin(targetURI, mTargetURI.origin);
+    }
+    if (sourceURI) {
+      sourceURI->GetAsciiSpec(mSourceURI.spec);
+      ExtractOrigin(sourceURI, mSourceURI.origin);
+    }
+  }
+
+  NS_IMETHOD Run() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(!NS_IsMainThread(), "Running prediction event on main thread");
+
+    Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_WAIT_TIME,
+                                   mEnqueueTime);
+
+    TimeStamp startTime = TimeStamp::Now();
+
+    nsresult rv = NS_OK;
+
+    switch (mReason) {
+      case nsINetworkSeer::PREDICT_LOAD:
+        gSeer->PredictForPageload(mTargetURI, mVerifier, 0, mEnqueueTime);
+        break;
+      case nsINetworkSeer::PREDICT_STARTUP:
+        gSeer->PredictForStartup(mVerifier, mEnqueueTime);
+        break;
+      default:
+        MOZ_ASSERT(false, "Got unexpected value for predict reason");
+        rv = NS_ERROR_UNEXPECTED;
+    }
+
+    gSeer->FreeSpaceInQueue();
+
+    Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_WORK_TIME,
+                                   startTime);
+
+    return rv;
+  }
+
+private:
+  Seer::UriInfo mTargetURI;
+  Seer::UriInfo mSourceURI;
+  SeerPredictReason mReason;
+  SeerVerifierHandle mVerifier;
+  TimeStamp mEnqueueTime;
+};
+
+// Predicting for a link is easy, and doesn't require the round-trip to the
+// seer thread and back to the main thread, since we don't have to hit the db
+// for that.
+void
+Seer::PredictForLink(nsIURI *targetURI, nsIURI *sourceURI,
+                     nsINetworkSeerVerifier *verifier)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "Predicting for link off main thread");
+
+  if (!mSpeculativeService) {
+    return;
+  }
+
+  if (!mEnableHoverOnSSL) {
+    bool isSSL = false;
+    sourceURI->SchemeIs("https", &isSSL);
+    if (isSSL) {
+      // We don't want to predict from an HTTPS page, to avoid info leakage
+      SEER_LOG(("Not predicting for link hover - on an SSL page"));
+      return;
+    }
+  }
+
+  mSpeculativeService->SpeculativeConnect(targetURI, this);
+  if (verifier) {
+    verifier->OnPredictPreconnect(targetURI);
+  }
+}
+
+// This runnable runs on the main thread, and is responsible for actually
+// firing off predictive actions (such as TCP/TLS preconnects and DNS lookups)
+class SeerPredictionRunner : public nsRunnable
+{
+public:
+  SeerPredictionRunner(SeerVerifierHandle &verifier, TimeStamp predictStartTime)
+    :mVerifier(verifier)
+    ,mPredictStartTime(predictStartTime)
+  { }
+
+  void AddPreconnect(const nsACString &uri)
+  {
+    mPreconnects.AppendElement(uri);
+  }
+
+  void AddPreresolve(const nsACString &uri)
+  {
+    mPreresolves.AppendElement(uri);
+  }
+
+  bool HasWork() const
+  {
+    return !(mPreconnects.IsEmpty() && mPreresolves.IsEmpty());
+  }
+
+  NS_IMETHOD Run() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(NS_IsMainThread(), "Running prediction off main thread");
+
+    Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_TIME_TO_ACTION,
+                                   mPredictStartTime);
+
+    uint32_t len, i;
+
+    len = mPreconnects.Length();
+    for (i = 0; i < len; ++i) {
+      nsCOMPtr<nsIURI> uri;
+      nsresult rv = NS_NewURI(getter_AddRefs(uri), mPreconnects[i]);
+      if (NS_FAILED(rv)) {
+        continue;
+      }
+
+      ++gSeer->mAccumulators->mTotalPredictions;
+      ++gSeer->mAccumulators->mTotalPreconnects;
+      gSeer->mSpeculativeService->SpeculativeConnect(uri, gSeer);
+      if (mVerifier) {
+        mVerifier->OnPredictPreconnect(uri);
+      }
+    }
+
+    len = mPreresolves.Length();
+    nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+    for (i = 0; i < len; ++i) {
+      nsCOMPtr<nsIURI> uri;
+      nsresult rv = NS_NewURI(getter_AddRefs(uri), mPreresolves[i]);
+      if (NS_FAILED(rv)) {
+        continue;
+      }
+
+      ++gSeer->mAccumulators->mTotalPredictions;
+      ++gSeer->mAccumulators->mTotalPreresolves;
+      nsAutoCString hostname;
+      uri->GetAsciiHost(hostname);
+      nsCOMPtr<nsICancelable> tmpCancelable;
+      gSeer->mDnsService->AsyncResolve(hostname,
+                                       (nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
+                                        nsIDNSService::RESOLVE_SPECULATE),
+                                       gSeer->mDNSListener, nullptr,
+                                       getter_AddRefs(tmpCancelable));
+      if (mVerifier) {
+        mVerifier->OnPredictDNS(uri);
+      }
+    }
+
+    mPreconnects.Clear();
+    mPreresolves.Clear();
+
+    return NS_OK;
+  }
+
+private:
+  nsTArray<nsCString> mPreconnects;
+  nsTArray<nsCString> mPreresolves;
+  SeerVerifierHandle mVerifier;
+  TimeStamp mPredictStartTime;
+};
+
+// This calculates how much to degrade our confidence in our data based on
+// the last time this top-level resource was loaded. This "global degradation"
+// applies to *all* subresources we have associated with the top-level
+// resource. This will be in addition to any reduction in confidence we have
+// associated with a particular subresource.
+int
+Seer::CalculateGlobalDegradation(PRTime now, PRTime lastLoad)
+{
+  int globalDegradation;
+  PRTime delta = now - lastLoad;
+  if (delta < ONE_DAY) {
+    globalDegradation = mPageDegradationDay;
+  } else if (delta < ONE_WEEK) {
+    globalDegradation = mPageDegradationWeek;
+  } else if (delta < ONE_MONTH) {
+    globalDegradation = mPageDegradationMonth;
+  } else if (delta < ONE_YEAR) {
+    globalDegradation = mPageDegradationYear;
+  } else {
+    globalDegradation = mPageDegradationMax;
+  }
+
+  Telemetry::Accumulate(Telemetry::SEER_GLOBAL_DEGRADATION, globalDegradation);
+  return globalDegradation;
+}
+
+// This calculates our overall confidence that a particular subresource will be
+// loaded as part of a top-level load.
+// @param baseConfidence - the basic confidence we have for this subresource,
+//                         which is the percentage of time this top-level load
+//                         loads the subresource in question
+// @param lastHit - the timestamp of the last time we loaded this subresource as
+//                  part of this top-level load
+// @param lastPossible - the timestamp of the last time we performed this
+//                       top-level load
+// @param globalDegradation - the degradation for this top-level load as
+//                            determined by CalculateGlobalDegradation
+int
+Seer::CalculateConfidence(int baseConfidence, PRTime lastHit,
+                          PRTime lastPossible, int globalDegradation)
+{
+  ++mAccumulators->mPredictionsCalculated;
+
+  int maxConfidence = 100;
+  int confidenceDegradation = 0;
+
+  if (lastHit < lastPossible) {
+    // We didn't load this subresource the last time this top-level load was
+    // performed, so let's not bother preconnecting (at the very least).
+    maxConfidence = mPreconnectMinConfidence - 1;
+
+    // Now calculate how much we want to degrade our confidence based on how
+    // long it's been between the last time we did this top-level load and the
+    // last time this top-level load included this subresource.
+    PRTime delta = lastPossible - lastHit;
+    if (delta == 0) {
+      confidenceDegradation = 0;
+    } else if (delta < ONE_DAY) {
+      confidenceDegradation = mSubresourceDegradationDay;
+    } else if (delta < ONE_WEEK) {
+      confidenceDegradation = mSubresourceDegradationWeek;
+    } else if (delta < ONE_MONTH) {
+      confidenceDegradation = mSubresourceDegradationMonth;
+    } else if (delta < ONE_YEAR) {
+      confidenceDegradation = mSubresourceDegradationYear;
+    } else {
+      confidenceDegradation = mSubresourceDegradationMax;
+      maxConfidence = 0;
+    }
+  }
+
+  // Calculate our confidence and clamp it to between 0 and maxConfidence
+  // (<= 100)
+  int confidence = baseConfidence - confidenceDegradation - globalDegradation;
+  confidence = std::max(confidence, 0);
+  confidence = std::min(confidence, maxConfidence);
+
+  Telemetry::Accumulate(Telemetry::SEER_BASE_CONFIDENCE, baseConfidence);
+  Telemetry::Accumulate(Telemetry::SEER_SUBRESOURCE_DEGRADATION,
+                        confidenceDegradation);
+  Telemetry::Accumulate(Telemetry::SEER_CONFIDENCE, confidence);
+  return confidence;
+}
+
+// (Maybe) adds a predictive action to the prediction runner, based on our
+// calculated confidence for the subresource in question.
+void
+Seer::SetupPrediction(int confidence, const nsACString &uri,
+                      SeerPredictionRunner *runner)
+{
+    if (confidence >= mPreconnectMinConfidence) {
+      runner->AddPreconnect(uri);
+    } else if (confidence >= mPreresolveMinConfidence) {
+      runner->AddPreresolve(uri);
+    }
+}
+
+// This gets the data about the top-level load from our database, either from
+// the pages table (which is specific to a particular URI), or from the hosts
+// table (which is for a particular origin).
+bool
+Seer::LookupTopLevel(QueryType queryType, const nsACString &key,
+                     TopLevelInfo &info)
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "LookupTopLevel called on main thread.");
+
+  nsCOMPtr<mozIStorageStatement> stmt;
+  if (queryType == QUERY_PAGE) {
+    stmt = mStatements.GetCachedStatement(
+        NS_LITERAL_CSTRING("SELECT id, loads, last_load FROM moz_pages WHERE "
+                           "uri = :key;"));
+  } else {
+    stmt = mStatements.GetCachedStatement(
+        NS_LITERAL_CSTRING("SELECT id, loads, last_load FROM moz_hosts WHERE "
+                           "origin = :key;"));
+  }
+  NS_ENSURE_TRUE(stmt, false);
+  mozStorageStatementScoper scope(stmt);
+
+  nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  bool hasRows;
+  rv = stmt->ExecuteStep(&hasRows);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  if (!hasRows) {
+    return false;
+  }
+
+  rv = stmt->GetInt32(0, &info.id);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  rv = stmt->GetInt32(1, &info.loadCount);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  rv = stmt->GetInt64(2, &info.lastLoad);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  return true;
+}
+
+// Insert data about either a top-level page or a top-level origin into
+// the database.
+void
+Seer::AddTopLevel(QueryType queryType, const nsACString &key, PRTime now)
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "AddTopLevel called on main thread.");
+
+  nsCOMPtr<mozIStorageStatement> stmt;
+  if (queryType == QUERY_PAGE) {
+    stmt = mStatements.GetCachedStatement(
+        NS_LITERAL_CSTRING("INSERT INTO moz_pages (uri, loads, last_load) "
+                           "VALUES (:key, 1, :now);"));
+  } else {
+    stmt = mStatements.GetCachedStatement(
+        NS_LITERAL_CSTRING("INSERT INTO moz_hosts (origin, loads, last_load) "
+                           "VALUES (:key, 1, :now);"));
+  }
+  if (!stmt) {
+    return;
+  }
+  mozStorageStatementScoper scope(stmt);
+
+  // Loading a page implicitly makes the seer learn about the page,
+  // so since we don't have it already, let's add it.
+  nsresult rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key);
+  RETURN_IF_FAILED(rv);
+
+  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now);
+  RETURN_IF_FAILED(rv);
+
+  rv = stmt->Execute();
+}
+
+// Update data about either a top-level page or a top-level origin in the
+// database.
+void
+Seer::UpdateTopLevel(QueryType queryType, const TopLevelInfo &info, PRTime now)
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "UpdateTopLevel called on main thread.");
+
+  nsCOMPtr<mozIStorageStatement> stmt;
+  if (queryType == QUERY_PAGE) {
+    stmt = mStatements.GetCachedStatement(
+        NS_LITERAL_CSTRING("UPDATE moz_pages SET loads = :load_count, "
+                           "last_load = :now WHERE id = :id;"));
+  } else {
+    stmt = mStatements.GetCachedStatement(
+        NS_LITERAL_CSTRING("UPDATE moz_hosts SET loads = :load_count, "
+                           "last_load = :now WHERE id = :id;"));
+  }
+  if (!stmt) {
+    return;
+  }
+  mozStorageStatementScoper scope(stmt);
+
+  // First, let's update the page in the database, since loading a page
+  // implicitly learns about the page.
+  nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("load_count"),
+                                      info.loadCount + 1);
+  RETURN_IF_FAILED(rv);
+
+  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now);
+  RETURN_IF_FAILED(rv);
+
+  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id);
+  RETURN_IF_FAILED(rv);
+
+  rv = stmt->Execute();
+}
+
+// Tries to predict for a top-level load (either page-based or origin-based).
+// Returns false if it failed to predict at all, true if it did some sort of
+// prediction.
+// @param queryType - whether to predict based on page or origin
+// @param info - the db info about the top-level resource
+bool
+Seer::TryPredict(QueryType queryType, const TopLevelInfo &info, PRTime now,
+                 SeerVerifierHandle &verifier, TimeStamp &predictStartTime)
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "TryPredict called on main thread.");
+
+  int globalDegradation = CalculateGlobalDegradation(now, info.lastLoad);
+
+  // Now let's look up the subresources we know about for this page
+  nsCOMPtr<mozIStorageStatement> stmt;
+  if (queryType == QUERY_PAGE) {
+    stmt = mStatements.GetCachedStatement(
+        NS_LITERAL_CSTRING("SELECT uri, hits, last_hit FROM moz_subresources "
+                           "WHERE pid = :id;"));
+  } else {
+    stmt = mStatements.GetCachedStatement(
+        NS_LITERAL_CSTRING("SELECT origin, hits, last_hit FROM moz_subhosts "
+                           "WHERE hid = :id;"));
+  }
+  NS_ENSURE_TRUE(stmt, false);
+  mozStorageStatementScoper scope(stmt);
+
+  nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  bool hasRows;
+  rv = stmt->ExecuteStep(&hasRows);
+  if (NS_FAILED(rv) || !hasRows) {
+    return false;
+  }
+
+  nsRefPtr<SeerPredictionRunner> runner =
+    new SeerPredictionRunner(verifier, predictStartTime);
+
+  while (hasRows) {
+    int32_t hitCount;
+    PRTime lastHit;
+    nsAutoCString subresource;
+    int baseConfidence, confidence;
+
+    // We use goto nextrow here instead of just failling, because we want
+    // to do some sort of prediction if at all possible. Of course, it's
+    // probably unlikely that subsequent rows will succeed if one fails, but
+    // it's worth a shot.
+
+    rv = stmt->GetUTF8String(0, subresource);
+    if NS_FAILED(rv) {
+      goto nextrow;
+    }
+
+    rv = stmt->GetInt32(1, &hitCount);
+    if (NS_FAILED(rv)) {
+      goto nextrow;
+    }
+
+    rv = stmt->GetInt64(2, &lastHit);
+    if (NS_FAILED(rv)) {
+      goto nextrow;
+    }
+
+    baseConfidence = (hitCount * 100) / info.loadCount;
+    confidence = CalculateConfidence(baseConfidence, lastHit, info.lastLoad,
+                                     globalDegradation);
+    SetupPrediction(confidence, subresource, runner);
+
+nextrow:
+    rv = stmt->ExecuteStep(&hasRows);
+    NS_ENSURE_SUCCESS(rv, false);
+  }
+
+  bool predicted = false;
+
+  if (runner->HasWork()) {
+    NS_DispatchToMainThread(runner);
+    predicted = true;
+  }
+
+  return predicted;
+}
+
+// Find out if a top-level page is likely to redirect.
+bool
+Seer::WouldRedirect(const TopLevelInfo &info, PRTime now, UriInfo &newUri)
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "WouldRedirect called on main thread.");
+
+  nsCOMPtr<mozIStorageStatement> stmt = mStatements.GetCachedStatement(
+      NS_LITERAL_CSTRING("SELECT uri, origin, hits, last_hit "
+                         "FROM moz_redirects WHERE pid = :id;"));
+  NS_ENSURE_TRUE(stmt, false);
+  mozStorageStatementScoper scope(stmt);
+
+  nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  bool hasRows;
+  rv = stmt->ExecuteStep(&hasRows);
+  if (NS_FAILED(rv) || !hasRows) {
+    return false;
+  }
+
+  rv = stmt->GetUTF8String(0, newUri.spec);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  rv = stmt->GetUTF8String(1, newUri.origin);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  int32_t hitCount;
+  rv = stmt->GetInt32(2, &hitCount);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  PRTime lastHit;
+  rv = stmt->GetInt64(3, &lastHit);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  int globalDegradation = CalculateGlobalDegradation(now, info.lastLoad);
+  int baseConfidence = (hitCount * 100) / info.loadCount;
+  int confidence = CalculateConfidence(baseConfidence, lastHit, info.lastLoad,
+                                       globalDegradation);
+
+  if (confidence > mRedirectLikelyConfidence) {
+    return true;
+  }
+
+  return false;
+}
+
+// This will add a page to our list of startup pages if it's being loaded
+// before our startup window has expired.
+void
+Seer::MaybeLearnForStartup(const UriInfo &uri, const PRTime now)
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "MaybeLearnForStartup called on main thread.");
+
+  if ((now - mStartupTime) < STARTUP_WINDOW) {
+    LearnForStartup(uri);
+  }
+}
+
+const int MAX_PAGELOAD_DEPTH = 10;
+
+// This is the driver for prediction based on a new pageload.
+void
+Seer::PredictForPageload(const UriInfo &uri, SeerVerifierHandle &verifier,
+                         int stackCount, TimeStamp &predictStartTime)
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "PredictForPageload called on main thread.");
+
+  if (stackCount > MAX_PAGELOAD_DEPTH) {
+    SEER_LOG(("Too deep into pageload prediction"));
+    return;
+  }
+
+  if (NS_FAILED(EnsureInitStorage())) {
+    return;
+  }
+
+  PRTime now = PR_Now();
+
+  MaybeLearnForStartup(uri, now);
+
+  TopLevelInfo pageInfo;
+  TopLevelInfo originInfo;
+  bool havePage = LookupTopLevel(QUERY_PAGE, uri.spec, pageInfo);
+  bool haveOrigin = LookupTopLevel(QUERY_ORIGIN, uri.origin, originInfo);
+
+  if (!havePage) {
+    AddTopLevel(QUERY_PAGE, uri.spec, now);
+  } else {
+    UpdateTopLevel(QUERY_PAGE, pageInfo, now);
+  }
+
+  if (!haveOrigin) {
+    AddTopLevel(QUERY_ORIGIN, uri.origin, now);
+  } else {
+    UpdateTopLevel(QUERY_ORIGIN, originInfo, now);
+  }
+
+  UriInfo newUri;
+  if (havePage && WouldRedirect(pageInfo, now, newUri)) {
+    nsRefPtr<SeerPredictionRunner> runner =
+      new SeerPredictionRunner(verifier, predictStartTime);
+    runner->AddPreconnect(newUri.spec);
+    NS_DispatchToMainThread(runner);
+    PredictForPageload(newUri, verifier, stackCount + 1, predictStartTime);
+    return;
+  }
+
+  bool predicted = false;
+
+  // We always try to be as specific as possible in our predictions, so try
+  // to predict based on the full URI before we fall back to the origin.
+  if (havePage) {
+    predicted = TryPredict(QUERY_PAGE, pageInfo, now, verifier,
+                           predictStartTime);
+  }
+
+  if (!predicted && haveOrigin) {
+    predicted = TryPredict(QUERY_ORIGIN, originInfo, now, verifier,
+                           predictStartTime);
+  }
+
+  if (!predicted) {
+    Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_TIME_TO_INACTION,
+                                   predictStartTime);
+  }
+}
+
+// This is the driver for predicting at browser startup time based on pages that
+// have previously been loaded close to startup.
+void
+Seer::PredictForStartup(SeerVerifierHandle &verifier,
+                        TimeStamp &predictStartTime)
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "PredictForStartup called on main thread");
+
+  if (NS_FAILED(EnsureInitStorage())) {
+    return;
+  }
+
+  nsCOMPtr<mozIStorageStatement> stmt = mStatements.GetCachedStatement(
+      NS_LITERAL_CSTRING("SELECT uri, hits, last_hit FROM moz_startup_pages;"));
+  if (!stmt) {
+    return;
+  }
+  mozStorageStatementScoper scope(stmt);
+  nsresult rv;
+  bool hasRows;
+
+  nsRefPtr<SeerPredictionRunner> runner =
+    new SeerPredictionRunner(verifier, predictStartTime);
+
+  rv = stmt->ExecuteStep(&hasRows);
+  RETURN_IF_FAILED(rv);
+
+  while (hasRows) {
+    nsAutoCString uri;
+    int32_t hitCount;
+    PRTime lastHit;
+    int baseConfidence, confidence;
+
+    // We use goto nextrow here instead of just failling, because we want
+    // to do some sort of prediction if at all possible. Of course, it's
+    // probably unlikely that subsequent rows will succeed if one fails, but
+    // it's worth a shot.
+
+    rv = stmt->GetUTF8String(0, uri);
+    if (NS_FAILED(rv)) {
+      goto nextrow;
+    }
+
+    rv = stmt->GetInt32(1, &hitCount);
+    if (NS_FAILED(rv)) {
+      goto nextrow;
+    }
+
+    rv = stmt->GetInt64(2, &lastHit);
+    if (NS_FAILED(rv)) {
+      goto nextrow;
+    }
+
+    baseConfidence = (hitCount * 100) / mStartupCount;
+    confidence = CalculateConfidence(baseConfidence, lastHit,
+                                     mLastStartupTime, 0);
+    SetupPrediction(confidence, uri, runner);
+
+nextrow:
+    rv = stmt->ExecuteStep(&hasRows);
+    RETURN_IF_FAILED(rv);
+  }
+
+  if (runner->HasWork()) {
+    NS_DispatchToMainThread(runner);
+  } else {
+    Telemetry::AccumulateTimeDelta(Telemetry::SEER_PREDICT_TIME_TO_INACTION,
+                                   predictStartTime);
+  }
+}
+
+// All URIs we get passed *must* be http or https if they're not null. This
+// helps ensure that.
+static bool
+IsNullOrHttp(nsIURI *uri)
+{
+  if (!uri) {
+    return true;
+  }
+
+  bool isHTTP = false;
+  uri->SchemeIs("http", &isHTTP);
+  if (!isHTTP) {
+    uri->SchemeIs("https", &isHTTP);
+  }
+
+  return isHTTP;
+}
+
+nsresult
+Seer::ReserveSpaceInQueue()
+{
+  MutexAutoLock lock(mQueueSizeLock);
+
+  if (mQueueSize >= mMaxQueueSize) {
+    SEER_LOG(("Not enqueuing event - queue too large"));
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  mQueueSize++;
+  return NS_OK;
+}
+
+void
+Seer::FreeSpaceInQueue()
+{
+  MutexAutoLock lock(mQueueSizeLock);
+  MOZ_ASSERT(mQueueSize > 0, "unexpected mQueueSize");
+  mQueueSize--;
+}
+
+// Called from the main thread to initiate predictive actions
+NS_IMETHODIMP
+Seer::Predict(nsIURI *targetURI, nsIURI *sourceURI, SeerPredictReason reason,
+              nsILoadContext *loadContext, nsINetworkSeerVerifier *verifier)
+{
+  MOZ_ASSERT(NS_IsMainThread(),
+             "Seer interface methods must be called on the main thread");
+
+  if (!mInitialized) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  if (!mEnabled) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  if (loadContext && loadContext->UsePrivateBrowsing()) {
+    // Don't want to do anything in PB mode
+    return NS_OK;
+  }
+
+  if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+    // Nothing we can do for non-HTTP[S] schemes
+    return NS_OK;
+  }
+
+  // Ensure we've been given the appropriate arguments for the kind of
+  // prediction we're being asked to do
+  switch (reason) {
+    case nsINetworkSeer::PREDICT_LINK:
+      if (!targetURI || !sourceURI) {
+        return NS_ERROR_INVALID_ARG;
+      }
+      // Link hover is a special case where we can predict without hitting the
+      // db, so let's go ahead and fire off that prediction here.
+      PredictForLink(targetURI, sourceURI, verifier);
+      return NS_OK;
+    case nsINetworkSeer::PREDICT_LOAD:
+      if (!targetURI || sourceURI) {
+        return NS_ERROR_INVALID_ARG;
+      }
+      break;
+    case nsINetworkSeer::PREDICT_STARTUP:
+      if (targetURI || sourceURI) {
+        return NS_ERROR_INVALID_ARG;
+      }
+      break;
+    default:
+      return NS_ERROR_INVALID_ARG;
+  }
+
+  ++mAccumulators->mPredictAttempts;
+  nsresult rv = ReserveSpaceInQueue();
+  if (NS_FAILED(rv)) {
+    ++mAccumulators->mPredictFullQueue;
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsRefPtr<SeerPredictionEvent> event = new SeerPredictionEvent(targetURI,
+                                                                sourceURI,
+                                                                reason,
+                                                                verifier);
+  return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+// A runnable for updating our information in the database. This must always
+// be dispatched to the seer thread.
+class SeerLearnEvent : public nsRunnable
+{
+public:
+  SeerLearnEvent(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason)
+    :mReason(reason)
+  {
+    MOZ_ASSERT(NS_IsMainThread(), "Creating learn event off main thread");
+
+    mEnqueueTime = TimeStamp::Now();
+
+    targetURI->GetAsciiSpec(mTargetURI.spec);
+    ExtractOrigin(targetURI, mTargetURI.origin);
+    if (sourceURI) {
+      sourceURI->GetAsciiSpec(mSourceURI.spec);
+      ExtractOrigin(sourceURI, mSourceURI.origin);
+    }
+  }
+
+  NS_IMETHOD Run() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(!NS_IsMainThread(), "Running learn off main thread");
+
+    nsresult rv = NS_OK;
+
+    Telemetry::AccumulateTimeDelta(Telemetry::SEER_LEARN_WAIT_TIME,
+                                   mEnqueueTime);
+
+    TimeStamp startTime = TimeStamp::Now();
+
+    switch (mReason) {
+    case nsINetworkSeer::LEARN_LOAD_TOPLEVEL:
+      gSeer->LearnForToplevel(mTargetURI);
+      break;
+    case nsINetworkSeer::LEARN_LOAD_REDIRECT:
+      gSeer->LearnForRedirect(mTargetURI, mSourceURI);
+      break;
+    case nsINetworkSeer::LEARN_LOAD_SUBRESOURCE:
+      gSeer->LearnForSubresource(mTargetURI, mSourceURI);
+      break;
+    case nsINetworkSeer::LEARN_STARTUP:
+      gSeer->LearnForStartup(mTargetURI);
+      break;
+    default:
+      MOZ_ASSERT(false, "Got unexpected value for learn reason");
+      rv = NS_ERROR_UNEXPECTED;
+    }
+
+    gSeer->FreeSpaceInQueue();
+
+    Telemetry::AccumulateTimeDelta(Telemetry::SEER_LEARN_WORK_TIME, startTime);
+
+    return rv;
+  }
+private:
+  Seer::UriInfo mTargetURI;
+  Seer::UriInfo mSourceURI;
+  SeerLearnReason mReason;
+  TimeStamp mEnqueueTime;
+};
+
+void
+Seer::LearnForToplevel(const UriInfo &uri)
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "LearnForToplevel called on main thread.");
+
+  if (NS_FAILED(EnsureInitStorage())) {
+    return;
+  }
+
+  PRTime now = PR_Now();
+
+  MaybeLearnForStartup(uri, now);
+
+  TopLevelInfo pageInfo;
+  TopLevelInfo originInfo;
+  bool havePage = LookupTopLevel(QUERY_PAGE, uri.spec, pageInfo);
+  bool haveOrigin = LookupTopLevel(QUERY_ORIGIN, uri.origin, originInfo);
+
+  if (!havePage) {
+    AddTopLevel(QUERY_PAGE, uri.spec, now);
+  } else {
+    UpdateTopLevel(QUERY_PAGE, pageInfo, now);
+  }
+
+  if (!haveOrigin) {
+    AddTopLevel(QUERY_ORIGIN, uri.origin, now);
+  } else {
+    UpdateTopLevel(QUERY_ORIGIN, originInfo, now);
+  }
+}
+
+// Queries to look up information about a *specific* subresource associated
+// with a *specific* top-level load.
+bool
+Seer::LookupSubresource(QueryType queryType, const int32_t parentId,
+                        const nsACString &key, SubresourceInfo &info)
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "LookupSubresource called on main thread.");
+
+  nsCOMPtr<mozIStorageStatement> stmt;
+  if (queryType == QUERY_PAGE) {
+    stmt = mStatements.GetCachedStatement(
+        NS_LITERAL_CSTRING("SELECT id, hits, last_hit FROM moz_subresources "
+                           "WHERE pid = :parent_id AND uri = :key;"));
+  } else {
+    stmt = mStatements.GetCachedStatement(
+        NS_LITERAL_CSTRING("SELECT id, hits, last_hit FROM moz_subhosts WHERE "
+                           "hid = :parent_id AND origin = :key;"));
+  }
+  NS_ENSURE_TRUE(stmt, false);
+  mozStorageStatementScoper scope(stmt);
+
+  nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("parent_id"),
+                                      parentId);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  bool hasRows;
+  rv = stmt->ExecuteStep(&hasRows);
+  NS_ENSURE_SUCCESS(rv, false);
+  if (!hasRows) {
+    return false;
+  }
+
+  rv = stmt->GetInt32(0, &info.id);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  rv = stmt->GetInt32(1, &info.hitCount);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  rv = stmt->GetInt64(2, &info.lastHit);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  return true;
+}
+
+// Add information about a new subresource associated with a top-level load.
+void
+Seer::AddSubresource(QueryType queryType, const int32_t parentId,
+                     const nsACString &key, const PRTime now)
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "AddSubresource called on main thread.");
+
+  nsCOMPtr<mozIStorageStatement> stmt;
+  if (queryType == QUERY_PAGE) {
+    stmt = mStatements.GetCachedStatement(
+        NS_LITERAL_CSTRING("INSERT INTO moz_subresources "
+                           "(pid, uri, hits, last_hit) VALUES "
+                           "(:parent_id, :key, 1, :now);"));
+  } else {
+    stmt = mStatements.GetCachedStatement(
+        NS_LITERAL_CSTRING("INSERT INTO moz_subhosts "
+                           "(hid, origin, hits, last_hit) VALUES "
+                           "(:parent_id, :key, 1, :now);"));
+  }
+  if (!stmt) {
+    return;
+  }
+  mozStorageStatementScoper scope(stmt);
+
+  nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("parent_id"),
+                                      parentId);
+  RETURN_IF_FAILED(rv);
+
+  rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("key"), key);
+  RETURN_IF_FAILED(rv);
+
+  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now);
+  RETURN_IF_FAILED(rv);
+
+  rv = stmt->Execute();
+}
+
+// Update the information about a particular subresource associated with a
+// top-level load
+void
+Seer::UpdateSubresource(QueryType queryType, const SubresourceInfo &info,
+                        const PRTime now)
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "UpdateSubresource called on main thread.");
+
+  nsCOMPtr<mozIStorageStatement> stmt;
+  if (queryType == QUERY_PAGE) {
+    stmt = mStatements.GetCachedStatement(
+        NS_LITERAL_CSTRING("UPDATE moz_subresources SET hits = :hit_count, "
+                           "last_hit = :now WHERE id = :id;"));
+  } else {
+    stmt = mStatements.GetCachedStatement(
+        NS_LITERAL_CSTRING("UPDATE moz_subhosts SET hits = :hit_count, "
+                           "last_hit = :now WHERE id = :id;"));
+  }
+  if (!stmt) {
+    return;
+  }
+  mozStorageStatementScoper scope(stmt);
+
+  nsresult rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("hit_count"),
+                                      info.hitCount + 1);
+  RETURN_IF_FAILED(rv);
+
+  rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("now"), now);
+  RETURN_IF_FAILED(rv);
+
+  rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("id"), info.id);
+  RETURN_IF_FAILED(rv);
+
+  rv = stmt->Execute();
+}
+
+// Called when a subresource has been hit from a top-level load. Uses the two
+// helper functions above to update the database appropriately.
+void
+Seer::LearnForSubresource(const UriInfo &targetURI, const UriInfo &sourceURI)
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "LearnForSubresource called on main thread.");
+
+  if (NS_FAILED(EnsureInitStorage())) {
+    return;
+  }
+
+  TopLevelInfo pageInfo, originInfo;
+  bool havePage = LookupTopLevel(QUERY_PAGE, sourceURI.spec, pageInfo);
+  bool haveOrigin = LookupTopLevel(QUERY_ORIGIN, sourceURI.origin,
+                                   originInfo);
+
+  if (!havePage && !haveOrigin) {
+    // Nothing to do, since we know nothing about the top level resource
+    return;
+  }
+
+  SubresourceInfo resourceInfo;
+  bool haveResource = false;
+  if (havePage) {
+    haveResource = LookupSubresource(QUERY_PAGE, pageInfo.id, targetURI.spec,
+                                     resourceInfo);
+  }
+
+  SubresourceInfo hostInfo;
+  bool haveHost = false;
+  if (haveOrigin) {
+    haveHost = LookupSubresource(QUERY_ORIGIN, originInfo.id, targetURI.origin,
+                                 hostInfo);
+  }
+
+  PRTime now = PR_Now();
+
+  if (haveResource) {
+    UpdateSubresource(QUERY_PAGE, resourceInfo, now);
+  } else if (havePage) {
+    AddSubresource(QUERY_PAGE, pageInfo.id, targetURI.spec, now);
+  }
+  // Can't add a subresource to a page we don't have in our db.
+
+  if (haveHost) {
+    UpdateSubresource(QUERY_ORIGIN, hostInfo, now);
+  } else if (haveOrigin) {
+    AddSubresource(QUERY_ORIGIN, originInfo.id, targetURI.origin, now);
+  }
+  // Can't add a subhost to a host we don't have in our db
+}
+
+// This is called when a top-level loaded ended up redirecting to a different
+// URI so we can keep track of that fact.
+void
+Seer::LearnForRedirect(const UriInfo &targetURI, const UriInfo &sourceURI)
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "LearnForRedirect called on main thread.");
+
+  if (NS_FAILED(EnsureInitStorage())) {
+    return;
+  }
+
+  PRTime now = PR_Now();
+  nsresult rv;
+
+  nsCOMPtr<mozIStorageStatement> getPage = mStatements.GetCachedStatement(
+      NS_LITERAL_CSTRING("SELECT id FROM moz_pages WHERE uri = :spec;"));
+  if (!getPage) {
+    return;
+  }
+  mozStorageStatementScoper scopedPage(getPage);
+
+  // look up source in moz_pages
+  rv = getPage->BindUTF8StringByName(NS_LITERAL_CSTRING("spec"),
+                                     sourceURI.spec);
+  RETURN_IF_FAILED(rv);
+
+  bool hasRows;
+  rv = getPage->ExecuteStep(&hasRows);
+  if (NS_FAILED(rv) || !hasRows) {
+    return;
+  }
+
+  int32_t pageId;
+  rv = getPage->GetInt32(0, &pageId);
+  RETURN_IF_FAILED(rv);
+
+  nsCOMPtr<mozIStorageStatement> getRedirect = mStatements.GetCachedStatement(
+      NS_LITERAL_CSTRING("SELECT id, hits FROM moz_redirects WHERE "
+                         "pid = :page_id AND uri = :spec;"));
+  if (!getRedirect) {
+    return;
+  }
+  mozStorageStatementScoper scopedRedirect(getRedirect);
+
+  rv = getRedirect->BindInt32ByName(NS_LITERAL_CSTRING("page_id"), pageId);
+  RETURN_IF_FAILED(rv);
+
+  rv = getRedirect->BindUTF8StringByName(NS_LITERAL_CSTRING("spec"),
+                                         targetURI.spec);
+  RETURN_IF_FAILED(rv);
+
+  rv = getRedirect->ExecuteStep(&hasRows);
+  RETURN_IF_FAILED(rv);
+
+  if (!hasRows) {
+    // This is the first time we've seen this top-level redirect to this URI
+    nsCOMPtr<mozIStorageStatement> addRedirect = mStatements.GetCachedStatement(
+        NS_LITERAL_CSTRING("INSERT INTO moz_redirects "
+                           "(pid, uri, origin, hits, last_hit) VALUES "
+                           "(:page_id, :spec, :origin, 1, :now);"));
+    if (!addRedirect) {
+      return;
+    }
+    mozStorageStatementScoper scopedAdd(addRedirect);
+
+    rv = addRedirect->BindInt32ByName(NS_LITERAL_CSTRING("page_id"), pageId);
+    RETURN_IF_FAILED(rv);
+
+    rv = addRedirect->BindUTF8StringByName(NS_LITERAL_CSTRING("spec"),
+                                           targetURI.spec);
+    RETURN_IF_FAILED(rv);
+
+    rv = addRedirect->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"),
+                                           targetURI.origin);
+    RETURN_IF_FAILED(rv);
+
+    rv = addRedirect->BindInt64ByName(NS_LITERAL_CSTRING("now"), now);
+    RETURN_IF_FAILED(rv);
+
+    rv = addRedirect->Execute();
+  } else {
+    // We've seen this redirect before
+    int32_t redirId, hits;
+    rv = getRedirect->GetInt32(0, &redirId);
+    RETURN_IF_FAILED(rv);
+
+    rv = getRedirect->GetInt32(1, &hits);
+    RETURN_IF_FAILED(rv);
+
+    nsCOMPtr<mozIStorageStatement> updateRedirect =
+      mStatements.GetCachedStatement(
+          NS_LITERAL_CSTRING("UPDATE moz_redirects SET hits = :hits, "
+                             "last_hit = :now WHERE id = :redir;"));
+    if (!updateRedirect) {
+      return;
+    }
+    mozStorageStatementScoper scopedUpdate(updateRedirect);
+
+    rv = updateRedirect->BindInt32ByName(NS_LITERAL_CSTRING("hits"), hits + 1);
+    RETURN_IF_FAILED(rv);
+
+    rv = updateRedirect->BindInt64ByName(NS_LITERAL_CSTRING("now"), now);
+    RETURN_IF_FAILED(rv);
+
+    rv = updateRedirect->BindInt32ByName(NS_LITERAL_CSTRING("redir"), redirId);
+    RETURN_IF_FAILED(rv);
+
+    updateRedirect->Execute();
+  }
+}
+
+// Add information about a top-level load to our list of startup pages
+void
+Seer::LearnForStartup(const UriInfo &uri)
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "LearnForStartup called on main thread.");
+
+  if (NS_FAILED(EnsureInitStorage())) {
+    return;
+  }
+
+  nsCOMPtr<mozIStorageStatement> getPage = mStatements.GetCachedStatement(
+      NS_LITERAL_CSTRING("SELECT id, hits FROM moz_startup_pages WHERE "
+                         "uri = :origin;"));
+  if (!getPage) {
+    return;
+  }
+  mozStorageStatementScoper scopedPage(getPage);
+  nsresult rv;
+
+  rv = getPage->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"), uri.origin);
+  RETURN_IF_FAILED(rv);
+
+  bool hasRows;
+  rv = getPage->ExecuteStep(&hasRows);
+  RETURN_IF_FAILED(rv);
+
+  if (hasRows) {
+    // We've loaded this page on startup before
+    int32_t pageId, hitCount;
+
+    rv = getPage->GetInt32(0, &pageId);
+    RETURN_IF_FAILED(rv);
+
+    rv = getPage->GetInt32(1, &hitCount);
+    RETURN_IF_FAILED(rv);
+
+    nsCOMPtr<mozIStorageStatement> updatePage = mStatements.GetCachedStatement(
+        NS_LITERAL_CSTRING("UPDATE moz_startup_pages SET hits = :hit_count, "
+                           "last_hit = :startup_time WHERE id = :page_id;"));
+    if (!updatePage) {
+      return;
+    }
+    mozStorageStatementScoper scopedUpdate(updatePage);
+
+    rv = updatePage->BindInt32ByName(NS_LITERAL_CSTRING("hit_count"),
+                                     hitCount + 1);
+    RETURN_IF_FAILED(rv);
+
+    rv = updatePage->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"),
+                                     mStartupTime);
+    RETURN_IF_FAILED(rv);
+
+    rv = updatePage->BindInt32ByName(NS_LITERAL_CSTRING("page_id"), pageId);
+    RETURN_IF_FAILED(rv);
+
+    updatePage->Execute();
+  } else {
+    // New startup page
+    nsCOMPtr<mozIStorageStatement> addPage = mStatements.GetCachedStatement(
+        NS_LITERAL_CSTRING("INSERT INTO moz_startup_pages (uri, hits, "
+                           "last_hit) VALUES (:origin, 1, :startup_time);"));
+    if (!addPage) {
+      return;
+    }
+    mozStorageStatementScoper scopedAdd(addPage);
+    rv = addPage->BindUTF8StringByName(NS_LITERAL_CSTRING("origin"),
+                                       uri.origin);
+    RETURN_IF_FAILED(rv);
+
+    rv = addPage->BindInt64ByName(NS_LITERAL_CSTRING("startup_time"),
+                                  mStartupTime);
+    RETURN_IF_FAILED(rv);
+
+    addPage->Execute();
+  }
+}
+
+// Called from the main thread to update the database
+NS_IMETHODIMP
+Seer::Learn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason,
+            nsILoadContext *loadContext)
+{
+  MOZ_ASSERT(NS_IsMainThread(),
+             "Seer interface methods must be called on the main thread");
+
+  if (!mInitialized) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  if (!mEnabled) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  if (loadContext && loadContext->UsePrivateBrowsing()) {
+    // Don't want to do anything in PB mode
+    return NS_OK;
+  }
+
+  if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  switch (reason) {
+  case nsINetworkSeer::LEARN_LOAD_TOPLEVEL:
+  case nsINetworkSeer::LEARN_STARTUP:
+    if (!targetURI || sourceURI) {
+      return NS_ERROR_INVALID_ARG;
+    }
+    break;
+  case nsINetworkSeer::LEARN_LOAD_REDIRECT:
+  case nsINetworkSeer::LEARN_LOAD_SUBRESOURCE:
+    if (!targetURI || !sourceURI) {
+      return NS_ERROR_INVALID_ARG;
+    }
+    break;
+  default:
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  ++mAccumulators->mLearnAttempts;
+  nsresult rv = ReserveSpaceInQueue();
+  if (NS_FAILED(rv)) {
+    ++mAccumulators->mLearnFullQueue;
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsRefPtr<SeerLearnEvent> event = new SeerLearnEvent(targetURI, sourceURI,
+                                                      reason);
+  return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+// Runnable to clear out the database. Dispatched from the main thread to the
+// seer thread
+class SeerResetEvent : public nsRunnable
+{
+public:
+  SeerResetEvent()
+  { }
+
+  NS_IMETHOD Run() MOZ_OVERRIDE
+  {
+    MOZ_ASSERT(!NS_IsMainThread(), "Running reset on main thread");
+
+    gSeer->ResetInternal();
+
+    return NS_OK;
+  }
+};
+
+// Helper that actually does the database wipe.
+void
+Seer::ResetInternal()
+{
+  MOZ_ASSERT(!NS_IsMainThread(), "Resetting db on main thread");
+
+  nsresult rv = EnsureInitStorage();
+  RETURN_IF_FAILED(rv);
+
+  mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_redirects"));
+  mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_startup_pages"));
+  mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_startups"));
+
+  // These cascade to moz_subresources and moz_subhosts, respectively.
+  mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_pages"));
+  mDB->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_hosts"));
+}
+
+// Called on the main thread to clear out all our knowledge. Tabula Rasa FTW!
+NS_IMETHODIMP
+Seer::Reset()
+{
+  MOZ_ASSERT(NS_IsMainThread(),
+             "Seer interface methods must be called on the main thread");
+
+  if (!mInitialized) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  nsRefPtr<SeerResetEvent> event = new SeerResetEvent();
+  return mIOThread->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+// Helper functions to make using the seer easier from native code
+
+static nsresult
+EnsureGlobalSeer(nsINetworkSeer **aSeer)
+{
+  nsresult rv;
+  nsCOMPtr<nsINetworkSeer> seer = do_GetService("@mozilla.org/network/seer;1",
+                                                &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  NS_IF_ADDREF(*aSeer = seer);
+  return NS_OK;
+}
+
+nsresult
+SeerPredict(nsIURI *targetURI, nsIURI *sourceURI, SeerPredictReason reason,
+            nsILoadContext *loadContext, nsINetworkSeerVerifier *verifier)
+{
+  nsCOMPtr<nsINetworkSeer> seer;
+  nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return seer->Predict(targetURI, sourceURI, reason, loadContext, verifier);
+}
+
+nsresult
+SeerLearn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason,
+          nsILoadContext *loadContext)
+{
+  nsCOMPtr<nsINetworkSeer> seer;
+  nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  return seer->Learn(targetURI, sourceURI, reason, loadContext);
+}
+
+nsresult
+SeerLearn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason,
+          nsILoadGroup *loadGroup)
+{
+  nsCOMPtr<nsINetworkSeer> seer;
+  nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsILoadContext> loadContext;
+
+  if (loadGroup) {
+    nsCOMPtr<nsIInterfaceRequestor> callbacks;
+    loadGroup->GetNotificationCallbacks(getter_AddRefs(callbacks));
+    if (callbacks) {
+      loadContext = do_GetInterface(callbacks);
+    }
+  }
+
+  return seer->Learn(targetURI, sourceURI, reason, loadContext);
+}
+
+nsresult
+SeerLearn(nsIURI *targetURI, nsIURI *sourceURI, SeerLearnReason reason,
+          nsIDocument *document)
+{
+  nsCOMPtr<nsINetworkSeer> seer;
+  nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsILoadContext> loadContext;
+
+  if (document) {
+    loadContext = document->GetLoadContext();
+  }
+
+  return seer->Learn(targetURI, sourceURI, reason, loadContext);
+}
+
+nsresult
+SeerLearnRedirect(nsIURI *targetURI, nsIChannel *channel,
+                  nsILoadContext *loadContext)
+{
+  nsCOMPtr<nsINetworkSeer> seer;
+  nsresult rv = EnsureGlobalSeer(getter_AddRefs(seer));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIURI> sourceURI;
+  rv = channel->GetOriginalURI(getter_AddRefs(sourceURI));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  bool sameUri;
+  rv = targetURI->Equals(sourceURI, &sameUri);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (sameUri) {
+    return NS_OK;
+  }
+
+  return seer->Learn(targetURI, sourceURI,
+                     nsINetworkSeer::LEARN_LOAD_REDIRECT, loadContext);
+}
+
+} // ::mozilla::net
+} // ::mozilla
new file mode 100644
--- /dev/null
+++ b/netwerk/base/src/Seer.h
@@ -0,0 +1,207 @@
+/* vim: set ts=2 sts=2 et sw=2: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_net_Seer_h
+#define mozilla_net_Seer_h
+
+#include "nsINetworkSeer.h"
+
+#include "nsCOMPtr.h"
+#include "nsIDNSListener.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIObserver.h"
+#include "nsISpeculativeConnect.h"
+#include "nsProxyRelease.h"
+
+#include "mozilla/Mutex.h"
+#include "mozilla/storage/StatementCache.h"
+#include "mozilla/TimeStamp.h"
+
+class nsIDNSService;
+class nsINetworkSeerVerifier;
+class nsIThread;
+
+class mozIStorageConnection;
+class mozIStorageService;
+class mozIStorageStatement;
+
+namespace mozilla {
+namespace net {
+
+typedef nsMainThreadPtrHandle<nsINetworkSeerVerifier> SeerVerifierHandle;
+
+class SeerPredictionRunner;
+struct SeerTelemetryAccumulators;
+class SeerDNSListener;
+
+class Seer : public nsINetworkSeer
+           , public nsIObserver
+           , public nsISpeculativeConnectionOverrider
+           , public nsIInterfaceRequestor
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSINETWORKSEER
+  NS_DECL_NSIOBSERVER
+  NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER
+  NS_DECL_NSIINTERFACEREQUESTOR
+
+  Seer();
+  virtual ~Seer();
+
+  nsresult Init();
+  void Shutdown();
+  static nsresult Create(nsISupports *outer, const nsIID& iid, void **result);
+
+private:
+  friend class SeerPredictionEvent;
+  friend class SeerLearnEvent;
+  friend class SeerResetEvent;
+  friend class SeerPredictionRunner;
+  friend class SeerDBShutdownRunner;
+
+  nsresult EnsureInitStorage();
+
+  // This is a proxy for the information we need from an nsIURI
+  struct UriInfo {
+    nsAutoCString spec;
+    nsAutoCString origin;
+  };
+
+  void PredictForLink(nsIURI *targetURI,
+                      nsIURI *sourceURI,
+                      nsINetworkSeerVerifier *verifier);
+  void PredictForPageload(const UriInfo &dest,
+                          SeerVerifierHandle &verifier,
+                          int stackCount,
+                          TimeStamp &predictStartTime);
+  void PredictForStartup(SeerVerifierHandle &verifier,
+                         TimeStamp &predictStartTime);
+
+  // Whether we're working on a page or an origin
+  enum QueryType {
+    QUERY_PAGE = 0,
+    QUERY_ORIGIN
+  };
+
+  // Holds info from the db about a top-level page or origin
+  struct TopLevelInfo {
+    int32_t id;
+    int32_t loadCount;
+    PRTime lastLoad;
+  };
+
+  // Holds info from the db about a subresource
+  struct SubresourceInfo {
+    int32_t id;
+    int32_t hitCount;
+    PRTime lastHit;
+  };
+
+  nsresult ReserveSpaceInQueue();
+  void FreeSpaceInQueue();
+
+  int CalculateGlobalDegradation(PRTime now,
+                                 PRTime lastLoad);
+  int CalculateConfidence(int baseConfidence,
+                          PRTime lastHit,
+                          PRTime lastPossible,
+                          int globalDegradation);
+  void SetupPrediction(int confidence,
+                       const nsACString &uri,
+                       SeerPredictionRunner *runner);
+
+  bool LookupTopLevel(QueryType queryType,
+                      const nsACString &key,
+                      TopLevelInfo &info);
+  void AddTopLevel(QueryType queryType,
+                   const nsACString &key,
+                   PRTime now);
+  void UpdateTopLevel(QueryType queryType,
+                      const TopLevelInfo &info,
+                      PRTime now);
+  bool TryPredict(QueryType queryType,
+                  const TopLevelInfo &info,
+                  PRTime now,
+                  SeerVerifierHandle &verifier,
+                  TimeStamp &predictStartTime);
+  bool WouldRedirect(const TopLevelInfo &info,
+                     PRTime now,
+                     UriInfo &newUri);
+
+  bool LookupSubresource(QueryType queryType,
+                         const int32_t parentId,
+                         const nsACString &key,
+                         SubresourceInfo &info);
+  void AddSubresource(QueryType queryType,
+                      const int32_t parentId,
+                      const nsACString &key, PRTime now);
+  void UpdateSubresource(QueryType queryType,
+                         const SubresourceInfo &info,
+                         PRTime now);
+
+  void MaybeLearnForStartup(const UriInfo &uri, const PRTime now);
+
+  void LearnForToplevel(const UriInfo &uri);
+  void LearnForSubresource(const UriInfo &targetURI, const UriInfo &sourceURI);
+  void LearnForRedirect(const UriInfo &targetURI, const UriInfo &sourceURI);
+  void LearnForStartup(const UriInfo &uri);
+
+  void ResetInternal();
+
+  // Observer-related stuff
+  nsresult InstallObserver();
+  void RemoveObserver();
+
+  bool mInitialized;
+
+  bool mEnabled;
+  bool mEnableHoverOnSSL;
+
+  int mPageDegradationDay;
+  int mPageDegradationWeek;
+  int mPageDegradationMonth;
+  int mPageDegradationYear;
+  int mPageDegradationMax;
+
+  int mSubresourceDegradationDay;
+  int mSubresourceDegradationWeek;
+  int mSubresourceDegradationMonth;
+  int mSubresourceDegradationYear;
+  int mSubresourceDegradationMax;
+
+  int mPreconnectMinConfidence;
+  int mPreresolveMinConfidence;
+  int mRedirectLikelyConfidence;
+
+  int32_t mMaxQueueSize;
+
+  nsCOMPtr<nsIThread> mIOThread;
+
+  nsCOMPtr<nsISpeculativeConnect> mSpeculativeService;
+
+  nsCOMPtr<nsIFile> mDBFile;
+  nsCOMPtr<mozIStorageService> mStorageService;
+  nsCOMPtr<mozIStorageConnection> mDB;
+  mozilla::storage::StatementCache<mozIStorageStatement> mStatements;
+
+  PRTime mStartupTime;
+  PRTime mLastStartupTime;
+  int32_t mStartupCount;
+
+  nsCOMPtr<nsIDNSService> mDnsService;
+
+  int32_t mQueueSize;
+  mozilla::Mutex mQueueSizeLock;
+
+  nsAutoPtr<SeerTelemetryAccumulators> mAccumulators;
+
+  nsRefPtr<SeerDNSListener> mDNSListener;
+};
+
+} // ::mozilla::net
+} // ::mozilla
+
+#endif // mozilla_net_Seer_h
--- a/netwerk/base/src/moz.build
+++ b/netwerk/base/src/moz.build
@@ -68,16 +68,17 @@ SOURCES += [
     'nsTransportUtils.cpp',
     'nsUDPServerSocket.cpp',
     'nsUnicharStreamLoader.cpp',
     'nsURIChecker.cpp',
     'nsURLHelper.cpp',
     'nsURLParsers.cpp',
     'ProxyAutoConfig.cpp',
     'RedirectChannelRegistrar.cpp',
+    'Seer.cpp',
     'StreamingProtocolService.cpp',
     'Tickler.cpp',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'os2':
     SOURCES += [
         'nsURLHelperOS2.cpp',
     ]
--- a/netwerk/build/nsNetCID.h
+++ b/netwerk/build/nsNetCID.h
@@ -431,16 +431,27 @@
 #define NS_REDIRECTCHANNELREGISTRAR_CID \
 { /* {b69043a6-8929-4d60-8d17-a27e44a8393e} */ \
     0xb69043a6, \
     0x8929, \
     0x4d60, \
     { 0x8d, 0x17, 0xa2, 0x7e, 0x44, 0xa8, 0x39, 0x3e } \
 }
 
+// service implementing nsINetworkSeer
+#define NS_NETWORKSEER_CONTRACTID \
+    "@mozilla.org/network/seer;1"
+#define NS_NETWORKSEER_CID \
+{ /* {1C218009-A531-46AD-8351-1E7F45D5A3C4} */ \
+    0x1C218009, \
+    0xA531, \
+    0x46AD, \
+    { 0x83, 0x51, 0x1E, 0x7F, 0x45, 0xD5, 0xA3, 0xC4 } \
+}
+
 /******************************************************************************
  * netwerk/cache/ classes
  */
 
 // service implementing nsICacheService.
 #define NS_CACHESERVICE_CONTRACTID \
     "@mozilla.org/network/cache-service;1"
 #define NS_CACHESERVICE_CID                          \
--- a/netwerk/build/nsNetModule.cpp
+++ b/netwerk/build/nsNetModule.cpp
@@ -31,16 +31,17 @@
 #include "nsApplicationCacheService.h"
 #include "nsMimeTypes.h"
 #include "nsNetStrings.h"
 #include "nsDNSPrefetch.h"
 #include "nsAboutProtocolHandler.h"
 #include "nsXULAppAPI.h"
 #include "nsCategoryCache.h"
 #include "nsIContentSniffer.h"
+#include "Seer.h"
 #include "nsNetUtil.h"
 #include "nsIThreadPool.h"
 #include "mozilla/net/NeckoChild.h"
 
 #include "nsNetCID.h"
 
 #ifndef XP_MACOSX
 #define BUILD_BINHEX_DECODER 1
@@ -805,16 +806,17 @@ NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERV
 #elif defined(MOZ_ENABLE_QTNETWORK)
 NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID);
 #elif defined(MOZ_WIDGET_ANDROID)
 NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID);
 #endif
 NS_DEFINE_NAMED_CID(NS_SERIALIZATION_HELPER_CID);
 NS_DEFINE_NAMED_CID(NS_REDIRECTCHANNELREGISTRAR_CID);
 NS_DEFINE_NAMED_CID(NS_CACHE_STORAGE_SERVICE_CID);
+NS_DEFINE_NAMED_CID(NS_NETWORKSEER_CID);
 
 static const mozilla::Module::CIDEntry kNeckoCIDs[] = {
     { &kNS_IOSERVICE_CID, false, nullptr, nsIOServiceConstructor },
     { &kNS_STREAMTRANSPORTSERVICE_CID, false, nullptr, nsStreamTransportServiceConstructor },
     { &kNS_SOCKETTRANSPORTSERVICE_CID, false, nullptr, nsSocketTransportServiceConstructor },
     { &kNS_SERVERSOCKET_CID, false, nullptr, nsServerSocketConstructor },
     { &kNS_UDPSERVERSOCKET_CID, false, nullptr, nsUDPServerSocketConstructor },
     { &kNS_SOCKETPROVIDERSERVICE_CID, false, nullptr, nsSocketProviderService::Create },
@@ -945,16 +947,17 @@ static const mozilla::Module::CIDEntry k
 #elif defined(MOZ_ENABLE_QTNETWORK)
     { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsQtNetworkLinkServiceConstructor },
 #elif defined(MOZ_WIDGET_ANDROID)
     { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsAndroidNetworkLinkServiceConstructor },
 #endif
     { &kNS_SERIALIZATION_HELPER_CID, false, nullptr, nsSerializationHelperConstructor },
     { &kNS_REDIRECTCHANNELREGISTRAR_CID, false, nullptr, RedirectChannelRegistrarConstructor },
     { &kNS_CACHE_STORAGE_SERVICE_CID, false, nullptr, CacheStorageServiceConstructor },
+    { &kNS_NETWORKSEER_CID, false, NULL, mozilla::net::Seer::Create },
     { nullptr }
 };
 
 static const mozilla::Module::ContractIDEntry kNeckoContracts[] = {
     { NS_IOSERVICE_CONTRACTID, &kNS_IOSERVICE_CID },
     { NS_NETUTIL_CONTRACTID, &kNS_IOSERVICE_CID },
     { NS_STREAMTRANSPORTSERVICE_CONTRACTID, &kNS_STREAMTRANSPORTSERVICE_CID },
     { NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &kNS_SOCKETTRANSPORTSERVICE_CID },
@@ -1088,16 +1091,17 @@ static const mozilla::Module::ContractID
 #elif defined(MOZ_ENABLE_QTNETWORK)
     { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID },
 #elif defined(MOZ_WIDGET_ANDROID)
     { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID },
 #endif
     { NS_SERIALIZATION_HELPER_CONTRACTID, &kNS_SERIALIZATION_HELPER_CID },
     { NS_REDIRECTCHANNELREGISTRAR_CONTRACTID, &kNS_REDIRECTCHANNELREGISTRAR_CID },
     { NS_CACHE_STORAGE_SERVICE_CONTRACTID, &kNS_CACHE_STORAGE_SERVICE_CID },
+    { NS_NETWORKSEER_CONTRACTID, &kNS_NETWORKSEER_CID },
     { nullptr }
 };
 
 static const mozilla::Module kNeckoModule = {
     mozilla::Module::kVersion,
     kNeckoCIDs,
     kNeckoContracts,
     kNeckoCategories,
--- a/netwerk/protocol/http/nsHttpConnectionMgr.cpp
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.cpp
@@ -313,37 +313,80 @@ nsHttpConnectionMgr::DoShiftReloadConnec
 
     nsresult rv = PostEvent(&nsHttpConnectionMgr::OnMsgDoShiftReloadConnectionCleanup,
                             0, connInfo);
     if (NS_SUCCEEDED(rv))
         connInfo.forget();
     return rv;
 }
 
+class SpeculativeConnectArgs
+{
+public:
+    SpeculativeConnectArgs() { mOverridesOK = false; }
+    virtual ~SpeculativeConnectArgs() {}
+
+    // Added manually so we can use nsRefPtr without inheriting from
+    // nsISupports
+    NS_IMETHOD_(nsrefcnt) AddRef(void);
+    NS_IMETHOD_(nsrefcnt) Release(void);
+
+public: // intentional!
+    nsRefPtr<NullHttpTransaction> mTrans;
+
+    bool mOverridesOK;
+    uint32_t mParallelSpeculativeConnectLimit;
+    bool mIgnoreIdle;
+    bool mIgnorePossibleSpdyConnections;
+
+    // As above, added manually so we can use nsRefPtr without inheriting from
+    // nsISupports
+protected:
+    ::mozilla::ThreadSafeAutoRefCnt mRefCnt;
+    NS_DECL_OWNINGTHREAD
+};
+
+NS_IMPL_ADDREF(SpeculativeConnectArgs)
+NS_IMPL_RELEASE(SpeculativeConnectArgs)
+
 nsresult
 nsHttpConnectionMgr::SpeculativeConnect(nsHttpConnectionInfo *ci,
                                         nsIInterfaceRequestor *callbacks,
                                         uint32_t caps)
 {
+    MOZ_ASSERT(NS_IsMainThread(), "nsHttpConnectionMgr::SpeculativeConnect called off main thread!");
+
     LOG(("nsHttpConnectionMgr::SpeculativeConnect [ci=%s]\n",
          ci->HashKey().get()));
 
+    nsRefPtr<SpeculativeConnectArgs> args = new SpeculativeConnectArgs();
+
     // Wrap up the callbacks and the target to ensure they're released on the target
     // thread properly.
     nsCOMPtr<nsIInterfaceRequestor> wrappedCallbacks;
     NS_NewInterfaceRequestorAggregation(callbacks, nullptr, getter_AddRefs(wrappedCallbacks));
 
     caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0;
-    nsRefPtr<NullHttpTransaction> trans =
-        new NullHttpTransaction(ci, wrappedCallbacks, caps);
+    args->mTrans = new NullHttpTransaction(ci, wrappedCallbacks, caps);
+
+    nsCOMPtr<nsISpeculativeConnectionOverrider> overrider =
+        do_GetInterface(callbacks);
+    if (overrider) {
+        args->mOverridesOK = true;
+        overrider->GetParallelSpeculativeConnectLimit(
+            &args->mParallelSpeculativeConnectLimit);
+        overrider->GetIgnoreIdle(&args->mIgnoreIdle);
+        overrider->GetIgnorePossibleSpdyConnections(
+            &args->mIgnorePossibleSpdyConnections);
+    }
 
     nsresult rv =
-        PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, trans);
+        PostEvent(&nsHttpConnectionMgr::OnMsgSpeculativeConnect, 0, args);
     if (NS_SUCCEEDED(rv))
-        trans.forget();
+        args.forget();
     return rv;
 }
 
 nsresult
 nsHttpConnectionMgr::GetSocketThreadTarget(nsIEventTarget **target)
 {
     EnsureSocketThreadTarget();
 
@@ -1222,36 +1265,38 @@ nsHttpConnectionMgr::ClosePersistentConn
                                                   void *closure)
 {
     nsHttpConnectionMgr *self = static_cast<nsHttpConnectionMgr *>(closure);
     self->ClosePersistentConnections(ent);
     return PL_DHASH_NEXT;
 }
 
 bool
-nsHttpConnectionMgr::RestrictConnections(nsConnectionEntry *ent)
+nsHttpConnectionMgr::RestrictConnections(nsConnectionEntry *ent,
+                                         bool ignorePossibleSpdyConnections)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
     // If this host is trying to negotiate a SPDY session right now,
     // don't create any new ssl connections until the result of the
     // negotiation is known.
 
     bool doRestrict = ent->mConnInfo->UsingSSL() &&
         gHttpHandler->IsSpdyEnabled() &&
-        (!ent->mTestedSpdy || ent->mUsingSpdy) &&
+        ((!ent->mTestedSpdy && !ignorePossibleSpdyConnections) ||
+         ent->mUsingSpdy) &&
         (ent->mHalfOpens.Length() || ent->mActiveConns.Length());
 
     // If there are no restrictions, we are done
     if (!doRestrict)
         return false;
 
     // If the restriction is based on a tcp handshake in progress
     // let that connect and then see if it was SPDY or not
-    if (ent->UnconnectedHalfOpens())
+    if (ent->UnconnectedHalfOpens() && !ignorePossibleSpdyConnections)
         return true;
 
     // There is a concern that a host is using a mix of HTTP/1 and SPDY.
     // In that case we don't want to restrict connections just because
     // there is a single active HTTP/1 session in use.
     if (ent->mUsingSpdy && ent->mActiveConns.Length()) {
         bool confirmedRestrict = false;
         for (uint32_t index = 0; index < ent->mActiveConns.Length(); ++index) {
@@ -2513,36 +2558,48 @@ nsConnectionHandle::TakeTransport(nsISoc
     return mConn->TakeTransport(aTransport, aInputStream, aOutputStream);
 }
 
 void
 nsHttpConnectionMgr::OnMsgSpeculativeConnect(int32_t, void *param)
 {
     MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
 
-    nsRefPtr<NullHttpTransaction> trans =
-        dont_AddRef(static_cast<NullHttpTransaction *>(param));
+    nsRefPtr<SpeculativeConnectArgs> args =
+        dont_AddRef(static_cast<SpeculativeConnectArgs *>(param));
 
     LOG(("nsHttpConnectionMgr::OnMsgSpeculativeConnect [ci=%s]\n",
-         trans->ConnectionInfo()->HashKey().get()));
+         args->mTrans->ConnectionInfo()->HashKey().get()));
 
     nsConnectionEntry *ent =
-        GetOrCreateConnectionEntry(trans->ConnectionInfo());
+        GetOrCreateConnectionEntry(args->mTrans->ConnectionInfo());
 
     // If spdy has previously made a preferred entry for this host via
     // the ip pooling rules. If so, connect to the preferred host instead of
     // the one directly passed in here.
     nsConnectionEntry *preferredEntry = GetSpdyPreferredEnt(ent);
     if (preferredEntry)
         ent = preferredEntry;
 
-    if (mNumHalfOpenConns < gHttpHandler->ParallelSpeculativeConnectLimit() &&
-        !ent->mIdleConns.Length() && !RestrictConnections(ent) &&
-        !AtActiveConnectionLimit(ent, trans->Caps())) {
-        CreateTransport(ent, trans, trans->Caps(), true);
+    uint32_t parallelSpeculativeConnectLimit =
+        gHttpHandler->ParallelSpeculativeConnectLimit();
+    bool ignorePossibleSpdyConnections = false;
+    bool ignoreIdle = false;
+
+    if (args->mOverridesOK) {
+        parallelSpeculativeConnectLimit = args->mParallelSpeculativeConnectLimit;
+        ignorePossibleSpdyConnections = args->mIgnorePossibleSpdyConnections;
+        ignoreIdle = args->mIgnoreIdle;
+    }
+
+    if (mNumHalfOpenConns < parallelSpeculativeConnectLimit &&
+        (ignoreIdle || !ent->mIdleConns.Length()) &&
+        !RestrictConnections(ent, ignorePossibleSpdyConnections) &&
+        !AtActiveConnectionLimit(ent, args->mTrans->Caps())) {
+        CreateTransport(ent, args->mTrans, args->mTrans->Caps(), true);
     }
     else {
         LOG(("  Transport not created due to existing connection count\n"));
     }
 }
 
 bool
 nsHttpConnectionMgr::nsConnectionHandle::IsPersistent()
--- a/netwerk/protocol/http/nsHttpConnectionMgr.h
+++ b/netwerk/protocol/http/nsHttpConnectionMgr.h
@@ -507,17 +507,17 @@ private:
     nsresult DispatchAbstractTransaction(nsConnectionEntry *,
                                          nsAHttpTransaction *,
                                          uint32_t,
                                          nsHttpConnection *,
                                          int32_t);
     nsresult BuildPipeline(nsConnectionEntry *,
                            nsAHttpTransaction *,
                            nsHttpPipeline **);
-    bool     RestrictConnections(nsConnectionEntry *);
+    bool     RestrictConnections(nsConnectionEntry *, bool = false);
     nsresult ProcessNewTransaction(nsHttpTransaction *);
     nsresult EnsureSocketThreadTarget();
     void     ClosePersistentConnections(nsConnectionEntry *ent);
     void     ReportProxyTelemetry(nsConnectionEntry *ent);
     nsresult CreateTransport(nsConnectionEntry *, nsAHttpTransaction *,
                              uint32_t, bool);
     void     AddActiveConn(nsHttpConnection *, nsConnectionEntry *);
     void     DecrementActiveConnCount(nsHttpConnection *);
--- a/netwerk/sctp/datachannel/DataChannel.cpp
+++ b/netwerk/sctp/datachannel/DataChannel.cpp
@@ -843,19 +843,19 @@ DataChannelConnection::Connect(const cha
   NS_DispatchToMainThread(new DataChannelOnMessageAvailable(
                             DataChannelOnMessageAvailable::ON_CONNECTION,
                             this, (DataChannel *) nullptr));
   return true;
 }
 #endif
 
 DataChannel *
-DataChannelConnection::FindChannelByStream(uint16_t streamOut)
+DataChannelConnection::FindChannelByStream(uint16_t stream)
 {
-  return mStreams.SafeElementAt(streamOut);
+  return mStreams.SafeElementAt(stream);
 }
 
 uint16_t
 DataChannelConnection::FindFreeStream()
 {
   uint32_t i, j, limit;
 
   limit = mStreams.Length();
@@ -930,16 +930,27 @@ DataChannelConnection::SendControlMessag
                     SCTP_SENDV_SNDINFO, 0) < 0) {
     //LOG(("***failed: sctp_sendv")); don't log because errno is a return!
     return (0);
   }
   return (1);
 }
 
 int32_t
+DataChannelConnection::SendOpenAckMessage(uint16_t stream)
+{
+  struct rtcweb_datachannel_ack ack;
+
+  memset(&ack, 0, sizeof(struct rtcweb_datachannel_ack));
+  ack.msg_type = DATA_CHANNEL_ACK;
+
+  return SendControlMessage(&ack, sizeof(ack), stream);
+}
+
+int32_t
 DataChannelConnection::SendOpenRequestMessage(const nsACString& label,
                                               const nsACString& protocol,
                                               uint16_t stream, bool unordered,
                                               uint16_t prPolicy, uint32_t prValue)
 {
   int label_len = label.Length(); // not including nul
   int proto_len = protocol.Length(); // not including nul
   struct rtcweb_datachannel_open_request *req =
@@ -1030,16 +1041,33 @@ DataChannelConnection::SendDeferredMessa
                                     DataChannelOnMessageAvailable::ON_CHANNEL_CLOSED, this,
                                     channel));
         }
       }
     }
     if (still_blocked)
       break;
 
+    if (channel->mFlags & DATA_CHANNEL_FLAGS_SEND_ACK) {
+      if (SendOpenAckMessage(channel->mStream)) {
+        channel->mFlags &= ~DATA_CHANNEL_FLAGS_SEND_ACK;
+        sent = true;
+      } else {
+        if (errno == EAGAIN || errno == EWOULDBLOCK) {
+          still_blocked = true;
+        } else {
+          // Close the channel, inform the user
+          CloseInt(channel);
+          // XXX send error via DataChannelOnMessageAvailable (bug 843625)
+        }
+      }
+    }
+    if (still_blocked)
+      break;
+
     if (channel->mFlags & DATA_CHANNEL_FLAGS_SEND_DATA) {
       bool failed_send = false;
       int32_t result;
 
       if (channel->mState == CLOSED || channel->mState == CLOSING) {
         channel->mBufferedData.Clear();
       }
       while (!channel->mBufferedData.IsEmpty() &&
@@ -1100,22 +1128,24 @@ DataChannelConnection::HandleOpenRequest
   nsRefPtr<DataChannel> channel;
   uint32_t prValue;
   uint16_t prPolicy;
   uint32_t flags;
 
   mLock.AssertCurrentThreadOwns();
 
   if (length != (sizeof(*req) - 1) + ntohs(req->label_length) + ntohs(req->protocol_length)) {
-    LOG(("Inconsistent length: %u, should be %u", length,
+    LOG(("%s: Inconsistent length: %u, should be %u", __FUNCTION__, length,
          (sizeof(*req) - 1) + ntohs(req->label_length) + ntohs(req->protocol_length)));
     if (length < (sizeof(*req) - 1) + ntohs(req->label_length) + ntohs(req->protocol_length))
       return;
   }
 
+  LOG(("%s: length %u, sizeof(*req) = %u", __FUNCTION__, length, sizeof(*req)));
+
   switch (req->channel_type) {
     case DATA_CHANNEL_RELIABLE:
     case DATA_CHANNEL_RELIABLE_UNORDERED:
       prPolicy = SCTP_PR_SCTP_NONE;
       break;
     case DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT:
     case DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT_UNORDERED:
       prPolicy = SCTP_PR_SCTP_RTX;
@@ -1171,22 +1201,30 @@ DataChannelConnection::HandleOpenRequest
   LOG(("%s: sending ON_CHANNEL_CREATED for %s/%s: %u", __FUNCTION__,
        channel->mLabel.get(), channel->mProtocol.get(), stream));
   NS_DispatchToMainThread(new DataChannelOnMessageAvailable(
                             DataChannelOnMessageAvailable::ON_CHANNEL_CREATED,
                             this, channel));
 
   LOG(("%s: deferring sending ON_CHANNEL_OPEN for %p", __FUNCTION__, channel.get()));
 
+  if (!SendOpenAckMessage(stream)) {
+    // XXX Only on EAGAIN!?  And if not, then close the channel??
+    channel->mFlags |= DATA_CHANNEL_FLAGS_SEND_ACK;
+    StartDefer();
+  }
+
   // Now process any queued data messages for the channel (which will
   // themselves likely get queued until we leave WAITING_TO_OPEN, plus any
   // more that come in before that happens)
   DeliverQueuedData(stream);
 }
 
+// NOTE: the updated spec from the IETF says we should set in-order until we receive an ACK.
+// That would make this code moot.  Keep it for now for backwards compatibility.
 void
 DataChannelConnection::DeliverQueuedData(uint16_t stream)
 {
   mLock.AssertCurrentThreadOwns();
 
   uint32_t i = 0;
   while (i < mQueuedData.Length()) {
     // Careful! we may modify the array length from within the loop!
@@ -1200,16 +1238,33 @@ DataChannelConnection::DeliverQueuedData
       mQueuedData.RemoveElementAt(i);
       continue; // don't bump index since we removed the element
     }
     i++;
   }
 }
 
 void
+DataChannelConnection::HandleOpenAckMessage(const struct rtcweb_datachannel_ack *ack,
+                                            size_t length, uint16_t stream)
+{
+  DataChannel *channel;
+
+  mLock.AssertCurrentThreadOwns();
+
+  channel = FindChannelByStream(stream);
+  NS_ENSURE_TRUE_VOID(channel);
+
+  LOG(("OpenAck received for stream %u, waiting=%d", stream,
+       (channel->mFlags & DATA_CHANNEL_FLAGS_WAITING_ACK) ? 1 : 0));
+
+  channel->mFlags &= ~DATA_CHANNEL_FLAGS_WAITING_ACK;
+}
+
+void
 DataChannelConnection::HandleUnknownMessage(uint32_t ppid, size_t length, uint16_t stream)
 {
   /* XXX: Send an error message? */
   LOG(("unknown DataChannel message received: %u, len %ld on stream %lu", ppid, length, stream));
   // XXX Log to JS error console if possible
 }
 
 void
@@ -1220,16 +1275,18 @@ DataChannelConnection::HandleDataMessage
   DataChannel *channel;
   const char *buffer = (const char *) data;
 
   mLock.AssertCurrentThreadOwns();
 
   channel = FindChannelByStream(stream);
 
   // XXX A closed channel may trip this... check
+  // NOTE: the updated spec from the IETF says we should set in-order until we receive an ACK.
+  // That would make this code moot.  Keep it for now for backwards compatibility.
   if (!channel) {
     // In the updated 0-RTT open case, the sender can send data immediately
     // after Open, and doesn't set the in-order bit (since we don't have a
     // response or ack).  Also, with external negotiation, data can come in
     // before we're told about the external negotiation.  We need to buffer
     // data until either a) Open comes in, if the ordering get messed up,
     // or b) the app tells us this channel was externally negotiated.  When
     // these occur, we deliver the data.
@@ -1313,32 +1370,38 @@ DataChannelConnection::HandleDataMessage
   }
 }
 
 // Called with mLock locked!
 void
 DataChannelConnection::HandleMessage(const void *buffer, size_t length, uint32_t ppid, uint16_t stream)
 {
   const struct rtcweb_datachannel_open_request *req;
+  const struct rtcweb_datachannel_ack *ack;
 
   mLock.AssertCurrentThreadOwns();
 
   switch (ppid) {
     case DATA_CHANNEL_PPID_CONTROL:
-      // structure includes a possibly-unused char label[1] (in a packed structure)
-      NS_ENSURE_TRUE_VOID(length >= sizeof(*req) - 1);
+      req = static_cast<const struct rtcweb_datachannel_open_request *>(buffer);
 
-      req = static_cast<const struct rtcweb_datachannel_open_request *>(buffer);
+      NS_ENSURE_TRUE_VOID(length >= sizeof(*ack)); // smallest message
       switch (req->msg_type) {
         case DATA_CHANNEL_OPEN_REQUEST:
-          LOG(("length %u, sizeof(*req) = %u", length, sizeof(*req)));
-          NS_ENSURE_TRUE_VOID(length >= sizeof(*req));
+          // structure includes a possibly-unused char label[1] (in a packed structure)
+          NS_ENSURE_TRUE_VOID(length >= sizeof(*req) - 1);
 
           HandleOpenRequestMessage(req, length, stream);
           break;
+        case DATA_CHANNEL_ACK:
+          // >= sizeof(*ack) checked above
+
+          ack = static_cast<const struct rtcweb_datachannel_ack *>(buffer);
+          HandleOpenAckMessage(ack, length, stream);
+          break;
         default:
           HandleUnknownMessage(ppid, length, stream);
           break;
       }
       break;
     case DATA_CHANNEL_PPID_DOMSTRING:
     case DATA_CHANNEL_PPID_DOMSTRING_LAST:
     case DATA_CHANNEL_PPID_BINARY:
@@ -1576,30 +1639,30 @@ DataChannelConnection::ClearResets()
       LOG(("Forgetting channel %u (%p) with pending reset",channel->mStream, channel.get()));
       mStreams[channel->mStream] = nullptr;
     }
   }
   mStreamsResetting.Clear();
 }
 
 void
-DataChannelConnection::ResetOutgoingStream(uint16_t streamOut)
+DataChannelConnection::ResetOutgoingStream(uint16_t stream)
 {
   uint32_t i;
 
   mLock.AssertCurrentThreadOwns();
   LOG(("Connection %p: Resetting outgoing stream %u",
-       (void *) this, streamOut));
+       (void *) this, stream));
   // Rarely has more than a couple items and only for a short time
   for (i = 0; i < mStreamsResetting.Length(); ++i) {
-    if (mStreamsResetting[i] == streamOut) {
+    if (mStreamsResetting[i] == stream) {
       return;
     }
   }
-  mStreamsResetting.AppendElement(streamOut);
+  mStreamsResetting.AppendElement(stream);
 }
 
 void
 DataChannelConnection::SendOutgoingStreamReset()
 {
   struct sctp_reset_streams *srs;
   uint32_t i;
   size_t len;
@@ -2005,16 +2068,21 @@ DataChannelConnection::OpenFinish(alread
 
 #ifdef TEST_QUEUED_DATA
   // It's painful to write a test for this...
   channel->mState = OPEN;
   channel->mReady = true;
   SendMsgInternal(channel, "Help me!", 8, DATA_CHANNEL_PPID_DOMSTRING_LAST);
 #endif
 
+  if (channel->mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED) {
+    // Don't send unordered until this gets cleared
+    channel->mFlags |= DATA_CHANNEL_FLAGS_WAITING_ACK;
+  }
+
   if (!(channel->mFlags & DATA_CHANNEL_FLAGS_EXTERNAL_NEGOTIATED)) {
     if (!SendOpenRequestMessage(channel->mLabel, channel->mProtocol,
                                 stream,
                                 !!(channel->mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED),
                                 channel->mPrPolicy, channel->mPrValue)) {
       LOG(("SendOpenRequest failed, errno = %d", errno));
       if (errno == EAGAIN || errno == EWOULDBLOCK) {
         channel->mFlags |= DATA_CHANNEL_FLAGS_SEND_REQ;
@@ -2073,24 +2141,26 @@ DataChannelConnection::SendMsgInternal(D
 {
   uint16_t flags;
   struct sctp_sendv_spa spa;
   int32_t result;
 
   NS_ENSURE_TRUE(channel->mState == OPEN || channel->mState == CONNECTING, 0);
   NS_WARN_IF_FALSE(length > 0, "Length is 0?!");
 
-  flags = (channel->mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED) ? SCTP_UNORDERED : 0;
-
-  // To avoid problems where an in-order OPEN_RESPONSE is lost and an
+  // To avoid problems where an in-order OPEN is lost and an
   // out-of-order data message "beats" it, require data to be in-order
   // until we get an ACK.
-  if (channel->mState == CONNECTING) {
-    flags &= ~SCTP_UNORDERED;
+  if ((channel->mFlags & DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED) &&
+      !(channel->mFlags & DATA_CHANNEL_FLAGS_WAITING_ACK)) {
+    flags = SCTP_UNORDERED;
+  } else {
+    flags = 0;
   }
+
   spa.sendv_sndinfo.snd_ppid = htonl(ppid);
   spa.sendv_sndinfo.snd_sid = channel->mStream;
   spa.sendv_sndinfo.snd_flags = flags;
   spa.sendv_sndinfo.snd_context = 0;
   spa.sendv_sndinfo.snd_assoc_id = 0;
   spa.sendv_flags = SCTP_SEND_SNDINFO_VALID;
 
   if (channel->mPrPolicy != SCTP_PR_SCTP_NONE) {
--- a/netwerk/sctp/datachannel/DataChannel.h
+++ b/netwerk/sctp/datachannel/DataChannel.h
@@ -202,42 +202,45 @@ private:
   static void DTLSConnectThread(void *data);
   int SendPacket(const unsigned char* data, size_t len, bool release);
   void SctpDtlsInput(TransportFlow *flow, const unsigned char *data, size_t len);
   static int SctpDtlsOutput(void *addr, void *buffer, size_t length, uint8_t tos, uint8_t set_df);
 #endif
   DataChannel* FindChannelByStream(uint16_t stream);
   uint16_t FindFreeStream();
   bool RequestMoreStreams(int32_t aNeeded = 16);
-  int32_t SendControlMessage(void *msg, uint32_t len, uint16_t streamOut);
+  int32_t SendControlMessage(void *msg, uint32_t len, uint16_t stream);
   int32_t SendOpenRequestMessage(const nsACString& label, const nsACString& protocol,
-                                 uint16_t streamOut,
+                                 uint16_t stream,
                                  bool unordered, uint16_t prPolicy, uint32_t prValue);
+  int32_t SendOpenAckMessage(uint16_t stream);
   int32_t SendMsgInternal(DataChannel *channel, const char *data,
                           uint32_t length, uint32_t ppid);
   int32_t SendBinary(DataChannel *channel, const char *data,
                      uint32_t len, uint32_t ppid_partial, uint32_t ppid_final);
   int32_t SendMsgCommon(uint16_t stream, const nsACString &aMsg, bool isBinary);
 
   void DeliverQueuedData(uint16_t stream);
 
   already_AddRefed<DataChannel> OpenFinish(already_AddRefed<DataChannel> channel) NS_WARN_UNUSED_RESULT;
 
   void StartDefer();
   bool SendDeferredMessages();
   void ProcessQueuedOpens();
   void ClearResets();
   void SendOutgoingStreamReset();
-  void ResetOutgoingStream(uint16_t streamOut);
+  void ResetOutgoingStream(uint16_t stream);
   void HandleOpenRequestMessage(const struct rtcweb_datachannel_open_request *req,
                                 size_t length,
-                                uint16_t streamIn);
-  void HandleUnknownMessage(uint32_t ppid, size_t length, uint16_t streamIn);
-  void HandleDataMessage(uint32_t ppid, const void *buffer, size_t length, uint16_t streamIn);
-  void HandleMessage(const void *buffer, size_t length, uint32_t ppid, uint16_t streamIn);
+                                uint16_t stream);
+  void HandleOpenAckMessage(const struct rtcweb_datachannel_ack *ack,
+                            size_t length, uint16_t stream);
+  void HandleUnknownMessage(uint32_t ppid, size_t length, uint16_t stream);
+  void HandleDataMessage(uint32_t ppid, const void *buffer, size_t length, uint16_t stream);
+  void HandleMessage(const void *buffer, size_t length, uint32_t ppid, uint16_t stream);
   void HandleAssociationChangeEvent(const struct sctp_assoc_change *sac);
   void HandlePeerAddressChangeEvent(const struct sctp_paddr_change *spc);
   void HandleRemoteErrorEvent(const struct sctp_remote_error *sre);
   void HandleShutdownEvent(const struct sctp_shutdown_event *sse);
   void HandleAdaptationIndication(const struct sctp_adaptation_event *sai);
   void HandleSendFailedEvent(const struct sctp_send_failed_event *ssfe);
   void HandleStreamResetEvent(const struct sctp_stream_reset_event *strrst);
   void HandleStreamChangeEvent(const struct sctp_stream_change_event *strchg);
--- a/netwerk/sctp/datachannel/DataChannelProtocol.h
+++ b/netwerk/sctp/datachannel/DataChannelProtocol.h
@@ -30,33 +30,39 @@
 #define DATA_CHANNEL_FLAGS_SEND_REQ             0x00000001
 #define DATA_CHANNEL_FLAGS_SEND_RSP             0x00000002
 #define DATA_CHANNEL_FLAGS_SEND_ACK             0x00000004
 #define DATA_CHANNEL_FLAGS_OUT_OF_ORDER_ALLOWED 0x00000008
 #define DATA_CHANNEL_FLAGS_SEND_DATA            0x00000010
 #define DATA_CHANNEL_FLAGS_FINISH_OPEN          0x00000020
 #define DATA_CHANNEL_FLAGS_FINISH_RSP           0x00000040
 #define DATA_CHANNEL_FLAGS_EXTERNAL_NEGOTIATED  0x00000080
+#define DATA_CHANNEL_FLAGS_WAITING_ACK          0x00000100
 
 #define INVALID_STREAM (0xFFFF)
 // max is 0xFFFF: Streams 0 to 0xFFFE = 0xFFFF streams
 #define MAX_NUM_STREAMS (2048)
 
 struct rtcweb_datachannel_open_request {
   uint8_t  msg_type; // DATA_CHANNEL_OPEN
   uint8_t  channel_type;  
   int16_t  priority;
   uint32_t reliability_param;
   uint16_t label_length;
   uint16_t protocol_length;
   char     label[1]; // (and protocol) keep VC++ happy...
 } SCTP_PACKED;
 
+struct rtcweb_datachannel_ack {
+  uint8_t  msg_type; // DATA_CHANNEL_ACK
+} SCTP_PACKED;
+
 /* msg_type values: */
-/* 0-2 were used in an early version of the protocol with 3-way handshakes */
+/* 0-1 were used in an early version of the protocol with 3-way handshakes */
+#define DATA_CHANNEL_ACK                      2
 #define DATA_CHANNEL_OPEN_REQUEST             3
 
 /* channel_type values: */
 #define DATA_CHANNEL_RELIABLE                 0x00
 #define DATA_CHANNEL_PARTIAL_RELIABLE_REXMIT  0x01
 #define DATA_CHANNEL_PARTIAL_RELIABLE_TIMED   0x02
 
 #define DATA_CHANNEL_RELIABLE_UNORDERED                0x80
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_seer.js
@@ -0,0 +1,295 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+
+var seer = null;
+var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
+var profile = null;
+
+function extract_origin(uri) {
+  var o = uri.scheme + "://" + uri.asciiHost;
+  if (uri.port !== -1) {
+    o = o + ":" + uri.port;
+  }
+  return o;
+}
+
+var LoadContext = function _loadContext() {
+};
+
+LoadContext.prototype = {
+  usePrivateBrowsing: false,
+
+  getInterface: function loadContext_getInterface(iid) {
+    return this.QueryInterface(iid);
+  },
+
+  QueryInterface: function loadContext_QueryInterface(iid) {
+    if (iid.equals(Ci.nsINetworkSeerVerifier) ||
+        iid.equals(Ci.nsILoadContext)) {
+      return this;
+    }
+
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  }
+};
+
+var load_context = new LoadContext();
+
+var Verifier = function _verifier(testing, expected_preconnects, expected_preresolves) {
+  this.verifying = testing;
+  this.expected_preconnects = expected_preconnects;
+  this.expected_preresolves = expected_preresolves;
+};
+
+Verifier.prototype = {
+  verifying: null,
+  expected_preconnects: null,
+  expected_preresolves: null,
+
+  getInterface: function verifier_getInterface(iid) {
+    return this.QueryInterface(iid);
+  },
+
+  QueryInterface: function verifier_QueryInterface(iid) {
+    if (iid.equals(Ci.nsINetworkSeerVerifier) ||
+        iid.equals(Ci.nsISupports)) {
+      return this;
+    }
+
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  },
+
+  maybe_run_next_test: function verifier_maybe_run_next_test() {
+    if (this.expected_preconnects.length === 0 &&
+        this.expected_preresolves.length === 0) {
+      do_check_true(true, "Well this is unexpected...");
+      run_next_test();
+    }
+  },
+
+  onPredictPreconnect: function verifier_onPredictPreconnect(uri) {
+    var origin = extract_origin(uri);
+    var index = this.expected_preconnects.indexOf(origin);
+    if (index == -1) {
+      do_check_true(false, "Got preconnect for unexpected uri " + origin);
+    } else {
+      this.expected_preconnects.splice(index, 1);
+    }
+    this.maybe_run_next_test();
+  },
+
+  onPredictDNS: function verifier_onPredictDNS(uri) {
+    var origin = extract_origin(uri);
+    var index = this.expected_preresolves.indexOf(origin);
+    if (index == -1) {
+      do_check_true(false, "Got preresolve for unexpected uri " + origin);
+    } else {
+      this.expected_preresolves.splice(index, 1);
+    }
+    this.maybe_run_next_test();
+  }
+};
+
+function reset_seer() {
+  seer.reset();
+}
+
+function newURI(s) {
+  return ios.newURI(s, null, null);
+}
+
+function test_link_hover() {
+  reset_seer();
+  var uri = newURI("http://localhost:4444/foo/bar");
+  var referrer = newURI("http://localhost:4444/foo");
+  var preconns = ["http://localhost:4444"];
+
+  var verifier = new Verifier("hover", preconns, []);
+  seer.predict(uri, referrer, seer.PREDICT_LINK, load_context, verifier);
+}
+
+function test_pageload() {
+  reset_seer();
+  var toplevel = "http://localhost:4444/index.html";
+  var subresources = [
+    "http://localhost:4444/style.css",
+    "http://localhost:4443/jquery.js",
+    "http://localhost:4444/image.png"
+  ];
+
+  var tluri = newURI(toplevel);
+  seer.learn(tluri, null, seer.LEARN_LOAD_TOPLEVEL, load_context);
+  var preconns = [];
+  for (var i = 0; i < subresources.length; i++) {
+    var sruri = newURI(subresources[i]);
+    seer.learn(sruri, tluri, seer.LEARN_LOAD_SUBRESOURCE, load_context);
+    preconns.push(extract_origin(sruri));
+  }
+
+  var verifier = new Verifier("pageload", preconns, []);
+  seer.predict(tluri, null, seer.PREDICT_LOAD, load_context, verifier);
+}
+
+function test_redirect() {
+  reset_seer();
+  var initial = "http://localhost:4443/redirect";
+  var target = "http://localhost:4444/index.html";
+  var subresources = [
+    "http://localhost:4444/style.css",
+    "http://localhost:4443/jquery.js",
+    "http://localhost:4444/image.png"
+  ];
+
+  var inituri = newURI(initial);
+  var targeturi = newURI(target);
+  seer.learn(inituri, null, seer.LEARN_LOAD_TOPLEVEL, load_context);
+  seer.learn(targeturi, inituri, seer.LEARN_LOAD_REDIRECT, load_context);
+  seer.learn(targeturi, null, seer.LEARN_LOAD_TOPLEVEL, load_context);
+
+  var preconns = [];
+  preconns.push(extract_origin(targeturi));
+  for (var i = 0; i < subresources.length; i++) {
+    var sruri = newURI(subresources[i]);
+    seer.learn(sruri, targeturi, seer.LEARN_LOAD_SUBRESOURCE, load_context);
+    preconns.push(extract_origin(sruri));
+  }
+
+  var verifier = new Verifier("redirect", preconns, []);
+  seer.predict(inituri, null, seer.PREDICT_LOAD, load_context, verifier);
+}
+
+function test_startup() {
+  reset_seer();
+  var uris = [
+    "http://localhost:4444/startup",
+    "http://localhost:4443/startup"
+  ];
+  var preconns = [];
+  for (var i = 0; i < uris.length; i++) {
+    var uri = newURI(uris[i]);
+    seer.learn(uri, null, seer.LEARN_STARTUP, load_context);
+    preconns.push(extract_origin(uri));
+  }
+
+  var verifier = new Verifier("startup", preconns, []);
+  seer.predict(null, null, seer.PREDICT_STARTUP, load_context, verifier);
+}
+
+// A class used to guarantee serialization of SQL queries so we can properly
+// update last hit times on subresources to ensure the seer tries to do DNS
+// preresolve on them instead of preconnecting
+var DnsContinueVerifier = function _dnsContinueVerifier(subresource, tluri, preresolves) {
+  this.subresource = subresource;
+  this.tluri = tluri;
+  this.preresolves = preresolves;
+};
+
+DnsContinueVerifier.prototype = {
+  subresource: null,
+  tluri: null,
+  preresolves: null,
+
+  getInterface: function _dnsContinueVerifier_getInterface(iid) {
+    return this.QueryInterface(iid);
+  },
+
+  QueryInterface: function _dnsContinueVerifier_QueryInterface(iid) {
+    if (iid.equals(Ci.nsISupports) ||
+        iid.equals(Ci.nsINetworkSeerVerifier)) {
+      return this;
+    }
+
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  },
+
+  onPredictPreconnect: function _dnsContinueVerifier_onPredictPreconnect() {
+    // This means that the seer has learned and done our "checkpoint" prediction
+    // Now we can get on with the prediction we actually want to test
+
+    // tstamp is 10 days older than now - just over 1 week, which will ensure we
+    // hit our cutoff for dns vs. preconnect. This is all in usec, hence the
+    // x1000 on the Date object value.
+    var tstamp = (new Date().valueOf() * 1000) - (10 * 86400 * 1000000);
+
+    var dbfile = FileUtils.getFile("ProfD", ["seer.sqlite"]);
+    var dbconn = Services.storage.openDatabase(dbfile);
+    // We also need to update hits, since the toplevel has been "loaded" a
+    // second time (from the prediction that kicked off this callback) to ensure
+    // that the seer will try to do anything for this subresource.
+    var stmt = "UPDATE moz_subresources SET last_hit = " + tstamp + ", hits = 2 WHERE uri = '" + this.subresource + "';";
+    dbconn.executeSimpleSQL(stmt);
+    dbconn.close();
+
+    var verifier = new Verifier("dns", [], this.preresolves);
+    seer.predict(this.tluri, null, seer.PREDICT_LOAD, load_context, verifier);
+  },
+
+  onPredictDNS: function _dnsContinueVerifier_onPredictDNS() {
+    do_check_true(false, "Shouldn't have gotten a preresolve prediction here!");
+  }
+};
+
+function test_dns() {
+  reset_seer();
+  var toplevel = "http://localhost:4444/index.html";
+  var subresource = "http://localhost:4443/jquery.js";
+
+  var tluri = newURI(toplevel);
+  seer.learn(tluri, null, seer.LEARN_LOAD_TOPLEVEL, load_context);
+  var sruri = newURI(subresource);
+  seer.learn(sruri, tluri, seer.LEARN_LOAD_SUBRESOURCE, load_context);
+
+  var preresolves = [extract_origin(sruri)];
+  var continue_verifier = new DnsContinueVerifier(subresource, tluri, preresolves);
+  // Fire off a prediction that will do preconnects so we know when the seer
+  // thread has gotten to the point where we can update the database manually
+  seer.predict(tluri, null, seer.PREDICT_LOAD, load_context, continue_verifier);
+}
+
+function test_origin() {
+  reset_seer();
+  var toplevel = "http://localhost:4444/index.html";
+  var subresources = [
+    "http://localhost:4444/style.css",
+    "http://localhost:4443/jquery.js",
+    "http://localhost:4444/image.png"
+  ];
+
+  var tluri = newURI(toplevel);
+  seer.learn(tluri, null, seer.LEARN_LOAD_TOPLEVEL, load_context);
+  var preconns = [];
+  for (var i = 0; i < subresources.length; i++) {
+    var sruri = newURI(subresources[i]);
+    seer.learn(sruri, tluri, seer.LEARN_LOAD_SUBRESOURCE, load_context);
+    var origin = extract_origin(sruri);
+    if (preconns.indexOf(origin) === -1) {
+      preconns.push(origin);
+    }
+  }
+
+  var loaduri = newURI("http://localhost:4444/anotherpage.html");
+  var verifier = new Verifier("origin", preconns, []);
+  seer.predict(loaduri, null, seer.PREDICT_LOAD, load_context, verifier);
+}
+
+var tests = [
+  test_link_hover,
+  test_pageload,
+  test_redirect,
+  test_startup,
+  test_dns,
+  test_origin
+];
+
+function run_test() {
+  tests.forEach(add_test);
+  profile = do_get_profile();
+  seer = Cc["@mozilla.org/network/seer;1"].getService(Ci.nsINetworkSeer);
+  do_register_cleanup(reset_seer);
+  run_next_test();
+}
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -290,8 +290,13 @@ skip-if = os == "android"
 [test_bug856978.js]
 [test_unix_domain.js]
 # The xpcshell temp directory on Android doesn't seem to let us create
 # Unix domain sockets. (Perhaps it's a FAT filesystem?)
 skip-if = os == "android"
 [test_addr_in_use_error.js]
 [test_about_networking.js]
 [test_ping_aboutnetworking.js]
+[test_seer.js]
+# Android version detection w/in gecko does not work right on infra, so we just
+# disable this test on all android versions, even though it's enabled on 2.3+ in
+# the wild.
+skip-if = os == "android"
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -1754,16 +1754,136 @@
   },
   "DNS_FAILED_LOOKUP_TIME": {
     "kind": "exponential",
     "high": "60000",
     "n_buckets": 50,
     "extended_statistics_ok": true,
     "description": "Time for an unsuccessful DNS OS resolution (msec)"
   },
+  "SEER_PREDICT_ATTEMPTS": {
+      "kind": "exponential",
+      "high": "1000 * 1000",
+      "n_buckets": 50,
+      "extended_statistics_ok": true,
+      "description": "Number of times nsINetworkSeer::Predict is called and attempts to predict"
+  },
+  "SEER_LEARN_ATTEMPTS": {
+      "kind": "exponential",
+      "high": "1000 * 1000",
+      "n_buckets": 50,
+      "extended_statistics_ok": true,
+      "description": "Number of times nsINetworkSeer::Learn is called and attempts to learn"
+  },
+  "SEER_PREDICT_FULL_QUEUE": {
+      "kind": "exponential",
+      "high": "60000",
+      "n_buckets": 50,
+      "extended_statistics_ok": true,
+      "description": "Number of times nsINetworkSeer::Predict doesn't continue because the queue is full"
+  },
+  "SEER_LEARN_FULL_QUEUE": {
+      "kind": "exponential",
+      "high": "60000",
+      "n_buckets": 50,
+      "extended_statistics_ok": true,
+      "description": "Number of times nsINetworkSeer::Learn doesn't continue because the queue is full"
+  },
+  "SEER_PREDICT_WAIT_TIME": {
+      "kind": "exponential",
+      "high": "3000",
+      "n_buckets": 10,
+      "extended_statistics_ok": true,
+      "description": "Amount of time a predict event waits in the queue (ms)"
+  },
+  "SEER_PREDICT_WORK_TIME": {
+      "kind": "exponential",
+      "high": "3000",
+      "n_buckets": 10,
+      "extended_statistics_ok": true,
+      "description": "Amount of time spent doing the work for predict (ms)"
+  },
+  "SEER_LEARN_WAIT_TIME": {
+      "kind": "exponential",
+      "high": "3000",
+      "n_buckets": 10,
+      "extended_statistics_ok": true,
+      "description": "Amount of time a learn event waits in the queue (ms)"
+  },
+  "SEER_LEARN_WORK_TIME": {
+      "kind": "exponential",
+      "high": "3000",
+      "n_buckets": 10,
+      "extended_statistics_ok": true,
+      "description": "Amount of time spent doing the work for learn (ms)"
+  },
+  "SEER_TOTAL_PREDICTIONS": {
+      "kind": "exponential",
+      "high": "1000 * 1000",
+      "n_buckets": 50,
+      "extended_statistics_ok": true,
+      "description": "How many actual predictions (preresolves, preconnects, ...) happen"
+  },
+  "SEER_TOTAL_PRECONNECTS": {
+      "kind": "exponential",
+      "high": "1000 * 1000",
+      "n_buckets": 50,
+      "extended_statistics_ok": true,
+      "description": "How many actual preconnects happen"
+  },
+  "SEER_TOTAL_PRERESOLVES": {
+      "kind": "exponential",
+      "high": "1000 * 1000",
+      "n_buckets": 50,
+      "extended_statistics_ok": true,
+      "description": "How many actual preresolves happen"
+  },
+  "SEER_PREDICTIONS_CALCULATED": {
+      "kind": "exponential",
+      "high": "1000 * 1000",
+      "n_buckets": 50,
+      "extended_statistics_ok": true,
+      "description": "How many prediction calculations are performed"
+  },
+  "SEER_GLOBAL_DEGRADATION": {
+      "kind": "linear",
+      "high": "100",
+      "n_buckets": 50,
+      "description": "The global degradation calculated"
+  },
+  "SEER_SUBRESOURCE_DEGRADATION": {
+      "kind": "linear",
+      "high": "100",
+      "n_buckets": 50,
+      "description": "The degradation calculated for a subresource"
+  },
+  "SEER_BASE_CONFIDENCE": {
+      "kind": "linear",
+      "high": "100",
+      "n_buckets": 50,
+      "description": "The base confidence calculated for a subresource"
+  },
+  "SEER_CONFIDENCE": {
+      "kind": "linear",
+      "high": "100",
+      "n_buckets": 50,
+      "description": "The final confidence calculated for a subresource"
+  },
+  "SEER_PREDICT_TIME_TO_ACTION": {
+      "kind": "exponential",
+      "high": "3000",
+      "n_buckets": 10,
+      "description": "How long it takes from the time Predict() is called to the time we take action"
+  },
+  "SEER_PREDICT_TIME_TO_INACTION": {
+      "kind": "exponential",
+      "high": "3000",
+      "n_buckets": 10,
+      "description": "How long it takes from the time Predict() is called to the time we figure out there's nothing to do"
+  },
   "FIND_PLUGINS": {
     "kind": "exponential",
     "high": "3000",
     "n_buckets": 10,
     "extended_statistics_ok": true,
     "description": "Time spent scanning filesystem for plugins (ms)"
   },
   "CHECK_JAVA_ENABLED": {
--- a/toolkit/forgetaboutsite/ForgetAboutSite.jsm
+++ b/toolkit/forgetaboutsite/ForgetAboutSite.jsm
@@ -214,10 +214,16 @@ this.ForgetAboutSite = {
 
     // Content Preferences
     let cps2 = Cc["@mozilla.org/content-pref/service;1"].
                getService(Ci.nsIContentPrefService2);
     cps2.removeBySubdomain(aDomain, null, {
       handleCompletion: function() onContentPrefsRemovalFinished(),
       handleError: function() {}
     });
+
+    // Predictive network data - like cache, no way to clear this per
+    // domain, so just trash it all
+    let ns = Cc["@mozilla.org/network/seer;1"].
+             getService(Ci.nsINetworkSeer);
+    ns.reset();
   }
 };