Bug 959752 - Make the network predictor work under e10s. r=mcmanus
authorNicholas Hurley <hurley@todesschaf.org>
Thu, 18 Jun 2015 11:23:00 +0200
changeset 249736 185a9bd1dc62f0c8ed7492449998fdf3da77e1fd
parent 249735 c99d0c402d0016f28ce4b5fc92c25088f3cdb9d1
child 249737 03655a6288c280457b78d71e818606af48ae60f3
push id28936
push userryanvm@gmail.com
push dateFri, 19 Jun 2015 20:34:42 +0000
treeherdermozilla-central@c319f262ce3e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmcmanus
bugs959752
milestone41.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
Bug 959752 - Make the network predictor work under e10s. r=mcmanus
netwerk/base/Predictor.cpp
netwerk/base/Predictor.h
netwerk/ipc/NeckoChild.cpp
netwerk/ipc/NeckoChild.h
netwerk/ipc/NeckoParent.cpp
netwerk/ipc/NeckoParent.h
netwerk/ipc/PNecko.ipdl
netwerk/test/unit/test_predictor.js
netwerk/test/unit_ipc/test_predictor_wrap.js
netwerk/test/unit_ipc/xpcshell.ini
--- a/netwerk/base/Predictor.cpp
+++ b/netwerk/base/Predictor.cpp
@@ -35,16 +35,19 @@
 #include "mozilla/Logging.h"
 
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 
 #include "mozilla/net/NeckoCommon.h"
 
 #include "LoadContextInfo.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "SerializedLoadContext.h"
+#include "mozilla/net/NeckoChild.h"
 
 #if defined(ANDROID) && !defined(MOZ_WIDGET_GONK)
 #include "nsIPropertyBag2.h"
 static const int32_t ANDROID_23_VERSION = 10;
 #endif
 
 using namespace mozilla;
 
@@ -250,18 +253,18 @@ Predictor::Action::OnCacheEntryAvailable
   }
   PREDICTOR_LOG(("OnCacheEntryAvailable %p called. entry=%p mFullUri=%d mPredict=%d "
                  "mPredictReason=%d mLearnReason=%d mTargetURI=%s "
                  "mSourceURI=%s mStackCount=%d isNew=%d result=0x%08x",
                  this, entry, mFullUri, mPredict, mPredictReason, mLearnReason,
                  targetURI.get(), sourceURI.get(), mStackCount,
                  isNew, result));
   if (NS_FAILED(result)) {
-    PREDICTOR_LOG(("OnCacheEntryAvailable %p FAILED to get cache entry. "
-                   "Aborting.", this));
+    PREDICTOR_LOG(("OnCacheEntryAvailable %p FAILED to get cache entry (0x%08X). "
+                   "Aborting.", this, result));
     return NS_OK;
   }
   Telemetry::AccumulateTimeDelta(Telemetry::PREDICTOR_WAIT_TIME,
                                  mStartTime);
   if (mPredict) {
     bool predicted = mPredictor->PredictInternal(mPredictReason, entry, isNew,
                                                  mFullUri, mTargetURI,
                                                  mVerifier, mStackCount);
@@ -284,17 +287,18 @@ Predictor::Action::OnCacheEntryAvailable
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS(Predictor,
                   nsINetworkPredictor,
                   nsIObserver,
                   nsISpeculativeConnectionOverrider,
                   nsIInterfaceRequestor,
-                  nsICacheEntryMetaDataVisitor)
+                  nsICacheEntryMetaDataVisitor,
+                  nsINetworkPredictorVerifier)
 
 Predictor::Predictor()
   :mInitialized(false)
   ,mEnabled(true)
   ,mEnableHoverOnSSL(false)
   ,mPageDegradationDay(PREDICTOR_PAGE_DELTA_DAY_DEFAULT)
   ,mPageDegradationWeek(PREDICTOR_PAGE_DELTA_WEEK_DEFAULT)
   ,mPageDegradationMonth(PREDICTOR_PAGE_DELTA_MONTH_DEFAULT)
@@ -529,16 +533,18 @@ Predictor::OnMetaDataElement(const char 
   return NS_OK;
 }
 
 // Predictor::nsINetworkPredictor
 
 nsresult
 Predictor::Init()
 {
+  MOZ_DIAGNOSTIC_ASSERT(!IsNeckoChild());
+
   if (!NS_IsMainThread()) {
     MOZ_ASSERT(false, "Predictor::Init called off the main thread!");
     return NS_ERROR_UNEXPECTED;
   }
 
   nsresult rv = NS_OK;
 
 #if defined(ANDROID) && !defined(MOZ_WIDGET_GONK)
@@ -731,16 +737,21 @@ Predictor::Create(nsISupports *aOuter, c
 
   nsresult rv;
 
   if (aOuter != nullptr) {
     return NS_ERROR_NO_AGGREGATION;
   }
 
   nsRefPtr<Predictor> svc = new Predictor();
+  if (IsNeckoChild()) {
+    // Child threads only need to be call into the public interface methods
+    // so we don't bother with initialization
+    return svc->QueryInterface(aIID, aResult);
+  }
 
   rv = svc->Init();
   if (NS_FAILED(rv)) {
     PREDICTOR_LOG(("Failed to initialize predictor, predictor 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
@@ -751,83 +762,117 @@ Predictor::Create(nsISupports *aOuter, c
 }
 
 // Called from the main thread to initiate predictive actions
 NS_IMETHODIMP
 Predictor::Predict(nsIURI *targetURI, nsIURI *sourceURI,
                    PredictorPredictReason reason, nsILoadContext *loadContext,
                    nsINetworkPredictorVerifier *verifier)
 {
+  MOZ_ASSERT(NS_IsMainThread(),
+             "Predictor interface methods must be called on the main thread");
+
+  PREDICTOR_LOG(("Predictor::Predict"));
+
   if (IsNeckoChild()) {
-    // TODO - e10s-ify the predictor
+    MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
+
+    PREDICTOR_LOG(("    called on child process"));
+
+    ipc::OptionalURIParams serTargetURI, serSourceURI;
+    SerializeURI(targetURI, serTargetURI);
+    SerializeURI(sourceURI, serSourceURI);
+
+    IPC::SerializedLoadContext serLoadContext;
+    serLoadContext.Init(loadContext);
+
+    // If two different threads are predicting concurently, this will be
+    // overwritten. Thankfully, we only use this in tests, which will
+    // overwrite mVerifier perhaps multiple times for each individual test;
+    // however, within each test, the multiple predict calls should have the
+    // same verifier.
+    if (verifier) {
+      PREDICTOR_LOG(("    was given a verifier"));
+      mChildVerifier = verifier;
+    }
+    PREDICTOR_LOG(("    forwarding to parent process"));
+    gNeckoChild->SendPredPredict(serTargetURI, serSourceURI,
+                                 reason, serLoadContext, verifier);
     return NS_OK;
   }
 
-  MOZ_ASSERT(NS_IsMainThread(),
-             "Predictor interface methods must be called on the main thread");
+  PREDICTOR_LOG(("    called on parent process"));
 
   if (!mInitialized) {
+    PREDICTOR_LOG(("    not initialized"));
     return NS_OK;
   }
 
   if (!mEnabled) {
+    PREDICTOR_LOG(("    not enabled"));
     return NS_OK;
   }
 
   if (loadContext && loadContext->UsePrivateBrowsing()) {
     // Don't want to do anything in PB mode
+    PREDICTOR_LOG(("    in PB mode"));
     return NS_OK;
   }
 
   if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
     // Nothing we can do for non-HTTP[S] schemes
+    PREDICTOR_LOG(("    got non-http[s] URI"));
     return NS_OK;
   }
 
   // Ensure we've been given the appropriate arguments for the kind of
   // prediction we're being asked to do
   nsCOMPtr<nsIURI> uriKey = targetURI;
   nsCOMPtr<nsIURI> originKey;
   switch (reason) {
     case nsINetworkPredictor::PREDICT_LINK:
       if (!targetURI || !sourceURI) {
+        PREDICTOR_LOG(("    link invalid URI state"));
         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 nsINetworkPredictor::PREDICT_LOAD:
       if (!targetURI || sourceURI) {
+        PREDICTOR_LOG(("    load invalid URI state"));
         return NS_ERROR_INVALID_ARG;
       }
       break;
     case nsINetworkPredictor::PREDICT_STARTUP:
       if (targetURI || sourceURI) {
+        PREDICTOR_LOG(("    startup invalid URI state"));
         return NS_ERROR_INVALID_ARG;
       }
       uriKey = mStartupURI;
       originKey = mStartupURI;
       break;
     default:
+      PREDICTOR_LOG(("    invalid reason"));
       return NS_ERROR_INVALID_ARG;
   }
 
   Predictor::Reason argReason;
   argReason.mPredict = reason;
 
   // First we open the regular cache entry, to ensure we don't gum up the works
   // waiting on the less-important predictor-only cache entry
   nsRefPtr<Predictor::Action> uriAction =
     new Predictor::Action(Predictor::Action::IS_FULL_URI,
                           Predictor::Action::DO_PREDICT, argReason, targetURI,
                           nullptr, verifier, this);
   nsAutoCString uriKeyStr;
   uriKey->GetAsciiSpec(uriKeyStr);
-  PREDICTOR_LOG(("Predict uri=%s reason=%d action=%p", uriKeyStr.get(),
+  PREDICTOR_LOG(("    Predict uri=%s reason=%d action=%p", uriKeyStr.get(),
                  reason, uriAction.get()));
   uint32_t openFlags = nsICacheStorage::OPEN_READONLY |
                        nsICacheStorage::OPEN_SECRETLY |
                        nsICacheStorage::OPEN_PRIORITY |
                        nsICacheStorage::CHECK_MULTITHREADED;
   mCacheDiskStorage->AsyncOpenURI(uriKey, EmptyCString(), openFlags, uriAction);
 
   // Now we do the origin-only (and therefore predictor-only) entry
@@ -839,97 +884,106 @@ Predictor::Predict(nsIURI *targetURI, ns
   }
 
   nsRefPtr<Predictor::Action> originAction =
     new Predictor::Action(Predictor::Action::IS_ORIGIN,
                           Predictor::Action::DO_PREDICT, argReason,
                           targetOrigin, nullptr, verifier, this);
   nsAutoCString originKeyStr;
   originKey->GetAsciiSpec(originKeyStr);
-  PREDICTOR_LOG(("Predict origin=%s reason=%d action=%p", originKeyStr.get(),
+  PREDICTOR_LOG(("    Predict origin=%s reason=%d action=%p", originKeyStr.get(),
                  reason, originAction.get()));
   openFlags = nsICacheStorage::OPEN_READONLY |
               nsICacheStorage::OPEN_SECRETLY |
               nsICacheStorage::CHECK_MULTITHREADED;
   mCacheDiskStorage->AsyncOpenURI(originKey,
                                   NS_LITERAL_CSTRING(PREDICTOR_ORIGIN_EXTENSION),
                                   openFlags, originAction);
 
+  PREDICTOR_LOG(("    predict returning"));
   return NS_OK;
 }
 
 bool
 Predictor::PredictInternal(PredictorPredictReason reason, nsICacheEntry *entry,
                            bool isNew, bool fullUri, nsIURI *targetURI,
                            nsINetworkPredictorVerifier *verifier,
                            uint8_t stackCount)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  PREDICTOR_LOG(("Predictor::PredictInternal"));
   bool rv = false;
 
   if (reason == nsINetworkPredictor::PREDICT_LOAD) {
     MaybeLearnForStartup(targetURI, fullUri);
   }
 
   if (isNew) {
     // nothing else we can do here
+    PREDICTOR_LOG(("    new entry"));
     return rv;
   }
 
   switch (reason) {
     case nsINetworkPredictor::PREDICT_LOAD:
       rv = PredictForPageload(entry, stackCount, verifier);
       break;
     case nsINetworkPredictor::PREDICT_STARTUP:
       rv = PredictForStartup(entry, verifier);
       break;
     default:
+      PREDICTOR_LOG(("    invalid reason"));
       MOZ_ASSERT(false, "Got unexpected value for prediction reason");
   }
 
   return rv;
 }
 
 void
 Predictor::PredictForLink(nsIURI *targetURI, nsIURI *sourceURI,
                           nsINetworkPredictorVerifier *verifier)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  PREDICTOR_LOG(("Predictor::PredictForLink"));
   if (!mSpeculativeService) {
+    PREDICTOR_LOG(("    missing speculative service"));
     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
-      PREDICTOR_LOG(("Not predicting for link hover - on an SSL page"));
+      PREDICTOR_LOG(("    Not predicting for link hover - on an SSL page"));
       return;
     }
   }
 
   mSpeculativeService->SpeculativeConnect(targetURI, nullptr);
   if (verifier) {
+    PREDICTOR_LOG(("    sending verification"));
     verifier->OnPredictPreconnect(targetURI);
   }
 }
 
 // This is the driver for prediction based on a new pageload.
 const uint8_t MAX_PAGELOAD_DEPTH = 10;
 bool
 Predictor::PredictForPageload(nsICacheEntry *entry, uint8_t stackCount,
                               nsINetworkPredictorVerifier *verifier)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  PREDICTOR_LOG(("Predictor::PredictForPageload"));
+
   if (stackCount > MAX_PAGELOAD_DEPTH) {
-    PREDICTOR_LOG(("PredictForPageload exceeded recursion depth!"));
+    PREDICTOR_LOG(("    exceeded recursion depth!"));
     return false;
   }
 
   uint32_t lastLoad;
   nsresult rv = entry->GetLastFetched(&lastLoad);
   NS_ENSURE_SUCCESS(rv, false);
 
   int32_t globalDegradation = CalculateGlobalDegradation(lastLoad);
@@ -945,17 +999,17 @@ Predictor::PredictForPageload(nsICacheEn
     Predictor::Reason reason;
     reason.mPredict = nsINetworkPredictor::PREDICT_LOAD;
     nsRefPtr<Predictor::Action> redirectAction =
       new Predictor::Action(Predictor::Action::IS_FULL_URI,
                             Predictor::Action::DO_PREDICT, reason, redirectURI,
                             nullptr, verifier, this, stackCount + 1);
     nsAutoCString redirectUriString;
     redirectURI->GetAsciiSpec(redirectUriString);
-    PREDICTOR_LOG(("Predict redirect uri=%s action=%p", redirectUriString.get(),
+    PREDICTOR_LOG(("    Predict redirect uri=%s action=%p", redirectUriString.get(),
                    redirectAction.get()));
     uint32_t openFlags = nsICacheStorage::OPEN_READONLY |
                          nsICacheStorage::OPEN_SECRETLY |
                          nsICacheStorage::OPEN_PRIORITY |
                          nsICacheStorage::CHECK_MULTITHREADED;
     mCacheDiskStorage->AsyncOpenURI(redirectURI, EmptyCString(), openFlags,
                                     redirectAction);
     return RunPredictions(verifier);
@@ -969,16 +1023,17 @@ Predictor::PredictForPageload(nsICacheEn
 // This is the driver for predicting at browser startup time based on pages that
 // have previously been loaded close to startup.
 bool
 Predictor::PredictForStartup(nsICacheEntry *entry,
                              nsINetworkPredictorVerifier *verifier)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  PREDICTOR_LOG(("Predictor::PredictForStartup"));
   int32_t globalDegradation = CalculateGlobalDegradation(mLastStartupTime);
   CalculatePredictions(entry, mLastStartupTime, mStartupCount,
                        globalDegradation);
   return RunPredictions(verifier);
 }
 
 // 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"
@@ -1124,55 +1179,61 @@ Predictor::SetupPrediction(int32_t confi
 }
 
 // Runs predictions that have been set up.
 bool
 Predictor::RunPredictions(nsINetworkPredictorVerifier *verifier)
 {
   MOZ_ASSERT(NS_IsMainThread(), "Running prediction off main thread");
 
+  PREDICTOR_LOG(("Predictor::RunPredictions"));
+
   bool predicted = false;
   uint32_t len, i;
 
   nsTArray<nsCOMPtr<nsIURI>> preconnects, preresolves;
   preconnects.SwapElements(mPreconnects);
   preresolves.SwapElements(mPreresolves);
 
   Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PREDICTIONS> totalPredictions;
   Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRECONNECTS> totalPreconnects;
   Telemetry::AutoCounter<Telemetry::PREDICTOR_TOTAL_PRERESOLVES> totalPreresolves;
 
   len = preconnects.Length();
   for (i = 0; i < len; ++i) {
+    PREDICTOR_LOG(("    doing preconnect"));
     nsCOMPtr<nsIURI> uri = preconnects[i];
     ++totalPredictions;
     ++totalPreconnects;
     mSpeculativeService->SpeculativeConnect(uri, this);
     predicted = true;
     if (verifier) {
+      PREDICTOR_LOG(("    sending preconnect verification"));
       verifier->OnPredictPreconnect(uri);
     }
   }
 
   len = preresolves.Length();
   nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
   for (i = 0; i < len; ++i) {
     nsCOMPtr<nsIURI> uri = preresolves[i];
     ++totalPredictions;
     ++totalPreresolves;
     nsAutoCString hostname;
     uri->GetAsciiHost(hostname);
+    PREDICTOR_LOG(("    doing preresolve %s", hostname.get()));
     nsCOMPtr<nsICancelable> tmpCancelable;
     mDnsService->AsyncResolve(hostname,
                               (nsIDNSService::RESOLVE_PRIORITY_MEDIUM |
                                nsIDNSService::RESOLVE_SPECULATE),
                               mDNSListener, nullptr,
                               getter_AddRefs(tmpCancelable));
     predicted = true;
     if (verifier) {
+      PREDICTOR_LOG(("    sending preresolve verification"));
       verifier->OnPredictDNS(uri);
     }
   }
 
   return predicted;
 }
 
 // Find out if a top-level page is likely to redirect.
@@ -1188,79 +1249,106 @@ Predictor::WouldRedirect(nsICacheEntry *
 }
 
 // Called from the main thread to update the database
 NS_IMETHODIMP
 Predictor::Learn(nsIURI *targetURI, nsIURI *sourceURI,
                  PredictorLearnReason reason,
                  nsILoadContext *loadContext)
 {
+  MOZ_ASSERT(NS_IsMainThread(),
+             "Predictor interface methods must be called on the main thread");
+
+  PREDICTOR_LOG(("Predictor::Learn"));
+
   if (IsNeckoChild()) {
-    // TODO - e10s-ify the predictor
+    MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
+
+    PREDICTOR_LOG(("    called on child process"));
+
+    ipc::URIParams serTargetURI;
+    SerializeURI(targetURI, serTargetURI);
+
+    ipc::OptionalURIParams serSourceURI;
+    SerializeURI(sourceURI, serSourceURI);
+
+    IPC::SerializedLoadContext serLoadContext;
+    serLoadContext.Init(loadContext);
+
+    PREDICTOR_LOG(("    forwarding to parent"));
+    gNeckoChild->SendPredLearn(serTargetURI, serSourceURI, reason,
+                               serLoadContext);
     return NS_OK;
   }
 
-  MOZ_ASSERT(NS_IsMainThread(),
-             "Predictor interface methods must be called on the main thread");
+  PREDICTOR_LOG(("    called on parent process"));
 
   if (!mInitialized) {
+    PREDICTOR_LOG(("    not initialized"));
     return NS_OK;
   }
 
   if (!mEnabled) {
+    PREDICTOR_LOG(("    not enabled"));
     return NS_OK;
   }
 
   if (loadContext && loadContext->UsePrivateBrowsing()) {
     // Don't want to do anything in PB mode
+    PREDICTOR_LOG(("    in PB mode"));
     return NS_OK;
   }
 
   if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
+    PREDICTOR_LOG(("    got non-HTTP[S] URI"));
     return NS_ERROR_INVALID_ARG;
   }
 
   nsCOMPtr<nsIURI> targetOrigin;
   nsCOMPtr<nsIURI> sourceOrigin;
   nsCOMPtr<nsIURI> uriKey;
   nsCOMPtr<nsIURI> originKey;
   nsresult rv;
 
   switch (reason) {
   case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL:
     if (!targetURI || sourceURI) {
+      PREDICTOR_LOG(("    load toplevel invalid URI state"));
       return NS_ERROR_INVALID_ARG;
     }
     rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin), mIOService);
     NS_ENSURE_SUCCESS(rv, rv);
     uriKey = targetURI;
     originKey = targetOrigin;
     break;
   case nsINetworkPredictor::LEARN_STARTUP:
     if (!targetURI || sourceURI) {
+      PREDICTOR_LOG(("    startup invalid URI state"));
       return NS_ERROR_INVALID_ARG;
     }
     rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin), mIOService);
     NS_ENSURE_SUCCESS(rv, rv);
     uriKey = mStartupURI;
     originKey = mStartupURI;
     break;
   case nsINetworkPredictor::LEARN_LOAD_REDIRECT:
   case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE:
     if (!targetURI || !sourceURI) {
+      PREDICTOR_LOG(("    redirect/subresource invalid URI state"));
       return NS_ERROR_INVALID_ARG;
     }
     rv = ExtractOrigin(targetURI, getter_AddRefs(targetOrigin), mIOService);
     NS_ENSURE_SUCCESS(rv, rv);
     rv = ExtractOrigin(sourceURI, getter_AddRefs(sourceOrigin), mIOService);
     NS_ENSURE_SUCCESS(rv, rv);
     uriKey = sourceURI;
     originKey = sourceOrigin;
     break;
   default:
+    PREDICTOR_LOG(("    invalid reason"));
     return NS_ERROR_INVALID_ARG;
   }
 
   Telemetry::AutoCounter<Telemetry::PREDICTOR_LEARN_ATTEMPTS> learnAttempts;
   ++learnAttempts;
 
   Predictor::Reason argReason;
   argReason.mLearn = reason;
@@ -1272,17 +1360,17 @@ Predictor::Learn(nsIURI *targetURI, nsIU
                           Predictor::Action::DO_LEARN, argReason, targetURI,
                           sourceURI, nullptr, this);
   nsAutoCString uriKeyStr, targetUriStr, sourceUriStr;
   uriKey->GetAsciiSpec(uriKeyStr);
   targetURI->GetAsciiSpec(targetUriStr);
   if (sourceURI) {
     sourceURI->GetAsciiSpec(sourceUriStr);
   }
-  PREDICTOR_LOG(("Learn uriKey=%s targetURI=%s sourceURI=%s reason=%d "
+  PREDICTOR_LOG(("    Learn uriKey=%s targetURI=%s sourceURI=%s reason=%d "
                  "action=%p", uriKeyStr.get(), targetUriStr.get(),
                  sourceUriStr.get(), reason, uriAction.get()));
   // For learning full URI things, we *always* open readonly and secretly, as we
   // rely on actual pageloads to update the entry's metadata for us.
   uint32_t uriOpenFlags = nsICacheStorage::OPEN_READONLY |
                           nsICacheStorage::OPEN_SECRETLY |
                           nsICacheStorage::CHECK_MULTITHREADED;
   if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) {
@@ -1300,17 +1388,17 @@ Predictor::Learn(nsIURI *targetURI, nsIU
                           Predictor::Action::DO_LEARN, argReason, targetOrigin,
                           sourceOrigin, nullptr, this);
   nsAutoCString originKeyStr, targetOriginStr, sourceOriginStr;
   originKey->GetAsciiSpec(originKeyStr);
   targetOrigin->GetAsciiSpec(targetOriginStr);
   if (sourceOrigin) {
     sourceOrigin->GetAsciiSpec(sourceOriginStr);
   }
-  PREDICTOR_LOG(("Learn originKey=%s targetOrigin=%s sourceOrigin=%s reason=%d "
+  PREDICTOR_LOG(("    Learn originKey=%s targetOrigin=%s sourceOrigin=%s reason=%d "
                  "action=%p", originKeyStr.get(), targetOriginStr.get(),
                  sourceOriginStr.get(), reason, originAction.get()));
   uint32_t originOpenFlags;
   if (reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL) {
     // This is the only case when we want to update the 'last used' metadata on
     // the cache entry we're getting. This only applies to predictor-specific
     // entries.
     originOpenFlags = nsICacheStorage::OPEN_NORMALLY |
@@ -1319,56 +1407,62 @@ Predictor::Learn(nsIURI *targetURI, nsIU
     originOpenFlags = nsICacheStorage::OPEN_READONLY |
                       nsICacheStorage::OPEN_SECRETLY |
                       nsICacheStorage::CHECK_MULTITHREADED;
   }
   mCacheDiskStorage->AsyncOpenURI(originKey,
                                   NS_LITERAL_CSTRING(PREDICTOR_ORIGIN_EXTENSION),
                                   originOpenFlags, originAction);
 
+  PREDICTOR_LOG(("Predictor::Learn returning"));
   return NS_OK;
 }
 
 void
 Predictor::LearnInternal(PredictorLearnReason reason, nsICacheEntry *entry,
                          bool isNew, bool fullUri, nsIURI *targetURI,
                          nsIURI *sourceURI)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  PREDICTOR_LOG(("Predictor::LearnInternal"));
+
   nsCString junk;
   if (!fullUri && reason == nsINetworkPredictor::LEARN_LOAD_TOPLEVEL &&
       NS_FAILED(entry->GetMetaDataElement(SEEN_META_DATA, getter_Copies(junk)))) {
     // This is an origin-only entry that we haven't seen before. Let's mark it
     // as seen.
+    PREDICTOR_LOG(("    marking new origin entry as seen"));
     entry->SetMetaDataElement(SEEN_META_DATA, "1");
 
     // Need to ensure someone else can get to the entry if necessary
     entry->MetaDataReady();
     return;
   }
 
   switch (reason) {
     case nsINetworkPredictor::LEARN_LOAD_TOPLEVEL:
       // This actually has no work associated with it, since all we need to do
       // is update the timestamps and fetch count, and that's done for us by
       // opening the cache entry.
+      PREDICTOR_LOG(("    nothing to do for toplevel"));
       break;
     case nsINetworkPredictor::LEARN_LOAD_REDIRECT:
       if (fullUri) {
         LearnForRedirect(entry, targetURI);
       }
       break;
     case nsINetworkPredictor::LEARN_LOAD_SUBRESOURCE:
       LearnForSubresource(entry, targetURI);
       break;
     case nsINetworkPredictor::LEARN_STARTUP:
       LearnForStartup(entry, targetURI);
       break;
     default:
+      PREDICTOR_LOG(("    unexpected reason value"));
       MOZ_ASSERT(false, "Got unexpected value for learn reason!");
   }
 }
 
 NS_IMPL_ISUPPORTS(Predictor::SpaceCleaner, nsICacheEntryMetaDataVisitor)
 
 NS_IMETHODIMP
 Predictor::SpaceCleaner::OnMetaDataElement(const char *key, const char *value)
@@ -1406,16 +1500,18 @@ Predictor::SpaceCleaner::Finalize(nsICac
 
 // Called when a subresource has been hit from a top-level load. Uses the two
 // helper functions above to update the database appropriately.
 void
 Predictor::LearnForSubresource(nsICacheEntry *entry, nsIURI *targetURI)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  PREDICTOR_LOG(("Predictor::LearnForSubresource"));
+
   uint32_t lastLoad;
   nsresult rv = entry->GetLastFetched(&lastLoad);
   RETURN_IF_FAILED(rv);
 
   int32_t loadCount;
   rv = entry->GetFetchCount(&loadCount);
   RETURN_IF_FAILED(rv);
 
@@ -1430,16 +1526,17 @@ Predictor::LearnForSubresource(nsICacheE
 
   uint32_t hitCount, lastHit, flags;
   bool isNewResource = (NS_FAILED(rv) ||
                         !ParseMetaDataEntry(nullptr, value.BeginReading(),
                                             nullptr, hitCount, lastHit, flags));
 
   if (isNewResource) {
     // This is a new addition
+    PREDICTOR_LOG(("    new resource"));
     int32_t resourceCount;
     nsCString s;
     rv = entry->GetMetaDataElement("predictor::resource-count",
                                    getter_Copies(s));
     if (NS_FAILED(rv)) {
       resourceCount = 0;
     } else {
       resourceCount = atoi(s.BeginReading());
@@ -1452,16 +1549,17 @@ Predictor::LearnForSubresource(nsICacheE
     } else {
       ++resourceCount;
     }
     nsAutoCString count;
     count.AppendInt(resourceCount);
     entry->SetMetaDataElement("predictor::resource-count", count.BeginReading());
     hitCount = 1;
   } else {
+    PREDICTOR_LOG(("    existing resource"));
     hitCount = std::min(hitCount + 1, static_cast<uint32_t>(loadCount));
   }
 
   nsCString newValue;
   newValue.AppendInt(METADATA_VERSION);
   newValue.AppendLiteral(",");
   newValue.AppendInt(hitCount);
   newValue.AppendLiteral(",");
@@ -1476,36 +1574,39 @@ Predictor::LearnForSubresource(nsICacheE
 // This is called when a top-level loaded ended up redirecting to a different
 // URI so we can keep track of that fact.
 void
 Predictor::LearnForRedirect(nsICacheEntry *entry, nsIURI *targetURI)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // TODO - not doing redirects for first go around
+  PREDICTOR_LOG(("Predictor::LearnForRedirect"));
 }
 
 // This will add a page to our list of startup pages if it's being loaded
 // before our startup window has expired.
 void
 Predictor::MaybeLearnForStartup(nsIURI *uri, bool fullUri)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // TODO - not doing startup for first go around
+  PREDICTOR_LOG(("Predictor::MaybeLearnForStartup"));
 }
 
 // Add information about a top-level load to our list of startup pages
 void
 Predictor::LearnForStartup(nsICacheEntry *entry, nsIURI *targetURI)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // These actually do the same set of work, just on different entries, so we
   // can pass through to get the real work done here
+  PREDICTOR_LOG(("Predictor::LearnForStartup"));
   LearnForSubresource(entry, targetURI);
 }
 
 bool
 Predictor::ParseMetaDataEntry(const char *key, const char *value, nsIURI **uri,
                               uint32_t &hitCount, uint32_t &lastHit,
                               uint32_t &flags)
 {
@@ -1564,34 +1665,45 @@ Predictor::ParseMetaDataEntry(const char
   }
 
   return true;
 }
 
 NS_IMETHODIMP
 Predictor::Reset()
 {
+  MOZ_ASSERT(NS_IsMainThread(),
+             "Predictor interface methods must be called on the main thread");
+
+  PREDICTOR_LOG(("Predictor::Reset"));
+
   if (IsNeckoChild()) {
-    // TODO - e10s-ify the predictor
+    MOZ_DIAGNOSTIC_ASSERT(gNeckoChild);
+
+    PREDICTOR_LOG(("    forwarding to parent process"));
+    gNeckoChild->SendPredReset();
     return NS_OK;
   }
 
-  MOZ_ASSERT(NS_IsMainThread(),
-             "Predictor interface methods must be called on the main thread");
+  PREDICTOR_LOG(("    called on parent process"));
 
   if (!mInitialized) {
+    PREDICTOR_LOG(("    not initialized"));
     return NS_OK;
   }
 
   if (!mEnabled) {
+    PREDICTOR_LOG(("    not enabled"));
     return NS_OK;
   }
 
   nsRefPtr<Predictor::Resetter> reset = new Predictor::Resetter(this);
+  PREDICTOR_LOG(("    created a resetter"));
   mCacheDiskStorage->AsyncVisitStorage(reset, true);
+  PREDICTOR_LOG(("    Cache async launched, returning now"));
 
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS(Predictor::Resetter,
                   nsICacheEntryOpenCallback,
                   nsICacheEntryMetaDataVisitor,
                   nsICacheStorageVisitor);
@@ -1756,21 +1868,16 @@ EnsureGlobalPredictor(nsINetworkPredicto
   return NS_OK;
 }
 
 nsresult
 PredictorPredict(nsIURI *targetURI, nsIURI *sourceURI,
                  PredictorPredictReason reason, nsILoadContext *loadContext,
                  nsINetworkPredictorVerifier *verifier)
 {
-  if (IsNeckoChild()) {
-    // TODO - e10s-ify the predictor
-    return NS_OK;
-  }
-
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
     return NS_OK;
   }
 
   nsCOMPtr<nsINetworkPredictor> predictor;
   nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
@@ -1780,21 +1887,16 @@ PredictorPredict(nsIURI *targetURI, nsIU
                             loadContext, verifier);
 }
 
 nsresult
 PredictorLearn(nsIURI *targetURI, nsIURI *sourceURI,
                PredictorLearnReason reason,
                nsILoadContext *loadContext)
 {
-  if (IsNeckoChild()) {
-    // TODO - e10s-ify the predictor
-    return NS_OK;
-  }
-
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
     return NS_OK;
   }
 
   nsCOMPtr<nsINetworkPredictor> predictor;
   nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
@@ -1803,21 +1905,16 @@ PredictorLearn(nsIURI *targetURI, nsIURI
   return predictor->Learn(targetURI, sourceURI, reason, loadContext);
 }
 
 nsresult
 PredictorLearn(nsIURI *targetURI, nsIURI *sourceURI,
                PredictorLearnReason reason,
                nsILoadGroup *loadGroup)
 {
-  if (IsNeckoChild()) {
-    // TODO - e10s-ify the predictor
-    return NS_OK;
-  }
-
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
     return NS_OK;
   }
 
   nsCOMPtr<nsINetworkPredictor> predictor;
   nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
@@ -1836,21 +1933,16 @@ PredictorLearn(nsIURI *targetURI, nsIURI
   return predictor->Learn(targetURI, sourceURI, reason, loadContext);
 }
 
 nsresult
 PredictorLearn(nsIURI *targetURI, nsIURI *sourceURI,
                PredictorLearnReason reason,
                nsIDocument *document)
 {
-  if (IsNeckoChild()) {
-    // TODO - e10s-ify the predictor
-    return NS_OK;
-  }
-
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!IsNullOrHttp(targetURI) || !IsNullOrHttp(sourceURI)) {
     return NS_OK;
   }
 
   nsCOMPtr<nsINetworkPredictor> predictor;
   nsresult rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
@@ -1864,21 +1956,16 @@ PredictorLearn(nsIURI *targetURI, nsIURI
 
   return predictor->Learn(targetURI, sourceURI, reason, loadContext);
 }
 
 nsresult
 PredictorLearnRedirect(nsIURI *targetURI, nsIChannel *channel,
                        nsILoadContext *loadContext)
 {
-  if (IsNeckoChild()) {
-    // TODO - e10s-ify the predictor
-    return NS_OK;
-  }
-
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIURI> sourceURI;
   nsresult rv = channel->GetOriginalURI(getter_AddRefs(sourceURI));
   NS_ENSURE_SUCCESS(rv, rv);
 
   bool sameUri;
   rv = targetURI->Equals(sourceURI, &sameUri);
@@ -1896,10 +1983,56 @@ PredictorLearnRedirect(nsIURI *targetURI
   rv = EnsureGlobalPredictor(getter_AddRefs(predictor));
   NS_ENSURE_SUCCESS(rv, rv);
 
   return predictor->Learn(targetURI, sourceURI,
                           nsINetworkPredictor::LEARN_LOAD_REDIRECT,
                           loadContext);
 }
 
+// nsINetworkPredictorVerifier
+
+/**
+ * Call through to the child's verifier (only during tests).
+ */
+NS_IMETHODIMP
+Predictor::OnPredictPreconnect(nsIURI *aURI) {
+  if (IsNeckoChild()) {
+    MOZ_DIAGNOSTIC_ASSERT(mChildVerifier);
+    return mChildVerifier->OnPredictPreconnect(aURI);
+  }
+
+  MOZ_DIAGNOSTIC_ASSERT(gNeckoParent);
+
+  ipc::URIParams serURI;
+  SerializeURI(aURI, serURI);
+
+  if (!gNeckoParent->SendPredOnPredictPreconnect(serURI)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return NS_OK;
+}
+
+/**
+ * Call through to the child's verifier (only during tests)
+ */
+NS_IMETHODIMP
+Predictor::OnPredictDNS(nsIURI *aURI) {
+  if (IsNeckoChild()) {
+    MOZ_DIAGNOSTIC_ASSERT(mChildVerifier);
+    return mChildVerifier->OnPredictDNS(aURI);
+  }
+
+  MOZ_DIAGNOSTIC_ASSERT(gNeckoParent);
+
+  ipc::URIParams serURI;
+  SerializeURI(aURI, serURI);
+
+  if (!gNeckoParent->SendPredOnPredictDNS(serURI)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  return NS_OK;
+}
+
 } // ::mozilla::net
 } // ::mozilla
--- a/netwerk/base/Predictor.h
+++ b/netwerk/base/Predictor.h
@@ -2,16 +2,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_net_Predictor_h
 #define mozilla_net_Predictor_h
 
 #include "nsINetworkPredictor.h"
+#include "nsINetworkPredictorVerifier.h"
 
 #include "nsCOMPtr.h"
 #include "nsICacheEntry.h"
 #include "nsICacheEntryOpenCallback.h"
 #include "nsICacheStorageVisitor.h"
 #include "nsIDNSListener.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsIObserver.h"
@@ -20,45 +21,49 @@
 #include "nsString.h"
 #include "nsTArray.h"
 
 #include "mozilla/TimeStamp.h"
 
 class nsICacheStorage;
 class nsIDNSService;
 class nsIIOService;
-class nsINetworkPredictorVerifier;
 class nsITimer;
 
 namespace mozilla {
 namespace net {
 
 class Predictor : public nsINetworkPredictor
                 , public nsIObserver
                 , public nsISpeculativeConnectionOverrider
                 , public nsIInterfaceRequestor
                 , public nsICacheEntryMetaDataVisitor
+                , public nsINetworkPredictorVerifier
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSINETWORKPREDICTOR
   NS_DECL_NSIOBSERVER
   NS_DECL_NSISPECULATIVECONNECTIONOVERRIDER
   NS_DECL_NSIINTERFACEREQUESTOR
   NS_DECL_NSICACHEENTRYMETADATAVISITOR
+  NS_DECL_NSINETWORKPREDICTORVERIFIER
 
   Predictor();
 
   nsresult Init();
   void Shutdown();
   static nsresult Create(nsISupports *outer, const nsIID& iid, void **result);
 
 private:
   virtual ~Predictor();
 
+  // Stores callbacks for a child process predictor (for test purposes)
+  nsCOMPtr<nsINetworkPredictorVerifier> mChildVerifier;
+
   union Reason {
     PredictorLearnReason mLearn;
     PredictorPredictReason mPredict;
   };
 
   class DNSListener : public nsIDNSListener
   {
   public:
--- a/netwerk/ipc/NeckoChild.cpp
+++ b/netwerk/ipc/NeckoChild.cpp
@@ -22,16 +22,19 @@
 #include "mozilla/dom/network/TCPServerSocketChild.h"
 #include "mozilla/dom/network/UDPSocketChild.h"
 #ifdef NECKO_PROTOCOL_rtsp
 #include "mozilla/net/RtspControllerChild.h"
 #include "mozilla/net/RtspChannelChild.h"
 #endif
 #include "SerializedLoadContext.h"
 #include "nsIOService.h"
+#include "nsINetworkPredictor.h"
+#include "nsINetworkPredictorVerifier.h"
+#include "mozilla/ipc/URIUtils.h"
 
 using mozilla::dom::TCPSocketChild;
 using mozilla::dom::TCPServerSocketChild;
 using mozilla::dom::UDPSocketChild;
 
 namespace mozilla {
 namespace net {
 
@@ -328,16 +331,53 @@ NeckoChild::RecvAsyncAuthPromptForNested
   if (!tabChild) {
     MOZ_CRASH();
     return false;
   }
   tabChild->SendAsyncAuthPrompt(aUri, aRealm, aCallbackId);
   return true;
 }
 
+/* Predictor Messages */
+bool
+NeckoChild::RecvPredOnPredictPreconnect(const URIParams& aURI)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "PredictorChild::RecvOnPredictPreconnect "
+                                "off main thread.");
+
+  nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+
+  // Get the current predictor
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsINetworkPredictorVerifier> predictor =
+    do_GetService("@mozilla.org/network/predictor;1", &rv);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  predictor->OnPredictPreconnect(uri);
+  return true;
+}
+
+bool
+NeckoChild::RecvPredOnPredictDNS(const URIParams& aURI)
+{
+  MOZ_ASSERT(NS_IsMainThread(), "PredictorChild::RecvOnPredictDNS off "
+                                "main thread.");
+
+  nsCOMPtr<nsIURI> uri = DeserializeURI(aURI);
+
+  // Get the current predictor
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsINetworkPredictorVerifier> predictor =
+    do_GetService("@mozilla.org/network/predictor;1", &rv);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  predictor->OnPredictDNS(uri);
+  return true;
+}
+
 bool
 NeckoChild::RecvAppOfflineStatus(const uint32_t& aId, const bool& aOffline)
 {
   // Instantiate the service to make sure gIOService is initialized
   nsCOMPtr<nsIIOService> ioService = do_GetIOService();
   if (gIOService) {
     gIOService->SetAppOfflineInternal(aId, aOffline ?
       nsIAppOfflineInfo::OFFLINE : nsIAppOfflineInfo::ONLINE);
--- a/netwerk/ipc/NeckoChild.h
+++ b/netwerk/ipc/NeckoChild.h
@@ -75,16 +75,20 @@ protected:
   AllocPChannelDiverterChild(const ChannelDiverterArgs& channel) override;
   virtual bool
   DeallocPChannelDiverterChild(PChannelDiverterChild* actor) override;
   virtual bool RecvAsyncAuthPromptForNestedFrame(const TabId& aNestedFrameId,
                                                  const nsCString& aUri,
                                                  const nsString& aRealm,
                                                  const uint64_t& aCallbackId) override;
   virtual bool RecvAppOfflineStatus(const uint32_t& aId, const bool& aOffline) override;
+
+  /* Predictor Messsages */
+  virtual bool RecvPredOnPredictPreconnect(const URIParams& aURI) override;
+  virtual bool RecvPredOnPredictDNS(const URIParams& aURI) override;
 };
 
 /**
  * Reference to the PNecko Child protocol.
  * Null if this is not a content process.
  */
 extern PNeckoChild *gNeckoChild;
 
--- a/netwerk/ipc/NeckoParent.cpp
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -35,16 +35,17 @@
 #include "nsIAppsService.h"
 #include "nsEscape.h"
 #include "RemoteOpenFileParent.h"
 #include "SerializedLoadContext.h"
 #include "nsAuthInformationHolder.h"
 #include "nsIAuthPromptCallback.h"
 #include "nsPrincipal.h"
 #include "nsIOService.h"
+#include "nsINetworkPredictor.h"
 #include "mozilla/net/OfflineObserver.h"
 #include "nsISpeculativeConnect.h"
 
 using mozilla::dom::ContentParent;
 using mozilla::dom::TabContext;
 using mozilla::dom::TabParent;
 using mozilla::net::PTCPSocketParent;
 using mozilla::dom::TCPSocketParent;
@@ -52,16 +53,18 @@ using mozilla::net::PTCPServerSocketPare
 using mozilla::dom::TCPServerSocketParent;
 using mozilla::net::PUDPSocketParent;
 using mozilla::dom::UDPSocketParent;
 using IPC::SerializedLoadContext;
 
 namespace mozilla {
 namespace net {
 
+PNeckoParent *gNeckoParent = nullptr;
+
 // C++ file contents
 NeckoParent::NeckoParent()
 {
   // Init HTTP protocol handler now since we need atomTable up and running very
   // early (IPDL argument handling for PHttpChannel constructor needs it) so
   // normal init (during 1st Http channel request) isn't early enough.
   nsCOMPtr<nsIProtocolHandler> proto =
     do_GetService("@mozilla.org/network/protocol;1?name=http");
@@ -77,16 +80,17 @@ NeckoParent::NeckoParent()
     // corePath may be empty: we don't use it for all build types
     MOZ_ASSERT(!webPath.IsEmpty());
 
     LossyCopyUTF16toASCII(corePath, mCoreAppsBasePath);
     LossyCopyUTF16toASCII(webPath, mWebAppsBasePath);
   }
 
   mObserver = new OfflineObserver(this);
+  gNeckoParent = this;
 }
 
 NeckoParent::~NeckoParent()
 {
   if (mObserver) {
     mObserver->RemoveObserver();
   }
 }
@@ -848,16 +852,89 @@ NeckoParent::RecvOnAuthCancelled(const u
   if (!callback) {
     return true;
   }
   CallbackMap().erase(aCallbackId);
   callback->OnAuthCancelled(nullptr, aUserCancel);
   return true;
 }
 
+/* Predictor Messages */
+bool
+NeckoParent::RecvPredPredict(const ipc::OptionalURIParams& aTargetURI,
+                             const ipc::OptionalURIParams& aSourceURI,
+                             const uint32_t& aReason,
+                             const SerializedLoadContext& aLoadContext,
+                             const bool& hasVerifier)
+{
+  nsCOMPtr<nsIURI> targetURI = DeserializeURI(aTargetURI);
+  nsCOMPtr<nsIURI> sourceURI = DeserializeURI(aSourceURI);
+
+  // We only actually care about the loadContext.mPrivateBrowsing, so we'll just
+  // pass dummy params for nestFrameId, inBrowser and appId
+  uint64_t nestedFrameId = 0;
+  nsCOMPtr<nsILoadContext> loadContext;
+  if (aLoadContext.IsNotNull()) {
+    loadContext = new LoadContext(aLoadContext, nestedFrameId, NECKO_UNKNOWN_APP_ID, false);
+  }
+
+  // Get the current predictor
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsINetworkPredictor> predictor =
+    do_GetService("@mozilla.org/network/predictor;1", &rv);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  nsCOMPtr<nsINetworkPredictorVerifier> verifier;
+  if (hasVerifier) {
+    verifier = do_QueryInterface(predictor);
+  }
+  predictor->Predict(targetURI, sourceURI, aReason, loadContext, verifier);
+  return true;
+}
+
+bool
+NeckoParent::RecvPredLearn(const ipc::URIParams& aTargetURI,
+                           const ipc::OptionalURIParams& aSourceURI,
+                           const uint32_t& aReason,
+                           const SerializedLoadContext& aLoadContext)
+{
+  nsCOMPtr<nsIURI> targetURI = DeserializeURI(aTargetURI);
+  nsCOMPtr<nsIURI> sourceURI = DeserializeURI(aSourceURI);
+
+  // We only actually care about the loadContext.mPrivateBrowsing, so we'll just
+  // pass dummy params for nestFrameId, inBrowser and appId
+  uint64_t nestedFrameId = 0;
+  nsCOMPtr<nsILoadContext> loadContext;
+  if (aLoadContext.IsNotNull()) {
+    loadContext = new LoadContext(aLoadContext, nestedFrameId, NECKO_UNKNOWN_APP_ID, false);
+  }
+
+  // Get the current predictor
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsINetworkPredictor> predictor =
+    do_GetService("@mozilla.org/network/predictor;1", &rv);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  predictor->Learn(targetURI, sourceURI, aReason, loadContext);
+  return true;
+}
+
+bool
+NeckoParent::RecvPredReset()
+{
+  // Get the current predictor
+  nsresult rv = NS_OK;
+  nsCOMPtr<nsINetworkPredictor> predictor =
+    do_GetService("@mozilla.org/network/predictor;1", &rv);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  predictor->Reset();
+  return true;
+}
+
 nsresult
 NeckoParent::OfflineNotification(nsISupports *aSubject)
 {
   nsCOMPtr<nsIAppOfflineInfo> info(do_QueryInterface(aSubject));
   if (!info) {
     return NS_OK;
   }
 
--- a/netwerk/ipc/NeckoParent.h
+++ b/netwerk/ipc/NeckoParent.h
@@ -3,16 +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/. */
 
 #include "mozilla/net/PNeckoParent.h"
 #include "mozilla/net/NeckoCommon.h"
 #include "mozilla/net/OfflineObserver.h"
+#include "nsINetworkPredictor.h"
 
 #ifndef mozilla_net_NeckoParent_h
 #define mozilla_net_NeckoParent_h
 
 namespace mozilla {
 namespace net {
 
 // Used to override channel Private Browsing status if needed.
@@ -203,18 +204,36 @@ protected:
 
   virtual bool RecvOnAuthAvailable(const uint64_t& aCallbackId,
                                    const nsString& aUser,
                                    const nsString& aPassword,
                                    const nsString& aDomain) override;
   virtual bool RecvOnAuthCancelled(const uint64_t& aCallbackId,
                                    const bool& aUserCancel) override;
 
+  /* Predictor Messages */
+  virtual bool RecvPredPredict(const ipc::OptionalURIParams& aTargetURI,
+                               const ipc::OptionalURIParams& aSourceURI,
+                               const PredictorPredictReason& aReason,
+                               const IPC::SerializedLoadContext& aLoadContext,
+                               const bool& hasVerifier) override;
+
+  virtual bool RecvPredLearn(const ipc::URIParams& aTargetURI,
+                             const ipc::OptionalURIParams& aSourceURI,
+                             const PredictorPredictReason& aReason,
+                             const IPC::SerializedLoadContext& aLoadContext) override;
+  virtual bool RecvPredReset() override;
+
 private:
   nsCString mCoreAppsBasePath;
   nsCString mWebAppsBasePath;
   nsRefPtr<OfflineObserver> mObserver;
 };
 
+/**
+ * Reference to the PNecko Parent protocol.
+ */
+extern PNeckoParent *gNeckoParent;
+
 } // namespace net
 } // namespace mozilla
 
 #endif // mozilla_net_NeckoParent_h
--- a/netwerk/ipc/PNecko.ipdl
+++ b/netwerk/ipc/PNecko.ipdl
@@ -67,16 +67,25 @@ parent:
               FTPChannelCreationArgs args);
 
   PWebSocket(PBrowserOrId browser, SerializedLoadContext loadContext);
   PTCPServerSocket(uint16_t localPort, uint16_t backlog, nsString binaryType);
   PUDPSocket(Principal principal, nsCString filter);
 
   PDNSRequest(nsCString hostName, uint32_t flags, nsCString networkInterface);
 
+
+  /* Predictor Methods */
+  PredPredict(OptionalURIParams targetURI, OptionalURIParams sourceURI,
+              uint32_t reason, SerializedLoadContext loadContext,
+              bool hasVerifier);
+  PredLearn(URIParams targetURI, OptionalURIParams sourceURI,
+            uint32_t reason, SerializedLoadContext loadContext);
+  PredReset();
+
   PRemoteOpenFile(SerializedLoadContext loadContext,
                   URIParams fileuri,
                   OptionalURIParams appuri);
 
   SpeculativeConnect(URIParams uri, bool anonymous);
   HTMLDNSPrefetch(nsString hostname, uint16_t flags);
   CancelHTMLDNSPrefetch(nsString hostname, uint16_t flags, nsresult reason);
 
@@ -105,16 +114,20 @@ child:
    * NestedFrameId is the id corresponding to the PBrowser.  It is the same id
    * that was passed to the PBrowserOrId param in to the PHttpChannel constructor
    */
   AsyncAuthPromptForNestedFrame(TabId nestedFrameId, nsCString uri,
                                 nsString realm, uint64_t callbackId);
   // Notifies child that a given app is now offline (or online)
   AppOfflineStatus(uint32_t appId, bool offline);
 
+  /* Predictor Methods */
+  PredOnPredictPreconnect(URIParams uri);
+  PredOnPredictDNS(URIParams uri);
+
 both:
   // Actually we need PTCPSocket() for parent. But ipdl disallows us having different
   // signatures on parent and child. So when constructing the parent side object, we just 
   // leave host/port unused.
   PTCPSocket(nsString host, uint16_t port);
 };
 
 
--- a/netwerk/test/unit/test_predictor.js
+++ b/netwerk/test/unit/test_predictor.js
@@ -1,19 +1,22 @@
 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 running_single_process = false;
 
 var predictor = null;
-var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
-var profile = null;
+
+function is_child_process() {
+  return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT;
+}
 
 function extract_origin(uri) {
   var o = uri.scheme + "://" + uri.asciiHost;
   if (uri.port !== -1) {
     o = o + ":" + uri.port;
   }
   return o;
 }
@@ -95,21 +98,25 @@ Verifier.prototype = {
     } else {
       this.expected_preresolves.splice(index, 1);
     }
     this.maybe_run_next_test();
   }
 };
 
 function reset_predictor() {
-  predictor.reset();
+  if (running_single_process || is_child_process()) {
+    predictor.reset();
+  } else {
+    sendCommand("predictor.reset();");
+  }
 }
 
 function newURI(s) {
-  return ios.newURI(s, null, null);
+  return Services.io.newURI(s, null, null);
 }
 
 var prepListener = {
   numEntriesToOpen: 0,
   numEntriesOpened: 0,
   continueCallback: null,
 
   QueryInterface: function (iid) {
@@ -150,156 +157,195 @@ function open_and_continue(uris, continu
     },
 
     isPrivate: false,
     appId: Ci.nsILoadContextInfo.NO_APP_ID,
     isInBrowserElement: false,
     isAnonymous: false
   };
 
-  var css = Cc["@mozilla.org/netwerk/cache-storage-service;1"]
-    .getService(Ci.nsICacheStorageService);
-  var ds = css.diskCacheStorage(lci, false);
+  var ds = Services.cache2.diskCacheStorage(lci, false);
 
   prepListener.init(uris.length, continueCallback);
   for (var i = 0; i < uris.length; ++i) {
     ds.asyncOpenURI(uris[i], "", Ci.nsICacheStorage.OPEN_NORMALLY,
                     prepListener);
   }
 }
 
 function test_link_hover() {
+  if (!running_single_process && !is_child_process()) {
+    // This one we can just proxy to the child and be done with, no extra setup
+    // is necessary.
+    sendCommand("test_link_hover();");
+    return;
+  }
+
   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, []);
   predictor.predict(uri, referrer, predictor.PREDICT_LINK, load_context, verifier);
 }
 
-function test_pageload() {
-  var toplevel = "http://localhost:4444/index.html";
+const pageload_toplevel = newURI("http://localhost:4444/index.html");
+
+function continue_test_pageload() {
   var subresources = [
     "http://localhost:4444/style.css",
     "http://localhost:4443/jquery.js",
     "http://localhost:4444/image.png"
   ];
 
-  var tluri = newURI(toplevel);
-  open_and_continue([tluri], function () {
-    // This is necessary to learn the origin stuff
-    predictor.learn(tluri, null, predictor.LEARN_LOAD_TOPLEVEL, load_context);
-    var preconns = [];
-    for (var i = 0; i < subresources.length; i++) {
-      var sruri = newURI(subresources[i]);
-      predictor.learn(sruri, tluri, predictor.LEARN_LOAD_SUBRESOURCE, load_context);
-      preconns.push(extract_origin(sruri));
+  // This is necessary to learn the origin stuff
+  predictor.learn(pageload_toplevel, null, predictor.LEARN_LOAD_TOPLEVEL, load_context);
+  var preconns = [];
+  for (var i = 0; i < subresources.length; i++) {
+    var sruri = newURI(subresources[i]);
+    predictor.learn(sruri, pageload_toplevel, predictor.LEARN_LOAD_SUBRESOURCE, load_context);
+    preconns.push(extract_origin(sruri));
+  }
+
+  var verifier = new Verifier("pageload", preconns, []);
+  predictor.predict(pageload_toplevel, null, predictor.PREDICT_LOAD, load_context, verifier);
+}
+
+function test_pageload() {
+  open_and_continue([pageload_toplevel], function () {
+    if (running_single_process) {
+      continue_test_pageload();
+    } else {
+      sendCommand("continue_test_pageload();");
     }
-
-    var verifier = new Verifier("pageload", preconns, []);
-    predictor.predict(tluri, null, predictor.PREDICT_LOAD, load_context, verifier);
   });
 }
 
-function test_redirect() {
-  var initial = "http://localhost:4443/redirect";
-  var target = "http://localhost:4444/index.html";
+const redirect_inituri = newURI("http://localhost:4443/redirect");
+const redirect_targeturi = newURI("http://localhost:4444/index.html");
+
+function continue_test_redrect() {
   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);
-  open_and_continue([inituri, targeturi], function () {
-    predictor.learn(inituri, null, predictor.LEARN_LOAD_TOPLEVEL, load_context);
-    predictor.learn(targeturi, null, predictor.LEARN_LOAD_TOPLEVEL, load_context);
-    predictor.learn(targeturi, inituri, predictor.LEARN_LOAD_REDIRECT, load_context);
+  predictor.learn(redirect_inituri, null, predictor.LEARN_LOAD_TOPLEVEL, load_context);
+  predictor.learn(redirect_targeturi, null, predictor.LEARN_LOAD_TOPLEVEL, load_context);
+  predictor.learn(redirect_targeturi, redirect_inituri, predictor.LEARN_LOAD_REDIRECT, load_context);
+
+  var preconns = [];
+  preconns.push(extract_origin(redirect_targeturi));
+  for (var i = 0; i < subresources.length; i++) {
+    var sruri = newURI(subresources[i]);
+    predictor.learn(sruri, redirect_targeturi, predictor.LEARN_LOAD_SUBRESOURCE, load_context);
+    preconns.push(extract_origin(sruri));
+  }
 
-    var preconns = [];
-    preconns.push(extract_origin(targeturi));
-    for (var i = 0; i < subresources.length; i++) {
-      var sruri = newURI(subresources[i]);
-      predictor.learn(sruri, targeturi, predictor.LEARN_LOAD_SUBRESOURCE, load_context);
-      preconns.push(extract_origin(sruri));
+  var verifier = new Verifier("redirect", preconns, []);
+  predictor.predict(redirect_inituri, null, predictor.PREDICT_LOAD, load_context, verifier);
+}
+
+function test_redirect() {
+  open_and_continue([redirect_inituri, redirect_targeturi], function () {
+    if (running_single_process) {
+      continue_test_redirect();
+    } else {
+      sendCommand("continue_test_redirect();");
     }
-
-    var verifier = new Verifier("redirect", preconns, []);
-    predictor.predict(inituri, null, predictor.PREDICT_LOAD, load_context, verifier);
   });
 }
 
 function test_startup() {
+  if (!running_single_process && !is_child_process()) {
+    // This one we can just proxy to the child and be done with, no extra setup
+    // is necessary.
+    sendCommand("test_startup();");
+    return;
+  }
+
   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]);
     predictor.learn(uri, null, predictor.LEARN_STARTUP, load_context);
     preconns.push(extract_origin(uri));
   }
 
   var verifier = new Verifier("startup", preconns, []);
   predictor.predict(null, null, predictor.PREDICT_STARTUP, load_context, verifier);
 }
 
-function test_dns() {
-  var toplevel = "http://localhost:4444/index.html";
+const dns_toplevel = newURI("http://localhost:4444/index.html");
+
+function continue_test_dns() {
   var subresource = "http://localhost:4443/jquery.js";
 
-  var tluri = newURI(toplevel);
-  open_and_continue([tluri], function () {
-    predictor.learn(tluri, null, predictor.LEARN_LOAD_TOPLEVEL, load_context);
-    var sruri = newURI(subresource);
-    predictor.learn(sruri, tluri, predictor.LEARN_LOAD_SUBRESOURCE, load_context);
+  predictor.learn(dns_toplevel, null, predictor.LEARN_LOAD_TOPLEVEL, load_context);
+  var sruri = newURI(subresource);
+  predictor.learn(sruri, dns_toplevel, predictor.LEARN_LOAD_SUBRESOURCE, load_context);
+
+  var preresolves = [extract_origin(sruri)];
+  var verifier = new Verifier("dns", [], preresolves);
+  predictor.predict(dns_toplevel, null, predictor.PREDICT_LOAD, load_context, verifier);
+}
 
+function test_dns() {
+  open_and_continue([dns_toplevel], function () {
     // Ensure that this will do preresolves
-    prefs.setIntPref("network.predictor.preconnect-min-confidence", 101);
-
-    var preresolves = [extract_origin(sruri)];
-    var verifier = new Verifier("dns", [], preresolves);
-    predictor.predict(tluri, null, predictor.PREDICT_LOAD, load_context, verifier);
+    Services.prefs.setIntPref("network.predictor.preconnect-min-confidence", 101);
+    if (running_single_process) {
+      continue_test_dns();
+    } else {
+      sendCommand("continue_test_dns();");
+    }
   });
 }
 
-function test_origin() {
-  var toplevel = "http://localhost:4444/index.html";
+const origin_toplevel = newURI("http://localhost:4444/index.html");
+
+function continue_test_origin() {
   var subresources = [
     "http://localhost:4444/style.css",
     "http://localhost:4443/jquery.js",
     "http://localhost:4444/image.png"
   ];
+  predictor.learn(origin_toplevel, null, predictor.LEARN_LOAD_TOPLEVEL, load_context);
+  var preconns = [];
+  for (var i = 0; i < subresources.length; i++) {
+    var sruri = newURI(subresources[i]);
+    predictor.learn(sruri, origin_toplevel, predictor.LEARN_LOAD_SUBRESOURCE, load_context);
+    var origin = extract_origin(sruri);
+    if (preconns.indexOf(origin) === -1) {
+      preconns.push(origin);
+    }
+  }
 
-  var tluri = newURI(toplevel);
-  open_and_continue([tluri], function () {
-    predictor.learn(tluri, null, predictor.LEARN_LOAD_TOPLEVEL, load_context);
-    var preconns = [];
-    for (var i = 0; i < subresources.length; i++) {
-      var sruri = newURI(subresources[i]);
-      predictor.learn(sruri, tluri, predictor.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, []);
+  predictor.predict(loaduri, null, predictor.PREDICT_LOAD, load_context, verifier);
+}
+
+function test_origin() {
+  open_and_continue([origin_toplevel], function () {
+    if (running_single_process) {
+      continue_test_origin();
+    } else {
+      sendCommand("continue_test_origin();");
     }
-
-    var loaduri = newURI("http://localhost:4444/anotherpage.html");
-    var verifier = new Verifier("origin", preconns, []);
-    predictor.predict(loaduri, null, predictor.PREDICT_LOAD, load_context, verifier);
   });
 }
 
-var prefs;
-
 function cleanup() {
   observer.cleaningUp = true;
-  predictor.reset();
+  reset_predictor();
 }
 
 var tests = [
   // This must ALWAYS come first, to ensure a clean slate
   reset_predictor,
   test_link_hover,
   test_pageload,
   // TODO: These are disabled until the features are re-written
@@ -323,44 +369,52 @@ var observer = {
 
     throw Cr.NS_ERROR_NO_INTERFACE;
   },
 
   observe: function (subject, topic, data) {
     if (topic != "predictor-reset-complete") {
       return;
     }
+
     if (this.cleaningUp) {
       unregisterObserver();
     }
+
     run_next_test();
   }
 };
 
 function registerObserver() {
-  var svc = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
-  svc.addObserver(observer, "predictor-reset-complete", false);
+  Services.obs.addObserver(observer, "predictor-reset-complete", false);
 }
 
 function unregisterObserver() {
-  var svc = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
-  svc.removeObserver(observer, "predictor-reset-complete");
+  Services.obs.removeObserver(observer, "predictor-reset-complete");
+}
+
+function run_test_real() {
+  tests.forEach(add_test);
+  do_get_profile()
+  predictor = Cc["@mozilla.org/network/predictor;1"].getService(Ci.nsINetworkPredictor);
+
+  registerObserver();
+
+  Services.prefs.setBoolPref("network.predictor.enabled", true);
+  Services.prefs.setBoolPref("network.predictor.cleaned-up", true);
+  Services.prefs.setBoolPref("browser.cache.use_new_backend_temp", true);
+  Services.prefs.setIntPref("browser.cache.use_new_backend", 1);
+  do_register_cleanup(() => {
+    Services.prefs.clearUserPref("network.predictor.preconnect-min-confidence");
+    Services.prefs.clearUserPref("network.predictor.enabled");
+    Services.prefs.clearUserPref("network.predictor.cleaned-up");
+    Services.prefs.clearUserPref("browser.cache.use_new_backend_temp");
+    Services.prefs.clearUserPref("browser.cache.use_new_backend");
+  });
+
+  run_next_test();
 }
 
 function run_test() {
-  tests.forEach(add_test);
-  profile = do_get_profile();
-  prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
-  prefs.setBoolPref("network.predictor.enabled", true);
-  prefs.setBoolPref("network.predictor.cleaned-up", true);
-  prefs.setBoolPref("browser.cache.use_new_backend_temp", true);
-  prefs.setIntPref("browser.cache.use_new_backend", 1);
-  do_register_cleanup(() => {
-      prefs.clearUserPref("network.predictor.preconnect-min-confidence");
-      prefs.clearUserPref("network.predictor.enabled");
-      prefs.clearUserPref("network.predictor.cleaned-up");
-      prefs.clearUserPref("browser.cache.use_new_backend_temp");
-      prefs.clearUserPref("browser.cache.use_new_backend");
-  });
-  predictor = Cc["@mozilla.org/network/predictor;1"].getService(Ci.nsINetworkPredictor);
-  registerObserver();
-  run_next_test();
+  // This indirection is necessary to make e10s tests work as expected
+  running_single_process = true;
+  run_test_real();
 }
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit_ipc/test_predictor_wrap.js
@@ -0,0 +1,12 @@
+function run_test() {
+  var test_path = do_get_file("../unit/test_predictor.js").path.replace(/\\/g, "/");
+  load(test_path);
+  do_load_child_test_harness();
+  do_test_pending();
+  sendCommand("load(\"" + test_path + "\");", function () {
+    sendCommand("predictor = Cc[\"@mozilla.org/network/predictor;1\"].getService(Ci.nsINetworkPredictor);", function() {
+      run_test_real();
+      do_test_finished();
+    });
+  });
+}
--- a/netwerk/test/unit_ipc/xpcshell.ini
+++ b/netwerk/test/unit_ipc/xpcshell.ini
@@ -15,16 +15,17 @@ support-files = child_app_offline.js
 [test_dns_per_interface_wrap.js]
 [test_dns_service_wrap.js]
 [test_duplicate_headers_wrap.js]
 [test_event_sink_wrap.js]
 [test_head_wrap.js]
 [test_headers_wrap.js]
 [test_httpsuspend_wrap.js]
 [test_post_wrap.js]
+[test_predictor_wrap.js]
 [test_progress_wrap.js]
 [test_redirect-caching_canceled_wrap.js]
 [test_redirect-caching_failure_wrap.js]
 [test_redirect-caching_passing_wrap.js]
 [test_redirect_canceled_wrap.js]
 [test_redirect_failure_wrap.js]
 # Do not test the channel.redirectTo() API under e10s until 827269 is resolved
 [test_redirect_from_script_wrap.js]