Merge inbound to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 06 Mar 2015 16:18:04 -0500
changeset 232306 43fb1f92e8d41f1dc086b336532d9fa5720b6475
parent 232305 dacbd4fbf0ee03d12771aac3836de7cc9f8a8c21 (current diff)
parent 232241 1e4b7691802162848f7d249a7efc3a7bc5f18c23 (diff)
child 232307 5133b5b2e4a29d8f2a5ba7e65c9be1c24a49a139
child 232422 ab1e1648529541eb8a69018a7720db8e8d1a1ba8
child 232431 716b424d27c04ea82c76e71122b02f045bc71290
child 232442 8a1a30d98101f95736d9e53bf4854351b48d57de
push id56503
push userryanvm@gmail.com
push dateFri, 06 Mar 2015 21:24:01 +0000
treeherdermozilla-inbound@43fb1f92e8d4 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone39.0a1
first release with
nightly linux32
43fb1f92e8d4 / 39.0a1 / 20150307030233 / files
nightly linux64
43fb1f92e8d4 / 39.0a1 / 20150307030233 / files
nightly mac
43fb1f92e8d4 / 39.0a1 / 20150307030233 / files
nightly win32
43fb1f92e8d4 / 39.0a1 / 20150307030233 / files
nightly win64
43fb1f92e8d4 / 39.0a1 / 20150307030233 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to m-c. a=merge CLOSED TREE
security/manager/ssl/tests/unit/test_keysize/generate.py
testing/web-platform/meta/DOMEvents/tests/submissions/Microsoft/converted/dispatchEvent.DISPATCH_REQUEST_ERR.html.ini
testing/web-platform/meta/DOMEvents/tests/submissions/Microsoft/converted/dispatchEvent.NOT_SUPPORTED_ERR.html.ini
testing/web-platform/meta/DOMEvents/tests/submissions/Microsoft/converted/dispatchEvent.UNSPECIFIED_EVENT_TYPE_ERR.html.ini
testing/web-platform/meta/IndexedDB/interfaces.worker.ini
testing/web-platform/meta/WebIDL/ecmascript-binding/es-exceptions/constructor-object.worker.ini
testing/web-platform/meta/workers/interfaces.worker.ini
testing/web-platform/meta/workers/interfaces/DedicatedWorkerGlobalScope/EventTarget.worker.ini
testing/web-platform/meta/workers/semantics/interface-objects/001.worker.ini
testing/web-platform/tests/DOMEvents/tests/approved/Determined.candidate.EventListeners.html
testing/web-platform/tests/DOMEvents/tests/approved/Propagation.path.target.moved.html
testing/web-platform/tests/DOMEvents/tests/approved/Propagation.path.target.removed.html
testing/web-platform/tests/DOMEvents/tests/submissions/Microsoft/DOMEvents_harness.htm
testing/web-platform/tests/DOMEvents/tests/submissions/Microsoft/converted/Determined.candidate.EventListeners.html
testing/web-platform/tests/DOMEvents/tests/submissions/Microsoft/converted/Propagation.path.target.moved.html
testing/web-platform/tests/DOMEvents/tests/submissions/Microsoft/converted/Propagation.path.target.removed.html
testing/web-platform/tests/DOMEvents/tests/submissions/Microsoft/converted/dispatchEvent.DISPATCH_REQUEST_ERR.html
testing/web-platform/tests/DOMEvents/tests/submissions/Microsoft/converted/dispatchEvent.NOT_SUPPORTED_ERR.html
testing/web-platform/tests/DOMEvents/tests/submissions/Microsoft/converted/dispatchEvent.UNSPECIFIED_EVENT_TYPE_ERR.html
testing/web-platform/tests/DOMEvents/tests/submissions/Microsoft/dispatchEvent.NOT_SUPPORTED_ERR.html
testing/web-platform/tests/DOMEvents/tests/submissions/Microsoft/support/navigate.js
testing/web-platform/tests/serve.py
testing/web-platform/tests/tools/scripts/__init__.py
testing/web-platform/tests/tools/scripts/_env.py
testing/web-platform/tests/tools/scripts/html5lib_test.xml
testing/web-platform/tests/tools/scripts/html5lib_test_fragment.xml
testing/web-platform/tests/tools/scripts/lint.py
testing/web-platform/tests/tools/scripts/lint.whitelist
testing/web-platform/tests/tools/scripts/manifest.py
testing/web-platform/tests/tools/scripts/update_html5lib_tests.py
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1019,34 +1019,34 @@ pref("apz.asyncscroll.throttle", 40);
 pref("apz.pan_repaint_interval", 16);
 
 // APZ physics settings, tuned by UX designers
 pref("apz.fling_curve_function_x1", "0.41");
 pref("apz.fling_curve_function_y1", "0.0");
 pref("apz.fling_curve_function_x2", "0.80");
 pref("apz.fling_curve_function_y2", "1.0");
 pref("apz.fling_curve_threshold_inches_per_ms", "0.01");
-pref("apz.fling_friction", "0.00238");
+pref("apz.fling_friction", "0.0019");
 pref("apz.max_velocity_inches_per_ms", "0.07");
 
 // Tweak default displayport values to reduce the risk of running out of
 // memory when zooming in
 pref("apz.x_skate_size_multiplier", "1.25");
 pref("apz.y_skate_size_multiplier", "1.5");
 pref("apz.x_stationary_size_multiplier", "1.5");
 pref("apz.y_stationary_size_multiplier", "1.8");
 pref("apz.enlarge_displayport_when_clipped", true);
 // Use "sticky" axis locking
 pref("apz.axis_lock.mode", 2);
 
 // Overscroll-related settings
 pref("apz.overscroll.enabled", true);
-pref("apz.overscroll.stretch_factor", "0.15");
-pref("apz.overscroll.spring_stiffness", "0.002");
-pref("apz.overscroll.spring_friction", "0.02");
+pref("apz.overscroll.stretch_factor", "0.35");
+pref("apz.overscroll.spring_stiffness", "0.0018");
+pref("apz.overscroll.spring_friction", "0.015");
 pref("apz.overscroll.stop_distance_threshold", "5.0");
 pref("apz.overscroll.stop_velocity_threshold", "0.01");
 
 // For event-regions based hit-testing
 pref("layout.event-regions.enabled", true);
 
 // This preference allows FirefoxOS apps (and content, I think) to force
 // the use of software (instead of hardware accelerated) 2D canvases by
--- a/config/external/nss/nss.def
+++ b/config/external/nss/nss.def
@@ -520,17 +520,16 @@ SECKEY_DestroyPublicKey
 SECKEY_DestroySubjectPublicKeyInfo
 SECKEY_ECParamsToBasePointOrderLen
 SECKEY_ECParamsToKeySize
 SECKEY_EncodeDERSubjectPublicKeyInfo
 SECKEY_ExtractPublicKey
 SECKEY_GetPublicKeyType
 SECKEY_ImportDERPublicKey
 SECKEY_PublicKeyStrength
-SECKEY_PublicKeyStrengthInBits
 SECKEY_RSAPSSParamsTemplate DATA
 SECKEY_SignatureLen
 SECMIME_DecryptionAllowed
 SECMOD_AddNewModule
 SECMOD_AddNewModuleEx
 SECMOD_CancelWait
 SECMOD_CanDeleteInternalModule
 SECMOD_CloseUserDB
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -853,17 +853,17 @@ Console::Assert(JSContext* aCx, bool aCo
   if (!aCondition) {
     Method(aCx, MethodAssert, NS_LITERAL_STRING("assert"), aData);
   }
 }
 
 METHOD(Count, "count")
 
 void
-Console::__noSuchMethod__()
+Console::NoopMethod()
 {
   // Nothing to do.
 }
 
 static
 nsresult
 StackFrameToStackEntry(nsIStackFrame* aStackFrame,
                        ConsoleStackEntry& aStackEntry,
--- a/dom/base/Console.h
+++ b/dom/base/Console.h
@@ -96,17 +96,17 @@ public:
 
   void
   Assert(JSContext* aCx, bool aCondition, const Sequence<JS::Value>& aData);
 
   void
   Count(JSContext* aCx, const Sequence<JS::Value>& aData);
 
   void
-  __noSuchMethod__();
+  NoopMethod();
 
 private:
   enum MethodName
   {
     MethodLog,
     MethodInfo,
     MethodWarn,
     MethodError,
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -665,16 +665,36 @@ public:
   void MaybeForgiveSpamCount();
   bool IsClosedOrClosing() {
     return (mIsClosed ||
             mInClose ||
             mHavePendingClose ||
             mCleanedUp);
   }
 
+  bool
+  HadOriginalOpener() const
+  {
+    MOZ_ASSERT(IsOuterWindow());
+    return mHadOriginalOpener;
+  }
+
+  bool
+  IsTopLevelWindow()
+  {
+    MOZ_ASSERT(IsOuterWindow());
+    nsCOMPtr<nsIDOMWindow> parentWindow;
+    nsresult rv = GetScriptableTop(getter_AddRefs(parentWindow));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return false;
+    }
+
+    return parentWindow == static_cast<nsIDOMWindow*>(this);
+  }
+
   virtual void
   FirePopupBlockedEvent(nsIDocument* aDoc,
                         nsIURI* aPopupURI,
                         const nsAString& aPopupWindowName,
                         const nsAString& aPopupWindowFeatures) MOZ_OVERRIDE;
 
   virtual uint32_t GetSerial() MOZ_OVERRIDE {
     return mSerial;
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1778,18 +1778,18 @@ ReparentWrapper(JSContext* aCx, JS::Hand
 
   // Expandos from other compartments are attached to the target JS object.
   // Copy them over, and let the old ones die a natural death.
 
   // Note that at this point the DOM_OBJECT_SLOT for |newobj| has not been set.
   // CloneExpandoChain() will use this property of |newobj| when it calls
   // preserveWrapper() via attachExpandoObject() if |aObj| has expandos set, and
   // preserveWrapper() will not do anything in this case.  This is safe because
-  // if expandos are present then the wrapper will already already have been
-  // preserved called for this native.
+  // if expandos are present then the wrapper will already have been preserved
+  // for this native.
   if (!xpc::XrayUtils::CloneExpandoChain(aCx, newobj, aObj)) {
     return NS_ERROR_FAILURE;
   }
 
   // We've set up |newobj|, so we make it own the native by setting its reserved
   // slot and nulling out the reserved slot of |obj|.
   //
   // NB: It's important to do this _after_ copying the properties to
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1265,16 +1265,21 @@ DOMInterfaces = {
 'VTTCue': {
     'nativeType': 'mozilla::dom::TextTrackCue'
 },
 
 'VTTRegion': {
   'nativeType': 'mozilla::dom::TextTrackRegion',
 },
 
+'WindowClient': {
+    'nativeType': 'mozilla::dom::workers::ServiceWorkerWindowClient',
+    'headerFile': 'mozilla/dom/workers/bindings/ServiceWorkerWindowClient.h',
+},
+
 'WebGLActiveInfo': {
     'nativeType': 'mozilla::WebGLActiveInfo',
     'headerFile': 'WebGLActiveInfo.h'
 },
 
 'WebGLBuffer': {
     'nativeType': 'mozilla::WebGLBuffer',
     'headerFile': 'WebGLBuffer.h'
--- a/dom/cache/DBSchema.cpp
+++ b/dom/cache/DBSchema.cpp
@@ -334,17 +334,17 @@ DBSchema::CachePut(mozIStorageConnection
                    const nsID* aRequestBodyId,
                    const PCacheResponse& aResponse,
                    const nsID* aResponseBodyId,
                    nsTArray<nsID>& aDeletedBodyIdListOut)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aConn);
 
-  PCacheQueryParams params(false, false, false, false, false,
+  PCacheQueryParams params(false, false, false, false,
                            NS_LITERAL_STRING(""));
   nsAutoTArray<EntryId, 256> matches;
   nsresult rv = QueryCache(aConn, aCacheId, aRequest, params, matches);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = DeleteEntries(aConn, matches, aDeletedBodyIdListOut);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
@@ -670,39 +670,25 @@ DBSchema::QueryCache(mozIStorageConnecti
   if (aParams.ignoreSearch()) {
     urlToMatch = aRequest.urlWithoutQuery();
     query.AppendLiteral("request_url_no_query");
   } else {
     urlToMatch = aRequest.url();
     query.AppendLiteral("request_url");
   }
 
-  if (aParams.prefixMatch()) {
-    query.AppendLiteral(" LIKE ?2 ESCAPE '\\'");
-  } else {
-    query.AppendLiteral("=?2");
-  }
-
-  query.AppendLiteral(" GROUP BY entries.id ORDER BY entries.id;");
+  query.AppendLiteral("=?2 GROUP BY entries.id ORDER BY entries.id;");
 
   nsCOMPtr<mozIStorageStatement> state;
   nsresult rv = aConn->CreateStatement(query, getter_AddRefs(state));
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   rv = state->BindInt32Parameter(0, aCacheId);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
-  if (aParams.prefixMatch()) {
-    nsAutoString escapedUrlToMatch;
-    rv = state->EscapeStringForLIKE(urlToMatch, '\\', escapedUrlToMatch);
-    if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
-    urlToMatch = escapedUrlToMatch;
-    urlToMatch.AppendLiteral("%");
-  }
-
   rv = state->BindStringParameter(1, urlToMatch);
   if (NS_WARN_IF(NS_FAILED(rv))) { return rv; }
 
   bool hasMoreData = false;
   while (NS_SUCCEEDED(state->ExecuteStep(&hasMoreData)) && hasMoreData) {
     // no invalid EntryId, init to least likely real value
     EntryId entryId = INT32_MAX;
     rv = state->GetInt32(0, &entryId);
--- a/dom/cache/PCacheTypes.ipdlh
+++ b/dom/cache/PCacheTypes.ipdlh
@@ -17,17 +17,16 @@ namespace mozilla {
 namespace dom {
 namespace cache {
 
 struct PCacheQueryParams
 {
   bool ignoreSearch;
   bool ignoreMethod;
   bool ignoreVary;
-  bool prefixMatch;
   bool cacheNameSet;
   nsString cacheName;
 };
 
 struct PCacheReadStream
 {
   nsID id;
   OptionalInputStreamParams params;
--- a/dom/cache/TypeUtils.cpp
+++ b/dom/cache/TypeUtils.cpp
@@ -261,17 +261,16 @@ TypeUtils::ToPCacheResponse(PCacheRespon
 // static
 void
 TypeUtils::ToPCacheQueryParams(PCacheQueryParams& aOut,
                                const CacheQueryOptions& aIn)
 {
   aOut.ignoreSearch() = aIn.mIgnoreSearch;
   aOut.ignoreMethod() = aIn.mIgnoreMethod;
   aOut.ignoreVary() = aIn.mIgnoreVary;
-  aOut.prefixMatch() = aIn.mPrefixMatch;
   aOut.cacheNameSet() = aIn.mCacheName.WasPassed();
   if (aOut.cacheNameSet()) {
     aOut.cacheName() = aIn.mCacheName.Value();
   } else {
     aOut.cacheName() = NS_LITERAL_STRING("");
   }
 }
 
--- a/dom/media/webaudio/test/test_mediaElementAudioSourceNodeCrossOrigin.html
+++ b/dom/media/webaudio/test/test_mediaElementAudioSourceNodeCrossOrigin.html
@@ -36,17 +36,17 @@ tests.forEach(function(e) {
   if (e.cors) {
     a.crossOrigin = e.cors;
   }
   a.src = e.url;
   a.controls = true;
   var measn = e.ac.createMediaElementSource(a);
   var sp = e.ac.createScriptProcessor(2048, 1);
   // Set a couple expandos to track the status of the test
-  sp.iterationsLeft = 20;
+  sp.iterationsLeft = 200;
   sp.seenSound = false;
 
   measn.connect(sp);
   a.play();
   document.body.appendChild(a);
 
   function checkFinished(sp) {
     if (--sp.iterationsLeft == 0) {
--- a/dom/svg/SVGContentUtils.cpp
+++ b/dom/svg/SVGContentUtils.cpp
@@ -71,16 +71,17 @@ enum DashState {
 static DashState
 GetStrokeDashData(SVGContentUtils::AutoStrokeOptions* aStrokeOptions,
                   nsSVGElement* aElement,
                   const nsStyleSVG* aStyleSVG,
                   gfxTextContextPaint *aContextPaint)
 {
   size_t dashArrayLength;
   Float totalLengthOfDashes = 0.0, totalLengthOfGaps = 0.0;
+  Float pathScale = 1.0;
 
   if (aContextPaint && aStyleSVG->mStrokeDasharrayFromObject) {
     const FallibleTArray<gfxFloat>& dashSrc = aContextPaint->GetStrokeDashArray();
     dashArrayLength = dashSrc.Length();
     if (dashArrayLength <= 0) {
       return eContinuousStroke;
     }
     Float* dashPattern = aStrokeOptions->InitDashPattern(dashArrayLength);
@@ -95,17 +96,16 @@ GetStrokeDashData(SVGContentUtils::AutoS
       (i % 2 ? totalLengthOfGaps : totalLengthOfDashes) += dashSrc[i];
     }
   } else {
     const nsStyleCoord *dasharray = aStyleSVG->mStrokeDasharray;
     dashArrayLength = aStyleSVG->mStrokeDasharrayLength;
     if (dashArrayLength <= 0) {
       return eContinuousStroke;
     }
-    Float pathScale = 1.0;
     if (aElement->IsSVGElement(nsGkAtoms::path)) {
       pathScale = static_cast<SVGPathElement*>(aElement)->
         GetPathLengthScale(SVGPathElement::eForStroking);
       if (pathScale <= 0) {
         return eContinuousStroke;
       }
     }
     Float* dashPattern = aStrokeOptions->InitDashPattern(dashArrayLength);
@@ -156,17 +156,18 @@ GetStrokeDashData(SVGContentUtils::AutoS
       aStyleSVG->mStrokeLinecap == NS_STYLE_STROKE_LINECAP_BUTT) {
     return eNoStroke;
   }
 
   if (aContextPaint && aStyleSVG->mStrokeDashoffsetFromObject) {
     aStrokeOptions->mDashOffset = Float(aContextPaint->GetStrokeDashOffset());
   } else {
     aStrokeOptions->mDashOffset =
-      SVGContentUtils::CoordToFloat(aElement, aStyleSVG->mStrokeDashoffset);
+      SVGContentUtils::CoordToFloat(aElement, aStyleSVG->mStrokeDashoffset) *
+      pathScale;
   }
 
   return eDashedStroke;
 }
 
 void
 SVGContentUtils::GetStrokeOptions(AutoStrokeOptions* aStrokeOptions,
                                   nsSVGElement* aElement,
--- a/dom/tests/mochitest/general/test_consoleAPI.html
+++ b/dom/tests/mochitest/general/test_consoleAPI.html
@@ -33,17 +33,22 @@ function doTest() {
     "groupEnd": "function",
     "time": "function",
     "timeEnd": "function",
     "profile": "function",
     "profileEnd": "function",
     "assert": "function",
     "count": "function",
     "table": "function",
-    "__noSuchMethod__": "function"
+    "clear": "function",
+    "dirxml": "function",
+    "markTimeline": "function",
+    "timeline": "function",
+    "timelineEnd": "function",
+    "timeStamp": "function",
   };
 
   var foundProps = 0;
   for (var prop in console) {
     foundProps++;
     is(typeof(console[prop]), expectedProps[prop], "expect console prop " + prop + " exists");
   }
   is(foundProps, Object.keys(expectedProps).length, "found correct number of properties");
--- a/dom/webidl/Cache.webidl
+++ b/dom/webidl/Cache.webidl
@@ -28,17 +28,16 @@ Promise<boolean> delete(RequestInfo requ
 [Throws]
 Promise<sequence<Request>> keys(optional RequestInfo request, optional CacheQueryOptions options);
 };
 
 dictionary CacheQueryOptions {
 boolean ignoreSearch = false;
 boolean ignoreMethod = false;
 boolean ignoreVary = false;
-boolean prefixMatch = false;
 DOMString cacheName;
 };
 
 dictionary CacheBatchOperation {
 DOMString type;
 Request request;
 Response response;
 CacheQueryOptions options;
--- a/dom/webidl/Client.webidl
+++ b/dom/webidl/Client.webidl
@@ -5,13 +5,28 @@
  *
  * The origin of this IDL file is
  * http://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html
  *
  */
 
 [Exposed=ServiceWorker]
 interface Client {
-  readonly attribute unsigned long id;
+  readonly attribute USVString url;
 
   [Throws]
   void postMessage(any message, optional sequence<Transferable> transfer);
 };
+
+[Exposed=ServiceWorker]
+interface WindowClient : Client {
+  readonly attribute VisibilityState visibilityState;
+  readonly attribute boolean focused;
+  readonly attribute FrameType frameType;
+  Promise<WindowClient> focus();
+};
+
+enum FrameType {
+  "auxiliary",
+  "top-level",
+  "nested",
+  "none"
+};
--- a/dom/webidl/Clients.webidl
+++ b/dom/webidl/Clients.webidl
@@ -5,20 +5,21 @@
  *
  * The origin of this IDL file is
  * http://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html
  *
  */
 
 [Exposed=ServiceWorker]
 interface Clients {
-  // A list of client objects, identifiable by ID, that correspond to windows
-  // (or workers) that are "controlled" by this SW
+  // The objects returned will be new instances every time
   [Throws]
   Promise<sequence<Client>?> matchAll(optional ClientQueryOptions options);
+  Promise<WindowClient> openWindow(USVString url);
+  Promise<void> claim();
 };
 
 dictionary ClientQueryOptions {
   boolean includeUncontrolled = false;
   ClientType type = "window";
 };
 
 enum ClientType {
--- a/dom/webidl/Console.webidl
+++ b/dom/webidl/Console.webidl
@@ -23,17 +23,29 @@ interface Console {
   void timeEnd(optional any time);
 
   void profile(any... data);
   void profileEnd(any... data);
 
   void assert(boolean condition, any... data);
   void count(any... data);
 
-  void ___noSuchMethod__();
+  // No-op methods for compatibility with other browsers.
+  [BinaryName="noopMethod"]
+  void clear();
+  [BinaryName="noopMethod"]
+  void dirxml();
+  [BinaryName="noopMethod"]
+  void markTimeline();
+  [BinaryName="noopMethod"]
+  void timeline();
+  [BinaryName="noopMethod"]
+  void timelineEnd();
+  [BinaryName="noopMethod"]
+  void timeStamp();
 };
 
 // This is used to propagate console events to the observers.
 dictionary ConsoleEvent {
   (unsigned long or DOMString) ID;
   (unsigned long or DOMString) innerID;
   DOMString level = "";
   DOMString filename = "";
--- a/dom/webidl/FetchEvent.webidl
+++ b/dom/webidl/FetchEvent.webidl
@@ -7,17 +7,19 @@
  * http://slightlyoff.github.io/ServiceWorker/spec/service_worker/index.html
  */
 
 [Constructor(DOMString type, optional FetchEventInit eventInitDict),
  Func="mozilla::dom::workers::ServiceWorkerVisible",
  Exposed=(ServiceWorker)]
 interface FetchEvent : Event {
   readonly attribute Request request;
-  readonly attribute Client client; // The window issuing the request.
+
+  // https://github.com/slightlyoff/ServiceWorker/issues/631
+  readonly attribute Client? client; // The window issuing the request.
   readonly attribute boolean isReload;
 
   [Throws] void respondWith(Promise<Response> r);
   Promise<Response> forwardTo(USVString url);
   Promise<Response> default();
 };
 
 dictionary FetchEventInit : EventInit {
--- a/dom/webidl/ServiceWorkerContainer.webidl
+++ b/dom/webidl/ServiceWorkerContainer.webidl
@@ -33,20 +33,14 @@ interface ServiceWorkerContainer : Event
   attribute EventHandler onreloadpage;
   attribute EventHandler onerror;
   attribute EventHandler onmessage;
 };
 
 // Testing only.
 partial interface ServiceWorkerContainer {
   [Throws,Pref="dom.serviceWorkers.testing.enabled"]
-  Promise<any> clearAllServiceWorkerData();
-
-  [Throws,Pref="dom.serviceWorkers.testing.enabled"]
   DOMString getScopeForUrl(DOMString url);
-
-  [Throws,Pref="dom.serviceWorkers.testing.enabled"]
-  DOMString getControllingWorkerScriptURLForPath(DOMString path);
 };
 
 dictionary RegistrationOptionList {
   USVString scope = "/";
 };
--- a/dom/webidl/ServiceWorkerGlobalScope.webidl
+++ b/dom/webidl/ServiceWorkerGlobalScope.webidl
@@ -8,36 +8,26 @@
  *
  * You are granted a license to use, reproduce and create derivative works of
  * this document.
  */
 
 [Global=(Worker,ServiceWorker),
  Exposed=ServiceWorker]
 interface ServiceWorkerGlobalScope : WorkerGlobalScope {
-  // FIXME(nsm): Bug 982725
-  // readonly attribute CacheList caches;
-
   readonly attribute Clients clients;
 
-  // FIXME(nsm): Bug 995484
-  // ResponsePromise<any> fetch((Request or [EnsureUTF16] DOMString) request);
-
   void update();
 
   [Throws]
   Promise<boolean> unregister();
 
   attribute EventHandler oninstall;
   attribute EventHandler onactivate;
   attribute EventHandler onfetch;
   attribute EventHandler onbeforeevicted;
   attribute EventHandler onevicted;
 
   // The event.source of these MessageEvents are instances of Client
   attribute EventHandler onmessage;
-
-  // close() method inherited from WorkerGlobalScope is not exposed.
-  // FIXME(nsm): For now, overridden so it can be a no-op.
-  void close();
 };
 
 
--- a/dom/webidl/WorkerGlobalScope.webidl
+++ b/dom/webidl/WorkerGlobalScope.webidl
@@ -16,16 +16,17 @@
 interface WorkerGlobalScope : EventTarget {
   readonly attribute WorkerGlobalScope self;
 
   [Replaceable]
   readonly attribute Console console;
 
   readonly attribute WorkerLocation location;
 
+  [Throws]
   void close();
   attribute OnErrorEventHandler onerror;
 
   attribute EventHandler onoffline;
   attribute EventHandler ononline;
   // also has additional members in a partial interface
 };
 
--- a/dom/workers/ServiceWorkerClient.cpp
+++ b/dom/workers/ServiceWorkerClient.cpp
@@ -4,34 +4,58 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
 #include "ServiceWorkerClient.h"
 #include "ServiceWorkerContainer.h"
 
 #include "mozilla/dom/MessageEvent.h"
 #include "nsGlobalWindow.h"
+#include "nsIDocument.h"
 #include "WorkerPrivate.h"
 
-#include "mozilla/dom/ClientBinding.h"
-
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::dom::workers;
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ServiceWorkerClient, mOwner)
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(ServiceWorkerClient)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(ServiceWorkerClient)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerClient)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
+ServiceWorkerClientInfo::ServiceWorkerClientInfo(nsIDocument* aDoc)
+{
+  MOZ_ASSERT(aDoc);
+  MOZ_ASSERT(aDoc->GetWindow());
+
+  nsRefPtr<nsGlobalWindow> outerWindow = static_cast<nsGlobalWindow*>(aDoc->GetWindow());
+  mClientId = outerWindow->WindowID();
+  aDoc->GetURL(mUrl);
+  mVisibilityState = aDoc->VisibilityState();
+
+  ErrorResult result;
+  mFocused = aDoc->HasFocus(result);
+  if (result.Failed()) {
+    NS_WARNING("Failed to get focus information.");
+  }
+
+  if (!outerWindow->IsTopLevelWindow()) {
+    mFrameType = FrameType::Nested;
+  } else if (outerWindow->HadOriginalOpener()) {
+    mFrameType = FrameType::Auxiliary;
+  } else {
+    mFrameType = FrameType::Top_level;
+  }
+}
+
 JSObject*
 ServiceWorkerClient::WrapObject(JSContext* aCx)
 {
   return ClientBinding::Wrap(aCx, this);
 }
 
 namespace {
 
@@ -50,17 +74,17 @@ public:
   {
     mClonedObjects.SwapElements(aClonedObjects);
   }
 
   NS_IMETHOD
   Run()
   {
     AssertIsOnMainThread();
-    nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mId);
+    nsGlobalWindow* window = nsGlobalWindow::GetOuterWindowWithId(mId);
     if (!window) {
       return NS_ERROR_FAILURE;
     }
 
     ErrorResult result;
     dom::Navigator* navigator = window->GetNavigator(result);
     if (NS_WARN_IF(result.Failed())) {
       return result.ErrorCode();
--- a/dom/workers/ServiceWorkerClient.h
+++ b/dom/workers/ServiceWorkerClient.h
@@ -6,56 +6,87 @@
 
 #ifndef mozilla_dom_workers_serviceworkerclient_h
 #define mozilla_dom_workers_serviceworkerclient_h
 
 #include "nsCOMPtr.h"
 #include "nsWrapperCache.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ClientBinding.h"
 
 namespace mozilla {
 namespace dom {
 namespace workers {
 
-class ServiceWorkerClient MOZ_FINAL : public nsISupports,
-                                      public nsWrapperCache
+class ServiceWorkerClient;
+class ServiceWorkerWindowClient;
+
+// Used as a container object for information needed to create
+// client objects.
+class ServiceWorkerClientInfo MOZ_FINAL
+{
+  friend class ServiceWorkerClient;
+  friend class ServiceWorkerWindowClient;
+
+public:
+  explicit ServiceWorkerClientInfo(nsIDocument* aDoc);
+
+private:
+  uint64_t mClientId;
+  nsString mUrl;
+
+  // Window Clients
+  VisibilityState mVisibilityState;
+  bool mFocused;
+  FrameType mFrameType;
+};
+
+class ServiceWorkerClient : public nsISupports,
+                            public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ServiceWorkerClient)
 
-  ServiceWorkerClient(nsISupports* aOwner, uint64_t aId)
+  ServiceWorkerClient(nsISupports* aOwner,
+                      const ServiceWorkerClientInfo& aClientInfo)
     : mOwner(aOwner),
-      mId(aId)
+      mId(aClientInfo.mClientId),
+      mUrl(aClientInfo.mUrl)
   {
+    MOZ_ASSERT(aOwner);
   }
 
-  uint32_t Id() const
-  {
-    return mId;
-  }
-
-  nsISupports* GetParentObject() const
+  nsISupports*
+  GetParentObject() const
   {
     return mOwner;
   }
 
-  void PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
-                   const Optional<Sequence<JS::Value>>& aTransferable,
-                   ErrorResult& aRv);
+  void
+  GetUrl(nsAString& aUrl) const
+  {
+    aUrl.Assign(mUrl);
+  }
+
+  void
+  PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
+              const Optional<Sequence<JS::Value>>& aTransferable,
+              ErrorResult& aRv);
 
   JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
+protected:
+  virtual ~ServiceWorkerClient()
+  { }
+
 private:
-  ~ServiceWorkerClient()
-  {
-  }
-
   nsCOMPtr<nsISupports> mOwner;
   uint64_t mId;
+  nsString mUrl;
 };
 
 } // namespace workers
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_workers_serviceworkerclient_h
--- a/dom/workers/ServiceWorkerClients.cpp
+++ b/dom/workers/ServiceWorkerClients.cpp
@@ -3,16 +3,17 @@
  * 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/dom/Promise.h"
 
 #include "ServiceWorkerClient.h"
 #include "ServiceWorkerClients.h"
 #include "ServiceWorkerManager.h"
+#include "ServiceWorkerWindowClient.h"
 
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 #include "WorkerScope.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::dom::workers;
@@ -117,43 +118,43 @@ private:
   Mutex mCleanUpLock;
 
   bool mClean;
 };
 
 class ResolvePromiseWorkerRunnable MOZ_FINAL : public WorkerRunnable
 {
   nsRefPtr<PromiseHolder> mPromiseHolder;
-  nsAutoPtr<nsTArray<uint64_t>> mValue;
+  nsTArray<ServiceWorkerClientInfo> mValue;
 
 public:
   ResolvePromiseWorkerRunnable(WorkerPrivate* aWorkerPrivate,
                                PromiseHolder* aPromiseHolder,
-                               nsAutoPtr<nsTArray<uint64_t>>& aValue)
+                               nsTArray<ServiceWorkerClientInfo>& aValue)
     : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount),
-      mPromiseHolder(aPromiseHolder),
-      mValue(aValue)
+      mPromiseHolder(aPromiseHolder)
   {
     AssertIsOnMainThread();
+    mValue.SwapElements(aValue);
   }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
   {
     MOZ_ASSERT(aWorkerPrivate);
     aWorkerPrivate->AssertIsOnWorkerThread();
 
     Promise* promise = mPromiseHolder->GetPromise();
     MOZ_ASSERT(promise);
 
     nsTArray<nsRefPtr<ServiceWorkerClient>> ret;
-    for (size_t i = 0; i < mValue->Length(); i++) {
+    for (size_t i = 0; i < mValue.Length(); i++) {
       ret.AppendElement(nsRefPtr<ServiceWorkerClient>(
-            new ServiceWorkerClient(promise->GetParentObject(),
-                                    mValue->ElementAt(i))));
+            new ServiceWorkerWindowClient(promise->GetParentObject(),
+                                          mValue.ElementAt(i))));
     }
     promise->MaybeResolve(ret);
 
     // release the reference on the worker thread.
     mPromiseHolder->Clean();
 
     return true;
   }
@@ -211,17 +212,17 @@ public:
 
     MutexAutoLock lock(mPromiseHolder->mCleanUpLock);
     if (mPromiseHolder->mClean) {
       // Don't resolve the promise if it was already released.
       return NS_OK;
     }
 
     nsRefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
-    nsAutoPtr<nsTArray<uint64_t>> result(new nsTArray<uint64_t>());
+    nsTArray<ServiceWorkerClientInfo> result;
 
     swm->GetAllClients(mScope, result);
     nsRefPtr<ResolvePromiseWorkerRunnable> r =
       new ResolvePromiseWorkerRunnable(mWorkerPrivate, mPromiseHolder, result);
 
     AutoSafeJSContext cx;
     if (r->Dispatch(cx)) {
       return NS_OK;
@@ -277,8 +278,34 @@ ServiceWorkerClients::MatchAll(const Cli
   nsresult rv = NS_DispatchToMainThread(r);
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
   }
 
   return promise.forget();
 }
+
+already_AddRefed<Promise>
+ServiceWorkerClients::OpenWindow(const nsAString& aUrl)
+{
+  ErrorResult result;
+  nsRefPtr<Promise> promise = Promise::Create(mWorkerScope, result);
+  if (NS_WARN_IF(result.Failed())) {
+    return nullptr;
+  }
+
+  promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+  return promise.forget();
+}
+
+already_AddRefed<Promise>
+ServiceWorkerClients::Claim()
+{
+  ErrorResult result;
+  nsRefPtr<Promise> promise = Promise::Create(mWorkerScope, result);
+  if (NS_WARN_IF(result.Failed())) {
+    return nullptr;
+  }
+
+  promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+  return promise.forget();
+}
--- a/dom/workers/ServiceWorkerClients.h
+++ b/dom/workers/ServiceWorkerClients.h
@@ -26,16 +26,22 @@ public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ServiceWorkerClients)
 
   explicit ServiceWorkerClients(ServiceWorkerGlobalScope* aWorkerScope);
 
   already_AddRefed<Promise>
   MatchAll(const ClientQueryOptions& aOptions, ErrorResult& aRv);
 
+  already_AddRefed<Promise>
+  OpenWindow(const nsAString& aUrl);
+
+  already_AddRefed<Promise>
+  Claim();
+
   JSObject*
   WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   ServiceWorkerGlobalScope*
   GetParentObject() const
   {
     return mWorkerScope;
   }
--- a/dom/workers/ServiceWorkerContainer.cpp
+++ b/dom/workers/ServiceWorkerContainer.cpp
@@ -177,41 +177,24 @@ ServiceWorkerContainer::GetReady(ErrorRe
   nsCOMPtr<nsISupports> promise;
   aRv = swm->GetReadyPromise(GetOwner(), getter_AddRefs(promise));
 
   mReadyPromise = static_cast<Promise*>(promise.get());
   return mReadyPromise;
 }
 
 // Testing only.
-already_AddRefed<Promise>
-ServiceWorkerContainer::ClearAllServiceWorkerData(ErrorResult& aRv)
-{
-  aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
-  return nullptr;
-}
-
-// Testing only.
 void
 ServiceWorkerContainer::GetScopeForUrl(const nsAString& aUrl,
                                        nsString& aScope,
                                        ErrorResult& aRv)
 {
   nsCOMPtr<nsIServiceWorkerManager> swm = mozilla::services::GetServiceWorkerManager();
   if (!swm) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   aRv = swm->GetScopeForUrl(aUrl, aScope);
 }
 
-// Testing only.
-void
-ServiceWorkerContainer::GetControllingWorkerScriptURLForPath(
-                                                        const nsAString& aPath,
-                                                        nsString& aScriptURL,
-                                                        ErrorResult& aRv)
-{
-  aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
-}
 } // namespace dom
 } // namespace mozilla
--- a/dom/workers/ServiceWorkerContainer.h
+++ b/dom/workers/ServiceWorkerContainer.h
@@ -52,29 +52,19 @@ public:
 
   already_AddRefed<Promise>
   GetRegistrations(ErrorResult& aRv);
 
   Promise*
   GetReady(ErrorResult& aRv);
 
   // Testing only.
-  already_AddRefed<Promise>
-  ClearAllServiceWorkerData(ErrorResult& aRv);
-
-  // Testing only.
   void
   GetScopeForUrl(const nsAString& aUrl, nsString& aScope, ErrorResult& aRv);
 
-  // Testing only.
-  void
-  GetControllingWorkerScriptURLForPath(const nsAString& aPath,
-                                       nsString& aScriptURL,
-                                       ErrorResult& aRv);
-
   // DOMEventTargetHelper
   void DisconnectFromOwner() MOZ_OVERRIDE;
 
 private:
   ~ServiceWorkerContainer();
 
   void RemoveReadyPromise();
 
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -23,34 +23,33 @@
 #include "mozilla/dom/workers/bindings/ServiceWorker.h"
 
 using namespace mozilla::dom;
 
 BEGIN_WORKERS_NAMESPACE
 
 FetchEvent::FetchEvent(EventTarget* aOwner)
 : Event(aOwner, nullptr, nullptr)
-, mWindowId(0)
 , mIsReload(false)
 , mWaitToRespond(false)
 {
 }
 
 FetchEvent::~FetchEvent()
 {
 }
 
 void
 FetchEvent::PostInit(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
                      nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker,
-                     uint64_t aWindowId)
+                     nsAutoPtr<ServiceWorkerClientInfo>& aClientInfo)
 {
   mChannel = aChannel;
   mServiceWorker = aServiceWorker;
-  mWindowId = aWindowId;
+  mClientInfo = aClientInfo;
 }
 
 /*static*/ already_AddRefed<FetchEvent>
 FetchEvent::Constructor(const GlobalObject& aGlobal,
                         const nsAString& aType,
                         const FetchEventInit& aOptions,
                         ErrorResult& aRv)
 {
@@ -239,20 +238,24 @@ FetchEvent::RespondWith(Promise& aPromis
   }
 
   mWaitToRespond = true;
   nsRefPtr<RespondWithHandler> handler = new RespondWithHandler(mChannel, mServiceWorker);
   aPromise.AppendNativeHandler(handler);
 }
 
 already_AddRefed<ServiceWorkerClient>
-FetchEvent::Client()
+FetchEvent::GetClient()
 {
   if (!mClient) {
-    mClient = new ServiceWorkerClient(GetParentObject(), mWindowId);
+    if (!mClientInfo) {
+      return nullptr;
+    }
+
+    mClient = new ServiceWorkerClient(GetParentObject(), *mClientInfo);
   }
   nsRefPtr<ServiceWorkerClient> client = mClient;
   return client.forget();
 }
 
 already_AddRefed<Promise>
 FetchEvent::ForwardTo(const nsAString& aUrl)
 {
--- a/dom/workers/ServiceWorkerEvents.h
+++ b/dom/workers/ServiceWorkerEvents.h
@@ -27,17 +27,17 @@ class ServiceWorker;
 class ServiceWorkerClient;
 
 class FetchEvent MOZ_FINAL : public Event
 {
   nsMainThreadPtrHandle<nsIInterceptedChannel> mChannel;
   nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
   nsRefPtr<ServiceWorkerClient> mClient;
   nsRefPtr<Request> mRequest;
-  uint64_t mWindowId;
+  nsAutoPtr<ServiceWorkerClientInfo> mClientInfo;
   bool mIsReload;
   bool mWaitToRespond;
 protected:
   explicit FetchEvent(EventTarget* aOwner);
   ~FetchEvent();
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
@@ -46,17 +46,17 @@ public:
 
   virtual JSObject* WrapObjectInternal(JSContext* aCx) MOZ_OVERRIDE
   {
     return FetchEventBinding::Wrap(aCx, this);
   }
 
   void PostInit(nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
                 nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker,
-                uint64_t aWindowId);
+                nsAutoPtr<ServiceWorkerClientInfo>& aClientInfo);
 
   static already_AddRefed<FetchEvent>
   Constructor(const GlobalObject& aGlobal,
               const nsAString& aType,
               const FetchEventInit& aOptions,
               ErrorResult& aRv);
 
   bool
@@ -67,17 +67,17 @@ public:
 
   Request*
   Request_() const
   {
     return mRequest;
   }
 
   already_AddRefed<ServiceWorkerClient>
-  Client();
+  GetClient();
 
   bool
   IsReload() const
   {
     return mIsReload;
   }
 
   void
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -2083,29 +2083,29 @@ ServiceWorkerManager::GetServiceWorkerFo
 }
 
 class FetchEventRunnable : public WorkerRunnable
                          , public nsIHttpHeaderVisitor {
   nsMainThreadPtrHandle<nsIInterceptedChannel> mInterceptedChannel;
   nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
   nsTArray<nsCString> mHeaderNames;
   nsTArray<nsCString> mHeaderValues;
-  uint64_t mWindowId;
+  nsAutoPtr<ServiceWorkerClientInfo> mClientInfo;
   nsCString mSpec;
   nsCString mMethod;
   bool mIsReload;
 public:
   FetchEventRunnable(WorkerPrivate* aWorkerPrivate,
                      nsMainThreadPtrHandle<nsIInterceptedChannel>& aChannel,
                      nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker,
-                     uint64_t aWindowId)
+                     nsAutoPtr<ServiceWorkerClientInfo>& aClientInfo)
     : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
     , mInterceptedChannel(aChannel)
     , mServiceWorker(aServiceWorker)
-    , mWindowId(aWindowId)
+    , mClientInfo(aClientInfo)
   {
     MOZ_ASSERT(aWorkerPrivate);
   }
 
   NS_DECL_ISUPPORTS_INHERITED
 
   NS_IMETHOD
   VisitHeader(const nsACString& aHeader, const nsACString& aValue) MOZ_OVERRIDE
@@ -2220,17 +2220,17 @@ private:
     init.mCancelable = true;
     init.mIsReload.Construct(mIsReload);
     nsRefPtr<FetchEvent> event =
       FetchEvent::Constructor(globalObj, NS_LITERAL_STRING("fetch"), init, rv);
     if (NS_WARN_IF(rv.Failed())) {
       return false;
     }
 
-    event->PostInit(mInterceptedChannel, mServiceWorker, mWindowId);
+    event->PostInit(mInterceptedChannel, mServiceWorker, mClientInfo);
     event->SetTrusted(true);
 
     nsRefPtr<EventTarget> target = do_QueryObject(aWorkerPrivate->GlobalScope());
     nsresult rv2 = target->DispatchDOMEvent(nullptr, event, nullptr, nullptr);
     if (NS_WARN_IF(NS_FAILED(rv2)) || !event->WaitToRespond()) {
       nsCOMPtr<nsIRunnable> runnable = new ResumeRequest(mInterceptedChannel);
       MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(runnable)));
     }
@@ -2245,19 +2245,22 @@ ServiceWorkerManager::DispatchFetchEvent
 {
   MOZ_ASSERT(aChannel);
   nsCOMPtr<nsISupports> serviceWorker;
 
   bool isNavigation = false;
   nsresult rv = aChannel->GetIsNavigation(&isNavigation);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  nsAutoPtr<ServiceWorkerClientInfo> clientInfo;
+
   if (!isNavigation) {
     MOZ_ASSERT(aDoc);
     rv = GetDocumentController(aDoc->GetInnerWindow(), getter_AddRefs(serviceWorker));
+    clientInfo = new ServiceWorkerClientInfo(aDoc);
   } else {
     nsCOMPtr<nsIChannel> internalChannel;
     rv = aChannel->GetChannel(getter_AddRefs(internalChannel));
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsCOMPtr<nsIURI> uri;
     rv = internalChannel->GetURI(getter_AddRefs(uri));
     NS_ENSURE_SUCCESS(rv, rv);
@@ -2277,24 +2280,23 @@ ServiceWorkerManager::DispatchFetchEvent
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsMainThreadPtrHandle<nsIInterceptedChannel> handle(
     new nsMainThreadPtrHolder<nsIInterceptedChannel>(aChannel, false));
 
-  uint64_t windowId = aDoc ? aDoc->GetInnerWindow()->WindowID() : 0;
-
   nsRefPtr<ServiceWorker> sw = static_cast<ServiceWorker*>(serviceWorker.get());
   nsMainThreadPtrHandle<ServiceWorker> serviceWorkerHandle(
     new nsMainThreadPtrHolder<ServiceWorker>(sw));
 
+  // clientInfo is null if we don't have a controlled document
   nsRefPtr<FetchEventRunnable> event =
-    new FetchEventRunnable(sw->GetWorkerPrivate(), handle, serviceWorkerHandle, windowId);
+    new FetchEventRunnable(sw->GetWorkerPrivate(), handle, serviceWorkerHandle, clientInfo);
   rv = event->Init();
   NS_ENSURE_SUCCESS(rv, rv);
 
   AutoJSAPI api;
   api.Init();
   if (NS_WARN_IF(!event->Dispatch(api.cx()))) {
     return NS_ERROR_FAILURE;
   }
@@ -2514,42 +2516,46 @@ ServiceWorkerManager::Update(const nsASt
   return NS_OK;
 }
 
 namespace {
 
 class MOZ_STACK_CLASS FilterRegistrationData
 {
 public:
-  FilterRegistrationData(nsTArray<uint64_t>* aDocuments,
-                     ServiceWorkerRegistrationInfo* aRegistration)
+  FilterRegistrationData(nsTArray<ServiceWorkerClientInfo>& aDocuments,
+                         ServiceWorkerRegistrationInfo* aRegistration)
   : mDocuments(aDocuments),
     mRegistration(aRegistration)
   {
   }
 
-  nsTArray<uint64_t>* mDocuments;
+  nsTArray<ServiceWorkerClientInfo>& mDocuments;
   nsRefPtr<ServiceWorkerRegistrationInfo> mRegistration;
 };
 
 static PLDHashOperator
 EnumControlledDocuments(nsISupports* aKey,
                         ServiceWorkerRegistrationInfo* aRegistration,
                         void* aData)
 {
   FilterRegistrationData* data = static_cast<FilterRegistrationData*>(aData);
   if (data->mRegistration != aRegistration) {
     return PL_DHASH_NEXT;
   }
+
   nsCOMPtr<nsIDocument> document = do_QueryInterface(aKey);
-  if (!document || !document->GetInnerWindow()) {
+
+  if (!document || !document->GetWindow()) {
       return PL_DHASH_NEXT;
   }
 
-  data->mDocuments->AppendElement(document->GetInnerWindow()->WindowID());
+  ServiceWorkerClientInfo clientInfo(document);
+  data->mDocuments.AppendElement(clientInfo);
+
   return PL_DHASH_NEXT;
 }
 
 static PLDHashOperator
 FireControllerChangeOnMatchingDocument(nsISupports* aKey,
                                        ServiceWorkerRegistrationInfo* aValue,
                                        void* aData)
 {
@@ -2586,17 +2592,17 @@ FireControllerChangeOnMatchingDocument(n
   }
 
   return PL_DHASH_NEXT;
 }
 } // anonymous namespace
 
 void
 ServiceWorkerManager::GetAllClients(const nsCString& aScope,
-                                    nsTArray<uint64_t>* aControlledDocuments)
+                                    nsTArray<ServiceWorkerClientInfo>& aControlledDocuments)
 {
   nsRefPtr<ServiceWorkerRegistrationInfo> registration = GetRegistration(aScope);
 
   if (!registration) {
     // The registration was removed, leave the array empty.
     return;
   }
 
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -38,16 +38,17 @@ class BackgroundChild;
 
 namespace dom {
 
 class ServiceWorkerRegistration;
 
 namespace workers {
 
 class ServiceWorker;
+class ServiceWorkerClientInfo;
 class ServiceWorkerInfo;
 
 class ServiceWorkerJobQueue;
 
 class ServiceWorkerJob : public nsISupports
 {
 protected:
   // The queue keeps the jobs alive, so they can hold a rawptr back to the
@@ -374,17 +375,17 @@ public:
               nsString aFilename,
               nsString aLine,
               uint32_t aLineNumber,
               uint32_t aColumnNumber,
               uint32_t aFlags);
 
   void
   GetAllClients(const nsCString& aScope,
-                nsTArray<uint64_t>* aControlledDocuments);
+                nsTArray<ServiceWorkerClientInfo>& aControlledDocuments);
 
   static already_AddRefed<ServiceWorkerManager>
   GetInstance();
 
  void LoadRegistrations(
                  const nsTArray<ServiceWorkerRegistrationData>& aRegistrations);
 
 private:
new file mode 100644
--- /dev/null
+++ b/dom/workers/ServiceWorkerWindowClient.cpp
@@ -0,0 +1,34 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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 "ServiceWorkerWindowClient.h"
+
+#include "mozilla/dom/ClientBinding.h"
+
+using namespace mozilla::dom;
+using namespace mozilla::dom::workers;
+
+JSObject*
+ServiceWorkerWindowClient::WrapObject(JSContext* aCx)
+{
+  return WindowClientBinding::Wrap(aCx, this);
+}
+
+already_AddRefed<Promise>
+ServiceWorkerWindowClient::Focus() const
+{
+  ErrorResult result;
+  nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetParentObject());
+  MOZ_ASSERT(global);
+
+  nsRefPtr<Promise> promise = Promise::Create(global, result);
+  if (NS_WARN_IF(result.Failed())) {
+    return nullptr;
+  }
+
+  promise->MaybeReject(NS_ERROR_NOT_AVAILABLE);
+  return promise.forget();
+}
new file mode 100644
--- /dev/null
+++ b/dom/workers/ServiceWorkerWindowClient.h
@@ -0,0 +1,65 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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_dom_workers_serviceworkerwindowclient_h
+#define mozilla_dom_workers_serviceworkerwindowclient_h
+
+#include "ServiceWorkerClient.h"
+
+namespace mozilla {
+namespace dom {
+namespace workers {
+
+class ServiceWorkerWindowClient MOZ_FINAL : public ServiceWorkerClient
+{
+public:
+  ServiceWorkerWindowClient(nsISupports* aOwner,
+                            const ServiceWorkerClientInfo& aClientInfo)
+    : ServiceWorkerClient(aOwner, aClientInfo),
+      mVisibilityState(aClientInfo.mVisibilityState),
+      mFocused(aClientInfo.mFocused),
+      mFrameType(aClientInfo.mFrameType)
+  {
+  }
+
+  virtual JSObject*
+  WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+
+  mozilla::dom::VisibilityState
+  VisibilityState() const
+  {
+    return mVisibilityState;
+  }
+
+  bool
+  Focused() const
+  {
+    return mFocused;
+  }
+
+  mozilla::dom::FrameType
+  FrameType() const
+  {
+    return mFrameType;
+  }
+
+  already_AddRefed<Promise>
+  Focus() const;
+
+private:
+  ~ServiceWorkerWindowClient()
+  { }
+
+  mozilla::dom::VisibilityState mVisibilityState;
+  bool mFocused;
+  mozilla::dom::FrameType mFrameType;
+};
+
+} // namespace workers
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_workers_serviceworkerwindowclient_h
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -166,21 +166,25 @@ WorkerGlobalScope::GetExistingNavigator(
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   nsRefPtr<WorkerNavigator> navigator = mNavigator;
   return navigator.forget();
 }
 
 void
-WorkerGlobalScope::Close(JSContext* aCx)
+WorkerGlobalScope::Close(JSContext* aCx, ErrorResult& aRv)
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
-  mWorkerPrivate->CloseInternal(aCx);
+  if (mWorkerPrivate->IsServiceWorker()) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
+  } else {
+    mWorkerPrivate->CloseInternal(aCx);
+  }
 }
 
 OnErrorEventHandlerNonNull*
 WorkerGlobalScope::GetOnerror()
 {
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   EventListenerManager* elm = GetExistingListenerManager();
--- a/dom/workers/WorkerScope.h
+++ b/dom/workers/WorkerScope.h
@@ -93,17 +93,17 @@ public:
 
   already_AddRefed<WorkerNavigator>
   Navigator();
 
   already_AddRefed<WorkerNavigator>
   GetExistingNavigator() const;
 
   void
-  Close(JSContext* aCx);
+  Close(JSContext* aCx, ErrorResult& aRv);
 
   OnErrorEventHandlerNonNull*
   GetOnerror();
   void
   SetOnerror(OnErrorEventHandlerNonNull* aHandler);
 
   void
   ImportScripts(JSContext* aCx, const Sequence<nsString>& aScriptURLs,
@@ -214,22 +214,16 @@ public:
 
   void
   GetScope(nsString& aScope) const
   {
     aScope = mScope;
   }
 
   void
-  Close() const
-  {
-    // no-op close.
-  }
-
-  void
   Update();
 
   already_AddRefed<Promise>
   Unregister(ErrorResult& aRv);
 
   ServiceWorkerClients*
   Clients();
 
--- a/dom/workers/moz.build
+++ b/dom/workers/moz.build
@@ -29,16 +29,17 @@ EXPORTS.mozilla.dom.workers.bindings += 
     'FileReaderSync.h',
     'Location.h',
     'MessagePort.h',
     'Navigator.h',
     'Performance.h',
     'ServiceWorker.h',
     'ServiceWorkerClient.h',
     'ServiceWorkerClients.h',
+    'ServiceWorkerWindowClient.h',
     'SharedWorker.h',
     'URL.h',
     'WorkerFeature.h',
     'XMLHttpRequest.h',
     'XMLHttpRequestUpload.h',
 ]
 
 XPIDL_MODULE = 'dom_workers'
@@ -64,16 +65,17 @@ UNIFIED_SOURCES += [
     'ServiceWorker.cpp',
     'ServiceWorkerClient.cpp',
     'ServiceWorkerClients.cpp',
     'ServiceWorkerContainer.cpp',
     'ServiceWorkerEvents.cpp',
     'ServiceWorkerManager.cpp',
     'ServiceWorkerRegistrar.cpp',
     'ServiceWorkerRegistration.cpp',
+    'ServiceWorkerWindowClient.cpp',
     'SharedWorker.cpp',
     'URL.cpp',
     'WorkerDebuggerManager.cpp',
     'WorkerPrivate.cpp',
     'WorkerRunnable.cpp',
     'WorkerScope.cpp',
     'WorkerThread.cpp',
     'XMLHttpRequest.cpp',
--- a/dom/workers/test/console_worker.js
+++ b/dom/workers/test/console_worker.js
@@ -7,20 +7,16 @@ onmessage = function(event) {
   // TEST: does console exist?
   postMessage({event: 'console exists', status: !!console, last : false});
 
   postMessage({event: 'console is the same object', status: console === console, last: false});
 
   postMessage({event: 'trace without function', status: true, last : false});
 
   for (var i = 0; i < 10; ++i) {
-    console.what('1', 123, 321);
-  }
-
-  for (var i = 0; i < 10; ++i) {
     console.log(i, i, i);
   }
 
   function trace1() {
     function trace2() {
       function trace3() {
         console.trace("trace " + i);
       }
@@ -76,26 +72,27 @@ function nextSteps(event) {
   function namelessTimer() {
     console.time();
     console.timeEnd();
   }
 
   namelessTimer();
 
   var str = "Test Message."
-  console.foobar(str); // if this throws, we don't execute following funcs
   console.log(str);
   console.info(str);
   console.warn(str);
   console.error(str);
   console.exception(str);
   console.assert(true, str);
   console.assert(false, str);
   console.profile(str);
   console.profileEnd(str);
+  console.timeStamp();
+  console.clear();
   postMessage({event: '4 messages', status: true, last : false});
 
   // Recursive:
   if (event.data == true) {
     var worker = new Worker('console_worker.js');
     worker.onmessage = function(event) {
       postMessage(event.data);
     }
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/close_test.js
@@ -0,0 +1,19 @@
+function ok(v, msg) {
+  client.postMessage({status: "ok", result: !!v, message: msg});
+}
+
+var client;
+onmessage = function(e) {
+  if (e.data.message == "start") {
+    self.clients.matchAll().then(function(clients) {
+      client = clients[0];
+      try {
+        close();
+        ok(false, "close() should throw");
+      } catch (e) {
+        ok(e.name === "InvalidAccessError", "close() should throw InvalidAccessError");
+      }
+      client.postMessage({status: "done"});
+    });
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/match_all_clients/match_all_controlled.html
@@ -0,0 +1,61 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1058311 - controlled page</title>
+<script class="testbody" type="text/javascript">
+  var frameType = "none";
+  var testWindow = parent;
+
+  if (parent != window) {
+    frameType = "nested";
+  } else if (opener) {
+    frameType = "auxiliary";
+    testWindow = opener;
+  } else if (parent != window) {
+    frameType = "top-level";
+  } else {
+    postResult(false, "Unexpected frameType");
+  }
+
+  window.onload = function() {
+    navigator.serviceWorker.ready.then(function(swr) {
+        swr.active.postMessage("Start");
+    });
+  }
+
+  function postResult(result, msg) {
+    response = {
+      result: result,
+      message: msg
+    };
+
+    testWindow.postMessage(response, "*");
+  }
+
+  navigator.serviceWorker.onmessage = function(msg) {
+    // worker message;
+    result = msg.data.url == window.location;
+    postResult(result, "Client url test");
+
+    result = msg.data.visibilityState === document.visibilityState;
+    postResult(result, "Client visibility test. expected=" +document.visibilityState);
+
+    result = msg.data.focused === document.hasFocus();
+    postResult(result, "Client focus test. expected=" + document.hasFocus());
+
+    result = msg.data.frameType === frameType;
+    postResult(result, "Client frameType test. expected=" + frameType);
+
+    postResult(true, "DONE");
+    window.close();
+  };
+</script>
+
+</head>
+<body>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/match_all_properties_worker.js
@@ -0,0 +1,19 @@
+onmessage = function(e) {
+  dump("MatchAllPropertiesWorker:" + e.data + "\n");
+  self.clients.matchAll().then(function(res) {
+    if (!res.length) {
+      dump("ERROR: no clients are currently controlled.\n");
+    }
+
+    for (i = 0; i < res.length; i++) {
+      client = res[i];
+      response = {
+        url: client.url,
+        visibilityState: client.visibilityState,
+        focused: client.focused,
+        frameType: client.frameType
+      };
+      client.postMessage(response);
+    }
+  });
+}
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/message_receiver.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<script>
+  navigator.serviceWorker.onmessage = function(e) {
+    window.parent.postMessage(e.data, "*");
+  };
+</script>
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -19,24 +19,34 @@ support-files =
   sw_clients/service_worker_controlled.html
   match_all_worker.js
   worker_unregister.js
   worker_update.js
   message_posting_worker.js
   fetch/index.html
   fetch/fetch_worker_script.js
   fetch/fetch_tests.js
+  match_all_properties_worker.js
+  match_all_clients/match_all_controlled.html
+  test_serviceworker_interfaces.js
+  serviceworker_wrapper.js
+  message_receiver.html
+  close_test.js
 
 [test_unregister.html]
 skip-if = true # Bug 1133805
 [test_installation_simple.html]
 [test_fetch_event.html]
 [test_match_all.html]
 [test_install_event.html]
 [test_navigator.html]
 [test_scopes.html]
+skip-if = true # Bug 1037739
 [test_controller.html]
 [test_workerUpdate.html]
 skip-if = true # Bug 1133805
 [test_workerUnregister.html]
 skip-if = true # Bug 1133805
 [test_post_message.html]
 [test_post_message_advanced.html]
+[test_match_all_client_properties.html]
+[test_close.html]
+[test_serviceworker_interfaces.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/serviceworker_wrapper.js
@@ -0,0 +1,131 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+//
+// ServiceWorker equivalent of worker_wrapper.js.
+
+var client;
+
+function ok(a, msg) {
+  dump("OK: " + !!a + "  =>  " + a + ": " + msg + "\n");
+  client.postMessage({type: 'status', status: !!a, msg: a + ": " + msg });
+}
+
+function is(a, b, msg) {
+  dump("IS: " + (a===b) + "  =>  " + a + " | " + b + ": " + msg + "\n");
+  client.postMessage({type: 'status', status: a === b, msg: a + " === " + b + ": " + msg });
+}
+
+function workerTestArrayEquals(a, b) {
+  if (!Array.isArray(a) || !Array.isArray(b) || a.length != b.length) {
+    return false;
+  }
+  for (var i = 0, n = a.length; i < n; ++i) {
+    if (a[i] !== b[i]) {
+      return false;
+    }
+  }
+  return true;
+}
+
+function workerTestDone() {
+  client.postMessage({ type: 'finish' });
+}
+
+function workerTestGetPrefs(prefs, cb) {
+  addEventListener('message', function workerTestGetPrefsCB(e) {
+    if (e.data.type != 'returnPrefs' ||
+        !workerTestArrayEquals(prefs, e.data.prefs)) {
+      return;
+    }
+    removeEventListener('message', workerTestGetPrefsCB);
+    cb(e.data.result);
+  });
+  client.postMessage({
+    type: 'getPrefs',
+    prefs: prefs
+  });
+}
+
+function workerTestGetPermissions(permissions, cb) {
+  addEventListener('message', function workerTestGetPermissionsCB(e) {
+    if (e.data.type != 'returnPermissions' ||
+        !workerTestArrayEquals(permissions, e.data.permissions)) {
+      return;
+    }
+    removeEventListener('message', workerTestGetPermissionsCB);
+    cb(e.data.result);
+  });
+  client.postMessage({
+    type: 'getPermissions',
+    permissions: permissions
+  });
+}
+
+function workerTestGetVersion(cb) {
+  addEventListener('message', function workerTestGetVersionCB(e) {
+    if (e.data.type !== 'returnVersion') {
+      return;
+    }
+    removeEventListener('message', workerTestGetVersionCB);
+    cb(e.data.result);
+  });
+  client.postMessage({
+    type: 'getVersion'
+  });
+}
+
+function workerTestGetUserAgent(cb) {
+  addEventListener('message', function workerTestGetUserAgentCB(e) {
+    if (e.data.type !== 'returnUserAgent') {
+      return;
+    }
+    removeEventListener('message', workerTestGetUserAgentCB);
+    cb(e.data.result);
+  });
+  client.postMessage({
+    type: 'getUserAgent'
+  });
+}
+
+function workerTestGetOSCPU(cb) {
+  addEventListener('message', function workerTestGetOSCPUCB(e) {
+    if (e.data.type !== 'returnOSCPU') {
+      return;
+    }
+    removeEventListener('message', workerTestGetOSCPUCB);
+    cb(e.data.result);
+  });
+  client.postMessage({
+    type: 'getOSCPU'
+  });
+}
+
+function workerTestGetIsB2G(cb) {
+  addEventListener('message', function workerTestGetIsB2GCB(e) {
+    if (e.data.type !== 'returnIsB2G') {
+      return;
+    }
+    removeEventListener('message', workerTestGetIsB2GCB);
+    cb(e.data.result);
+  });
+  client.postMessage({
+    type: 'getIsB2G'
+  });
+}
+
+addEventListener('message', function workerWrapperOnMessage(e) {
+  removeEventListener('message', workerWrapperOnMessage);
+  var data = e.data;
+  self.clients.matchAll().then(function(clients) {
+    client = clients[0];
+    try {
+      importScripts(data.script);
+    } catch(e) {
+      client.postMessage({
+        type: 'status',
+        status: false,
+        msg: 'worker failed to import ' + data.script + "; error: " + e.message
+      });
+    }
+  });
+});
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_close.html
@@ -0,0 +1,69 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1131353 - test WorkerGlobalScope.close() on service workers</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+<iframe></iframe>
+</div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+
+  var iframe;
+  function runTest() {
+    navigator.serviceWorker.register("close_test.js", {scope: "."})
+      .then(function(registration) {
+        if (registration.installing) {
+          registration.installing.onstatechange = function(e) {
+            setupSW(registration);
+            e.target.onstatechange = null;
+          };
+        } else {
+          setupSW(registration);
+        }
+      });
+
+    function setupSW(registration) {
+      var worker = registration.waiting ||
+                   registration.active;
+      var iframe = document.createElement("iframe");
+      iframe.src = "message_receiver.html";
+      iframe.onload = function() {
+        worker.postMessage({ message: "start" });
+      };
+      document.body.appendChild(iframe);
+    }
+
+    window.onmessage = function(e) {
+      if (e.data.status == "ok") {
+        ok(e.data.result, e.data.message);
+      } else if (e.data.status == "done") {
+        navigator.serviceWorker.getRegistration().then(function(registration) {
+          registration.unregister().then(function() {
+            SimpleTest.finish();
+          });
+        });
+      }
+    };
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  onload = function() {
+    SpecialPowers.pushPrefEnv({"set": [
+      ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.enabled", true],
+      ["dom.serviceWorkers.testing.enabled", true],
+    ]}, runTest);
+  };
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_match_all_client_properties.html
@@ -0,0 +1,93 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1058311 - Test matchAll clients properties </title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test"></pre>
+<script class="testbody" type="text/javascript">
+  var registration;
+  var clientURL = "match_all_clients/match_all_controlled.html";
+  function start() {
+    return navigator.serviceWorker.register("match_all_properties_worker.js",
+                                            { scope: "./match_all_clients/" })
+      .then((swr) => registration = swr);
+  }
+
+  function unregister() {
+    return registration.unregister().then(function(result) {
+      ok(result, "Unregister should return true.");
+    });
+  }
+
+  function getMessageListener() {
+    return new Promise(function(res, rej) {
+      window.onmessage = function(e) {
+        if (e.data.message === undefined) {
+          info("rejecting promise");
+          rej();
+          return;
+        }
+
+        ok(e.data.result, e.data.message);
+
+        if (!e.data.result) {
+          rej();
+        }
+        if (e.data.message == "DONE") {
+          info("DONE from: " + e.source);
+          res();
+        }
+      }
+    });
+  }
+
+  function testNestedWindow() {
+    var p = getMessageListener();
+
+    var content = document.getElementById("content");
+    ok(content, "Parent exists.");
+
+    iframe = document.createElement("iframe");
+
+    content.appendChild(iframe);
+    iframe.setAttribute('src', clientURL);
+
+    return p.then(() => content.removeChild(iframe));
+  }
+
+  function testAuxiliaryWindow() {
+    var p = getMessageListener();
+    var w = window.open(clientURL);
+
+    return p.then(() => w.close());
+  }
+
+  function runTest() {
+    info("catalin");
+    info(window.opener == undefined);
+    start()
+      .then(testAuxiliaryWindow)
+      .then(testNestedWindow)
+      .catch(function(e) {
+        ok(false, "Some test failed with error " + e);
+      }).then(SimpleTest.finish);
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  SpecialPowers.pushPrefEnv({"set": [
+    ["dom.serviceWorkers.enabled", true],
+    ["dom.serviceWorkers.testing.enabled", true]
+  ]}, runTest);
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.html
@@ -0,0 +1,113 @@
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Validate Interfaces Exposed to Service Workers</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="text/javascript" src="../worker_driver.js"></script>
+</head>
+<body>
+<script class="testbody" type="text/javascript">
+
+  function setupSW(registration) {
+    var worker = registration.waiting ||
+                 registration.active;
+    window.onmessage = function(event) {
+      if (event.data.type == 'finish') {
+        registration.unregister().then(function(success) {
+          ok(success, "The service worker should be unregistered successfully");
+
+          SimpleTest.finish();
+        });
+      } else if (event.data.type == 'status') {
+        ok(event.data.status, event.data.msg);
+
+      } else if (event.data.type == 'getPrefs') {
+        var result = {};
+        event.data.prefs.forEach(function(pref) {
+          result[pref] = SpecialPowers.Services.prefs.getBoolPref(pref);
+        });
+        worker.postMessage({
+          type: 'returnPrefs',
+          prefs: event.data.prefs,
+          result: result
+        });
+
+      } else if (event.data.type == 'getPermissions') {
+        var result = {};
+        event.data.permissions.forEach(function(permission) {
+          result[permission] = SpecialPowers.hasPermission(permission, window.document);
+        });
+        worker.postMessage({
+          type: 'returnPermissions',
+          permissions: event.data.permissions,
+          result: result
+        });
+
+      } else if (event.data.type == 'getVersion') {
+        var result = SpecialPowers.Cc['@mozilla.org/xre/app-info;1'].getService(SpecialPowers.Ci.nsIXULAppInfo).version;
+        worker.postMessage({
+          type: 'returnVersion',
+          result: result
+        });
+
+      } else if (event.data.type == 'getUserAgent') {
+        worker.postMessage({
+          type: 'returnUserAgent',
+          result: navigator.userAgent
+        });
+      } else if (event.data.type == 'getOSCPU') {
+        worker.postMessage({
+          type: 'returnOSCPU',
+          result: navigator.oscpu
+        });
+      } else if (event.data.type == 'getIsB2G') {
+        worker.postMessage({
+          type: 'returnIsB2G',
+          result: SpecialPowers.isB2G
+        });
+      }
+    }
+
+    worker.onerror = function(event) {
+      ok(false, 'Worker had an error: ' + event.data);
+      SimpleTest.finish();
+    };
+
+    var iframe = document.createElement("iframe");
+    iframe.src = "message_receiver.html";
+    iframe.onload = function() {
+      worker.postMessage({ script: "test_serviceworker_interfaces.js" });
+    };
+    document.body.appendChild(iframe);
+  }
+
+  function runTest() {
+    navigator.serviceWorker.register("serviceworker_wrapper.js", {scope: "."})
+      .then(function(registration) {
+        if (registration.installing) {
+          registration.installing.onstatechange = function(e) {
+            setupSW(registration);
+            e.target.onstatechange = null;
+          };
+        } else {
+          setupSW(registration);
+        }
+      });
+  }
+
+  SimpleTest.waitForExplicitFinish();
+  onload = function() {
+    SpecialPowers.pushPrefEnv({"set": [
+      ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.enabled", true],
+      ["dom.serviceWorkers.testing.enabled", true],
+      ["dom.fetch.enabled", true],
+      ["dom.caches.enabled", true]
+    ]}, runTest);
+  };
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
@@ -0,0 +1,280 @@
+// This is a list of all interfaces that are exposed to workers.
+// Please only add things to this list with great care and proper review
+// from the associated module peers.
+
+// This file lists global interfaces we want exposed and verifies they
+// are what we intend. Each entry in the arrays below can either be a
+// simple string with the interface name, or an object with a 'name'
+// property giving the interface name as a string, and additional
+// properties which qualify the exposure of that interface. For example:
+//
+// [
+//   "AGlobalInterface",
+//   {name: "ExperimentalThing", release: false},
+//   {name: "OptionalThing", pref: "some.thing.enabled"},
+// ];
+//
+// See createInterfaceMap() below for a complete list of properties.
+
+// IMPORTANT: Do not change this list without review from
+//            a JavaScript Engine peer!
+var ecmaGlobals =
+  [
+    "Array",
+    "ArrayBuffer",
+    "Boolean",
+    "DataView",
+    "Date",
+    "Error",
+    "EvalError",
+    "Float32Array",
+    "Float64Array",
+    "Function",
+    "Infinity",
+    "Int16Array",
+    "Int32Array",
+    "Int8Array",
+    "InternalError",
+    {name: "Intl", b2g: false, android: false},
+    "Iterator",
+    "JSON",
+    "Map",
+    "Math",
+    "NaN",
+    "Number",
+    "Object",
+    "Proxy",
+    "RangeError",
+    "ReferenceError",
+    "RegExp",
+    "Set",
+    {name: "SharedArrayBuffer", nightly: true},
+    {name: "SharedInt8Array", nightly: true},
+    {name: "SharedUint8Array", nightly: true},
+    {name: "SharedUint8ClampedArray", nightly: true},
+    {name: "SharedInt16Array", nightly: true},
+    {name: "SharedUint16Array", nightly: true},
+    {name: "SharedInt32Array", nightly: true},
+    {name: "SharedUint32Array", nightly: true},
+    {name: "SharedFloat32Array", nightly: true},
+    {name: "SharedFloat64Array", nightly: true},
+    {name: "SIMD", nightly: true},
+    {name: "Atomics", nightly: true},
+    "StopIteration",
+    "String",
+    "Symbol",
+    "SyntaxError",
+    {name: "TypedObject", nightly: true},
+    "TypeError",
+    "Uint16Array",
+    "Uint32Array",
+    "Uint8Array",
+    "Uint8ClampedArray",
+    "URIError",
+    "WeakMap",
+    "WeakSet",
+  ];
+// IMPORTANT: Do not change the list above without review from
+//            a JavaScript Engine peer!
+
+// IMPORTANT: Do not change the list below without review from a DOM peer!
+var interfaceNamesInGlobalScope =
+  [
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "Blob",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "BroadcastChannel", pref: "dom.broadcastChannel.enabled" },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "Cache", pref: "dom.caches.enabled" },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "CacheStorage", pref: "dom.caches.enabled" },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "Client",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "Clients",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "DataStore", b2g: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "DataStoreCursor", b2g: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "DOMError",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "DOMException",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "DOMStringList",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "Event",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "EventTarget",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "ExtendableEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "FetchEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "File",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "FileReaderSync",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "Headers", pref: "dom.fetch.enabled" },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "IDBCursor",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "IDBDatabase",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "IDBFactory",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "IDBIndex",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "IDBKeyRange",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "IDBObjectStore",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "IDBOpenDBRequest",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "IDBRequest",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "IDBTransaction",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "IDBVersionChangeEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "ImageData",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "InstallEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "MessageEvent",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "MessagePort",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "Performance",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "Promise",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "Request",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "Response",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "ServiceWorker",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "ServiceWorkerGlobalScope",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "TextDecoder",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "TextEncoder",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "XMLHttpRequest",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "XMLHttpRequestEventTarget",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "XMLHttpRequestUpload",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "URL",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "URLSearchParams",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+   { name: "WebSocket", pref: "dom.workers.websocket.enabled" },
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "WindowClient",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "WorkerGlobalScope",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "WorkerLocation",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+    "WorkerNavigator",
+// IMPORTANT: Do not change this list without review from a DOM peer!
+  ];
+// IMPORTANT: Do not change the list above without review from a DOM peer!
+
+function createInterfaceMap(prefMap, permissionMap, version, userAgent, isB2G) {
+  var isNightly = version.endsWith("a1");
+  var isRelease = !version.contains("a");
+  var isDesktop = !/Mobile|Tablet/.test(userAgent);
+  var isAndroid = !!navigator.userAgent.contains("Android");
+
+  var interfaceMap = {};
+
+  function addInterfaces(interfaces)
+  {
+    for (var entry of interfaces) {
+      if (typeof(entry) === "string") {
+        interfaceMap[entry] = true;
+      } else if ((entry.nightly === !isNightly) ||
+                 (entry.desktop === !isDesktop) ||
+                 (entry.android === !isAndroid) ||
+                 (entry.b2g === !isB2G) ||
+                 (entry.release === !isRelease) ||
+                 (entry.pref && !prefMap[entry.pref])  ||
+                 (entry.permission && !permissionMap[entry.permission])) {
+        interfaceMap[entry.name] = false;
+      } else {
+        interfaceMap[entry.name] = true;
+      }
+    }
+  }
+
+  addInterfaces(ecmaGlobals);
+  addInterfaces(interfaceNamesInGlobalScope);
+
+  return interfaceMap;
+}
+
+function runTest(prefMap, permissionMap, version, userAgent, isB2G) {
+  var interfaceMap = createInterfaceMap(prefMap, permissionMap, version, userAgent, isB2G);
+  for (var name of Object.getOwnPropertyNames(self)) {
+    // An interface name should start with an upper case character.
+    if (!/^[A-Z]/.test(name)) {
+      continue;
+    }
+    ok(interfaceMap[name],
+       "If this is failing: DANGER, are you sure you want to expose the new interface " + name +
+       " to all webpages as a property on the service worker? Do not make a change to this file without a " +
+       " review from a DOM peer for that specific change!!! (or a JS peer for changes to ecmaGlobals)");
+    delete interfaceMap[name];
+  }
+  for (var name of Object.keys(interfaceMap)) {
+    ok(name in self === interfaceMap[name],
+       name + " should " + (interfaceMap[name] ? "" : " NOT") + " be defined on the global scope");
+    if (!interfaceMap[name]) {
+      delete interfaceMap[name];
+    }
+  }
+  is(Object.keys(interfaceMap).length, 0,
+     "The following interface(s) are not enumerated: " + Object.keys(interfaceMap).join(", "));
+}
+
+function appendPrefs(prefs, interfaces) {
+  for (var entry of interfaces) {
+    if (entry.pref !== undefined && prefs.indexOf(entry.pref) === -1) {
+      prefs.push(entry.pref);
+    }
+  }
+}
+
+var prefs = [];
+appendPrefs(prefs, ecmaGlobals);
+appendPrefs(prefs, interfaceNamesInGlobalScope);
+
+function appendPermissions(permissions, interfaces) {
+  for (var entry of interfaces) {
+    if (entry.permission !== undefined &&
+        permissions.indexOf(entry.permission) === -1) {
+      permissions.push(entry.permission);
+    }
+  }
+}
+
+var permissions = [];
+appendPermissions(permissions, ecmaGlobals);
+appendPermissions(permissions, interfaceNamesInGlobalScope);
+
+workerTestGetPrefs(prefs, function(prefMap) {
+  workerTestGetPermissions(permissions, function(permissionMap) {
+    workerTestGetVersion(function(version) {
+      workerTestGetUserAgent(function(userAgent) {
+        workerTestGetIsB2G(function(isB2G) {
+          runTest(prefMap, permissionMap, version, userAgent, isB2G);
+          workerTestDone();
+	});
+      });
+    });
+  });
+});
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -3093,17 +3093,17 @@ void AsyncPanZoomController::ShareCompos
 
       // Get the cross process mutex handle to share with the content process
       mSharedLock = new CrossProcessMutex("AsyncPanZoomControlLock");
       CrossProcessMutexHandle handle = mSharedLock->ShareToProcess(processHandle);
 
       // Send the shared memory handle and cross process handle to the content
       // process by an asynchronous ipc call. Include the APZC unique ID
       // so the content process know which APZC sent this shared FrameMetrics.
-      if (!compositor->SendSharedCompositorFrameMetrics(mem, handle, mAPZCId)) {
+      if (!compositor->SendSharedCompositorFrameMetrics(mem, handle, mLayersId, mAPZCId)) {
         APZC_LOG("%p failed to share FrameMetrics with content process.", this);
       }
     }
   }
 }
 
 }
 }
--- a/gfx/layers/ipc/CompositorChild.cpp
+++ b/gfx/layers/ipc/CompositorChild.cpp
@@ -116,24 +116,37 @@ CompositorChild::Get()
 
 PLayerTransactionChild*
 CompositorChild::AllocPLayerTransactionChild(const nsTArray<LayersBackend>& aBackendHints,
                                              const uint64_t& aId,
                                              TextureFactoryIdentifier*,
                                              bool*)
 {
   MOZ_ASSERT(mCanSend);
-  LayerTransactionChild* c = new LayerTransactionChild();
+  LayerTransactionChild* c = new LayerTransactionChild(aId);
   c->AddIPDLReference();
   return c;
 }
 
+/*static*/ PLDHashOperator
+CompositorChild::RemoveSharedMetricsForLayersId(const uint64_t& aKey,
+                                                nsAutoPtr<SharedFrameMetricsData>& aData,
+                                                void* aLayerTransactionChild)
+{
+  uint64_t childId = static_cast<LayerTransactionChild*>(aLayerTransactionChild)->GetId();
+  if (aData->GetLayersId() == childId) {
+    return PLDHashOperator::PL_DHASH_REMOVE;
+  }
+  return PLDHashOperator::PL_DHASH_NEXT;
+}
+
 bool
 CompositorChild::DeallocPLayerTransactionChild(PLayerTransactionChild* actor)
 {
+  mFrameMetricsTable.Enumerate(RemoveSharedMetricsForLayersId, actor);
   static_cast<LayerTransactionChild*>(actor)->ReleaseIPDLReference();
   return true;
 }
 
 bool
 CompositorChild::RecvInvalidateAll()
 {
   if (mLayerManager) {
@@ -334,19 +347,21 @@ CompositorChild::ActorDestroy(ActorDestr
     FROM_HERE,
     NewRunnableMethod(this, &CompositorChild::Release));
 }
 
 bool
 CompositorChild::RecvSharedCompositorFrameMetrics(
     const mozilla::ipc::SharedMemoryBasic::Handle& metrics,
     const CrossProcessMutexHandle& handle,
+    const uint64_t& aLayersId,
     const uint32_t& aAPZCId)
 {
-  SharedFrameMetricsData* data = new SharedFrameMetricsData(metrics, handle, aAPZCId);
+  SharedFrameMetricsData* data = new SharedFrameMetricsData(
+    metrics, handle, aLayersId, aAPZCId);
   mFrameMetricsTable.Put(data->GetViewID(), data);
   return true;
 }
 
 bool
 CompositorChild::RecvReleaseSharedCompositorFrameMetrics(
     const ViewID& aId,
     const uint32_t& aAPZCId)
@@ -359,19 +374,21 @@ CompositorChild::RecvReleaseSharedCompos
     mFrameMetricsTable.Remove(aId);
   }
   return true;
 }
 
 CompositorChild::SharedFrameMetricsData::SharedFrameMetricsData(
     const ipc::SharedMemoryBasic::Handle& metrics,
     const CrossProcessMutexHandle& handle,
-    const uint32_t& aAPZCId) :
-    mMutex(nullptr),
-    mAPZCId(aAPZCId)
+    const uint64_t& aLayersId,
+    const uint32_t& aAPZCId)
+  : mMutex(nullptr)
+  , mLayersId(aLayersId)
+  , mAPZCId(aAPZCId)
 {
   mBuffer = new ipc::SharedMemoryBasic(metrics);
   mBuffer->Map(sizeof(FrameMetrics));
   mMutex = new CrossProcessMutex(handle);
   MOZ_COUNT_CTOR(SharedFrameMetricsData);
 }
 
 CompositorChild::SharedFrameMetricsData::~SharedFrameMetricsData()
@@ -398,16 +415,22 @@ CompositorChild::SharedFrameMetricsData:
 {
   FrameMetrics* frame = static_cast<FrameMetrics*>(mBuffer->memory());
   MOZ_ASSERT(frame);
   // Not locking to read of mScrollId since it should not change after being
   // initially set.
   return frame->GetScrollId();
 }
 
+uint64_t
+CompositorChild::SharedFrameMetricsData::GetLayersId() const
+{
+  return mLayersId;
+}
+
 uint32_t
 CompositorChild::SharedFrameMetricsData::GetAPZCId()
 {
   return mAPZCId;
 }
 
 
 bool
--- a/gfx/layers/ipc/CompositorChild.h
+++ b/gfx/layers/ipc/CompositorChild.h
@@ -123,47 +123,55 @@ private:
                                 bool* aSuccess) MOZ_OVERRIDE;
 
   virtual bool DeallocPLayerTransactionChild(PLayerTransactionChild *aChild) MOZ_OVERRIDE;
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
 
   virtual bool RecvSharedCompositorFrameMetrics(const mozilla::ipc::SharedMemoryBasic::Handle& metrics,
                                                 const CrossProcessMutexHandle& handle,
+                                                const uint64_t& aLayersId,
                                                 const uint32_t& aAPZCId) MOZ_OVERRIDE;
 
   virtual bool RecvReleaseSharedCompositorFrameMetrics(const ViewID& aId,
                                                        const uint32_t& aAPZCId) MOZ_OVERRIDE;
 
   virtual bool
   RecvRemotePaintIsReady() MOZ_OVERRIDE;
 
   // Class used to store the shared FrameMetrics, mutex, and APZCId  in a hash table
   class SharedFrameMetricsData {
   public:
     SharedFrameMetricsData(
         const mozilla::ipc::SharedMemoryBasic::Handle& metrics,
         const CrossProcessMutexHandle& handle,
+        const uint64_t& aLayersId,
         const uint32_t& aAPZCId);
 
     ~SharedFrameMetricsData();
 
     void CopyFrameMetrics(FrameMetrics* aFrame);
     FrameMetrics::ViewID GetViewID();
+    uint64_t GetLayersId() const;
     uint32_t GetAPZCId();
 
   private:
     // Pointer to the class that allows access to the shared memory that contains
     // the shared FrameMetrics
     nsRefPtr<mozilla::ipc::SharedMemoryBasic> mBuffer;
     CrossProcessMutex* mMutex;
+    uint64_t mLayersId;
     // Unique ID of the APZC that is sharing the FrameMetrics
     uint32_t mAPZCId;
   };
 
+  static PLDHashOperator RemoveSharedMetricsForLayersId(const uint64_t& aKey,
+                                                        nsAutoPtr<SharedFrameMetricsData>& aData,
+                                                        void* aLayerTransactionChild);
+
   nsRefPtr<ClientLayerManager> mLayerManager;
 
   // The ViewID of the FrameMetrics is used as the key for this hash table.
   // While this should be safe to use since the ViewID is unique
   nsClassHashtable<nsUint64HashKey, SharedFrameMetricsData> mFrameMetricsTable;
 
   // When we're in a child process, this is the process-global
   // compositor that we use to forward transactions directly to the
--- a/gfx/layers/ipc/LayerTransactionChild.h
+++ b/gfx/layers/ipc/LayerTransactionChild.h
@@ -46,21 +46,24 @@ public:
   {
     mForwarder = aForwarder;
   }
 
   virtual void SendFenceHandle(AsyncTransactionTracker* aTracker,
                                PTextureChild* aTexture,
                                const FenceHandle& aFence);
 
+  uint64_t GetId() const { return mId; }
+
 protected:
-  LayerTransactionChild()
+  explicit LayerTransactionChild(const uint64_t& aId)
     : mForwarder(nullptr)
     , mIPCOpen(false)
     , mDestroyed(false)
+    , mId(aId)
   {}
   ~LayerTransactionChild() { }
 
   virtual PLayerChild* AllocPLayerChild() MOZ_OVERRIDE;
   virtual bool DeallocPLayerChild(PLayerChild* actor) MOZ_OVERRIDE;
 
   virtual PCompositableChild* AllocPCompositableChild(const TextureInfo& aInfo) MOZ_OVERRIDE;
   virtual bool DeallocPCompositableChild(PCompositableChild* actor) MOZ_OVERRIDE;
@@ -85,14 +88,15 @@ protected:
     Release();
   }
   friend class CompositorChild;
   friend class layout::RenderFrameChild;
 
   ShadowLayerForwarder* mForwarder;
   bool mIPCOpen;
   bool mDestroyed;
+  uint64_t mId;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // MOZILLA_LAYERS_LAYERTRANSACTIONCHILD_H
--- a/gfx/layers/ipc/PCompositor.ipdl
+++ b/gfx/layers/ipc/PCompositor.ipdl
@@ -128,14 +128,14 @@ parent:
    * @see PBrowser
    * @see RemotePaintIsReady
    */
   async RequestNotifyAfterRemotePaint();
 
 child:
   // Send back Compositor Frame Metrics from APZCs so tiled layers can
   // update progressively.
-  async SharedCompositorFrameMetrics(Handle metrics, CrossProcessMutexHandle mutex, uint32_t aAPZCId);
+  async SharedCompositorFrameMetrics(Handle metrics, CrossProcessMutexHandle mutex, uint64_t aLayersId, uint32_t aAPZCId);
   async ReleaseSharedCompositorFrameMetrics(ViewID aId, uint32_t aAPZCId);
 };
 
 } // layers
 } // mozilla
--- a/gfx/thebes/gfxFontFamilyList.h
+++ b/gfx/thebes/gfxFontFamilyList.h
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GFX_FONT_FAMILY_LIST_H
 #define GFX_FONT_FAMILY_LIST_H
 
 #include "nsDebug.h"
 #include "nsISupportsImpl.h"
 #include "nsString.h"
+#include "nsUnicharUtils.h"
 #include "nsTArray.h"
 #include "mozilla/MemoryReporting.h"
 
 namespace mozilla {
 
 /**
  * type of font family name, either a name (e.g. Helvetica) or a
  * generic (e.g. serif, sans-serif), with the ability to distinguish
@@ -275,16 +276,36 @@ public:
             if (mDefaultFontType == eFamily_serif) {
                 aFamilyList.AppendLiteral("serif");
             } else {
                 aFamilyList.AppendLiteral("sans-serif");
             }
         }
     }
 
+    // searches for a specific non-generic name, lowercase comparison
+    bool Contains(const nsAString& aFamilyName) const {
+        uint32_t len = mFontlist.Length();
+        nsAutoString fam(aFamilyName);
+        ToLowerCase(fam);
+        for (uint32_t i = 0; i < len; i++) {
+            const FontFamilyName& name = mFontlist[i];
+            if (name.mType != eFamily_named &&
+                name.mType != eFamily_named_quoted) {
+                continue;
+            }
+            nsAutoString listname(name.mName);
+            ToLowerCase(listname);
+            if (listname.Equals(fam)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     FontFamilyType GetDefaultFontType() const { return mDefaultFontType; }
     void SetDefaultFontType(FontFamilyType aType) {
         NS_ASSERTION(aType == eFamily_none || aType == eFamily_serif ||
                      aType == eFamily_sans_serif,
                      "default font type must be either serif or sans-serif");
         mDefaultFontType = aType;
     }
 
--- a/gfx/thebes/gfxPlatformGtk.cpp
+++ b/gfx/thebes/gfxPlatformGtk.cpp
@@ -58,17 +58,18 @@ static cairo_user_data_key_t cairo_gdk_d
     bool gfxPlatformGtk::sUseXRender = true;
 #endif
 
 gfxPlatformGtk::gfxPlatformGtk()
 {
     if (!sFontconfigUtils)
         sFontconfigUtils = gfxFontconfigUtils::GetFontconfigUtils();
 #ifdef MOZ_X11
-    sUseXRender = mozilla::Preferences::GetBool("gfx.xrender.enabled");
+    sUseXRender = (GDK_IS_X11_DISPLAY(gdk_display_get_default())) ? 
+                    mozilla::Preferences::GetBool("gfx.xrender.enabled") : false;
 #endif
 
     uint32_t canvasMask = BackendTypeBit(BackendType::CAIRO) | BackendTypeBit(BackendType::SKIA);
     uint32_t contentMask = BackendTypeBit(BackendType::CAIRO) | BackendTypeBit(BackendType::SKIA);
     InitBackendPrefs(canvasMask, BackendType::CAIRO,
                      contentMask, BackendType::CAIRO);
 }
 
@@ -271,21 +272,25 @@ gfxPlatformGtk::GetScreenDepth() const
 
 void
 gfxPlatformGtk::GetPlatformCMSOutputProfile(void *&mem, size_t &size)
 {
     mem = nullptr;
     size = 0;
 
 #ifdef MOZ_X11
+    GdkDisplay *display = gdk_display_get_default();
+    if (!GDK_IS_X11_DISPLAY(display))
+        return;
+
     const char EDID1_ATOM_NAME[] = "XFree86_DDC_EDID1_RAWDATA";
     const char ICC_PROFILE_ATOM_NAME[] = "_ICC_PROFILE";
 
     Atom edidAtom, iccAtom;
-    Display *dpy = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+    Display *dpy = GDK_DISPLAY_XDISPLAY(display);
     // In xpcshell tests, we never initialize X and hence don't have a Display.
     // In this case, there's no output colour management to be done, so we just
     // return with nullptr.
     if (!dpy)
         return;
  
     Window root = gdk_x11_get_default_root_xwindow();
 
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -1798,16 +1798,32 @@ gfxFontGroup::FamilyFace::CheckState(boo
         }
         if (ufe->WaitForUserFont()) {
             aSkipDrawing = true;
         }
     }
 }
 
 bool
+gfxFontGroup::FamilyFace::EqualsUserFont(const gfxUserFontEntry* aUserFont) const
+{
+    gfxFontEntry* fe = FontEntry();
+    // if there's a font, the entry is the underlying platform font
+    if (mFontCreated) {
+        gfxFontEntry* pfe = aUserFont->GetPlatformFontEntry();
+        if (pfe == fe) {
+            return true;
+        }
+    } else if (fe == aUserFont) {
+        return true;
+    }
+    return false;
+}
+
+bool
 gfxFontGroup::FontLoadingForFamily(gfxFontFamily* aFamily, uint32_t aCh) const
 {
     uint32_t count = mFonts.Length();
     for (uint32_t i = 0; i < count; ++i) {
         const FamilyFace& ff = mFonts[i];
         if (ff.IsLoading() && ff.Family() == aFamily) {
             const gfxUserFontEntry* ufe =
                 static_cast<gfxUserFontEntry*>(ff.FontEntry());
@@ -3025,16 +3041,31 @@ gfxFontGroup::UpdateUserFonts()
             }
             ff.CheckState(mSkipDrawing);
         }
 
         mCurrGeneration = GetGeneration();
     }
 }
 
+bool
+gfxFontGroup::ContainsUserFont(const gfxUserFontEntry* aUserFont)
+{
+    UpdateUserFonts();
+    // search through the fonts list for a specific user font
+    uint32_t len = mFonts.Length();
+    for (uint32_t i = 0; i < len; i++) {
+        FamilyFace& ff = mFonts[i];
+        if (ff.EqualsUserFont(aUserFont)) {
+            return true;
+        }
+    }
+    return false;
+}
+
 struct PrefFontCallbackData {
     explicit PrefFontCallbackData(nsTArray<nsRefPtr<gfxFontFamily> >& aFamiliesArray)
         : mPrefFamilies(aFamiliesArray)
     {}
 
     nsTArray<nsRefPtr<gfxFontFamily> >& mPrefFamilies;
 
     static bool AddFontFamilyEntry(eFontPrefLang aLang, const nsAString& aName, void *aClosure)
--- a/gfx/thebes/gfxTextRun.h
+++ b/gfx/thebes/gfxTextRun.h
@@ -19,16 +19,17 @@
 #include "nsUnicodeScriptCodes.h"
 
 #ifdef DEBUG
 #include <stdio.h>
 #endif
 
 class gfxContext;
 class gfxFontGroup;
+class gfxUserFontEntry;
 class gfxUserFontSet;
 class gfxTextContextPaint;
 class nsIAtom;
 class nsILanguageAtomService;
 class gfxMissingFontRecorder;
 
 /**
  * Callback for Draw() to use when drawing text with mode
@@ -859,16 +860,19 @@ public:
 
     // This will call UpdateUserFonts() if the user font set is changed.
     void SetUserFontSet(gfxUserFontSet *aUserFontSet);
 
     // If there is a user font set, check to see whether the font list or any
     // caches need updating.
     virtual void UpdateUserFonts();
 
+    // search for a specific userfont in the list of fonts
+    bool ContainsUserFont(const gfxUserFontEntry* aUserFont);
+
     bool ShouldSkipDrawing() const {
         return mSkipDrawing;
     }
 
     class LazyReferenceContextGetter {
     public:
       virtual already_AddRefed<gfxContext> GetRefContext() = 0;
     };
@@ -997,16 +1001,18 @@ protected:
             } else {
                 NS_IF_RELEASE(mFontEntry);
             }
             mFont = aFont;
             mFontCreated = true;
             mLoading = false;
         }
 
+        bool EqualsUserFont(const gfxUserFontEntry* aUserFont) const;
+
     private:
         nsRefPtr<gfxFontFamily> mFamily;
         // either a font or a font entry exists
         union {
             gfxFont*            mFont;
             gfxFontEntry*       mFontEntry;
         };
         bool                    mNeedsBold   : 1;
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -915,16 +915,31 @@ gfxUserFontFamily*
 gfxUserFontSet::LookupFamily(const nsAString& aFamilyName) const
 {
     nsAutoString key(aFamilyName);
     ToLowerCase(key);
 
     return mFontFamilies.GetWeak(key);
 }
 
+bool
+gfxUserFontSet::ContainsUserFontSetFonts(const FontFamilyList& aFontList) const
+{
+    for (const FontFamilyName& name : aFontList.GetFontlist()) {
+        if (name.mType != eFamily_named &&
+            name.mType != eFamily_named_quoted) {
+            continue;
+        }
+        if (LookupFamily(name.mName)) {
+            return true;
+        }
+    }
+    return false;
+}
+
 gfxUserFontFamily*
 gfxUserFontSet::GetFamily(const nsAString& aFamilyName)
 {
     nsAutoString key(aFamilyName);
     ToLowerCase(key);
 
     gfxUserFontFamily* family = mFontFamilies.GetWeak(key);
     if (!family) {
--- a/gfx/thebes/gfxUserFontSet.h
+++ b/gfx/thebes/gfxUserFontSet.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 GFX_USER_FONT_SET_H
 #define GFX_USER_FONT_SET_H
 
 #include "gfxFont.h"
+#include "gfxFontFamilyList.h"
 #include "nsRefPtrHashtable.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsIURI.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptError.h"
 #include "nsURIHashKey.h"
 #include "mozilla/net/ReferrerPolicy.h"
@@ -233,16 +234,19 @@ public:
     {
         return LookupFamily(aFamilyName) != nullptr;
     }
 
     // Look up and return the gfxUserFontFamily in mFontFamilies with
     // the given name
     gfxUserFontFamily* LookupFamily(const nsAString& aName) const;
 
+    // Look up names in a fontlist and return true if any are in the set
+    bool ContainsUserFontSetFonts(const mozilla::FontFamilyList& aFontList) const;
+
     // Lookup a font entry for a given style, returns null if not loaded.
     // aFamily must be a family returned by our LookupFamily method.
     // (only used by gfxPangoFontGroup for now)
     gfxUserFontEntry* FindUserFontEntryAndLoad(gfxFontFamily* aFamily,
                                                const gfxFontStyle& aFontStyle,
                                                bool& aNeedsBold,
                                                bool& aWaitForUserFont);
 
@@ -548,17 +552,17 @@ public:
                  uint32_t aItalicStyle,
                  const nsTArray<gfxFontFeature>& aFeatureSettings,
                  uint32_t aLanguageOverride,
                  gfxSparseBitSet* aUnicodeRanges);
 
     virtual gfxFont* CreateFontInstance(const gfxFontStyle* aFontStyle,
                                         bool aNeedsBold);
 
-    gfxFontEntry* GetPlatformFontEntry() { return mPlatformFontEntry; }
+    gfxFontEntry* GetPlatformFontEntry() const { return mPlatformFontEntry; }
 
     // is the font loading or loaded, or did it fail?
     UserFontLoadState LoadState() const { return mUserFontLoadState; }
 
     // whether to wait before using fallback font or not
     bool WaitForUserFont() const {
         return mUserFontLoadState == STATUS_LOADING &&
                mFontDataLoadingState < LOADING_SLOWLY;
--- a/image/decoders/icon/gtk/nsIconChannel.cpp
+++ b/image/decoders/icon/gtk/nsIconChannel.cpp
@@ -5,27 +5,16 @@
 
 #include <stdlib.h>
 #include <unistd.h>
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Endian.h"
 #include <algorithm>
 
-#ifdef MOZ_ENABLE_GNOMEUI
-// Older versions of these headers seem to be missing an extern "C"
-extern "C" {
-#include <libgnome/libgnome.h>
-#include <libgnomeui/gnome-icon-theme.h>
-#include <libgnomeui/gnome-icon-lookup.h>
-
-#include <libgnomevfs/gnome-vfs-file-info.h>
-#include <libgnomevfs/gnome-vfs-ops.h>
-}
-#endif
 #ifdef MOZ_ENABLE_GIO
 #include <gio/gio.h>
 #endif
 
 #include <gtk/gtk.h>
 
 #include "nsMimeTypes.h"
 #include "nsIMIMEService.h"
@@ -38,51 +27,16 @@ extern "C" {
 #include "prlink.h"
 
 #include "nsIconChannel.h"
 
 NS_IMPL_ISUPPORTS(nsIconChannel,
                   nsIRequest,
                   nsIChannel)
 
-#ifdef MOZ_ENABLE_GNOMEUI
-// These let us have a soft dependency on libgnomeui rather than a hard one.
-// These are just basically the prototypes of the functions in the libraries.
-typedef char* (*_GnomeIconLookup_fn)(GtkIconTheme* icon_theme,
-                                     GnomeThumbnailFactory* thumbnail_factory,
-                                     const char* file_uri,
-                                     const char* custom_icon,
-                                     GnomeVFSFileInfo* file_info,
-                                     const char* mime_type,
-                                     GnomeIconLookupFlags flags,
-                                     GnomeIconLookupResultFlags* result);
-typedef GnomeIconTheme* (*_GnomeIconThemeNew_fn)(void);
-typedef int (*_GnomeInit_fn)(const char* app_id, const char* app_version,
-                             int argc, char** argv,
-                             const struct poptOption* options,
-                             int flags, poptContext* return_ctx);
-typedef GnomeProgram* (*_GnomeProgramGet_fn)(void);
-typedef GnomeVFSResult (*_GnomeVFSGetFileInfo_fn)(const gchar* text_uri,
-                        GnomeVFSFileInfo* info,
-                        GnomeVFSFileInfoOptions options);
-typedef void (*_GnomeVFSFileInfoClear_fn)(GnomeVFSFileInfo* info);
-
-static PRLibrary* gLibGnomeUI = nullptr;
-static PRLibrary* gLibGnome = nullptr;
-static PRLibrary* gLibGnomeVFS = nullptr;
-static bool gTriedToLoadGnomeLibs = false;
-
-static _GnomeIconLookup_fn _gnome_icon_lookup = nullptr;
-static _GnomeIconThemeNew_fn _gnome_icon_theme_new = nullptr;
-static _GnomeInit_fn _gnome_init = nullptr;
-static _GnomeProgramGet_fn _gnome_program_get = nullptr;
-static _GnomeVFSGetFileInfo_fn _gnome_vfs_get_file_info = nullptr;
-static _GnomeVFSFileInfoClear_fn _gnome_vfs_file_info_clear = nullptr;
-#endif //MOZ_ENABLE_GNOMEUI
-
 static nsresult
 moz_gdk_pixbuf_to_channel(GdkPixbuf* aPixbuf, nsIURI* aURI,
                           nsIChannel** aChannel)
 {
   int width = gdk_pixbuf_get_width(aPixbuf);
   int height = gdk_pixbuf_get_height(aPixbuf);
   NS_ENSURE_TRUE(height < 256 && width < 256 && height > 0 && width > 0 &&
                  gdk_pixbuf_get_colorspace(aPixbuf) == GDK_COLORSPACE_RGB &&
@@ -157,19 +111,16 @@ moz_gdk_pixbuf_to_channel(GdkPixbuf* aPi
                                   nullPrincipal,
                                   nsILoadInfo::SEC_NORMAL,
                                   nsIContentPolicy::TYPE_OTHER,
                                   NS_LITERAL_CSTRING(IMAGE_ICON_MS));
 }
 
 static GtkWidget* gProtoWindow = nullptr;
 static GtkWidget* gStockImageWidget = nullptr;
-#ifdef MOZ_ENABLE_GNOMEUI
-static GnomeIconTheme* gIconTheme = nullptr;
-#endif //MOZ_ENABLE_GNOMEUI
 
 static void
 ensure_stock_image_widget()
 {
   // Only the style of the GtkImage needs to be used, but the widget is kept
   // to track dynamic style changes.
   if (!gProtoWindow) {
     gProtoWindow = gtk_window_new(GTK_WINDOW_POPUP);
@@ -178,110 +129,16 @@ ensure_stock_image_widget()
 
     gStockImageWidget = gtk_image_new();
     gtk_container_add(GTK_CONTAINER(protoLayout), gStockImageWidget);
 
     gtk_widget_ensure_style(gStockImageWidget);
   }
 }
 
-#ifdef MOZ_ENABLE_GNOMEUI
-static nsresult
-ensure_libgnomeui()
-{
-  // Attempt to get the libgnomeui symbol references. We do it this way
-  // so that stock icons from Init() don't get held back by InitWithGnome()'s
-  // libgnomeui dependency.
-  if (!gTriedToLoadGnomeLibs) {
-    gLibGnomeUI = PR_LoadLibrary("libgnomeui-2.so.0");
-    if (!gLibGnomeUI) {
-      return NS_ERROR_NOT_AVAILABLE;
-    }
-
-    _gnome_init =
-      (_GnomeInit_fn)PR_FindFunctionSymbol(gLibGnomeUI,
-                                           "gnome_init_with_popt_table");
-    _gnome_icon_theme_new =
-      (_GnomeIconThemeNew_fn)PR_FindFunctionSymbol(gLibGnomeUI,
-                                                   "gnome_icon_theme_new");
-    _gnome_icon_lookup =
-      (_GnomeIconLookup_fn)PR_FindFunctionSymbol(gLibGnomeUI,
-                                                 "gnome_icon_lookup");
-
-    if (!_gnome_init || !_gnome_icon_theme_new || !_gnome_icon_lookup) {
-      PR_UnloadLibrary(gLibGnomeUI);
-      gLibGnomeUI = nullptr;
-      return NS_ERROR_NOT_AVAILABLE;
-    }
-  }
-
-  if (!gLibGnomeUI) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  return NS_OK;
-}
-
-static nsresult
-ensure_libgnome()
-{
-  if (!gTriedToLoadGnomeLibs) {
-    gLibGnome = PR_LoadLibrary("libgnome-2.so.0");
-    if (!gLibGnome) {
-      return NS_ERROR_NOT_AVAILABLE;
-    }
-
-    _gnome_program_get =
-      (_GnomeProgramGet_fn)PR_FindFunctionSymbol(gLibGnome,
-                                                 "gnome_program_get");
-    if (!_gnome_program_get) {
-      PR_UnloadLibrary(gLibGnome);
-      gLibGnome = nullptr;
-      return NS_ERROR_NOT_AVAILABLE;
-    }
-  }
-
-  if (!gLibGnome) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  return NS_OK;
-}
-
-static nsresult
-ensure_libgnomevfs()
-{
-  if (!gTriedToLoadGnomeLibs) {
-    gLibGnomeVFS = PR_LoadLibrary("libgnomevfs-2.so.0");
-    if (!gLibGnomeVFS) {
-      return NS_ERROR_NOT_AVAILABLE;
-    }
-
-    _gnome_vfs_get_file_info =
-      (_GnomeVFSGetFileInfo_fn)PR_FindFunctionSymbol(gLibGnomeVFS,
-                                                     "gnome_vfs_get_file_info");
-    _gnome_vfs_file_info_clear =
-        (_GnomeVFSFileInfoClear_fn)PR_FindFunctionSymbol(gLibGnomeVFS,
-                                                         "gnome_vfs_file_"
-                                                         "info_clear");
-    if (!_gnome_vfs_get_file_info || !_gnome_vfs_file_info_clear) {
-      PR_UnloadLibrary(gLibGnomeVFS);
-      gLibGnomeVFS = nullptr;
-      return NS_ERROR_NOT_AVAILABLE;
-    }
-  }
-
-  if (!gLibGnomeVFS) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  return NS_OK;
-}
-#endif //MOZ_ENABLE_GNOMEUI
-
 static GtkIconSize
 moz_gtk_icon_size(const char* name)
 {
   if (strcmp(name, "button") == 0) {
     return GTK_ICON_SIZE_BUTTON;
   }
 
   if (strcmp(name, "menu") == 0) {
@@ -302,17 +159,17 @@ moz_gtk_icon_size(const char* name)
 
   if (strcmp(name, "dialog") == 0) {
     return GTK_ICON_SIZE_DIALOG;
   }
 
   return GTK_ICON_SIZE_MENU;
 }
 
-#if defined(MOZ_ENABLE_GNOMEUI) || defined(MOZ_ENABLE_GIO)
+#ifdef MOZ_ENABLE_GIO
 static int32_t
 GetIconSize(nsIMozIconURI* aIconURI)
 {
   nsAutoCString iconSizeString;
 
   aIconURI->GetIconSize(iconSizeString);
   if (iconSizeString.IsEmpty()) {
     uint32_t size;
@@ -341,159 +198,17 @@ ScaleIconBuf(GdkPixbuf** aBuf, int32_t i
     g_object_unref(*aBuf);
     *aBuf = scaled;
     if (!scaled) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
   }
   return NS_OK;
 }
-#endif
 
-#ifdef MOZ_ENABLE_GNOMEUI
-nsresult
-nsIconChannel::InitWithGnome(nsIMozIconURI* aIconURI)
-{
-  nsresult rv;
-
-  if (NS_FAILED(ensure_libgnomeui()) || NS_FAILED(ensure_libgnome()) ||
-      NS_FAILED(ensure_libgnomevfs())) {
-    gTriedToLoadGnomeLibs = true;
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  gTriedToLoadGnomeLibs = true;
-
-  if (!_gnome_program_get()) {
-    // Get the brandShortName from the string bundle to pass to GNOME
-    // as the application name.  This may be used for things such as
-    // the title of grouped windows in the panel.
-    nsCOMPtr<nsIStringBundleService> bundleService =
-      do_GetService(NS_STRINGBUNDLE_CONTRACTID);
-
-    NS_ASSERTION(bundleService, "String bundle service must be present!");
-
-    nsCOMPtr<nsIStringBundle> bundle;
-    bundleService->CreateBundle("chrome://branding/locale/brand.properties",
-                                getter_AddRefs(bundle));
-    nsAutoString appName;
-
-    if (bundle) {
-      bundle->GetStringFromName(MOZ_UTF16("brandShortName"),
-                                getter_Copies(appName));
-    } else {
-      NS_WARNING(
-         "brand.properties not present, using default application name");
-      appName.AssignLiteral(MOZ_UTF16("Gecko"));
-    }
-
-    char* empty[] = { "" };
-    _gnome_init(NS_ConvertUTF16toUTF8(appName).get(),
-                "1.0", 1, empty, nullptr, 0, nullptr);
-  }
-
-  uint32_t iconSize = GetIconSize(aIconURI);
-  nsAutoCString type;
-  aIconURI->GetContentType(type);
-
-  GnomeVFSFileInfo fileInfo = {0};
-  fileInfo.refcount = 1; // In case some GnomeVFS function addrefs and
-                         // releases it
-
-  nsAutoCString spec;
-  nsCOMPtr<nsIURL> url;
-  rv = aIconURI->GetIconURL(getter_AddRefs(url));
-  if (url) {
-    url->GetAsciiSpec(spec);
-    // Only ask gnome-vfs for a GnomeVFSFileInfo for file: uris, to avoid a
-    // network request
-    bool isFile;
-    if (NS_SUCCEEDED(url->SchemeIs("file", &isFile)) && isFile) {
-      _gnome_vfs_get_file_info(spec.get(), &fileInfo,
-                               GNOME_VFS_FILE_INFO_DEFAULT);
-    }
-    else {
-      // The filename we get is UTF-8-compatible, which matches gnome
-      // expectations.
-      // See also:
-      // http://lists.gnome.org/archives/gnome-vfs-list/2004-March/msg00049.html
-      // "Whenever we can detect the charset used for the URI type we try to
-      //  convert it to/from utf8 automatically inside gnome-vfs."
-      // I'll interpret that as "otherwise, this field is random junk".
-      nsAutoCString name;
-      url->GetFileName(name);
-      fileInfo.name = g_strdup(name.get());
-
-      if (!type.IsEmpty()) {
-        fileInfo.valid_fields = GNOME_VFS_FILE_INFO_FIELDS_MIME_TYPE;
-        fileInfo.mime_type = g_strdup(type.get());
-      }
-    }
-  }
-
-  if (type.IsEmpty()) {
-    nsCOMPtr<nsIMIMEService> ms(do_GetService("@mozilla.org/mime;1"));
-    if (ms) {
-      nsAutoCString fileExt;
-      aIconURI->GetFileExtension(fileExt);
-      if (!fileExt.IsEmpty()) {
-        ms->GetTypeFromExtension(fileExt, type);
-      }
-    }
-  }
-  // Get the icon theme
-  if (!gIconTheme) {
-    gIconTheme = _gnome_icon_theme_new();
-
-    if (!gIconTheme) {
-      _gnome_vfs_file_info_clear(&fileInfo);
-      return NS_ERROR_NOT_AVAILABLE;
-    }
-  }
-
-  char* name = _gnome_icon_lookup(gIconTheme, nullptr, spec.get(), nullptr,
-                                  &fileInfo, type.get(),
-                                  GNOME_ICON_LOOKUP_FLAGS_NONE, nullptr);
-
-  _gnome_vfs_file_info_clear(&fileInfo);
-  if (!name) {
-    return NS_ERROR_NOT_AVAILABLE;
-  }
-
-  // Get the default theme associated with the screen
-  // Do NOT free.
-  GtkIconTheme* theme = gtk_icon_theme_get_default();
-  if (!theme) {
-    g_free(name);
-    return NS_ERROR_UNEXPECTED;
-  }
-
-  GError* err = nullptr;
-  GdkPixbuf* buf = gtk_icon_theme_load_icon(theme, name, iconSize,
-                                            (GtkIconLookupFlags)0, &err);
-  g_free(name);
-
-  if (!buf) {
-    if (err) {
-      g_error_free(err);
-    }
-    return NS_ERROR_UNEXPECTED;
-  }
-
-  rv = ScaleIconBuf(&buf, iconSize);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = moz_gdk_pixbuf_to_channel(buf, aIconURI,
-                                 getter_AddRefs(mRealChannel));
-  g_object_unref(buf);
-  return rv;
-}
-#endif // MOZ_ENABLE_GNOMEUI
-
-#ifdef MOZ_ENABLE_GIO
 nsresult
 nsIconChannel::InitWithGIO(nsIMozIconURI* aIconURI)
 {
   GIcon *icon = nullptr;
   nsCOMPtr<nsIURL> fileURI;
 
   // Read icon content
   aIconURI->GetIconURL(getter_AddRefs(fileURI));
@@ -589,25 +304,21 @@ nsresult
 nsIconChannel::Init(nsIURI* aURI)
 {
   nsCOMPtr<nsIMozIconURI> iconURI = do_QueryInterface(aURI);
   NS_ASSERTION(iconURI, "URI is not an nsIMozIconURI");
 
   nsAutoCString stockIcon;
   iconURI->GetStockIcon(stockIcon);
   if (stockIcon.IsEmpty()) {
-#ifdef MOZ_ENABLE_GNOMEUI
-    return InitWithGnome(iconURI);
-#else
 #ifdef MOZ_ENABLE_GIO
     return InitWithGIO(iconURI);
 #else
     return NS_ERROR_NOT_AVAILABLE;
 #endif
-#endif
   }
 
   // Search for stockIcon
   nsAutoCString iconSizeString;
   iconURI->GetIconSize(iconSizeString);
 
   nsAutoCString iconStateString;
   iconURI->GetIconState(iconStateString);
@@ -705,28 +416,9 @@ nsIconChannel::Init(nsIURI* aURI)
 
 void
 nsIconChannel::Shutdown() {
   if (gProtoWindow) {
     gtk_widget_destroy(gProtoWindow);
     gProtoWindow = nullptr;
     gStockImageWidget = nullptr;
   }
-#ifdef MOZ_ENABLE_GNOMEUI
-  if (gIconTheme) {
-    g_object_unref(G_OBJECT(gIconTheme));
-    gIconTheme = nullptr;
-  }
-  gTriedToLoadGnomeLibs = false;
-  if (gLibGnomeUI) {
-    PR_UnloadLibrary(gLibGnomeUI);
-    gLibGnomeUI = nullptr;
-  }
-  if (gLibGnome) {
-    PR_UnloadLibrary(gLibGnome);
-    gLibGnome = nullptr;
-  }
-  if (gLibGnomeVFS) {
-    PR_UnloadLibrary(gLibGnomeVFS);
-    gLibGnomeVFS = nullptr;
-  }
-#endif //MOZ_ENABLE_GNOMEUI
 }
--- a/image/decoders/icon/gtk/nsIconChannel.h
+++ b/image/decoders/icon/gtk/nsIconChannel.h
@@ -32,14 +32,12 @@ class nsIconChannel MOZ_FINAL : public n
     /// If this method fails, no other function must be called on this object.
     nsresult Init(nsIURI* aURI);
   private:
     ~nsIconChannel() { }
     /// The channel to the temp icon file (e.g. to /tmp/2qy9wjqw.html).
     /// Will always be non-null after a successful Init.
     nsCOMPtr<nsIChannel> mRealChannel;
 
-    /// Called by Init if we need to use the gnomeui library.
-    nsresult InitWithGnome(nsIMozIconURI* aURI);
     nsresult InitWithGIO(nsIMozIconURI* aIconURI);
 };
 
 #endif
--- a/js/src/jit/VMFunctions.h
+++ b/js/src/jit/VMFunctions.h
@@ -247,21 +247,16 @@ struct VMFunction
     {
         // Check for valid failure/return type.
         MOZ_ASSERT_IF(outParam != Type_Void, returnType == Type_Bool);
         MOZ_ASSERT(returnType == Type_Bool ||
                    returnType == Type_Object);
     }
 
     VMFunction(const VMFunction &o) {
-        init(o);
-    }
-
-    void init(const VMFunction &o) {
-        MOZ_ASSERT(!wrapped);
         *this = o;
         addToFunctions();
     }
 
   private:
     // Add this to the global list of VMFunctions.
     void addToFunctions();
 };
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1965,27 +1965,19 @@ ArenaList::removeRemainingArenas(ArenaHe
 #endif
     ArenaHeader *remainingArenas = *arenap;
     *arenap = nullptr;
     check();
     return remainingArenas;
 }
 
 static bool
-ShouldRelocateAllArenas(JSRuntime *runtime)
-{
-    // In compacting zeal mode and in debug builds on 64 bit architectures, we
-    // relocate all arenas. The purpose of this is to balance test coverage of
-    // object moving with test coverage of the arena selection routine in
-    // pickArenasToRelocate().
-#if defined(DEBUG) && defined(JS_PUNBOX64)
-    return true;
-#else
-    return runtime->gc.zeal() == ZealCompactValue;
-#endif
+ShouldRelocateAllArenas(JS::gcreason::Reason reason)
+{
+    return reason == JS::gcreason::DEBUG_GC;
 }
 
 /*
  * Choose which arenas to relocate all cells from. Return an arena cursor that
  * can be passed to removeRemainingArenas().
  */
 ArenaHeader **
 ArenaList::pickArenasToRelocate(size_t &arenaTotalOut, size_t &relocTotalOut)
@@ -2182,17 +2174,17 @@ ArenaLists::relocateArenas(ArenaHeader *
     MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
     MOZ_ASSERT(runtime_->isHeapCompacting());
     MOZ_ASSERT(!runtime_->gc.isBackgroundSweeping());
 
     // Flush all the freeLists back into the arena headers
     purge();
     checkEmptyFreeLists();
 
-    if (ShouldRelocateAllArenas(runtime_)) {
+    if (ShouldRelocateAllArenas(reason)) {
         for (size_t i = 0; i < FINALIZE_LIMIT; i++) {
             if (CanRelocateAllocKind(AllocKind(i))) {
                 ArenaList &al = arenaLists[i];
                 ArenaHeader *allArenas = al.head();
                 al.clear();
                 relocatedListOut = al.relocateArenas(allArenas, relocatedListOut, stats);
             }
         }
@@ -5526,17 +5518,17 @@ GCRuntime::compactPhase(JS::gcreason::Re
 #endif
 
     // Release the relocated arenas, or in debug builds queue them to be
     // released until the start of the next GC unless this is the last GC or we
     // are doing a last ditch GC.
 #ifndef DEBUG
     releaseRelocatedArenas(relocatedList);
 #else
-    if (reason == JS::gcreason::DESTROY_RUNTIME || reason == JS::gcreason::LAST_DITCH) {
+    if (reason != JS::gcreason::DEBUG_GC) {
         releaseRelocatedArenas(relocatedList);
     } else {
         MOZ_ASSERT(!relocatedArenasToRelease);
         protectRelocatedArenas(relocatedList);
         relocatedArenasToRelease = relocatedList;
     }
 #endif
 
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -1003,27 +1003,24 @@ XrayTraits::cloneExpandoChain(JSContext 
 
     RootedObject oldHead(cx, getExpandoChain(src));
 
 #ifdef DEBUG
     // When this is called from dom::ReparentWrapper() there will be no native
     // set for |dst|. Eventually it will be set to that of |src|.  This will
     // prevent attachExpandoObject() from preserving the wrapper, but this is
     // not a problem because in this case the wrapper will already have been
-    // preserved when expandos were originally added to |src|. In this case
-    // assert the wrapper for |src| has been preserved.
+    // preserved when expandos were originally added to |src|. Assert the
+    // wrapper for |src| has been preserved if it has expandos set.
     if (oldHead) {
-        nsISupports *dstId = mozilla::dom::UnwrapDOMObjectToISupports(dst);
-        if (!dstId) {
-            nsISupports *srcId = mozilla::dom::UnwrapDOMObjectToISupports(src);
-            if (srcId) {
-              nsWrapperCache* cache = nullptr;
-              CallQueryInterface(srcId, &cache);
-              MOZ_ASSERT_IF(cache, cache->PreservingWrapper());
-            }
+        nsISupports *identity = mozilla::dom::UnwrapDOMObjectToISupports(src);
+        if (identity) {
+            nsWrapperCache* cache = nullptr;
+            CallQueryInterface(identity, &cache);
+            MOZ_ASSERT_IF(cache, cache->PreservingWrapper());
         }
     }
 #endif
 
     while (oldHead) {
         RootedObject exclusive(cx, JS_GetReservedSlot(oldHead,
                                                       JSSLOT_EXPANDO_EXCLUSIVE_GLOBAL)
                                                      .toObjectOrNull());
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -61,16 +61,17 @@
 #include "gfxPrefs.h"
 #include "nsIDOMChromeWindow.h"
 #include "nsFrameLoader.h"
 #include "mozilla/dom/FontFaceSet.h"
 #include "nsContentUtils.h"
 #include "nsPIWindowRoot.h"
 #include "mozilla/Preferences.h"
 #include "gfxTextRun.h"
+#include "nsFontFaceUtils.h"
 
 // Needed for Start/Stop of Image Animation
 #include "imgIContainer.h"
 #include "nsIImageLoadingContent.h"
 
 #include "nsCSSParser.h"
 #include "nsBidiUtils.h"
 #include "nsServiceManagerUtils.h"
@@ -1847,16 +1848,17 @@ nsPresContext::RebuildAllStyleData(nsCha
                                    nsRestyleHint aRestyleHint)
 {
   if (!mShell) {
     // We must have been torn down. Nothing to do here.
     return;
   }
 
   mUsesRootEMUnits = false;
+  mUsesExChUnits = false;
   mUsesViewportUnits = false;
   RebuildUserFontSet();
   RebuildCounterStyles();
 
   RestyleManager()->RebuildAllStyleData(aExtraHint, aRestyleHint);
 }
 
 void
@@ -2139,34 +2141,57 @@ nsPresContext::RebuildUserFontSet()
       NS_NewRunnableMethod(this, &nsPresContext::HandleRebuildUserFontSet);
     if (NS_SUCCEEDED(NS_DispatchToCurrentThread(ev))) {
       mPostedFlushUserFontSet = true;
     }
   }
 }
 
 void
-nsPresContext::UserFontSetUpdated()
+nsPresContext::UserFontSetUpdated(gfxUserFontEntry* aUpdatedFont)
 {
   if (!mShell)
     return;
 
-  // Changes to the set of available fonts can cause updates to layout by:
-  //
-  //   1. Changing the font used for text, which changes anything that
-  //      depends on text measurement, including line breaking and
-  //      intrinsic widths, and any other parts of layout that depend on
-  //      font metrics.  This requires a style change reflow to update.
-  //
-  //   2. Changing the value of the 'ex' and 'ch' units in style data,
-  //      which also depend on font metrics.  Updating this information
-  //      requires rebuilding the rule tree from the top, avoiding the
-  //      reuse of cached data even when no style rules have changed.
-
-  PostRebuildAllStyleDataEvent(NS_STYLE_HINT_REFLOW, eRestyle_ForceDescendants);
+  bool usePlatformFontList = true;
+#if defined(MOZ_WIDGET_GTK) || defined(MOZ_WIDGET_QT)
+  usePlatformFontList = false;
+#endif
+
+  // xxx - until the Linux platform font list is always used, use full
+  // restyle to force updates with gfxPangoFontGroup usage
+  // Note: this method is called without a font when rules in the userfont set
+  // are updated, which may occur during reflow as a result of the lazy
+  // initialization of the userfont set. It would be better to avoid a full
+  // restyle but until this method is only called outside of reflow, schedule a
+  // full restyle in these cases.
+  if (!usePlatformFontList || !aUpdatedFont) {
+    PostRebuildAllStyleDataEvent(NS_STYLE_HINT_REFLOW, eRestyle_ForceDescendants);
+    return;
+  }
+
+  // Special case - if either the 'ex' or 'ch' units are used, these
+  // depend upon font metrics. Updating this information requires
+  // rebuilding the rule tree from the top, avoiding the reuse of cached
+  // data even when no style rules have changed.
+
+  if (UsesExChUnits()) {
+    // xxx - dbaron said this should work but get ex/ch related reftest failures
+    // PostRebuildAllStyleDataEvent(nsChangeHint(0), eRestyle_ForceDescendants);
+    PostRebuildAllStyleDataEvent(NS_STYLE_HINT_REFLOW, eRestyle_ForceDescendants);
+    return;
+  }
+
+  // Iterate over the frame tree looking for frames associated with the
+  // downloadable font family in question. If a frame's nsStyleFont has
+  // the name, check the font group associated with the metrics to see if
+  // it contains that specific font (i.e. the one chosen within the family
+  // given the weight, width, and slant from the nsStyleFont). If it does,
+  // mark that frame dirty and skip inspecting its descendants.
+  nsFontFaceUtils::MarkDirtyForFontChange(mShell->GetRootFrame(), aUpdatedFont);
 }
 
 FontFaceSet*
 nsPresContext::Fonts()
 {
   if (!mFontFaceSet) {
     mFontFaceSet = new FontFaceSet(mDocument->GetInnerWindow(), this);
     GetUserFontSet();  // this will cause the user font set to be created/updated
--- a/layout/base/nsPresContext.h
+++ b/layout/base/nsPresContext.h
@@ -51,16 +51,17 @@ class nsIContent;
 class nsIFrame;
 class nsFrameManager;
 class nsILinkHandler;
 class nsIAtom;
 class nsICSSPseudoComparator;
 struct nsStyleBackground;
 struct nsStyleBorder;
 class nsIRunnable;
+class gfxUserFontEntry;
 class gfxUserFontSet;
 class gfxTextPerfMetrics;
 struct nsFontFaceRuleContainer;
 class nsPluginFrame;
 class nsTransitionManager;
 class nsAnimationManager;
 class nsRefreshDriver;
 class nsIWidget;
@@ -885,17 +886,17 @@ public:
 #endif
 
   void FlushUserFontSet();
   void RebuildUserFontSet(); // asynchronously
 
   // Should be called whenever the set of fonts available in the user
   // font set changes (e.g., because a new font loads, or because the
   // user font set is changed and fonts become unavailable).
-  void UserFontSetUpdated();
+  void UserFontSetUpdated(gfxUserFontEntry* aUpdatedFont = nullptr);
 
   gfxMissingFontRecorder *MissingFontRecorder() { return mMissingFonts; }
   void NotifyMissingFonts();
 
   mozilla::dom::FontFaceSet* Fonts();
 
   void FlushCounterStyles();
   void RebuildCounterStyles(); // asynchronously
@@ -1021,16 +1022,24 @@ public:
   bool UsesRootEMUnits() const {
     return mUsesRootEMUnits;
   }
 
   void SetUsesRootEMUnits(bool aValue) {
     mUsesRootEMUnits = aValue;
   }
 
+  bool UsesExChUnits() const {
+    return mUsesExChUnits;
+  }
+
+  void SetUsesExChUnits(bool aValue) {
+    mUsesExChUnits = aValue;
+  }
+
   bool UsesViewportUnits() const {
     return mUsesViewportUnits;
   }
 
   void SetUsesViewportUnits(bool aValue) {
     mUsesViewportUnits = aValue;
   }
 
@@ -1343,16 +1352,18 @@ protected:
   // entire viewport
   unsigned              mAllInvalidated : 1;
 
   // Are we currently drawing an SVG glyph?
   unsigned              mIsGlyph : 1;
 
   // Does the associated document use root-em (rem) units?
   unsigned              mUsesRootEMUnits : 1;
+  // Does the associated document use ex or ch units?
+  unsigned              mUsesExChUnits : 1;
   // Does the associated document use viewport units (vw/vh/vmin/vmax)?
   unsigned              mUsesViewportUnits : 1;
 
   // Has there been a change to the viewport's dimensions?
   unsigned              mPendingViewportChange : 1;
 
   // Is the current mFontFaceSet valid?
   unsigned              mFontFaceSetDirty : 1;
--- a/layout/mathml/nsMathMLFrame.cpp
+++ b/layout/mathml/nsMathMLFrame.cpp
@@ -226,16 +226,17 @@ nsMathMLFrame::CalcLength(nsPresContext*
 
   nsCSSUnit unit = aCSSValue.GetUnit();
 
   if (eCSSUnit_EM == unit) {
     const nsStyleFont* font = aStyleContext->StyleFont();
     return NSToCoordRound(aCSSValue.GetFloatValue() * (float)font->mFont.size);
   }
   else if (eCSSUnit_XHeight == unit) {
+    aPresContext->SetUsesExChUnits(true);
     nsRefPtr<nsFontMetrics> fm;
     nsLayoutUtils::GetFontMetricsForStyleContext(aStyleContext,
                                                  getter_AddRefs(fm),
                                                  aFontSizeInflation);
     nscoord xHeight = fm->XHeight();
     return NSToCoordRound(aCSSValue.GetFloatValue() * (float)xHeight);
   }
 
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-face/reflow-sanity-1-data.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>test @font-face reflow sanity</title>
+<meta charset="utf-8">
+<style>
+body { margin: 20px }
+
+@font-face {
+  font-family: reflow1data;
+  src: url(data:font/opentype;base64,AAEAAAANAIAAAwBQRkZUTU6u6MkAAAXcAAAAHE9TLzJWYWQKAAABWAAAAFZjbWFwAA8D7wAAAcAAAAFCY3Z0IAAhAnkAAAMEAAAABGdhc3D//wADAAAF1AAAAAhnbHlmCC6aTwAAAxQAAACMaGVhZO8ooBcAAADcAAAANmhoZWEIkAV9AAABFAAAACRobXR4EZQAhQAAAbAAAAAQbG9jYQBwAFQAAAMIAAAACm1heHAASQA9AAABOAAAACBuYW1lehAVOgAAA6AAAAIHcG9zdP+uADUAAAWoAAAAKgABAAAAAQAAMhPyuV8PPPUACwPoAAAAAMU4Lm0AAAAAxTgubQAh/5wFeAK8AAAACAACAAAAAAAAAAEAAAK8/5wAWgXcAAAAAAV4AAEAAAAAAAAAAAAAAAAAAAAEAAEAAAAEAAwAAwAAAAAAAgAAAAEAAQAAAEAALgAAAAAAAQXcAfQABQAAAooCvAAAAIwCigK8AAAB4AAxAQIAAAIABgkAAAAAAAAAAAABAAAAAAAAAAAAAAAAUGZFZABAAEEAQQMg/zgAWgK8AGQAAAABAAAAAAAABdwAIQAAAAAF3AAABdwAZAAAAAMAAAADAAAAHAABAAAAAAA8AAMAAQAAABwABAAgAAAABAAEAAEAAABB//8AAABB////wgABAAAAAAAAAQYAAAEAAAAAAAAAAQIAAAACAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAnkAAAAqACoAKgBGAAAAAgAhAAABKgKaAAMABwAusQEALzyyBwQA7TKxBgXcPLIDAgDtMgCxAwAvPLIFBADtMrIHBgH8PLIBAgDtMjMRIREnMxEjIQEJ6MfHApr9ZiECWAAAAwBk/5wFeAK8AAMABwALAAABNSEVATUhFQE1IRUB9AH0/UQDhPu0BRQB9MjI/tTIyP7UyMgAAAAAAA4ArgABAAAAAAAAACYATgABAAAAAAABAAUAgQABAAAAAAACAAYAlQABAAAAAAADACEA4AABAAAAAAAEAAUBDgABAAAAAAAFABABNgABAAAAAAAGAAUBUwADAAEECQAAAEwAAAADAAEECQABAAoAdQADAAEECQACAAwAhwADAAEECQADAEIAnAADAAEECQAEAAoBAgADAAEECQAFACABFAADAAEECQAGAAoBRwBDAG8AcAB5AHIAaQBnAGgAdAAgACgAYwApACAAMgAwADAAOAAgAE0AbwB6AGkAbABsAGEAIABDAG8AcgBwAG8AcgBhAHQAaQBvAG4AAENvcHlyaWdodCAoYykgMjAwOCBNb3ppbGxhIENvcnBvcmF0aW9uAABNAGEAcgBrAEEAAE1hcmtBAABNAGUAZABpAHUAbQAATWVkaXVtAABGAG8AbgB0AEYAbwByAGcAZQAgADIALgAwACAAOgAgAE0AYQByAGsAQQAgADoAIAA1AC0AMQAxAC0AMgAwADAAOAAARm9udEZvcmdlIDIuMCA6IE1hcmtBIDogNS0xMS0yMDA4AABNAGEAcgBrAEEAAE1hcmtBAABWAGUAcgBzAGkAbwBuACAAMAAwADEALgAwADAAMAAgAABWZXJzaW9uIDAwMS4wMDAgAABNAGEAcgBrAEEAAE1hcmtBAAAAAgAAAAAAAP+DADIAAAABAAAAAAAAAAAAAAAAAAAAAAAEAAAAAQACACQAAAAAAAH//wACAAAAAQAAAADEPovuAAAAAMU4Lm0AAAAAxTgubQ==);
+}
+
+@font-face {
+  font-family: reflow1data;
+  src: url(data:font/opentype;base64,AAEAAAANAIAAAwBQRkZUTU6u6MkAAAXcAAAAHE9TLzJWYmQLAAABWAAAAFZjbWFwAw8D7QAAAcAAAAFCY3Z0IAAhAnkAAAMEAAAABGdhc3D//wADAAAF1AAAAAhnbHlmCC6aTwAAAxQAAACMaGVhZO8ooBcAAADcAAAANmhoZWEIkAV9AAABFAAAACRobXR4EZQAhQAAAbAAAAAQbG9jYQBwAFQAAAMIAAAACm1heHAASQA9AAABOAAAACBuYW1lfBIXPAAAA6AAAAIHcG9zdP+vADUAAAWoAAAAKgABAAAAAQAAKAvut18PPPUACwPoAAAAAMU4Lm0AAAAAxTgubQAh/5wFeAK8AAAACAACAAAAAAAAAAEAAAK8/5wAWgXcAAAAAAV4AAEAAAAAAAAAAAAAAAAAAAAEAAEAAAAEAAwAAwAAAAAAAgAAAAEAAQAAAEAALgAAAAAAAQXcAfQABQAAAooCvAAAAIwCigK8AAAB4AAxAQIAAAIABgkAAAAAAAAAAAABAAAAAAAAAAAAAAAAUGZFZABAAEIAQgMg/zgAWgK8AGQAAAABAAAAAAAABdwAIQAAAAAF3AAABdwAZAAAAAMAAAADAAAAHAABAAAAAAA8AAMAAQAAABwABAAgAAAABAAEAAEAAABC//8AAABC////wQABAAAAAAAAAQYAAAEAAAAAAAAAAQIAAAACAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAnkAAAAqACoAKgBGAAAAAgAhAAABKgKaAAMABwAusQEALzyyBwQA7TKxBgXcPLIDAgDtMgCxAwAvPLIFBADtMrIHBgH8PLIBAgDtMjMRIREnMxEjIQEJ6MfHApr9ZiECWAAAAwBk/5wFeAK8AAMABwALAAABNSEVATUhFQE1IRUB9AH0/UQDhPu0BRQB9MjI/tTIyP7UyMgAAAAAAA4ArgABAAAAAAAAACYATgABAAAAAAABAAUAgQABAAAAAAACAAYAlQABAAAAAAADACEA4AABAAAAAAAEAAUBDgABAAAAAAAFABABNgABAAAAAAAGAAUBUwADAAEECQAAAEwAAAADAAEECQABAAoAdQADAAEECQACAAwAhwADAAEECQADAEIAnAADAAEECQAEAAoBAgADAAEECQAFACABFAADAAEECQAGAAoBRwBDAG8AcAB5AHIAaQBnAGgAdAAgACgAYwApACAAMgAwADAAOAAgAE0AbwB6AGkAbABsAGEAIABDAG8AcgBwAG8AcgBhAHQAaQBvAG4AAENvcHlyaWdodCAoYykgMjAwOCBNb3ppbGxhIENvcnBvcmF0aW9uAABNAGEAcgBrAEIAAE1hcmtCAABNAGUAZABpAHUAbQAATWVkaXVtAABGAG8AbgB0AEYAbwByAGcAZQAgADIALgAwACAAOgAgAE0AYQByAGsAQgAgADoAIAA1AC0AMQAxAC0AMgAwADAAOAAARm9udEZvcmdlIDIuMCA6IE1hcmtCIDogNS0xMS0yMDA4AABNAGEAcgBrAEIAAE1hcmtCAABWAGUAcgBzAGkAbwBuACAAMAAwADEALgAwADAAMAAgAABWZXJzaW9uIDAwMS4wMDAgAABNAGEAcgBrAEIAAE1hcmtCAAAAAgAAAAAAAP+DADIAAAABAAAAAAAAAAAAAAAAAAAAAAAEAAAAAQACACUAAAAAAAH//wACAAAAAQAAAADEPovuAAAAAMU4Lm0AAAAAxTgubQ==);
+  font-weight: bold;
+}
+
+@font-face {
+  font-family: reflow1data;
+  src: url(data:font/opentype;base64,AAEAAAANAIAAAwBQRkZUTU6u6MkAAAXcAAAAHE9TLzJWY2QMAAABWAAAAFZjbWFwABID7gAAAcAAAAFCY3Z0IAAhAnkAAAMEAAAABGdhc3D//wADAAAF1AAAAAhnbHlmCC6aTwAAAxQAAACMaGVhZO8ooBcAAADcAAAANmhoZWEIkAV9AAABFAAAACRobXR4EZQAhQAAAbAAAAAQbG9jYQBwAFQAAAMIAAAACm1heHAASQA9AAABOAAAACBuYW1lfhQZPgAAA6AAAAIHcG9zdP+wADUAAAWoAAAAKgABAAAAAQAAKf3qr18PPPUACwPoAAAAAMU4Lm0AAAAAxTgubQAh/5wFeAK8AAAACAACAAAAAAAAAAEAAAK8/5wAWgXcAAAAAAV4AAEAAAAAAAAAAAAAAAAAAAAEAAEAAAAEAAwAAwAAAAAAAgAAAAEAAQAAAEAALgAAAAAAAQXcAfQABQAAAooCvAAAAIwCigK8AAAB4AAxAQIAAAIABgkAAAAAAAAAAAABAAAAAAAAAAAAAAAAUGZFZABAAEMAQwMg/zgAWgK8AGQAAAABAAAAAAAABdwAIQAAAAAF3AAABdwAZAAAAAMAAAADAAAAHAABAAAAAAA8AAMAAQAAABwABAAgAAAABAAEAAEAAABD//8AAABD////wAABAAAAAAAAAQYAAAEAAAAAAAAAAQIAAAACAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAnkAAAAqACoAKgBGAAAAAgAhAAABKgKaAAMABwAusQEALzyyBwQA7TKxBgXcPLIDAgDtMgCxAwAvPLIFBADtMrIHBgH8PLIBAgDtMjMRIREnMxEjIQEJ6MfHApr9ZiECWAAAAwBk/5wFeAK8AAMABwALAAABNSEVATUhFQE1IRUB9AH0/UQDhPu0BRQB9MjI/tTIyP7UyMgAAAAAAA4ArgABAAAAAAAAACYATgABAAAAAAABAAUAgQABAAAAAAACAAYAlQABAAAAAAADACEA4AABAAAAAAAEAAUBDgABAAAAAAAFABABNgABAAAAAAAGAAUBUwADAAEECQAAAEwAAAADAAEECQABAAoAdQADAAEECQACAAwAhwADAAEECQADAEIAnAADAAEECQAEAAoBAgADAAEECQAFACABFAADAAEECQAGAAoBRwBDAG8AcAB5AHIAaQBnAGgAdAAgACgAYwApACAAMgAwADAAOAAgAE0AbwB6AGkAbABsAGEAIABDAG8AcgBwAG8AcgBhAHQAaQBvAG4AAENvcHlyaWdodCAoYykgMjAwOCBNb3ppbGxhIENvcnBvcmF0aW9uAABNAGEAcgBrAEMAAE1hcmtDAABNAGUAZABpAHUAbQAATWVkaXVtAABGAG8AbgB0AEYAbwByAGcAZQAgADIALgAwACAAOgAgAE0AYQByAGsAQwAgADoAIAA1AC0AMQAxAC0AMgAwADAAOAAARm9udEZvcmdlIDIuMCA6IE1hcmtDIDogNS0xMS0yMDA4AABNAGEAcgBrAEMAAE1hcmtDAABWAGUAcgBzAGkAbwBuACAAMAAwADEALgAwADAAMAAgAABWZXJzaW9uIDAwMS4wMDAgAABNAGEAcgBrAEMAAE1hcmtDAAAAAgAAAAAAAP+DADIAAAABAAAAAAAAAAAAAAAAAAAAAAAEAAAAAQACACYAAAAAAAH//wACAAAAAQAAAADEPovuAAAAAMU4Lm0AAAAAxTgubQ==);
+  font-style: italic;
+}
+
+@font-face {
+  font-family: reflow1data;
+  src: url(data:font/opentype;base64,AAEAAAANAIAAAwBQRkZUTU6u6MkAAAXcAAAAHE9TLzJWZGQNAAABWAAAAFZjbWFwAA8G7wAAAcAAAAFCY3Z0IAAhAnkAAAMEAAAABGdhc3D//wADAAAF1AAAAAhnbHlmCC6aTwAAAxQAAACMaGVhZO8ooBcAAADcAAAANmhoZWEIkAV9AAABFAAAACRobXR4EZQAhQAAAbAAAAAQbG9jYQBwAFQAAAMIAAAACm1heHAASQA9AAABOAAAACBuYW1lgBYbQAAAA6AAAAIHcG9zdP+xADUAAAWoAAAAKgABAAAAAQAAJfvgp18PPPUACwPoAAAAAMU4Lm0AAAAAxTgubQAh/5wFeAK8AAAACAACAAAAAAAAAAEAAAK8/5wAWgXcAAAAAAV4AAEAAAAAAAAAAAAAAAAAAAAEAAEAAAAEAAwAAwAAAAAAAgAAAAEAAQAAAEAALgAAAAAAAQXcAfQABQAAAooCvAAAAIwCigK8AAAB4AAxAQIAAAIABgkAAAAAAAAAAAABAAAAAAAAAAAAAAAAUGZFZABAAEQARAMg/zgAWgK8AGQAAAABAAAAAAAABdwAIQAAAAAF3AAABdwAZAAAAAMAAAADAAAAHAABAAAAAAA8AAMAAQAAABwABAAgAAAABAAEAAEAAABE//8AAABE////vwABAAAAAAAAAQYAAAEAAAAAAAAAAQIAAAACAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAhAnkAAAAqACoAKgBGAAAAAgAhAAABKgKaAAMABwAusQEALzyyBwQA7TKxBgXcPLIDAgDtMgCxAwAvPLIFBADtMrIHBgH8PLIBAgDtMjMRIREnMxEjIQEJ6MfHApr9ZiECWAAAAwBk/5wFeAK8AAMABwALAAABNSEVATUhFQE1IRUB9AH0/UQDhPu0BRQB9MjI/tTIyP7UyMgAAAAAAA4ArgABAAAAAAAAACYATgABAAAAAAABAAUAgQABAAAAAAACAAYAlQABAAAAAAADACEA4AABAAAAAAAEAAUBDgABAAAAAAAFABABNgABAAAAAAAGAAUBUwADAAEECQAAAEwAAAADAAEECQABAAoAdQADAAEECQACAAwAhwADAAEECQADAEIAnAADAAEECQAEAAoBAgADAAEECQAFACABFAADAAEECQAGAAoBRwBDAG8AcAB5AHIAaQBnAGgAdAAgACgAYwApACAAMgAwADAAOAAgAE0AbwB6AGkAbABsAGEAIABDAG8AcgBwAG8AcgBhAHQAaQBvAG4AAENvcHlyaWdodCAoYykgMjAwOCBNb3ppbGxhIENvcnBvcmF0aW9uAABNAGEAcgBrAEQAAE1hcmtEAABNAGUAZABpAHUAbQAATWVkaXVtAABGAG8AbgB0AEYAbwByAGcAZQAgADIALgAwACAAOgAgAE0AYQByAGsARAAgADoAIAA1AC0AMQAxAC0AMgAwADAAOAAARm9udEZvcmdlIDIuMCA6IE1hcmtEIDogNS0xMS0yMDA4AABNAGEAcgBrAEQAAE1hcmtEAABWAGUAcgBzAGkAbwBuACAAMAAwADEALgAwADAAMAAgAABWZXJzaW9uIDAwMS4wMDAgAABNAGEAcgBrAEQAAE1hcmtEAAAAAgAAAAAAAP+DADIAAAABAAAAAAAAAAAAAAAAAAAAAAAEAAAAAQACACcAAAAAAAH//wACAAAAAQAAAADEPovuAAAAAMU4Lm0AAAAAxTgubQ==);
+  font-weight: bold;
+  font-style: italic;
+}
+
+div#test {
+  font-family: reflow1data;
+  font-size: 400%;
+  line-height: 1em;
+}
+
+div#test p {
+  margin: 0;
+  display: inline-block;
+}
+</style>
+</head>
+
+<body>
+<div id=test>
+<p>A</p>
+<p style="font-weight: bold">B</p>
+<p style="font-style: italic">C</p>
+<p style="font-style: italic; font-weight: bold">D</p>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-face/reflow-sanity-1-ref.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>test @font-face reflow sanity</title>
+<meta charset="utf-8">
+<style>
+body { margin: 20px }
+
+@font-face {
+  font-family: reflow1ref;
+  src: url(../fonts/markA.ttf);
+}
+
+div#test {
+  font-family: reflow1ref;
+  font-size: 400%;
+  line-height: 1em;
+}
+
+div#test p {
+  margin: 0;
+  display: inline-block;
+}
+</style>
+</head>
+
+<body>
+<div id=test>
+<p>A</p>
+<p>A</p>
+<p>A</p>
+<p>A</p>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-face/reflow-sanity-1.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>test @font-face reflow sanity</title>
+<meta charset="utf-8">
+<style>
+body { margin: 20px }
+
+@font-face {
+  font-family: reflow1;
+  src: url(../fonts/markA.ttf);
+}
+
+@font-face {
+  font-family: reflow1;
+  src: url(../fonts/markB.ttf);
+  font-weight: bold;
+}
+
+@font-face {
+  font-family: reflow1;
+  src: url(../fonts/markC.ttf);
+  font-style: italic;
+}
+
+@font-face {
+  font-family: reflow1;
+  src: url(../fonts/markD.ttf);
+  font-weight: bold;
+  font-style: italic;
+}
+
+div#test {
+  font-family: reflow1;
+  font-size: 400%;
+  line-height: 1em;
+}
+
+div#test p {
+  margin: 0;
+  display: inline-block;
+}
+</style>
+</head>
+
+<body>
+<div id=test>
+<p>A</p>
+<p style="font-weight: bold">B</p>
+<p style="font-style: italic">C</p>
+<p style="font-style: italic; font-weight: bold">D</p>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-face/reflow-sanity-delay-1-metrics.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>test @font-face reflow sanity</title>
+<meta charset="utf-8">
+<style>
+body { margin: 20px }
+
+@font-face {
+  font-family: reflow1metrics;
+  src: url(../fonts/markfonts-delay.sjs?font=markA&delay=2500&test=delay-1-metrics);
+}
+
+@font-face {
+  font-family: reflow1metrics;
+  src: url(../fonts/markfonts-delay.sjs?font=markB&delay=2200&test=delay-1-metrics);
+  font-weight: bold;
+}
+
+@font-face {
+  font-family: reflow1metrics;
+  src: url(../fonts/markfonts-delay.sjs?font=markC&delay=200&test=delay-1-metrics);
+  font-style: italic;
+}
+
+@font-face {
+  font-family: reflow1metrics;
+  src: url(../fonts/markfonts-delay.sjs?font=markD&delay=900&test=delay-1-metrics);
+  font-weight: bold;
+  font-style: italic;
+}
+
+div#test {
+  font-family: reflow1metrics;
+  font-size: 400%;
+  line-height: 1em;
+}
+
+div#test p {
+  margin: 0;
+  display: inline-block;
+}
+</style>
+</head>
+
+<body>
+<div id=test>
+<p style="font-family: reflow1metrics, cursive;">A</p>
+<p style="font-family: reflow1metrics, fantasy; font-weight: bold">B</p>
+<p style="font-family: reflow1metrics, monospace; font-style: italic">C</p>
+<p style="font-family: reflow1metrics, sans-serif; font-style: italic; font-weight: bold">D</p>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-face/reflow-sanity-delay-1a.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>test @font-face reflow sanity</title>
+<meta charset="utf-8">
+<style>
+body { margin: 20px }
+
+@font-face {
+  font-family: reflow1a;
+  src: url(../fonts/markfonts-delay.sjs?font=markA&delay=100&test=delay-1a);
+}
+
+@font-face {
+  font-family: reflow1a;
+  src: url(../fonts/markfonts-delay.sjs?font=markB&delay=1000&test=delay-1a);
+  font-weight: bold;
+}
+
+@font-face {
+  font-family: reflow1a;
+  src: url(../fonts/markfonts-delay.sjs?font=markC&delay=1500&test=delay-1a);
+  font-style: italic;
+}
+
+@font-face {
+  font-family: reflow1a;
+  src: url(../fonts/markfonts-delay.sjs?font=markD&delay=2000&test=delay-1a);
+  font-weight: bold;
+  font-style: italic;
+}
+
+div#test {
+  font-family: reflow1a;
+  font-size: 400%;
+  line-height: 1em;
+}
+
+div#test p {
+  margin: 0;
+  display: inline-block;
+}
+</style>
+</head>
+
+<body>
+<div id=test>
+<p>A</p>
+<p style="font-weight: bold">B</p>
+<p style="font-style: italic">C</p>
+<p style="font-style: italic; font-weight: bold">D</p>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-face/reflow-sanity-delay-1b.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>test @font-face reflow sanity</title>
+<meta charset="utf-8">
+<style>
+body { margin: 20px }
+
+@font-face {
+  font-family: reflow1b;
+  src: url(../fonts/markfonts-delay.sjs?font=markA&delay=2500&test=delay-1b);
+}
+
+@font-face {
+  font-family: reflow1b;
+  src: url(../fonts/markfonts-delay.sjs?font=markB&delay=500&test=delay-1b);
+  font-weight: bold;
+}
+
+@font-face {
+  font-family: reflow1b;
+  src: url(../fonts/markfonts-delay.sjs?font=markC&delay=1100&test=delay-1b);
+  font-style: italic;
+}
+
+@font-face {
+  font-family: reflow1b;
+  src: url(../fonts/markfonts-delay.sjs?font=markD&delay=100&test=delay-1b);
+  font-weight: bold;
+  font-style: italic;
+}
+
+div#test {
+  font-family: reflow1b;
+  font-size: 400%;
+  line-height: 1em;
+}
+
+div#test p {
+  margin: 0;
+  display: inline-block;
+}
+</style>
+</head>
+
+<body>
+<div id=test>
+<p>A</p>
+<p style="font-weight: bold">B</p>
+<p style="font-style: italic">C</p>
+<p style="font-style: italic; font-weight: bold">D</p>
+</div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/font-face/reflow-sanity-delay-1c.html
@@ -0,0 +1,54 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>test @font-face reflow sanity</title>
+<meta charset="utf-8">
+<style>
+body { margin: 20px }
+
+@font-face {
+  font-family: markA;
+  src: url(../fonts/markfonts-delay.sjs?font=markA&delay=2500&test=delay-1c);
+}
+
+@font-face {
+  font-family: markB;
+  src: url(../fonts/markfonts-delay.sjs?font=markB&delay=500&test=delay-1c);
+  font-weight: bold;
+}
+
+@font-face {
+  font-family: markC;
+  src: url(../fonts/markfonts-delay.sjs?font=markC&delay=1100&test=delay-1c);
+  font-style: italic;
+}
+
+@font-face {
+  font-family: markD;
+  src: url(../fonts/markfonts-delay.sjs?font=markD&delay=100&test=delay-1c);
+  font-weight: bold;
+  font-style: italic;
+}
+
+div#test {
+  font-family: markA;
+  font-size: 400%;
+  line-height: 1em;
+}
+
+div#test p {
+  margin: 0;
+  display: inline-block;
+}
+</style>
+</head>
+
+<body>
+<div id=test>
+<p style="font-family: marka, markb, markc, markd;">A</p>
+<p style="font-family: marka, markb, markc, markd; font-weight: bold">B</p>
+<p style="font-family: marka, markb, markc, markd; font-style: italic">C</p>
+<p style="font-family: marka, markb, markc, markd; font-style: italic; font-weight: bold">D</p>
+</div>
+</body>
+</html>
--- a/layout/reftests/font-face/reftest.list
+++ b/layout/reftests/font-face/reftest.list
@@ -164,8 +164,17 @@ HTTP(..) == font-redirect.html order-1-r
 == dynamic-duplicate-rule-1c.html dynamic-duplicate-rule-1-ref.html
 
 # Test for COLR and CPAL support
 HTTP(..) == color-1a.html color-1-ref.html
 HTTP(..) != color-1a.html color-1-notref.html
 HTTP(..) == color-1b.html color-1-ref.html
 
 pref(gfx.downloadable_fonts.woff2.enabled,true) HTTP(..) == woff2-1.html woff2-1-ref.html
+
+# sanity tests for reflow behavior with downloadable fonts
+HTTP(..) == reflow-sanity-1.html reflow-sanity-1-ref.html
+HTTP(..) == reflow-sanity-1-data.html reflow-sanity-1-ref.html
+HTTP(..) == reflow-sanity-1.html reflow-sanity-1-data.html
+HTTP(..) == reflow-sanity-delay-1a.html reflow-sanity-1-ref.html
+HTTP(..) == reflow-sanity-delay-1b.html reflow-sanity-1-ref.html
+HTTP(..) == reflow-sanity-delay-1c.html reflow-sanity-1-ref.html
+HTTP(..) == reflow-sanity-delay-1-metrics.html reflow-sanity-1-ref.html
--- a/layout/reftests/font-features/reftest.list
+++ b/layout/reftests/font-features/reftest.list
@@ -104,16 +104,16 @@ HTTP(..) != caps-fallback-allpetitecaps.
 # -- normal or fallback rendering
 HTTP(..) == caps-fallback-smallcaps1.html caps-fallback-smcp.html
 HTTP(..) == caps-fallback-smallcaps2.html caps-fallback-smcp.html
 HTTP(..) == caps-fallback-petitecaps.html caps-fallback-smcp.html
 HTTP(..) == caps-fallback-allsmallcaps.html caps-fallback-smcpc2sc.html
 HTTP(..) == caps-fallback-allpetitecaps.html caps-fallback-smcpc2sc.html
 
 # font-variant-position fallback
-HTTP(..) == subsuper-fallback.html subsuper-fallback-ref.html
+random-if(cocoaWidget) HTTP(..) == subsuper-fallback.html subsuper-fallback-ref.html # bug 1139269
 HTTP(..) != subsuper-fallback.html subsuper-fallback-notref1.html
 HTTP(..) != subsuper-fallback.html subsuper-fallback-notref2.html
 HTTP(..) != subsuper-fallback.html subsuper-fallback-notref3.html
 HTTP(..) != subsuper-fallback-omega.html subsuper-fallback-omega-notref.html
 HTTP(..) == subsuper-nofallback.html subsuper-nofallback-ref1.html
-HTTP(..) == subsuper-nofallback.html subsuper-nofallback-ref2.html
+random-if(cocoaWidget) HTTP(..) == subsuper-nofallback.html subsuper-nofallback-ref2.html # bug 1139269
 HTTP(..) != subsuper-nofallback.html subsuper-nofallback-notref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/fonts/markfonts-delay.sjs
@@ -0,0 +1,78 @@
+
+/* Data dumped using python code from image/test/reftest/generic/check-header.sjs */
+
+const markA_data = [
+  0x0,  0x1,  0x0,  0x0,  0x0,  0xD,  0x0,  0x80,  0x0,  0x3,  0x0,  0x50,  0x46,  0x46,  0x54,  0x4D,  0x4E,  0xAE,  0xE8,  0xC9,  0x0,  0x0,  0x5,  0xDC,  0x0,  0x0,  0x0,  0x1C,  0x4F,  0x53,  0x2F,  0x32,  0x56,  0x61,  0x64,  0xA,  0x0,  0x0,  0x1,  0x58,  0x0,  0x0,  0x0,  0x56,  0x63,  0x6D,  0x61,  0x70,  0x0,  0xF,  0x3,  0xEF,  0x0,  0x0,  0x1,  0xC0,  0x0,  0x0,  0x1,  0x42,  0x63,  0x76,  0x74,  0x20,  0x0,  0x21,  0x2,  0x79,  0x0,  0x0,  0x3,  0x4,  0x0,  0x0,  0x0,  0x4,  0x67,  0x61,  0x73,  0x70,  0xFF,  0xFF,  0x0,  0x3,  0x0,  0x0,  0x5,  0xD4,  0x0,  0x0,  0x0,  0x8,  0x67,  0x6C,  0x79,  0x66,  0x8,  0x2E,  0x9A,  0x4F,  0x0,  0x0,  0x3,  0x14,  0x0,  0x0,  0x0,  0x8C,  0x68,  0x65,  0x61,  0x64,  0xEF,  0x28,  0xA0,  0x17,  0x0,  0x0,  0x0,  0xDC,  0x0,  0x0,  0x0,  0x36,  0x68,  0x68,  0x65,  0x61,  0x8,  0x90,  0x5,  0x7D,  0x0,  0x0,  0x1,  0x14,  0x0,  0x0,  0x0,  0x24,  0x68,  0x6D,  0x74,  0x78,  0x11,  0x94,  0x0,  0x85,  0x0,  0x0,  0x1,  0xB0,  0x0,  0x0,  0x0,  0x10,  0x6C,  0x6F,  0x63,  0x61,  0x0,  0x70,  0x0,  0x54,  0x0,  0x0,  0x3,  0x8,  0x0,  0x0,  0x0,  0xA,  0x6D,  0x61,  0x78,  0x70,  0x0,  0x49,  0x0,  0x3D,  0x0,  0x0,  0x1,  0x38,  0x0,  0x0,  0x0,  0x20,  0x6E,  0x61,  0x6D,  0x65,  0x7A,  0x10,  0x15,  0x3A,  0x0,  0x0,  0x3,  0xA0,  0x0,  0x0,  0x2,  0x7,  0x70,  0x6F,  0x73,  0x74,  0xFF,  0xAE,  0x0,  0x35,  0x0,  0x0,  0x5,  0xA8,  0x0,  0x0,  0x0,  0x2A,  0x0,  0x1,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x32,  0x13,  0xF2,  0xB9,  0x5F,  0xF,  0x3C,  0xF5,  0x0,  0xB,  0x3,  0xE8,  0x0,  0x0,  0x0,  0x0,  0xC5,  0x38,  0x2E,  0x6D,  0x0,  0x0,  0x0,  0x0,  0xC5,  0x38,  0x2E,  0x6D,  0x0,  0x21,  0xFF,  0x9C,  0x5,  0x78,  0x2,  0xBC,  0x0,  0x0,  0x0,  0x8,  0x0,  0x2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x2,  0xBC,  0xFF,  0x9C,  0x0,  0x5A,  0x5,  0xDC,  0x0,  0x0,  0x0,  0x0,  0x5,  0x78,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4,  0x0,  0x1,  0x0,  0x0,  0x0,  0x4,  0x0,  0xC,  0x0,  0x3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2,  0x0,  0x0,  0x0,  0x1,  0x0,  0x1,  0x0,  0x0,  0x0,  0x40,  0x0,  0x2E,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x5,  0xDC,  0x1,  0xF4,  0x0,  0x5,  0x0,  0x0,  0x2,  0x8A,  0x2,  0xBC,  0x0,  0x0,  0x0,  0x8C,  0x2,  0x8A,  0x2,  0xBC,  0x0,  0x0,  0x1,  0xE0,  0x0,  0x31,  0x1,  0x2,  0x0,  0x0,  0x2,  0x0,  0x6,  0x9,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x50,  0x66,  0x45,  0x64,  0x0,  0x40,  0x0,  0x41,  0x0,  0x41,  0x3,  0x20,  0xFF,  0x38,  0x0,  0x5A,  0x2,  0xBC,  0x0,  0x64,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x5,  0xDC,  0x0,  0x21,  0x0,  0x0,  0x0,  0x0,  0x5,  0xDC,  0x0,  0x0,  0x5,  0xDC,  0x0,  0x64,  0x0,  0x0,  0x0,  0x3,  0x0,  0x0,  0x0,  0x3,  0x0,  0x0,  0x0,  0x1C,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3C,  0x0,  0x3,  0x0,  0x1,  0x0,  0x0,  0x0,  0x1C,  0x0,  0x4,  0x0,  0x20,  0x0,  0x0,  0x0,  0x4,  0x0,  0x4,  0x0,  0x1,  0x0,  0x0,  0x0,  0x41,  0xFF,  0xFF,  0x0,  0x0,  0x0,  0x41,  0xFF,  0xFF,  0xFF,  0xC2,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x6,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x2,  0x0,  0x0,  0x0,  0x2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x21,  0x2,  0x79,  0x0,  0x0,  0x0,  0x2A,  0x0,  0x2A,  0x0,  0x2A,  0x0,  0x46,  0x0,  0x0,  0x0,  0x2,  0x0,  0x21,  0x0,  0x0,  0x1,  0x2A,  0x2,  0x9A,  0x0,  0x3,  0x0,  0x7,  0x0,  0x2E,  0xB1,  0x1,  0x0,  0x2F,  0x3C,  0xB2,  0x7,  0x4,  0x0,  0xED,  0x32,  0xB1,  0x6,  0x5,  0xDC,  0x3C,  0xB2,  0x3,  0x2,  0x0,  0xED,  0x32,  0x0,  0xB1,  0x3,  0x0,  0x2F,  0x3C,  0xB2,  0x5,  0x4,  0x0,  0xED,  0x32,  0xB2,  0x7,  0x6,  0x1,  0xFC,  0x3C,  0xB2,  0x1,  0x2,  0x0,  0xED,  0x32,  0x33,  0x11,  0x21,  0x11,  0x27,  0x33,  0x11,  0x23,  0x21,  0x1,  0x9,  0xE8,  0xC7,  0xC7,  0x2,  0x9A,  0xFD,  0x66,  0x21,  0x2,  0x58,  0x0,  0x0,  0x3,  0x0,  0x64,  0xFF,  0x9C,  0x5,  0x78,  0x2,  0xBC,  0x0,  0x3,  0x0,  0x7,  0x0,  0xB,  0x0,  0x0,  0x1,  0x35,  0x21,  0x15,  0x1,  0x35,  0x21,  0x15,  0x1,  0x35,  0x21,  0x15,  0x1,  0xF4,  0x1,  0xF4,  0xFD,  0x44,  0x3,  0x84,  0xFB,  0xB4,  0x5,  0x14,  0x1,  0xF4,  0xC8,  0xC8,  0xFE,  0xD4,  0xC8,  0xC8,  0xFE,  0xD4,  0xC8,  0xC8,  0x0,  0x0,  0x0,  0x0,  0x0,  0xE,  0x0,  0xAE,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x26,  0x0,  0x4E,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x5,  0x0,  0x81,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2,  0x0,  0x6,  0x0,  0x95,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3,  0x0,  0x21,  0x0,  0xE0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4,  0x0,  0x5,  0x1,  0xE,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x5,  0x0,  0x10,  0x1,  0x36,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x6,  0x0,  0x5,  0x1,  0x53,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x0,  0x0,  0x4C,  0x0,  0x0,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x1,  0x0,  0xA,  0x0,  0x75,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x2,  0x0,  0xC,  0x0,  0x87,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x3,  0x0,  0x42,  0x0,  0x9C,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x4,  0x0,  0xA,  0x1,  0x2,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x5,  0x0,  0x20,  0x1,  0x14,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x6,  0x0,  0xA,  0x1,  0x47,  0x0,  0x43,  0x0,  0x6F,  0x0,  0x70,  0x0,  0x79,  0x0,  0x72,  0x0,  0x69,  0x0,  0x67,  0x0,  0x68,  0x0,  0x74,  0x0,  0x20,  0x0,  0x28,  0x0,  0x63,  0x0,  0x29,  0x0,  0x20,  0x0,  0x32,  0x0,  0x30,  0x0,  0x30,  0x0,  0x38,  0x0,  0x20,  0x0,  0x4D,  0x0,  0x6F,  0x0,  0x7A,  0x0,  0x69,  0x0,  0x6C,  0x0,  0x6C,  0x0,  0x61,  0x0,  0x20,  0x0,  0x43,  0x0,  0x6F,  0x0,  0x72,  0x0,  0x70,  0x0,  0x6F,  0x0,  0x72,  0x0,  0x61,  0x0,  0x74,  0x0,  0x69,  0x0,  0x6F,  0x0,  0x6E,  0x0,  0x0,  0x43,  0x6F,  0x70,  0x79,  0x72,  0x69,  0x67,  0x68,  0x74,  0x20,  0x28,  0x63,  0x29,  0x20,  0x32,  0x30,  0x30,  0x38,  0x20,  0x4D,  0x6F,  0x7A,  0x69,  0x6C,  0x6C,  0x61,  0x20,  0x43,  0x6F,  0x72,  0x70,  0x6F,  0x72,  0x61,  0x74,  0x69,  0x6F,  0x6E,  0x0,  0x0,  0x4D,  0x0,  0x61,  0x0,  0x72,  0x0,  0x6B,  0x0,  0x41,  0x0,  0x0,  0x4D,  0x61,  0x72,  0x6B,  0x41,  0x0,  0x0,  0x4D,  0x0,  0x65,  0x0,  0x64,  0x0,  0x69,  0x0,  0x75,  0x0,  0x6D,  0x0,  0x0,  0x4D,  0x65,  0x64,  0x69,  0x75,  0x6D,  0x0,  0x0,  0x46,  0x0,  0x6F,  0x0,  0x6E,  0x0,  0x74,  0x0,  0x46,  0x0,  0x6F,  0x0,  0x72,  0x0,  0x67,  0x0,  0x65,  0x0,  0x20,  0x0,  0x32,  0x0,  0x2E,  0x0,  0x30,  0x0,  0x20,  0x0,  0x3A,  0x0,  0x20,  0x0,  0x4D,  0x0,  0x61,  0x0,  0x72,  0x0,  0x6B,  0x0,  0x41,  0x0,  0x20,  0x0,  0x3A,  0x0,  0x20,  0x0,  0x35,  0x0,  0x2D,  0x0,  0x31,  0x0,  0x31,  0x0,  0x2D,  0x0,  0x32,  0x0,  0x30,  0x0,  0x30,  0x0,  0x38,  0x0,  0x0,  0x46,  0x6F,  0x6E,  0x74,  0x46,  0x6F,  0x72,  0x67,  0x65,  0x20,  0x32,  0x2E,  0x30,  0x20,  0x3A,  0x20,  0x4D,  0x61,  0x72,  0x6B,  0x41,  0x20,  0x3A,  0x20,  0x35,  0x2D,  0x31,  0x31,  0x2D,  0x32,  0x30,  0x30,  0x38,  0x0,  0x0,  0x4D,  0x0,  0x61,  0x0,  0x72,  0x0,  0x6B,  0x0,  0x41,  0x0,  0x0,  0x4D,  0x61,  0x72,  0x6B,  0x41,  0x0,  0x0,  0x56,  0x0,  0x65,  0x0,  0x72,  0x0,  0x73,  0x0,  0x69,  0x0,  0x6F,  0x0,  0x6E,  0x0,  0x20,  0x0,  0x30,  0x0,  0x30,  0x0,  0x31,  0x0,  0x2E,  0x0,  0x30,  0x0,  0x30,  0x0,  0x30,  0x0,  0x20,  0x0,  0x0,  0x56,  0x65,  0x72,  0x73,  0x69,  0x6F,  0x6E,  0x20,  0x30,  0x30,  0x31,  0x2E,  0x30,  0x30,  0x30,  0x20,  0x0,  0x0,  0x4D,  0x0,  0x61,  0x0,  0x72,  0x0,  0x6B,  0x0,  0x41,  0x0,  0x0,  0x4D,  0x61,  0x72,  0x6B,  0x41,  0x0,  0x0,  0x0,  0x2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xFF,  0x83,  0x0,  0x32,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4,  0x0,  0x0,  0x0,  0x1,  0x0,  0x2,  0x0,  0x24,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0xFF,  0xFF,  0x0,  0x2,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0xC4,  0x3E,  0x8B,  0xEE,  0x0,  0x0,  0x0,  0x0,  0xC5,  0x38,  0x2E,  0x6D,  0x0,  0x0,  0x0,  0x0,  0xC5,  0x38,  0x2E,  0x6D
+];
+
+const markB_data = [
+  0x0,  0x1,  0x0,  0x0,  0x0,  0xD,  0x0,  0x80,  0x0,  0x3,  0x0,  0x50,  0x46,  0x46,  0x54,  0x4D,  0x4E,  0xAE,  0xE8,  0xC9,  0x0,  0x0,  0x5,  0xDC,  0x0,  0x0,  0x0,  0x1C,  0x4F,  0x53,  0x2F,  0x32,  0x56,  0x62,  0x64,  0xB,  0x0,  0x0,  0x1,  0x58,  0x0,  0x0,  0x0,  0x56,  0x63,  0x6D,  0x61,  0x70,  0x3,  0xF,  0x3,  0xED,  0x0,  0x0,  0x1,  0xC0,  0x0,  0x0,  0x1,  0x42,  0x63,  0x76,  0x74,  0x20,  0x0,  0x21,  0x2,  0x79,  0x0,  0x0,  0x3,  0x4,  0x0,  0x0,  0x0,  0x4,  0x67,  0x61,  0x73,  0x70,  0xFF,  0xFF,  0x0,  0x3,  0x0,  0x0,  0x5,  0xD4,  0x0,  0x0,  0x0,  0x8,  0x67,  0x6C,  0x79,  0x66,  0x8,  0x2E,  0x9A,  0x4F,  0x0,  0x0,  0x3,  0x14,  0x0,  0x0,  0x0,  0x8C,  0x68,  0x65,  0x61,  0x64,  0xEF,  0x28,  0xA0,  0x17,  0x0,  0x0,  0x0,  0xDC,  0x0,  0x0,  0x0,  0x36,  0x68,  0x68,  0x65,  0x61,  0x8,  0x90,  0x5,  0x7D,  0x0,  0x0,  0x1,  0x14,  0x0,  0x0,  0x0,  0x24,  0x68,  0x6D,  0x74,  0x78,  0x11,  0x94,  0x0,  0x85,  0x0,  0x0,  0x1,  0xB0,  0x0,  0x0,  0x0,  0x10,  0x6C,  0x6F,  0x63,  0x61,  0x0,  0x70,  0x0,  0x54,  0x0,  0x0,  0x3,  0x8,  0x0,  0x0,  0x0,  0xA,  0x6D,  0x61,  0x78,  0x70,  0x0,  0x49,  0x0,  0x3D,  0x0,  0x0,  0x1,  0x38,  0x0,  0x0,  0x0,  0x20,  0x6E,  0x61,  0x6D,  0x65,  0x7C,  0x12,  0x17,  0x3C,  0x0,  0x0,  0x3,  0xA0,  0x0,  0x0,  0x2,  0x7,  0x70,  0x6F,  0x73,  0x74,  0xFF,  0xAF,  0x0,  0x35,  0x0,  0x0,  0x5,  0xA8,  0x0,  0x0,  0x0,  0x2A,  0x0,  0x1,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x28,  0xB,  0xEE,  0xB7,  0x5F,  0xF,  0x3C,  0xF5,  0x0,  0xB,  0x3,  0xE8,  0x0,  0x0,  0x0,  0x0,  0xC5,  0x38,  0x2E,  0x6D,  0x0,  0x0,  0x0,  0x0,  0xC5,  0x38,  0x2E,  0x6D,  0x0,  0x21,  0xFF,  0x9C,  0x5,  0x78,  0x2,  0xBC,  0x0,  0x0,  0x0,  0x8,  0x0,  0x2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x2,  0xBC,  0xFF,  0x9C,  0x0,  0x5A,  0x5,  0xDC,  0x0,  0x0,  0x0,  0x0,  0x5,  0x78,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4,  0x0,  0x1,  0x0,  0x0,  0x0,  0x4,  0x0,  0xC,  0x0,  0x3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2,  0x0,  0x0,  0x0,  0x1,  0x0,  0x1,  0x0,  0x0,  0x0,  0x40,  0x0,  0x2E,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x5,  0xDC,  0x1,  0xF4,  0x0,  0x5,  0x0,  0x0,  0x2,  0x8A,  0x2,  0xBC,  0x0,  0x0,  0x0,  0x8C,  0x2,  0x8A,  0x2,  0xBC,  0x0,  0x0,  0x1,  0xE0,  0x0,  0x31,  0x1,  0x2,  0x0,  0x0,  0x2,  0x0,  0x6,  0x9,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x50,  0x66,  0x45,  0x64,  0x0,  0x40,  0x0,  0x42,  0x0,  0x42,  0x3,  0x20,  0xFF,  0x38,  0x0,  0x5A,  0x2,  0xBC,  0x0,  0x64,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x5,  0xDC,  0x0,  0x21,  0x0,  0x0,  0x0,  0x0,  0x5,  0xDC,  0x0,  0x0,  0x5,  0xDC,  0x0,  0x64,  0x0,  0x0,  0x0,  0x3,  0x0,  0x0,  0x0,  0x3,  0x0,  0x0,  0x0,  0x1C,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3C,  0x0,  0x3,  0x0,  0x1,  0x0,  0x0,  0x0,  0x1C,  0x0,  0x4,  0x0,  0x20,  0x0,  0x0,  0x0,  0x4,  0x0,  0x4,  0x0,  0x1,  0x0,  0x0,  0x0,  0x42,  0xFF,  0xFF,  0x0,  0x0,  0x0,  0x42,  0xFF,  0xFF,  0xFF,  0xC1,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x6,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x2,  0x0,  0x0,  0x0,  0x2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x21,  0x2,  0x79,  0x0,  0x0,  0x0,  0x2A,  0x0,  0x2A,  0x0,  0x2A,  0x0,  0x46,  0x0,  0x0,  0x0,  0x2,  0x0,  0x21,  0x0,  0x0,  0x1,  0x2A,  0x2,  0x9A,  0x0,  0x3,  0x0,  0x7,  0x0,  0x2E,  0xB1,  0x1,  0x0,  0x2F,  0x3C,  0xB2,  0x7,  0x4,  0x0,  0xED,  0x32,  0xB1,  0x6,  0x5,  0xDC,  0x3C,  0xB2,  0x3,  0x2,  0x0,  0xED,  0x32,  0x0,  0xB1,  0x3,  0x0,  0x2F,  0x3C,  0xB2,  0x5,  0x4,  0x0,  0xED,  0x32,  0xB2,  0x7,  0x6,  0x1,  0xFC,  0x3C,  0xB2,  0x1,  0x2,  0x0,  0xED,  0x32,  0x33,  0x11,  0x21,  0x11,  0x27,  0x33,  0x11,  0x23,  0x21,  0x1,  0x9,  0xE8,  0xC7,  0xC7,  0x2,  0x9A,  0xFD,  0x66,  0x21,  0x2,  0x58,  0x0,  0x0,  0x3,  0x0,  0x64,  0xFF,  0x9C,  0x5,  0x78,  0x2,  0xBC,  0x0,  0x3,  0x0,  0x7,  0x0,  0xB,  0x0,  0x0,  0x1,  0x35,  0x21,  0x15,  0x1,  0x35,  0x21,  0x15,  0x1,  0x35,  0x21,  0x15,  0x1,  0xF4,  0x1,  0xF4,  0xFD,  0x44,  0x3,  0x84,  0xFB,  0xB4,  0x5,  0x14,  0x1,  0xF4,  0xC8,  0xC8,  0xFE,  0xD4,  0xC8,  0xC8,  0xFE,  0xD4,  0xC8,  0xC8,  0x0,  0x0,  0x0,  0x0,  0x0,  0xE,  0x0,  0xAE,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x26,  0x0,  0x4E,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x5,  0x0,  0x81,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2,  0x0,  0x6,  0x0,  0x95,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3,  0x0,  0x21,  0x0,  0xE0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4,  0x0,  0x5,  0x1,  0xE,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x5,  0x0,  0x10,  0x1,  0x36,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x6,  0x0,  0x5,  0x1,  0x53,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x0,  0x0,  0x4C,  0x0,  0x0,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x1,  0x0,  0xA,  0x0,  0x75,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x2,  0x0,  0xC,  0x0,  0x87,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x3,  0x0,  0x42,  0x0,  0x9C,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x4,  0x0,  0xA,  0x1,  0x2,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x5,  0x0,  0x20,  0x1,  0x14,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x6,  0x0,  0xA,  0x1,  0x47,  0x0,  0x43,  0x0,  0x6F,  0x0,  0x70,  0x0,  0x79,  0x0,  0x72,  0x0,  0x69,  0x0,  0x67,  0x0,  0x68,  0x0,  0x74,  0x0,  0x20,  0x0,  0x28,  0x0,  0x63,  0x0,  0x29,  0x0,  0x20,  0x0,  0x32,  0x0,  0x30,  0x0,  0x30,  0x0,  0x38,  0x0,  0x20,  0x0,  0x4D,  0x0,  0x6F,  0x0,  0x7A,  0x0,  0x69,  0x0,  0x6C,  0x0,  0x6C,  0x0,  0x61,  0x0,  0x20,  0x0,  0x43,  0x0,  0x6F,  0x0,  0x72,  0x0,  0x70,  0x0,  0x6F,  0x0,  0x72,  0x0,  0x61,  0x0,  0x74,  0x0,  0x69,  0x0,  0x6F,  0x0,  0x6E,  0x0,  0x0,  0x43,  0x6F,  0x70,  0x79,  0x72,  0x69,  0x67,  0x68,  0x74,  0x20,  0x28,  0x63,  0x29,  0x20,  0x32,  0x30,  0x30,  0x38,  0x20,  0x4D,  0x6F,  0x7A,  0x69,  0x6C,  0x6C,  0x61,  0x20,  0x43,  0x6F,  0x72,  0x70,  0x6F,  0x72,  0x61,  0x74,  0x69,  0x6F,  0x6E,  0x0,  0x0,  0x4D,  0x0,  0x61,  0x0,  0x72,  0x0,  0x6B,  0x0,  0x42,  0x0,  0x0,  0x4D,  0x61,  0x72,  0x6B,  0x42,  0x0,  0x0,  0x4D,  0x0,  0x65,  0x0,  0x64,  0x0,  0x69,  0x0,  0x75,  0x0,  0x6D,  0x0,  0x0,  0x4D,  0x65,  0x64,  0x69,  0x75,  0x6D,  0x0,  0x0,  0x46,  0x0,  0x6F,  0x0,  0x6E,  0x0,  0x74,  0x0,  0x46,  0x0,  0x6F,  0x0,  0x72,  0x0,  0x67,  0x0,  0x65,  0x0,  0x20,  0x0,  0x32,  0x0,  0x2E,  0x0,  0x30,  0x0,  0x20,  0x0,  0x3A,  0x0,  0x20,  0x0,  0x4D,  0x0,  0x61,  0x0,  0x72,  0x0,  0x6B,  0x0,  0x42,  0x0,  0x20,  0x0,  0x3A,  0x0,  0x20,  0x0,  0x35,  0x0,  0x2D,  0x0,  0x31,  0x0,  0x31,  0x0,  0x2D,  0x0,  0x32,  0x0,  0x30,  0x0,  0x30,  0x0,  0x38,  0x0,  0x0,  0x46,  0x6F,  0x6E,  0x74,  0x46,  0x6F,  0x72,  0x67,  0x65,  0x20,  0x32,  0x2E,  0x30,  0x20,  0x3A,  0x20,  0x4D,  0x61,  0x72,  0x6B,  0x42,  0x20,  0x3A,  0x20,  0x35,  0x2D,  0x31,  0x31,  0x2D,  0x32,  0x30,  0x30,  0x38,  0x0,  0x0,  0x4D,  0x0,  0x61,  0x0,  0x72,  0x0,  0x6B,  0x0,  0x42,  0x0,  0x0,  0x4D,  0x61,  0x72,  0x6B,  0x42,  0x0,  0x0,  0x56,  0x0,  0x65,  0x0,  0x72,  0x0,  0x73,  0x0,  0x69,  0x0,  0x6F,  0x0,  0x6E,  0x0,  0x20,  0x0,  0x30,  0x0,  0x30,  0x0,  0x31,  0x0,  0x2E,  0x0,  0x30,  0x0,  0x30,  0x0,  0x30,  0x0,  0x20,  0x0,  0x0,  0x56,  0x65,  0x72,  0x73,  0x69,  0x6F,  0x6E,  0x20,  0x30,  0x30,  0x31,  0x2E,  0x30,  0x30,  0x30,  0x20,  0x0,  0x0,  0x4D,  0x0,  0x61,  0x0,  0x72,  0x0,  0x6B,  0x0,  0x42,  0x0,  0x0,  0x4D,  0x61,  0x72,  0x6B,  0x42,  0x0,  0x0,  0x0,  0x2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xFF,  0x83,  0x0,  0x32,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4,  0x0,  0x0,  0x0,  0x1,  0x0,  0x2,  0x0,  0x25,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0xFF,  0xFF,  0x0,  0x2,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0xC4,  0x3E,  0x8B,  0xEE,  0x0,  0x0,  0x0,  0x0,  0xC5,  0x38,  0x2E,  0x6D,  0x0,  0x0,  0x0,  0x0,  0xC5,  0x38,  0x2E,  0x6D
+];
+
+const markC_data = [
+  0x0,  0x1,  0x0,  0x0,  0x0,  0xD,  0x0,  0x80,  0x0,  0x3,  0x0,  0x50,  0x46,  0x46,  0x54,  0x4D,  0x4E,  0xAE,  0xE8,  0xC9,  0x0,  0x0,  0x5,  0xDC,  0x0,  0x0,  0x0,  0x1C,  0x4F,  0x53,  0x2F,  0x32,  0x56,  0x63,  0x64,  0xC,  0x0,  0x0,  0x1,  0x58,  0x0,  0x0,  0x0,  0x56,  0x63,  0x6D,  0x61,  0x70,  0x0,  0x12,  0x3,  0xEE,  0x0,  0x0,  0x1,  0xC0,  0x0,  0x0,  0x1,  0x42,  0x63,  0x76,  0x74,  0x20,  0x0,  0x21,  0x2,  0x79,  0x0,  0x0,  0x3,  0x4,  0x0,  0x0,  0x0,  0x4,  0x67,  0x61,  0x73,  0x70,  0xFF,  0xFF,  0x0,  0x3,  0x0,  0x0,  0x5,  0xD4,  0x0,  0x0,  0x0,  0x8,  0x67,  0x6C,  0x79,  0x66,  0x8,  0x2E,  0x9A,  0x4F,  0x0,  0x0,  0x3,  0x14,  0x0,  0x0,  0x0,  0x8C,  0x68,  0x65,  0x61,  0x64,  0xEF,  0x28,  0xA0,  0x17,  0x0,  0x0,  0x0,  0xDC,  0x0,  0x0,  0x0,  0x36,  0x68,  0x68,  0x65,  0x61,  0x8,  0x90,  0x5,  0x7D,  0x0,  0x0,  0x1,  0x14,  0x0,  0x0,  0x0,  0x24,  0x68,  0x6D,  0x74,  0x78,  0x11,  0x94,  0x0,  0x85,  0x0,  0x0,  0x1,  0xB0,  0x0,  0x0,  0x0,  0x10,  0x6C,  0x6F,  0x63,  0x61,  0x0,  0x70,  0x0,  0x54,  0x0,  0x0,  0x3,  0x8,  0x0,  0x0,  0x0,  0xA,  0x6D,  0x61,  0x78,  0x70,  0x0,  0x49,  0x0,  0x3D,  0x0,  0x0,  0x1,  0x38,  0x0,  0x0,  0x0,  0x20,  0x6E,  0x61,  0x6D,  0x65,  0x7E,  0x14,  0x19,  0x3E,  0x0,  0x0,  0x3,  0xA0,  0x0,  0x0,  0x2,  0x7,  0x70,  0x6F,  0x73,  0x74,  0xFF,  0xB0,  0x0,  0x35,  0x0,  0x0,  0x5,  0xA8,  0x0,  0x0,  0x0,  0x2A,  0x0,  0x1,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x29,  0xFD,  0xEA,  0xAF,  0x5F,  0xF,  0x3C,  0xF5,  0x0,  0xB,  0x3,  0xE8,  0x0,  0x0,  0x0,  0x0,  0xC5,  0x38,  0x2E,  0x6D,  0x0,  0x0,  0x0,  0x0,  0xC5,  0x38,  0x2E,  0x6D,  0x0,  0x21,  0xFF,  0x9C,  0x5,  0x78,  0x2,  0xBC,  0x0,  0x0,  0x0,  0x8,  0x0,  0x2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x2,  0xBC,  0xFF,  0x9C,  0x0,  0x5A,  0x5,  0xDC,  0x0,  0x0,  0x0,  0x0,  0x5,  0x78,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4,  0x0,  0x1,  0x0,  0x0,  0x0,  0x4,  0x0,  0xC,  0x0,  0x3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2,  0x0,  0x0,  0x0,  0x1,  0x0,  0x1,  0x0,  0x0,  0x0,  0x40,  0x0,  0x2E,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x5,  0xDC,  0x1,  0xF4,  0x0,  0x5,  0x0,  0x0,  0x2,  0x8A,  0x2,  0xBC,  0x0,  0x0,  0x0,  0x8C,  0x2,  0x8A,  0x2,  0xBC,  0x0,  0x0,  0x1,  0xE0,  0x0,  0x31,  0x1,  0x2,  0x0,  0x0,  0x2,  0x0,  0x6,  0x9,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x50,  0x66,  0x45,  0x64,  0x0,  0x40,  0x0,  0x43,  0x0,  0x43,  0x3,  0x20,  0xFF,  0x38,  0x0,  0x5A,  0x2,  0xBC,  0x0,  0x64,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x5,  0xDC,  0x0,  0x21,  0x0,  0x0,  0x0,  0x0,  0x5,  0xDC,  0x0,  0x0,  0x5,  0xDC,  0x0,  0x64,  0x0,  0x0,  0x0,  0x3,  0x0,  0x0,  0x0,  0x3,  0x0,  0x0,  0x0,  0x1C,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3C,  0x0,  0x3,  0x0,  0x1,  0x0,  0x0,  0x0,  0x1C,  0x0,  0x4,  0x0,  0x20,  0x0,  0x0,  0x0,  0x4,  0x0,  0x4,  0x0,  0x1,  0x0,  0x0,  0x0,  0x43,  0xFF,  0xFF,  0x0,  0x0,  0x0,  0x43,  0xFF,  0xFF,  0xFF,  0xC0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x6,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x2,  0x0,  0x0,  0x0,  0x2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x21,  0x2,  0x79,  0x0,  0x0,  0x0,  0x2A,  0x0,  0x2A,  0x0,  0x2A,  0x0,  0x46,  0x0,  0x0,  0x0,  0x2,  0x0,  0x21,  0x0,  0x0,  0x1,  0x2A,  0x2,  0x9A,  0x0,  0x3,  0x0,  0x7,  0x0,  0x2E,  0xB1,  0x1,  0x0,  0x2F,  0x3C,  0xB2,  0x7,  0x4,  0x0,  0xED,  0x32,  0xB1,  0x6,  0x5,  0xDC,  0x3C,  0xB2,  0x3,  0x2,  0x0,  0xED,  0x32,  0x0,  0xB1,  0x3,  0x0,  0x2F,  0x3C,  0xB2,  0x5,  0x4,  0x0,  0xED,  0x32,  0xB2,  0x7,  0x6,  0x1,  0xFC,  0x3C,  0xB2,  0x1,  0x2,  0x0,  0xED,  0x32,  0x33,  0x11,  0x21,  0x11,  0x27,  0x33,  0x11,  0x23,  0x21,  0x1,  0x9,  0xE8,  0xC7,  0xC7,  0x2,  0x9A,  0xFD,  0x66,  0x21,  0x2,  0x58,  0x0,  0x0,  0x3,  0x0,  0x64,  0xFF,  0x9C,  0x5,  0x78,  0x2,  0xBC,  0x0,  0x3,  0x0,  0x7,  0x0,  0xB,  0x0,  0x0,  0x1,  0x35,  0x21,  0x15,  0x1,  0x35,  0x21,  0x15,  0x1,  0x35,  0x21,  0x15,  0x1,  0xF4,  0x1,  0xF4,  0xFD,  0x44,  0x3,  0x84,  0xFB,  0xB4,  0x5,  0x14,  0x1,  0xF4,  0xC8,  0xC8,  0xFE,  0xD4,  0xC8,  0xC8,  0xFE,  0xD4,  0xC8,  0xC8,  0x0,  0x0,  0x0,  0x0,  0x0,  0xE,  0x0,  0xAE,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x26,  0x0,  0x4E,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x5,  0x0,  0x81,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2,  0x0,  0x6,  0x0,  0x95,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3,  0x0,  0x21,  0x0,  0xE0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4,  0x0,  0x5,  0x1,  0xE,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x5,  0x0,  0x10,  0x1,  0x36,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x6,  0x0,  0x5,  0x1,  0x53,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x0,  0x0,  0x4C,  0x0,  0x0,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x1,  0x0,  0xA,  0x0,  0x75,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x2,  0x0,  0xC,  0x0,  0x87,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x3,  0x0,  0x42,  0x0,  0x9C,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x4,  0x0,  0xA,  0x1,  0x2,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x5,  0x0,  0x20,  0x1,  0x14,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x6,  0x0,  0xA,  0x1,  0x47,  0x0,  0x43,  0x0,  0x6F,  0x0,  0x70,  0x0,  0x79,  0x0,  0x72,  0x0,  0x69,  0x0,  0x67,  0x0,  0x68,  0x0,  0x74,  0x0,  0x20,  0x0,  0x28,  0x0,  0x63,  0x0,  0x29,  0x0,  0x20,  0x0,  0x32,  0x0,  0x30,  0x0,  0x30,  0x0,  0x38,  0x0,  0x20,  0x0,  0x4D,  0x0,  0x6F,  0x0,  0x7A,  0x0,  0x69,  0x0,  0x6C,  0x0,  0x6C,  0x0,  0x61,  0x0,  0x20,  0x0,  0x43,  0x0,  0x6F,  0x0,  0x72,  0x0,  0x70,  0x0,  0x6F,  0x0,  0x72,  0x0,  0x61,  0x0,  0x74,  0x0,  0x69,  0x0,  0x6F,  0x0,  0x6E,  0x0,  0x0,  0x43,  0x6F,  0x70,  0x79,  0x72,  0x69,  0x67,  0x68,  0x74,  0x20,  0x28,  0x63,  0x29,  0x20,  0x32,  0x30,  0x30,  0x38,  0x20,  0x4D,  0x6F,  0x7A,  0x69,  0x6C,  0x6C,  0x61,  0x20,  0x43,  0x6F,  0x72,  0x70,  0x6F,  0x72,  0x61,  0x74,  0x69,  0x6F,  0x6E,  0x0,  0x0,  0x4D,  0x0,  0x61,  0x0,  0x72,  0x0,  0x6B,  0x0,  0x43,  0x0,  0x0,  0x4D,  0x61,  0x72,  0x6B,  0x43,  0x0,  0x0,  0x4D,  0x0,  0x65,  0x0,  0x64,  0x0,  0x69,  0x0,  0x75,  0x0,  0x6D,  0x0,  0x0,  0x4D,  0x65,  0x64,  0x69,  0x75,  0x6D,  0x0,  0x0,  0x46,  0x0,  0x6F,  0x0,  0x6E,  0x0,  0x74,  0x0,  0x46,  0x0,  0x6F,  0x0,  0x72,  0x0,  0x67,  0x0,  0x65,  0x0,  0x20,  0x0,  0x32,  0x0,  0x2E,  0x0,  0x30,  0x0,  0x20,  0x0,  0x3A,  0x0,  0x20,  0x0,  0x4D,  0x0,  0x61,  0x0,  0x72,  0x0,  0x6B,  0x0,  0x43,  0x0,  0x20,  0x0,  0x3A,  0x0,  0x20,  0x0,  0x35,  0x0,  0x2D,  0x0,  0x31,  0x0,  0x31,  0x0,  0x2D,  0x0,  0x32,  0x0,  0x30,  0x0,  0x30,  0x0,  0x38,  0x0,  0x0,  0x46,  0x6F,  0x6E,  0x74,  0x46,  0x6F,  0x72,  0x67,  0x65,  0x20,  0x32,  0x2E,  0x30,  0x20,  0x3A,  0x20,  0x4D,  0x61,  0x72,  0x6B,  0x43,  0x20,  0x3A,  0x20,  0x35,  0x2D,  0x31,  0x31,  0x2D,  0x32,  0x30,  0x30,  0x38,  0x0,  0x0,  0x4D,  0x0,  0x61,  0x0,  0x72,  0x0,  0x6B,  0x0,  0x43,  0x0,  0x0,  0x4D,  0x61,  0x72,  0x6B,  0x43,  0x0,  0x0,  0x56,  0x0,  0x65,  0x0,  0x72,  0x0,  0x73,  0x0,  0x69,  0x0,  0x6F,  0x0,  0x6E,  0x0,  0x20,  0x0,  0x30,  0x0,  0x30,  0x0,  0x31,  0x0,  0x2E,  0x0,  0x30,  0x0,  0x30,  0x0,  0x30,  0x0,  0x20,  0x0,  0x0,  0x56,  0x65,  0x72,  0x73,  0x69,  0x6F,  0x6E,  0x20,  0x30,  0x30,  0x31,  0x2E,  0x30,  0x30,  0x30,  0x20,  0x0,  0x0,  0x4D,  0x0,  0x61,  0x0,  0x72,  0x0,  0x6B,  0x0,  0x43,  0x0,  0x0,  0x4D,  0x61,  0x72,  0x6B,  0x43,  0x0,  0x0,  0x0,  0x2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xFF,  0x83,  0x0,  0x32,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4,  0x0,  0x0,  0x0,  0x1,  0x0,  0x2,  0x0,  0x26,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0xFF,  0xFF,  0x0,  0x2,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0xC4,  0x3E,  0x8B,  0xEE,  0x0,  0x0,  0x0,  0x0,  0xC5,  0x38,  0x2E,  0x6D,  0x0,  0x0,  0x0,  0x0,  0xC5,  0x38,  0x2E,  0x6D
+];
+
+const markD_data = [
+  0x0,  0x1,  0x0,  0x0,  0x0,  0xD,  0x0,  0x80,  0x0,  0x3,  0x0,  0x50,  0x46,  0x46,  0x54,  0x4D,  0x4E,  0xAE,  0xE8,  0xC9,  0x0,  0x0,  0x5,  0xDC,  0x0,  0x0,  0x0,  0x1C,  0x4F,  0x53,  0x2F,  0x32,  0x56,  0x64,  0x64,  0xD,  0x0,  0x0,  0x1,  0x58,  0x0,  0x0,  0x0,  0x56,  0x63,  0x6D,  0x61,  0x70,  0x0,  0xF,  0x6,  0xEF,  0x0,  0x0,  0x1,  0xC0,  0x0,  0x0,  0x1,  0x42,  0x63,  0x76,  0x74,  0x20,  0x0,  0x21,  0x2,  0x79,  0x0,  0x0,  0x3,  0x4,  0x0,  0x0,  0x0,  0x4,  0x67,  0x61,  0x73,  0x70,  0xFF,  0xFF,  0x0,  0x3,  0x0,  0x0,  0x5,  0xD4,  0x0,  0x0,  0x0,  0x8,  0x67,  0x6C,  0x79,  0x66,  0x8,  0x2E,  0x9A,  0x4F,  0x0,  0x0,  0x3,  0x14,  0x0,  0x0,  0x0,  0x8C,  0x68,  0x65,  0x61,  0x64,  0xEF,  0x28,  0xA0,  0x17,  0x0,  0x0,  0x0,  0xDC,  0x0,  0x0,  0x0,  0x36,  0x68,  0x68,  0x65,  0x61,  0x8,  0x90,  0x5,  0x7D,  0x0,  0x0,  0x1,  0x14,  0x0,  0x0,  0x0,  0x24,  0x68,  0x6D,  0x74,  0x78,  0x11,  0x94,  0x0,  0x85,  0x0,  0x0,  0x1,  0xB0,  0x0,  0x0,  0x0,  0x10,  0x6C,  0x6F,  0x63,  0x61,  0x0,  0x70,  0x0,  0x54,  0x0,  0x0,  0x3,  0x8,  0x0,  0x0,  0x0,  0xA,  0x6D,  0x61,  0x78,  0x70,  0x0,  0x49,  0x0,  0x3D,  0x0,  0x0,  0x1,  0x38,  0x0,  0x0,  0x0,  0x20,  0x6E,  0x61,  0x6D,  0x65,  0x80,  0x16,  0x1B,  0x40,  0x0,  0x0,  0x3,  0xA0,  0x0,  0x0,  0x2,  0x7,  0x70,  0x6F,  0x73,  0x74,  0xFF,  0xB1,  0x0,  0x35,  0x0,  0x0,  0x5,  0xA8,  0x0,  0x0,  0x0,  0x2A,  0x0,  0x1,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x25,  0xFB,  0xE0,  0xA7,  0x5F,  0xF,  0x3C,  0xF5,  0x0,  0xB,  0x3,  0xE8,  0x0,  0x0,  0x0,  0x0,  0xC5,  0x38,  0x2E,  0x6D,  0x0,  0x0,  0x0,  0x0,  0xC5,  0x38,  0x2E,  0x6D,  0x0,  0x21,  0xFF,  0x9C,  0x5,  0x78,  0x2,  0xBC,  0x0,  0x0,  0x0,  0x8,  0x0,  0x2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x2,  0xBC,  0xFF,  0x9C,  0x0,  0x5A,  0x5,  0xDC,  0x0,  0x0,  0x0,  0x0,  0x5,  0x78,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4,  0x0,  0x1,  0x0,  0x0,  0x0,  0x4,  0x0,  0xC,  0x0,  0x3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2,  0x0,  0x0,  0x0,  0x1,  0x0,  0x1,  0x0,  0x0,  0x0,  0x40,  0x0,  0x2E,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x5,  0xDC,  0x1,  0xF4,  0x0,  0x5,  0x0,  0x0,  0x2,  0x8A,  0x2,  0xBC,  0x0,  0x0,  0x0,  0x8C,  0x2,  0x8A,  0x2,  0xBC,  0x0,  0x0,  0x1,  0xE0,  0x0,  0x31,  0x1,  0x2,  0x0,  0x0,  0x2,  0x0,  0x6,  0x9,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x50,  0x66,  0x45,  0x64,  0x0,  0x40,  0x0,  0x44,  0x0,  0x44,  0x3,  0x20,  0xFF,  0x38,  0x0,  0x5A,  0x2,  0xBC,  0x0,  0x64,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x5,  0xDC,  0x0,  0x21,  0x0,  0x0,  0x0,  0x0,  0x5,  0xDC,  0x0,  0x0,  0x5,  0xDC,  0x0,  0x64,  0x0,  0x0,  0x0,  0x3,  0x0,  0x0,  0x0,  0x3,  0x0,  0x0,  0x0,  0x1C,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3C,  0x0,  0x3,  0x0,  0x1,  0x0,  0x0,  0x0,  0x1C,  0x0,  0x4,  0x0,  0x20,  0x0,  0x0,  0x0,  0x4,  0x0,  0x4,  0x0,  0x1,  0x0,  0x0,  0x0,  0x44,  0xFF,  0xFF,  0x0,  0x0,  0x0,  0x44,  0xFF,  0xFF,  0xFF,  0xBF,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x6,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x2,  0x0,  0x0,  0x0,  0x2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x21,  0x2,  0x79,  0x0,  0x0,  0x0,  0x2A,  0x0,  0x2A,  0x0,  0x2A,  0x0,  0x46,  0x0,  0x0,  0x0,  0x2,  0x0,  0x21,  0x0,  0x0,  0x1,  0x2A,  0x2,  0x9A,  0x0,  0x3,  0x0,  0x7,  0x0,  0x2E,  0xB1,  0x1,  0x0,  0x2F,  0x3C,  0xB2,  0x7,  0x4,  0x0,  0xED,  0x32,  0xB1,  0x6,  0x5,  0xDC,  0x3C,  0xB2,  0x3,  0x2,  0x0,  0xED,  0x32,  0x0,  0xB1,  0x3,  0x0,  0x2F,  0x3C,  0xB2,  0x5,  0x4,  0x0,  0xED,  0x32,  0xB2,  0x7,  0x6,  0x1,  0xFC,  0x3C,  0xB2,  0x1,  0x2,  0x0,  0xED,  0x32,  0x33,  0x11,  0x21,  0x11,  0x27,  0x33,  0x11,  0x23,  0x21,  0x1,  0x9,  0xE8,  0xC7,  0xC7,  0x2,  0x9A,  0xFD,  0x66,  0x21,  0x2,  0x58,  0x0,  0x0,  0x3,  0x0,  0x64,  0xFF,  0x9C,  0x5,  0x78,  0x2,  0xBC,  0x0,  0x3,  0x0,  0x7,  0x0,  0xB,  0x0,  0x0,  0x1,  0x35,  0x21,  0x15,  0x1,  0x35,  0x21,  0x15,  0x1,  0x35,  0x21,  0x15,  0x1,  0xF4,  0x1,  0xF4,  0xFD,  0x44,  0x3,  0x84,  0xFB,  0xB4,  0x5,  0x14,  0x1,  0xF4,  0xC8,  0xC8,  0xFE,  0xD4,  0xC8,  0xC8,  0xFE,  0xD4,  0xC8,  0xC8,  0x0,  0x0,  0x0,  0x0,  0x0,  0xE,  0x0,  0xAE,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x26,  0x0,  0x4E,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0x0,  0x5,  0x0,  0x81,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x2,  0x0,  0x6,  0x0,  0x95,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x3,  0x0,  0x21,  0x0,  0xE0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4,  0x0,  0x5,  0x1,  0xE,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x5,  0x0,  0x10,  0x1,  0x36,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x6,  0x0,  0x5,  0x1,  0x53,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x0,  0x0,  0x4C,  0x0,  0x0,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x1,  0x0,  0xA,  0x0,  0x75,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x2,  0x0,  0xC,  0x0,  0x87,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x3,  0x0,  0x42,  0x0,  0x9C,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x4,  0x0,  0xA,  0x1,  0x2,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x5,  0x0,  0x20,  0x1,  0x14,  0x0,  0x3,  0x0,  0x1,  0x4,  0x9,  0x0,  0x6,  0x0,  0xA,  0x1,  0x47,  0x0,  0x43,  0x0,  0x6F,  0x0,  0x70,  0x0,  0x79,  0x0,  0x72,  0x0,  0x69,  0x0,  0x67,  0x0,  0x68,  0x0,  0x74,  0x0,  0x20,  0x0,  0x28,  0x0,  0x63,  0x0,  0x29,  0x0,  0x20,  0x0,  0x32,  0x0,  0x30,  0x0,  0x30,  0x0,  0x38,  0x0,  0x20,  0x0,  0x4D,  0x0,  0x6F,  0x0,  0x7A,  0x0,  0x69,  0x0,  0x6C,  0x0,  0x6C,  0x0,  0x61,  0x0,  0x20,  0x0,  0x43,  0x0,  0x6F,  0x0,  0x72,  0x0,  0x70,  0x0,  0x6F,  0x0,  0x72,  0x0,  0x61,  0x0,  0x74,  0x0,  0x69,  0x0,  0x6F,  0x0,  0x6E,  0x0,  0x0,  0x43,  0x6F,  0x70,  0x79,  0x72,  0x69,  0x67,  0x68,  0x74,  0x20,  0x28,  0x63,  0x29,  0x20,  0x32,  0x30,  0x30,  0x38,  0x20,  0x4D,  0x6F,  0x7A,  0x69,  0x6C,  0x6C,  0x61,  0x20,  0x43,  0x6F,  0x72,  0x70,  0x6F,  0x72,  0x61,  0x74,  0x69,  0x6F,  0x6E,  0x0,  0x0,  0x4D,  0x0,  0x61,  0x0,  0x72,  0x0,  0x6B,  0x0,  0x44,  0x0,  0x0,  0x4D,  0x61,  0x72,  0x6B,  0x44,  0x0,  0x0,  0x4D,  0x0,  0x65,  0x0,  0x64,  0x0,  0x69,  0x0,  0x75,  0x0,  0x6D,  0x0,  0x0,  0x4D,  0x65,  0x64,  0x69,  0x75,  0x6D,  0x0,  0x0,  0x46,  0x0,  0x6F,  0x0,  0x6E,  0x0,  0x74,  0x0,  0x46,  0x0,  0x6F,  0x0,  0x72,  0x0,  0x67,  0x0,  0x65,  0x0,  0x20,  0x0,  0x32,  0x0,  0x2E,  0x0,  0x30,  0x0,  0x20,  0x0,  0x3A,  0x0,  0x20,  0x0,  0x4D,  0x0,  0x61,  0x0,  0x72,  0x0,  0x6B,  0x0,  0x44,  0x0,  0x20,  0x0,  0x3A,  0x0,  0x20,  0x0,  0x35,  0x0,  0x2D,  0x0,  0x31,  0x0,  0x31,  0x0,  0x2D,  0x0,  0x32,  0x0,  0x30,  0x0,  0x30,  0x0,  0x38,  0x0,  0x0,  0x46,  0x6F,  0x6E,  0x74,  0x46,  0x6F,  0x72,  0x67,  0x65,  0x20,  0x32,  0x2E,  0x30,  0x20,  0x3A,  0x20,  0x4D,  0x61,  0x72,  0x6B,  0x44,  0x20,  0x3A,  0x20,  0x35,  0x2D,  0x31,  0x31,  0x2D,  0x32,  0x30,  0x30,  0x38,  0x0,  0x0,  0x4D,  0x0,  0x61,  0x0,  0x72,  0x0,  0x6B,  0x0,  0x44,  0x0,  0x0,  0x4D,  0x61,  0x72,  0x6B,  0x44,  0x0,  0x0,  0x56,  0x0,  0x65,  0x0,  0x72,  0x0,  0x73,  0x0,  0x69,  0x0,  0x6F,  0x0,  0x6E,  0x0,  0x20,  0x0,  0x30,  0x0,  0x30,  0x0,  0x31,  0x0,  0x2E,  0x0,  0x30,  0x0,  0x30,  0x0,  0x30,  0x0,  0x20,  0x0,  0x0,  0x56,  0x65,  0x72,  0x73,  0x69,  0x6F,  0x6E,  0x20,  0x30,  0x30,  0x31,  0x2E,  0x30,  0x30,  0x30,  0x20,  0x0,  0x0,  0x4D,  0x0,  0x61,  0x0,  0x72,  0x0,  0x6B,  0x0,  0x44,  0x0,  0x0,  0x4D,  0x61,  0x72,  0x6B,  0x44,  0x0,  0x0,  0x0,  0x2,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0xFF,  0x83,  0x0,  0x32,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x0,  0x4,  0x0,  0x0,  0x0,  0x1,  0x0,  0x2,  0x0,  0x27,  0x0,  0x0,  0x0,  0x0,  0x0,  0x1,  0xFF,  0xFF,  0x0,  0x2,  0x0,  0x0,  0x0,  0x1,  0x0,  0x0,  0x0,  0x0,  0xC4,  0x3E,  0x8B,  0xEE,  0x0,  0x0,  0x0,  0x0,  0xC5,  0x38,  0x2E,  0x6D,  0x0,  0x0,  0x0,  0x0,  0xC5,  0x38,  0x2E,  0x6D
+];
+
+const BinaryOutputStream = Components.Constructor("@mozilla.org/binaryoutputstream;1", "nsIBinaryOutputStream", "setOutputStream");
+
+/**
+ * The timer is needed when a delay is set. We need it to be out of the method
+ * so it is not eaten alive by the GC.
+ */
+var timer;
+
+function handleRequest(request, response) {
+  var query = {};
+  request.queryString.split('&').forEach(function (val) {
+    var [name, value] = val.split('=');
+    query[name] = unescape(value);
+  });
+
+  response.setStatusLine(request.httpVersion, 200, "OK");
+  response.setHeader("Content-Type", "font/opentype", false);
+
+  function fontWrite(data) {
+    var stream = new BinaryOutputStream(response.bodyOutputStream);
+    stream.writeByteArray(data, data.length);
+  }
+
+  var f;
+  switch (query["font"]) {
+    case "markB":
+      f = markB_data;
+      break;
+    case "markC":
+      f = markC_data;
+      break;
+    case "markD":
+      f = markD_data;
+      break;
+    case "markA":
+    default:
+      f = markA_data;
+      break;
+  }
+
+  // If there is no delay, we write the image and leave.
+  if (!("delay" in query)) {
+    fontWrite(f);
+    return;
+  }
+
+  // If there is a delay, we create a timer which, when it fires, will write
+  // image and leave.
+  response.processAsync();
+  const nsITimer = Components.interfaces.nsITimer;
+
+  timer = Components.classes["@mozilla.org/timer;1"].createInstance(nsITimer);
+  timer.initWithCallback(function() {
+    fontWrite(f);
+    response.finish();
+  }, query["delay"], nsITimer.TYPE_ONE_SHOT);
+}
+
+
--- a/layout/reftests/svg/reftest.list
+++ b/layout/reftests/svg/reftest.list
@@ -333,16 +333,17 @@ HTTP(..) == text-scale-02.svg text-scale
 HTTP(..) == text-scale-03.svg text-scale-03-ref.svg
 == text-stroke-scaling-01.svg text-stroke-scaling-01-ref.svg
 == stroke-dasharray-01.svg stroke-dasharray-01-ref.svg
 == stroke-dasharray-02.svg pass.svg
 == stroke-dasharray-03.svg pass.svg
 == stroke-dasharray-and-pathLength-01.svg pass.svg
 == stroke-dasharray-and-text-01.svg stroke-dasharray-and-text-01-ref.svg
 == stroke-dashoffset-01.svg pass.svg
+== stroke-dashoffset-and-pathLength-01.svg pass.svg
 == stroke-linecap-round-w-zero-length-segs-01.svg pass.svg
 == stroke-linecap-round-w-zero-length-segs-02.svg pass.svg
 == stroke-linecap-square-w-zero-length-segs-01.svg pass.svg
 == stroke-linecap-square-w-zero-length-segs-02.svg pass.svg
 == textPath-01.svg textPath-01-ref.svg
 == textPath-02.svg pass.svg
 == textPath-03.svg pass.svg
 == textPath-04.svg pass.svg
copy from layout/reftests/svg/stroke-dasharray-and-pathLength-01.svg
copy to layout/reftests/svg/stroke-dashoffset-and-pathLength-01.svg
--- a/layout/reftests/svg/stroke-dasharray-and-pathLength-01.svg
+++ b/layout/reftests/svg/stroke-dashoffset-and-pathLength-01.svg
@@ -1,20 +1,25 @@
 <!--
      Any copyright is dedicated to the Public Domain.
      http://creativecommons.org/publicdomain/zero/1.0/
 -->
 <svg xmlns="http://www.w3.org/2000/svg">
-  <title>Test stroke-dasharray with pathLength and scaling</title>
-  <rect width="100%" height="100%" fill="lime"/>
+  <title>Test stroke-dashoffset with pathLength</title>
   <!--
-    Here we set the 'pathLength' to twice the actual length of the path,
-    which should cause the stroke-dasharray values to be scaled down by one
-    half. Visually, this should effectively cancel out the 2x scaling along
-    the x-axis introduced by the 'transform' attribute.
+    The path is much longer than 100 user units, so if the pathLength is not
+    factored into the stroke-dashoffset calculation, the stroke will be visible.
+    A correct implementation will see the first dash pushed off the end of the
+    path which will mean that the path does not display at all.
   -->
-  <path d="M0.5,10 L100.5,10" stroke="red" stroke-width="18" stroke-dasharray="18 22" pathLength="200" transform="scale(2,1)"/>
-  <rect x="1" y="1" width="18" height="18" fill="lime"/>
-  <rect x="41" y="1" width="18" height="18" fill="lime"/>
-  <rect x="81" y="1" width="18" height="18" fill="lime"/>
-  <rect x="121" y="1" width="18" height="18" fill="lime"/>
-  <rect x="161" y="1" width="18" height="18" fill="lime"/>
+  <style>
+    path {
+      fill: none;
+      stroke-width: 5;
+      stroke: red;
+      stroke-dasharray: 100;
+      stroke-dashoffset: 100;
+    }
+  </style>
+  <rect width="100%" height="100%" fill="lime"/>
+
+  <path pathLength="100" d="M175,25 A150,150,0,1,1,175,325 A150,150,0,1,1,175,25"/>
 </svg>
--- a/layout/reftests/text-transform/reftest.list
+++ b/layout/reftests/text-transform/reftest.list
@@ -12,18 +12,18 @@ fails-if(B2G) random-if(winWidget) == sm
 HTTP(..) == fake-small-caps-1.html fake-small-caps-1-ref.html
 HTTP(..) == opentype-small-caps-1.html opentype-small-caps-1-ref.html
 HTTP(..) != opentype-small-caps-1.html opentype-small-caps-1-notref.html
 HTTP(..) == graphite-small-caps-1.html graphite-small-caps-1-ref.html
 HTTP(..) != graphite-small-caps-1.html graphite-small-caps-1-notref.html
 == uppercase-1.html uppercase-ref.html
 == uppercase-szlig-1.html uppercase-szlig-ref.html
 # these use DejaVu Sans via @font-face for consistency of results
-skip-if(B2G) HTTP(..) == all-upper.html all-upper-ref.html # bug 773482
-skip-if(B2G) HTTP(..) == all-lower.html all-lower-ref.html # bug 773482
+skip-if(B2G) fuzzy-if(cocoaWidget,250,15) HTTP(..) == all-upper.html all-upper-ref.html # bug 773482, 1140292
+skip-if(B2G) fuzzy-if(cocoaWidget,250,15) HTTP(..) == all-lower.html all-lower-ref.html # bug 773482, 1140292
 skip-if(B2G) HTTP(..) == all-title.html all-title-ref.html # bug 773482
 == smtp-upper.html smtp-upper-ref.html
 == smtp-lower.html smtp-lower-ref.html
 == smtp-title.html smtp-title-ref.html
 == turkish-casing-1.html turkish-casing-1-ref.html
 HTTP(..) != small-caps-turkish-1.html small-caps-turkish-1-notref.html
 == greek-uppercase-1a.html greek-uppercase-1-ref.html
 == greek-uppercase-1b.html greek-uppercase-1-ref.html
--- a/layout/style/moz.build
+++ b/layout/style/moz.build
@@ -136,16 +136,17 @@ UNIFIED_SOURCES += [
     'nsCSSScanner.cpp',
     'nsCSSValue.cpp',
     'nsDOMCSSAttrDeclaration.cpp',
     'nsDOMCSSDeclaration.cpp',
     'nsDOMCSSRect.cpp',
     'nsDOMCSSRGBColor.cpp',
     'nsDOMCSSValueList.cpp',
     'nsFontFaceLoader.cpp',
+    'nsFontFaceUtils.cpp',
     'nsHTMLCSSStyleSheet.cpp',
     'nsHTMLStyleSheet.cpp',
     'nsLayoutStylesheetCache.cpp',
     'nsMediaFeatures.cpp',
     'nsNthIndexCache.cpp',
     'nsROCSSPrimitiveValue.cpp',
     'nsRuleData.cpp',
     'nsRuleNode.cpp',
@@ -179,16 +180,17 @@ MSVC_ENABLE_PGO = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
     '../base',
     '../generic',
+    '../svg',
     '../xul',
     '/dom/base',
     '/dom/html',
     '/dom/xbl',
     '/dom/xul',
 ]
 
 JAR_MANIFESTS += ['jar.mn']
--- a/layout/style/nsFontFaceLoader.cpp
+++ b/layout/style/nsFontFaceLoader.cpp
@@ -116,17 +116,17 @@ nsFontFaceLoader::LoadTimerCallback(nsIT
   // before, we mark this entry as "loading slowly", so the fallback
   // font will be used in the meantime, and tell the context to refresh.
   if (updateUserFontSet) {
     ufe->mFontDataLoadingState = gfxUserFontEntry::LOADING_SLOWLY;
     nsPresContext* ctx = loader->mFontFaceSet->GetPresContext();
     NS_ASSERTION(ctx, "userfontset doesn't have a presContext?");
     if (ctx) {
       loader->mFontFaceSet->IncrementGeneration();
-      ctx->UserFontSetUpdated();
+      ctx->UserFontSetUpdated(loader->GetUserFontEntry());
       LOG(("userfonts (%p) timeout reflow\n", loader));
     }
   }
 }
 
 NS_IMPL_ISUPPORTS(nsFontFaceLoader, nsIStreamLoaderObserver)
 
 NS_IMETHODIMP
@@ -188,17 +188,17 @@ nsFontFaceLoader::OnStreamComplete(nsISt
   // and we need to load the next source.
   bool fontUpdate =
     mUserFontEntry->FontDataDownloadComplete(aString, aStringLen, aStatus);
 
   // when new font loaded, need to reflow
   if (fontUpdate) {
     // Update layout for the presence of the new font.  Since this is
     // asynchronous, reflows will coalesce.
-    ctx->UserFontSetUpdated();
+    ctx->UserFontSetUpdated(mUserFontEntry);
     LOG(("userfonts (%p) reflow\n", this));
   }
 
   // done with font set
   mFontFaceSet = nullptr;
   if (mLoadTimer) {
     mLoadTimer->Cancel();
     mLoadTimer = nullptr;
--- a/layout/style/nsFontFaceLoader.h
+++ b/layout/style/nsFontFaceLoader.h
@@ -40,16 +40,17 @@ public:
 
   void StartedLoading(nsIStreamLoader* aStreamLoader);
 
   static void LoadTimerCallback(nsITimer* aTimer, void* aClosure);
 
   static nsresult CheckLoadAllowed(nsIPrincipal* aSourcePrincipal,
                                    nsIURI* aTargetURI,
                                    nsISupports* aContext);
+  gfxUserFontEntry* GetUserFontEntry() const { return mUserFontEntry; }
 
 protected:
   virtual ~nsFontFaceLoader();
 
 private:
   nsRefPtr<gfxUserFontEntry>  mUserFontEntry;
   nsCOMPtr<nsIURI>        mFontURI;
   nsRefPtr<mozilla::dom::FontFaceSet> mFontFaceSet;
new file mode 100644
--- /dev/null
+++ b/layout/style/nsFontFaceUtils.cpp
@@ -0,0 +1,153 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=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 "gfxUserFontSet.h"
+#include "nsFontFaceUtils.h"
+#include "nsFontMetrics.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPlaceholderFrame.h"
+#include "nsTArray.h"
+#include "SVGTextFrame.h"
+
+static bool
+StyleContextContainsFont(nsStyleContext* aStyleContext,
+                         const gfxUserFontSet* aUserFontSet,
+                         const gfxUserFontEntry* aFont)
+{
+  // if the font is null, simply check to see whether fontlist includes
+  // downloadable fonts
+  if (!aFont) {
+    const FontFamilyList& fontlist =
+      aStyleContext->StyleFont()->mFont.fontlist;
+    return aUserFontSet->ContainsUserFontSetFonts(fontlist);
+  }
+
+  // first, check if the family name is in the fontlist
+  const nsString& familyName = aFont->FamilyName();
+  if (!aStyleContext->StyleFont()->mFont.fontlist.Contains(familyName)) {
+    return false;
+  }
+
+  // family name is in the fontlist, check to see if the font group
+  // associated with the frame includes the specific userfont
+  nsRefPtr<nsFontMetrics> fm;
+  nsLayoutUtils::GetFontMetricsForStyleContext(aStyleContext,
+                                               getter_AddRefs(fm),
+                                               1.0f);
+
+  if (fm->GetThebesFontGroup()->ContainsUserFont(aFont)) {
+    return true;
+  }
+
+  return false;
+}
+
+static bool
+FrameUsesFont(nsIFrame* aFrame, const gfxUserFontEntry* aFont)
+{
+  // check the style context of the frame
+  gfxUserFontSet* ufs = aFrame->PresContext()->GetUserFontSet();
+  if (StyleContextContainsFont(aFrame->StyleContext(), ufs, aFont)) {
+    return true;
+  }
+
+  // check additional style contexts
+  int32_t contextIndex = 0;
+  for (nsStyleContext* extraContext;
+       (extraContext = aFrame->GetAdditionalStyleContext(contextIndex));
+       ++contextIndex) {
+    if (StyleContextContainsFont(extraContext, ufs, aFont)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
+static void
+ScheduleReflow(nsIPresShell* aShell, nsIFrame* aFrame)
+{
+  nsIFrame* f = aFrame;
+  if (f->IsFrameOfType(nsIFrame::eSVG) || f->IsSVGText()) {
+    // SVG frames (and the non-SVG descendants of an SVGTextFrame) need special
+    // reflow handling.  We need to search upwards for the first displayed
+    // nsSVGOuterSVGFrame or non-SVG frame, which is the frame we can call
+    // FrameNeedsReflow on.  (This logic is based on
+    // nsSVGUtils::ScheduleReflowSVG and
+    // SVGTextFrame::ScheduleReflowSVGNonDisplayText.)
+    if (f->GetStateBits() & NS_FRAME_IS_NONDISPLAY) {
+      while (f) {
+        if (!(f->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
+          if (NS_SUBTREE_DIRTY(f)) {
+            // This is a displayed frame, so if it is already dirty, we
+            // will be reflowed soon anyway.  No need to call
+            // FrameNeedsReflow again, then.
+            return;
+          }
+          if (f->GetStateBits() & NS_STATE_IS_OUTER_SVG ||
+              !(f->IsFrameOfType(nsIFrame::eSVG) || f->IsSVGText())) {
+            break;
+          }
+          f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+        }
+        f = f->GetParent();
+      }
+      MOZ_ASSERT(f, "should have found an ancestor frame to reflow");
+    }
+  }
+
+  aShell->FrameNeedsReflow(f, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
+}
+
+/* static */ void
+nsFontFaceUtils::MarkDirtyForFontChange(nsIFrame* aSubtreeRoot,
+                                        const gfxUserFontEntry* aFont)
+{
+  nsAutoTArray<nsIFrame*, 4> subtrees;
+  subtrees.AppendElement(aSubtreeRoot);
+
+  nsIPresShell* ps = aSubtreeRoot->PresContext()->PresShell();
+
+  // check descendants, iterating over subtrees that may include
+  // additional subtrees associated with placeholders
+  do {
+    nsIFrame* subtreeRoot = subtrees.ElementAt(subtrees.Length() - 1);
+    subtrees.RemoveElementAt(subtrees.Length() - 1);
+
+    // Check all descendants to see if they use the font
+    nsAutoTArray<nsIFrame*, 32> stack;
+    stack.AppendElement(subtreeRoot);
+
+    do {
+      nsIFrame* f = stack.ElementAt(stack.Length() - 1);
+      stack.RemoveElementAt(stack.Length() - 1);
+
+      // if this frame uses the font, mark its descendants dirty
+      // and skip checking its children
+      if (FrameUsesFont(f, aFont)) {
+        ScheduleReflow(ps, f);
+      } else {
+        if (f->GetType() == nsGkAtoms::placeholderFrame) {
+          nsIFrame* oof = nsPlaceholderFrame::GetRealFrameForPlaceholder(f);
+          if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) {
+            // We have another distinct subtree we need to mark.
+            subtrees.AppendElement(oof);
+          }
+        }
+
+        nsIFrame::ChildListIterator lists(f);
+        for (; !lists.IsDone(); lists.Next()) {
+          nsFrameList::Enumerator childFrames(lists.CurrentList());
+          for (; !childFrames.AtEnd(); childFrames.Next()) {
+            nsIFrame* kid = childFrames.get();
+            stack.AppendElement(kid);
+          }
+        }
+      }
+    } while (!stack.IsEmpty());
+  } while (!subtrees.IsEmpty());
+}
new file mode 100644
--- /dev/null
+++ b/layout/style/nsFontFaceUtils.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ts=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/. */
+
+/* helper utilities for working with downloadable fonts */
+
+#ifndef nsFontFaceUtils_h_
+#define nsFontFaceUtils_h_
+
+class gfxUserFontEntry;
+class nsIFrame;
+
+class nsFontFaceUtils
+{
+public:
+  // mark dirty frames affected by a downloadable font
+  static void MarkDirtyForFontChange(nsIFrame* aSubtreeRoot,
+                                     const gfxUserFontEntry* aFont);
+};
+
+#endif /* !defined(nsFontFaceUtils_h_) */
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -503,22 +503,24 @@ static nscoord CalcLengthWith(const nsCS
   }
   switch (aValue.GetUnit()) {
     case eCSSUnit_EM: {
       // CSS2.1 specifies that this unit scales to the computed font
       // size, not the em-width in the font metrics, despite the name.
       return ScaleCoordRound(aValue, float(aFontSize));
     }
     case eCSSUnit_XHeight: {
+      aPresContext->SetUsesExChUnits(true);
       nsRefPtr<nsFontMetrics> fm =
         GetMetricsFor(aPresContext, aStyleContext, styleFont,
                       aFontSize, aUseUserFontSet);
       return ScaleCoordRound(aValue, float(fm->XHeight()));
     }
     case eCSSUnit_Char: {
+      aPresContext->SetUsesExChUnits(true);
       nsRefPtr<nsFontMetrics> fm =
         GetMetricsFor(aPresContext, aStyleContext, styleFont,
                       aFontSize, aUseUserFontSet);
       gfxFloat zeroWidth =
         fm->GetThebesFontGroup()->GetFirstValidFont()->
           GetMetrics(fm->Orientation()).zeroOrAveCharWidth;
 
       return ScaleCoordRound(aValue, ceil(aPresContext->AppUnitsPerDevPixel() *
--- a/media/mtransport/databuffer.h
+++ b/media/mtransport/databuffer.h
@@ -31,17 +31,16 @@ class DataBuffer {
   void Allocate(size_t len) {
     data_.reset(new uint8_t[len ? len : 1]);  // Don't depend on new [0].
     len_ = len;
   }
 
   const uint8_t *data() const { return data_.get(); }
   uint8_t *data() { return data_.get(); }
   size_t len() const { return len_; }
-  const bool empty() const { return len_ != 0; }
 
 private:
   UniquePtr<uint8_t[]> data_;
   size_t len_;
 
   DISALLOW_COPY_ASSIGN(DataBuffer);
 };
 
--- a/netwerk/cache2/CacheFileMetadata.cpp
+++ b/netwerk/cache2/CacheFileMetadata.cpp
@@ -20,37 +20,45 @@
 
 
 namespace mozilla {
 namespace net {
 
 #define kMinMetadataRead 1024  // TODO find optimal value from telemetry
 #define kAlignSize       4096
 
+// Most of the cache entries fit into one chunk due to current chunk size. Make
+// sure to tweak this value if kChunkSize is going to change.
+#define kInitialHashArraySize 1
+
+// Initial elements buffer size.
+#define kInitialBufSize 64
+
 #define kCacheEntryVersion 1
 
 #define NOW_SECONDS() (uint32_t(PR_Now() / PR_USEC_PER_SEC))
 
 NS_IMPL_ISUPPORTS(CacheFileMetadata, CacheFileIOListener)
 
 CacheFileMetadata::CacheFileMetadata(CacheFileHandle *aHandle, const nsACString &aKey)
   : CacheMemoryConsumer(NORMAL)
   , mHandle(aHandle)
-  , mFirstRead(true)
   , mHashArray(nullptr)
   , mHashArraySize(0)
   , mHashCount(0)
   , mOffset(-1)
   , mBuf(nullptr)
   , mBufSize(0)
   , mWriteBuf(nullptr)
   , mElementsSize(0)
   , mIsDirty(false)
   , mAnonymous(false)
   , mInBrowser(false)
+  , mAllocExactSize(false)
+  , mFirstRead(true)
   , mAppId(nsILoadContextInfo::NO_APP_ID)
 {
   LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, handle=%p, key=%s]",
        this, aHandle, PromiseFlatCString(aKey).get()));
 
   MOZ_COUNT_CTOR(CacheFileMetadata);
   memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
   mMetaHdr.mVersion = kCacheEntryVersion;
@@ -60,28 +68,29 @@ CacheFileMetadata::CacheFileMetadata(Cac
   DebugOnly<nsresult> rv;
   rv = ParseKey(aKey);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 }
 
 CacheFileMetadata::CacheFileMetadata(bool aMemoryOnly, const nsACString &aKey)
   : CacheMemoryConsumer(aMemoryOnly ? MEMORY_ONLY : NORMAL)
   , mHandle(nullptr)
-  , mFirstRead(true)
   , mHashArray(nullptr)
   , mHashArraySize(0)
   , mHashCount(0)
   , mOffset(0)
   , mBuf(nullptr)
   , mBufSize(0)
   , mWriteBuf(nullptr)
   , mElementsSize(0)
   , mIsDirty(true)
   , mAnonymous(false)
   , mInBrowser(false)
+  , mAllocExactSize(false)
+  , mFirstRead(true)
   , mAppId(nsILoadContextInfo::NO_APP_ID)
 {
   LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p, key=%s]",
        this, PromiseFlatCString(aKey).get()));
 
   MOZ_COUNT_CTOR(CacheFileMetadata);
   memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
   mMetaHdr.mVersion = kCacheEntryVersion;
@@ -92,28 +101,29 @@ CacheFileMetadata::CacheFileMetadata(boo
   DebugOnly<nsresult> rv;
   rv = ParseKey(aKey);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 }
 
 CacheFileMetadata::CacheFileMetadata()
   : CacheMemoryConsumer(DONT_REPORT /* This is a helper class */)
   , mHandle(nullptr)
-  , mFirstRead(true)
   , mHashArray(nullptr)
   , mHashArraySize(0)
   , mHashCount(0)
   , mOffset(0)
   , mBuf(nullptr)
   , mBufSize(0)
   , mWriteBuf(nullptr)
   , mElementsSize(0)
   , mIsDirty(false)
   , mAnonymous(false)
   , mInBrowser(false)
+  , mAllocExactSize(false)
+  , mFirstRead(true)
   , mAppId(nsILoadContextInfo::NO_APP_ID)
 {
   LOG(("CacheFileMetadata::CacheFileMetadata() [this=%p]", this));
 
   MOZ_COUNT_CTOR(CacheFileMetadata);
   memset(&mMetaHdr, 0, sizeof(CacheFileMetadataHeader));
 }
 
@@ -480,20 +490,21 @@ CacheFileMetadata::SetHash(uint32_t aInd
 
   MOZ_ASSERT(aIndex <= mHashCount);
 
   if (aIndex > mHashCount) {
     return NS_ERROR_INVALID_ARG;
   } else if (aIndex == mHashCount) {
     if ((aIndex + 1) * sizeof(CacheHash::Hash16_t) > mHashArraySize) {
       // reallocate hash array buffer
-      if (mHashArraySize == 0)
-        mHashArraySize = 32 * sizeof(CacheHash::Hash16_t);
-      else
+      if (mHashArraySize == 0) {
+        mHashArraySize = kInitialHashArraySize * sizeof(CacheHash::Hash16_t);
+      } else {
         mHashArraySize *= 2;
+      }
       mHashArray = static_cast<CacheHash::Hash16_t *>(
                      moz_xrealloc(mHashArray, mHashArraySize));
     }
 
     mHashCount++;
   }
 
   NetworkEndian::writeUint16(&mHashArray[aIndex], aHash);
@@ -702,19 +713,27 @@ CacheFileMetadata::OnDataRead(CacheFileH
   // We have all data according to offset information at the end of the entry.
   // Try to parse it.
   rv = ParseMetadata(realOffset, realOffset - usedOffset, true);
   if (NS_FAILED(rv)) {
     LOG(("CacheFileMetadata::OnDataRead() - Error parsing metadata, creating "
          "empty metadata. [this=%p]", this));
     InitEmptyMetadata();
     retval = NS_OK;
-  }
-  else {
+  } else {
     retval = NS_OK;
+
+    // Shrink elements buffer.
+    mBuf = static_cast<char *>(moz_xrealloc(mBuf, mElementsSize));
+    mBufSize = mElementsSize;
+
+    // There is usually no or just one call to SetMetadataElement() when the
+    // metadata is parsed from disk. Avoid allocating power of two sized buffer
+    // which we do in case of newly created metadata.
+    mAllocExactSize = true;
   }
 
   mListener.swap(listener);
   listener->OnMetadataRead(retval);
 
   return NS_OK;
 }
 
@@ -854,25 +873,22 @@ CacheFileMetadata::ParseMetadata(uint32_
   mHashArraySize = hashesLen;
   mHashCount = hashCount;
   if (mHashArraySize) {
     mHashArray = static_cast<CacheHash::Hash16_t *>(
                    moz_xmalloc(mHashArraySize));
     memcpy(mHashArray, mBuf + hashesOffset, mHashArraySize);
   }
 
-
   MarkDirty();
 
   mElementsSize = metaposOffset - elementsOffset;
   memmove(mBuf, mBuf + elementsOffset, mElementsSize);
   mOffset = aMetaOffset;
 
-  // TODO: shrink memory if buffer is too big
-
   DoMemoryReport(MemoryUsage());
 
   return NS_OK;
 }
 
 nsresult
 CacheFileMetadata::CheckElements(const char *aBuf, uint32_t aSize)
 {
@@ -900,21 +916,40 @@ CacheFileMetadata::CheckElements(const c
   }
   return NS_OK;
 }
 
 void
 CacheFileMetadata::EnsureBuffer(uint32_t aSize)
 {
   if (mBufSize < aSize) {
+    if (mAllocExactSize) {
+      // If this is not the only allocation, use power of two for following
+      // allocations.
+      mAllocExactSize = false;
+    } else {
+      // find smallest power of 2 greater than or equal to aSize
+      --aSize;
+      aSize |= aSize >> 1;
+      aSize |= aSize >> 2;
+      aSize |= aSize >> 4;
+      aSize |= aSize >> 8;
+      aSize |= aSize >> 16;
+      ++aSize;
+    }
+
+    if (aSize < kInitialBufSize) {
+      aSize = kInitialBufSize;
+    }
+
     mBufSize = aSize;
     mBuf = static_cast<char *>(moz_xrealloc(mBuf, mBufSize));
+
+    DoMemoryReport(MemoryUsage());
   }
-
-  DoMemoryReport(MemoryUsage());
 }
 
 nsresult
 CacheFileMetadata::ParseKey(const nsACString &aKey)
 {
   nsCOMPtr<nsILoadContextInfo> info = CacheFileUtils::ParseKey(aKey);
   NS_ENSURE_TRUE(info, NS_ERROR_FAILURE);
 
--- a/netwerk/cache2/CacheFileMetadata.h
+++ b/netwerk/cache2/CacheFileMetadata.h
@@ -171,31 +171,32 @@ private:
   void     InitEmptyMetadata();
   nsresult ParseMetadata(uint32_t aMetaOffset, uint32_t aBufOffset, bool aHaveKey);
   nsresult CheckElements(const char *aBuf, uint32_t aSize);
   void     EnsureBuffer(uint32_t aSize);
   nsresult ParseKey(const nsACString &aKey);
 
   nsRefPtr<CacheFileHandle>           mHandle;
   nsCString                           mKey;
-  bool                                mFirstRead;
-  mozilla::TimeStamp                  mReadStart;
   CacheHash::Hash16_t                *mHashArray;
   uint32_t                            mHashArraySize;
   uint32_t                            mHashCount;
   int64_t                             mOffset;
   char                               *mBuf; // used for parsing, then points
                                             // to elements
   uint32_t                            mBufSize;
   char                               *mWriteBuf;
   CacheFileMetadataHeader             mMetaHdr;
   uint32_t                            mElementsSize;
-  bool                                mIsDirty;
-  bool                                mAnonymous;
-  bool                                mInBrowser;
+  bool                                mIsDirty        : 1;
+  bool                                mAnonymous      : 1;
+  bool                                mInBrowser      : 1;
+  bool                                mAllocExactSize : 1;
+  bool                                mFirstRead      : 1;
+  mozilla::TimeStamp                  mReadStart;
   uint32_t                            mAppId;
   nsCOMPtr<CacheFileMetadataListener> mListener;
 };
 
 
 } // net
 } // mozilla
 
--- a/security/apps/AppTrustDomain.cpp
+++ b/security/apps/AppTrustDomain.cpp
@@ -25,24 +25,24 @@
 #include "manifest-signing-test-root.inc"
 
 using namespace mozilla::pkix;
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gPIPNSSLog;
 #endif
 
-static const unsigned int DEFAULT_MINIMUM_NON_ECC_BITS = 2048;
+static const unsigned int DEFAULT_MIN_RSA_BITS = 2048;
 
 namespace mozilla { namespace psm {
 
 AppTrustDomain::AppTrustDomain(ScopedCERTCertList& certChain, void* pinArg)
   : mCertChain(certChain)
   , mPinArg(pinArg)
-  , mMinimumNonECCBits(DEFAULT_MINIMUM_NON_ECC_BITS)
+  , mMinRSABits(DEFAULT_MIN_RSA_BITS)
 {
 }
 
 SECStatus
 AppTrustDomain::SetTrustedRoot(AppTrustedRoot trustedRoot)
 {
   SECItem trustedDER;
 
@@ -70,17 +70,17 @@ AppTrustDomain::SetTrustedRoot(AppTruste
       trustedDER.data = const_cast<uint8_t*>(marketplaceDevReviewersRoot);
       trustedDER.len = mozilla::ArrayLength(marketplaceDevReviewersRoot);
       break;
 
     case nsIX509CertDB::AppMarketplaceStageRoot:
       trustedDER.data = const_cast<uint8_t*>(marketplaceStageRoot);
       trustedDER.len = mozilla::ArrayLength(marketplaceStageRoot);
       // The staging root was generated with a 1024-bit key.
-      mMinimumNonECCBits = 1024u;
+      mMinRSABits = 1024u;
       break;
 
     case nsIX509CertDB::AppXPCShellRoot:
       trustedDER.data = const_cast<uint8_t*>(xpcshellRoot);
       trustedDER.len = mozilla::ArrayLength(xpcshellRoot);
       break;
 
     case nsIX509CertDB::TrustedHostedAppPublicRoot:
@@ -249,17 +249,17 @@ AppTrustDomain::CheckSignatureDigestAlgo
   // TODO: We should restrict signatures to SHA-256 or better.
   return Success;
 }
 
 Result
 AppTrustDomain::CheckRSAPublicKeyModulusSizeInBits(
   EndEntityOrCA /*endEntityOrCA*/, unsigned int modulusSizeInBits)
 {
-  if (modulusSizeInBits < mMinimumNonECCBits) {
+  if (modulusSizeInBits < mMinRSABits) {
     return Result::ERROR_INADEQUATE_KEY_SIZE;
   }
   return Success;
 }
 
 Result
 AppTrustDomain::VerifyRSAPKCS1SignedDigest(const SignedDigest& signedDigest,
                                            Input subjectPublicKeyInfo)
--- a/security/apps/AppTrustDomain.h
+++ b/security/apps/AppTrustDomain.h
@@ -56,14 +56,14 @@ public:
                            mozilla::pkix::DigestAlgorithm digestAlg,
                            /*out*/ uint8_t* digestBuf,
                            size_t digestBufLen) MOZ_OVERRIDE;
 
 private:
   /*out*/ ScopedCERTCertList& mCertChain;
   void* mPinArg; // non-owning!
   ScopedCERTCertificate mTrustedRoot;
-  unsigned int mMinimumNonECCBits;
+  unsigned int mMinRSABits;
 };
 
 } } // namespace mozilla::psm
 
 #endif // mozilla_psm_AppsTrustDomain_h
--- a/security/certverifier/CertVerifier.cpp
+++ b/security/certverifier/CertVerifier.cpp
@@ -151,18 +151,18 @@ BuildCertChainForOneKeyUsage(NSSCertDBTr
     }
   }
   if (ocspStaplingStatus) {
     *ocspStaplingStatus = trustDomain.GetOCSPStaplingStatus();
   }
   return rv;
 }
 
-static const unsigned int MINIMUM_NON_ECC_BITS = 2048;
-static const unsigned int MINIMUM_NON_ECC_BITS_WEAK = 1024;
+static const unsigned int MIN_RSA_BITS = 2048;
+static const unsigned int MIN_RSA_BITS_WEAK = 1024;
 
 SECStatus
 CertVerifier::VerifyCert(CERTCertificate* cert, SECCertificateUsage usage,
                          Time time, void* pinArg, const char* hostname,
                          const Flags flags,
             /*optional*/ const SECItem* stapledOCSPResponseSECItem,
         /*optional out*/ ScopedCERTCertList* builtChain,
         /*optional out*/ SECOidTag* evOidPolicy,
@@ -235,18 +235,17 @@ CertVerifier::VerifyCert(CERTCertificate
   }
 
   switch (usage) {
     case certificateUsageSSLClient: {
       // XXX: We don't really have a trust bit for SSL client authentication so
       // just use trustEmail as it is the closest alternative.
       NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache,
                                        pinArg, ocspGETConfig, pinningDisabled,
-                                       MINIMUM_NON_ECC_BITS_WEAK, nullptr,
-                                       builtChain);
+                                       MIN_RSA_BITS_WEAK, nullptr, builtChain);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::digitalSignature,
                           KeyPurposeId::id_kp_clientAuth,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
 
@@ -262,17 +261,17 @@ CertVerifier::VerifyCert(CERTCertificate
       SECStatus srv = GetFirstEVPolicy(cert, evPolicy, evPolicyOidTag);
       if (srv == SECSuccess) {
         NSSCertDBTrustDomain
           trustDomain(trustSSL,
                       ocspFetching == NSSCertDBTrustDomain::NeverFetchOCSP
                         ? NSSCertDBTrustDomain::LocalOnlyOCSPForEV
                         : NSSCertDBTrustDomain::FetchOCSPForEV,
                       mOCSPCache, pinArg, ocspGETConfig, mPinningMode,
-                      MINIMUM_NON_ECC_BITS, hostname, builtChain);
+                      MIN_RSA_BITS, hostname, builtChain);
         rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time,
                                           KeyUsage::digitalSignature,// (EC)DHE
                                           KeyUsage::keyEncipherment, // RSA
                                           KeyUsage::keyAgreement,    // (EC)DH
                                           KeyPurposeId::id_kp_serverAuth,
                                           evPolicy, stapledOCSPResponse,
                                           ocspStaplingStatus);
         if (rv == Success) {
@@ -287,18 +286,17 @@ CertVerifier::VerifyCert(CERTCertificate
       if (flags & FLAG_MUST_BE_EV) {
         rv = Result::ERROR_POLICY_VALIDATION_FAILED;
         break;
       }
 
       // Now try non-EV.
       NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache,
                                        pinArg, ocspGETConfig, mPinningMode,
-                                       MINIMUM_NON_ECC_BITS, hostname,
-                                       builtChain);
+                                       MIN_RSA_BITS, hostname, builtChain);
       rv = BuildCertChainForOneKeyUsage(trustDomain, certDER, time,
                                         KeyUsage::digitalSignature, // (EC)DHE
                                         KeyUsage::keyEncipherment, // RSA
                                         KeyUsage::keyAgreement, // (EC)DH
                                         KeyPurposeId::id_kp_serverAuth,
                                         CertPolicyId::anyPolicy,
                                         stapledOCSPResponse,
                                         ocspStaplingStatus);
@@ -307,17 +305,17 @@ CertVerifier::VerifyCert(CERTCertificate
           *keySizeStatus = KeySizeStatus::LargeMinimumSucceeded;
         }
         break;
       }
 
       // If that failed, try again with a smaller minimum key size.
       NSSCertDBTrustDomain trustDomainWeak(trustSSL, ocspFetching, mOCSPCache,
                                            pinArg, ocspGETConfig, mPinningMode,
-                                           MINIMUM_NON_ECC_BITS_WEAK, hostname,
+                                           MIN_RSA_BITS_WEAK, hostname,
                                            builtChain);
       rv = BuildCertChainForOneKeyUsage(trustDomainWeak, certDER, time,
                                         KeyUsage::digitalSignature, // (EC)DHE
                                         KeyUsage::keyEncipherment, // RSA
                                         KeyUsage::keyAgreement, // (EC)DH
                                         KeyPurposeId::id_kp_serverAuth,
                                         CertPolicyId::anyPolicy,
                                         stapledOCSPResponse,
@@ -331,30 +329,28 @@ CertVerifier::VerifyCert(CERTCertificate
       }
 
       break;
     }
 
     case certificateUsageSSLCA: {
       NSSCertDBTrustDomain trustDomain(trustSSL, ocspFetching, mOCSPCache,
                                        pinArg, ocspGETConfig, pinningDisabled,
-                                       MINIMUM_NON_ECC_BITS_WEAK, nullptr,
-                                       builtChain);
+                                       MIN_RSA_BITS_WEAK, nullptr, builtChain);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeCA, KeyUsage::keyCertSign,
                           KeyPurposeId::id_kp_serverAuth,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
 
     case certificateUsageEmailSigner: {
       NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache,
                                        pinArg, ocspGETConfig, pinningDisabled,
-                                       MINIMUM_NON_ECC_BITS_WEAK, nullptr,
-                                       builtChain);
+                                       MIN_RSA_BITS_WEAK, nullptr, builtChain);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::digitalSignature,
                           KeyPurposeId::id_kp_emailProtection,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
         rv = BuildCertChain(trustDomain, certDER, time,
                             EndEntityOrCA::MustBeEndEntity,
@@ -366,18 +362,17 @@ CertVerifier::VerifyCert(CERTCertificate
     }
 
     case certificateUsageEmailRecipient: {
       // TODO: The higher level S/MIME processing should pass in which key
       // usage it is trying to verify for, and base its algorithm choices
       // based on the result of the verification(s).
       NSSCertDBTrustDomain trustDomain(trustEmail, ocspFetching, mOCSPCache,
                                        pinArg, ocspGETConfig, pinningDisabled,
-                                       MINIMUM_NON_ECC_BITS_WEAK, nullptr,
-                                       builtChain);
+                                       MIN_RSA_BITS_WEAK, nullptr, builtChain);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::keyEncipherment, // RSA
                           KeyPurposeId::id_kp_emailProtection,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       if (rv == Result::ERROR_INADEQUATE_KEY_USAGE) {
         rv = BuildCertChain(trustDomain, certDER, time,
                             EndEntityOrCA::MustBeEndEntity,
@@ -387,18 +382,17 @@ CertVerifier::VerifyCert(CERTCertificate
       }
       break;
     }
 
     case certificateUsageObjectSigner: {
       NSSCertDBTrustDomain trustDomain(trustObjectSigning, ocspFetching,
                                        mOCSPCache, pinArg, ocspGETConfig,
                                        pinningDisabled,
-                                       MINIMUM_NON_ECC_BITS_WEAK, nullptr,
-                                       builtChain);
+                                       MIN_RSA_BITS_WEAK, nullptr, builtChain);
       rv = BuildCertChain(trustDomain, certDER, time,
                           EndEntityOrCA::MustBeEndEntity,
                           KeyUsage::digitalSignature,
                           KeyPurposeId::id_kp_codeSigning,
                           CertPolicyId::anyPolicy, stapledOCSPResponse);
       break;
     }
 
@@ -417,35 +411,33 @@ CertVerifier::VerifyCert(CERTCertificate
       } else {
         endEntityOrCA = EndEntityOrCA::MustBeEndEntity;
         keyUsage = KeyUsage::digitalSignature;
         eku = KeyPurposeId::id_kp_OCSPSigning;
       }
 
       NSSCertDBTrustDomain sslTrust(trustSSL, ocspFetching, mOCSPCache, pinArg,
                                     ocspGETConfig, pinningDisabled,
-                                    MINIMUM_NON_ECC_BITS_WEAK, nullptr,
-                                    builtChain);
+                                    MIN_RSA_BITS_WEAK, nullptr, builtChain);
       rv = BuildCertChain(sslTrust, certDER, time, endEntityOrCA,
                           keyUsage, eku, CertPolicyId::anyPolicy,
                           stapledOCSPResponse);
       if (rv == Result::ERROR_UNKNOWN_ISSUER) {
         NSSCertDBTrustDomain emailTrust(trustEmail, ocspFetching, mOCSPCache,
                                         pinArg, ocspGETConfig, pinningDisabled,
-                                        MINIMUM_NON_ECC_BITS_WEAK, nullptr,
-                                        builtChain);
+                                        MIN_RSA_BITS_WEAK, nullptr, builtChain);
         rv = BuildCertChain(emailTrust, certDER, time, endEntityOrCA,
                             keyUsage, eku, CertPolicyId::anyPolicy,
                             stapledOCSPResponse);
         if (rv == Result::ERROR_UNKNOWN_ISSUER) {
           NSSCertDBTrustDomain objectSigningTrust(trustObjectSigning,
                                                   ocspFetching, mOCSPCache,
                                                   pinArg, ocspGETConfig,
                                                   pinningDisabled,
-                                                  MINIMUM_NON_ECC_BITS_WEAK,
+                                                  MIN_RSA_BITS_WEAK,
                                                   nullptr, builtChain);
           rv = BuildCertChain(objectSigningTrust, certDER, time,
                               endEntityOrCA, keyUsage, eku,
                               CertPolicyId::anyPolicy, stapledOCSPResponse);
         }
       }
 
       break;
--- a/security/certverifier/NSSCertDBTrustDomain.cpp
+++ b/security/certverifier/NSSCertDBTrustDomain.cpp
@@ -48,26 +48,26 @@ typedef ScopedPtr<SECMODModule, SECMOD_D
 } // unnamed namespace
 
 NSSCertDBTrustDomain::NSSCertDBTrustDomain(SECTrustType certDBTrustType,
                                            OCSPFetching ocspFetching,
                                            OCSPCache& ocspCache,
              /*optional but shouldn't be*/ void* pinArg,
                                            CertVerifier::OcspGetConfig ocspGETConfig,
                                            CertVerifier::PinningMode pinningMode,
-                                           unsigned int minimumNonECCKeyBits,
+                                           unsigned int minRSABits,
                               /*optional*/ const char* hostname,
                               /*optional*/ ScopedCERTCertList* builtChain)
   : mCertDBTrustType(certDBTrustType)
   , mOCSPFetching(ocspFetching)
   , mOCSPCache(ocspCache)
   , mPinArg(pinArg)
   , mOCSPGetConfig(ocspGETConfig)
   , mPinningMode(pinningMode)
-  , mMinimumNonECCBits(minimumNonECCKeyBits)
+  , mMinRSABits(minRSABits)
   , mHostname(hostname)
   , mBuiltChain(builtChain)
   , mCertBlocklist(do_GetService(NS_CERTBLOCKLIST_CONTRACTID))
   , mOCSPStaplingStatus(CertVerifier::OCSP_STAPLING_NEVER_CHECKED)
 {
 }
 
 // E=igca@sgdn.pm.gouv.fr,CN=IGC/A,OU=DCSSI,O=PM/SGDN,L=Paris,ST=France,C=FR
@@ -715,17 +715,17 @@ NSSCertDBTrustDomain::CheckSignatureDige
 {
   return Success;
 }
 
 Result
 NSSCertDBTrustDomain::CheckRSAPublicKeyModulusSizeInBits(
   EndEntityOrCA /*endEntityOrCA*/, unsigned int modulusSizeInBits)
 {
-  if (modulusSizeInBits < mMinimumNonECCBits) {
+  if (modulusSizeInBits < mMinRSABits) {
     return Result::ERROR_INADEQUATE_KEY_SIZE;
   }
   return Success;
 }
 
 Result
 NSSCertDBTrustDomain::VerifyRSAPKCS1SignedDigest(
   const SignedDigest& signedDigest,
--- a/security/certverifier/NSSCertDBTrustDomain.h
+++ b/security/certverifier/NSSCertDBTrustDomain.h
@@ -51,17 +51,17 @@ public:
     FetchOCSPForEV = 3,
     LocalOnlyOCSPForEV = 4,
   };
 
   NSSCertDBTrustDomain(SECTrustType certDBTrustType, OCSPFetching ocspFetching,
                        OCSPCache& ocspCache, void* pinArg,
                        CertVerifier::OcspGetConfig ocspGETConfig,
                        CertVerifier::PinningMode pinningMode,
-                       unsigned int minimumNonECCKeyBits,
+                       unsigned int minRSABits,
           /*optional*/ const char* hostname = nullptr,
       /*optional out*/ ScopedCERTCertList* builtChain = nullptr);
 
   virtual Result FindIssuer(mozilla::pkix::Input encodedIssuerName,
                             IssuerChecker& checker,
                             mozilla::pkix::Time time) MOZ_OVERRIDE;
 
   virtual Result GetCertTrust(mozilla::pkix::EndEntityOrCA endEntityOrCA,
@@ -125,17 +125,17 @@ private:
     EncodedResponseSource responseSource, /*out*/ bool& expired);
 
   const SECTrustType mCertDBTrustType;
   const OCSPFetching mOCSPFetching;
   OCSPCache& mOCSPCache; // non-owning!
   void* mPinArg; // non-owning!
   const CertVerifier::OcspGetConfig mOCSPGetConfig;
   CertVerifier::PinningMode mPinningMode;
-  const unsigned int mMinimumNonECCBits;
+  const unsigned int mMinRSABits;
   const char* mHostname; // non-owning - only used for pinning checks
   ScopedCERTCertList* mBuiltChain; // non-owning
   nsCOMPtr<nsICertBlocklist> mCertBlocklist;
   CertVerifier::OCSPStaplingStatus mOCSPStaplingStatus;
 };
 
 } } // namespace mozilla::psm
 
--- a/security/manager/ssl/tests/unit/head_psm.js
+++ b/security/manager/ssl/tests/unit/head_psm.js
@@ -251,16 +251,19 @@ function add_connection_test(aHost, aExp
     this.host = aHost;
     let threadManager = Cc["@mozilla.org/thread-manager;1"]
                           .getService(Ci.nsIThreadManager);
     this.thread = threadManager.currentThread;
     this.defer = Promise.defer();
     let sts = Cc["@mozilla.org/network/socket-transport-service;1"]
                 .getService(Ci.nsISocketTransportService);
     this.transport = sts.createTransport(["ssl"], 1, aHost, REMOTE_PORT, null);
+    // See bug 1129771 - attempting to connect to [::1] when the server is
+    // listening on 127.0.0.1 causes frequent failures on OS X 10.10.
+    this.transport.connectionFlags |= Ci.nsISocketTransport.DISABLE_IPV6;
     this.transport.setEventSink(this, this.thread);
     this.inputStream = null;
     this.outputStream = null;
     this.connected = false;
   }
 
   Connection.prototype = {
     // nsITransportEventSink
old mode 100644
new mode 100755
--- a/security/manager/ssl/tests/unit/test_keysize/generate.py
+++ b/security/manager/ssl/tests/unit/test_keysize/generate.py
@@ -240,19 +240,16 @@ def generate_combination_chains():
     generate_cert_chain('prime256v1', '256',
                         'rsa', '1016',
                         'prime256v1', '256',
                         False)
 
 # Create a NSS DB for use by the OCSP responder.
 CertUtils.init_nss_db(srcdir)
 
-# TODO(bug 636807): SECKEY_PublicKeyStrengthInBits() rounds up the number of
-# bits to the next multiple of 8 - therefore the highest key size less than 1024
-# that can be tested is 1016, less than 2048 is 2040 and so on.
 generate_rsa_chains('1016', '1024', False)
 generate_rsa_chains('2040', '2048', True)
 generate_ecc_chains()
 generate_combination_chains()
 
 # Print a blank line and the information needed to enable EV for any roots
 # generated by this script.
 print
--- a/security/manager/ssl/tests/unit/test_keysize_ev.js
+++ b/security/manager/ssl/tests/unit/test_keysize_ev.js
@@ -80,17 +80,17 @@ function addKeySizeTestForEV(expectedNam
  * For debug builds which have the test EV roots compiled in, checks RSA chains
  * which contain certs with key sizes adequate for EV are validated as such,
  * while chains that contain any cert with an inadequate key size fail EV and
  * validate as DV.
  * For opt builds which don't have the test EV roots compiled in, checks that
  * none of the chains validate as EV.
  *
  * Note: This function assumes that the key size requirements for EV are greater
- * than or equal to the requirements for DV.
+ * than the requirements for DV.
  *
  * @param {Number} inadequateKeySize
  *        The inadequate key size of the generated certs.
  * @param {Number} adequateKeySize
  *        The adequate key size of the generated certs.
  */
 function checkRSAChains(inadequateKeySize, adequateKeySize) {
   // Reuse the existing test RSA EV root
--- a/testing/marionette/client/marionette/marionette_test.py
+++ b/testing/marionette/client/marionette/marionette_test.py
@@ -630,20 +630,20 @@ class MarionetteTestCase(CommonTestCase)
 
     def setUp(self):
         CommonTestCase.setUp(self)
         self.marionette.test_name = self.test_name
         self.marionette.execute_script("log('TEST-START: %s:%s')" %
                                        (self.filepath.replace('\\', '\\\\'), self.methodName))
 
     def tearDown(self):
-        self.marionette.check_for_crash()
-        self.marionette.set_context("content")
-        self.marionette.execute_script("log('TEST-END: %s:%s')" %
-                                       (self.filepath.replace('\\', '\\\\'), self.methodName))
+        if not self.marionette.check_for_crash():
+           self.marionette.set_context("content")
+           self.marionette.execute_script("log('TEST-END: %s:%s')" %
+                                          (self.filepath.replace('\\', '\\\\'), self.methodName))
         self.marionette.test_name = None
         CommonTestCase.tearDown(self)
 
     def get_new_emulator(self):
         self.extra_emulator_index += 1
         if len(self.marionette.extra_emulators) == self.extra_emulator_index:
             qemu  = Marionette(emulator=self.marionette.emulator.arch,
                                emulatorBinary=self.marionette.emulator.binary,
--- a/testing/marionette/marionette-server.js
+++ b/testing/marionette/marionette-server.js
@@ -202,25 +202,31 @@ MarionetteServerConnection.prototype = {
    */
   onPacket: function MSC_onPacket(aPacket) {
     // Dispatch the request
     if (this.requestTypes && this.requestTypes[aPacket.name]) {
       try {
         this.logRequest(aPacket.name, aPacket);
         this.requestTypes[aPacket.name].bind(this)(aPacket);
       } catch(e) {
-        this.conn.send({ error: ("error occurred while processing '" +
-                                 aPacket.name),
-                        message: e.message });
+        this.conn.send({from:this.actorID, error: {
+                                                    message: ("error occurred while processing '" +
+                                                              aPacket.name),
+                                                    status: 500,
+                                                    stacktrace: e.message }});
       }
     } else {
-      this.conn.send({ error: "unrecognizedPacketType",
-                       message: ('Marionette does not ' +
-                                 'recognize the packet type "' +
-                                 aPacket.name + '"') });
+      this.conn.send({from:this.actorID, error: {
+                                                  message: "unrecognizedPacketType",
+                                                  status: 500,
+                                                  stacktrace: ('Marionette does not ' +
+                                                               'recognize the packet type "' +
+                                                                aPacket.name + '"')
+                                                }
+                      });
     }
   },
 
   onClosed: function MSC_onClosed(aStatus) {
     this.server._connectionClosed(this);
     this.sessionTearDown();
   },
 
--- a/testing/mozbase/mozlog/mozlog/structured/commandline.py
+++ b/testing/mozbase/mozlog/mozlog/structured/commandline.py
@@ -1,13 +1,14 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import sys
+import os
 import optparse
 
 from collections import defaultdict
 from structuredlog import StructuredLogger, set_default_logger
 import handlers
 import formatters
 
 log_formatters = {
@@ -54,18 +55,21 @@ fmt_options = {
                "If specified, enables message buffering at the given buffer size limit.",
                ["mach", "tbpl"], "store"),
 }
 
 
 def log_file(name):
     if name == "-":
         return sys.stdout
-    else:
-        return open(name, "w")
+    # ensure we have a correct dirpath by using realpath
+    dirpath = os.path.dirname(os.path.realpath(name))
+    if not os.path.exists(dirpath):
+        os.makedirs(dirpath)
+    return open(name, "w")
 
 
 def add_logging_group(parser, include_formatters=None):
     """
     Add logging options to an argparse ArgumentParser or
     optparse OptionParser.
 
     Each formatter has a corresponding option of the form --log-{name}
--- a/testing/web-platform/harness/.gitignore
+++ b/testing/web-platform/harness/.gitignore
@@ -1,5 +1,7 @@
 *.py[co]
 *~
 *#
 \#*
-_virtualenv
\ No newline at end of file
+_virtualenv
+test/test.cfg
+test/metadata/MANIFEST.json
--- a/testing/web-platform/harness/MANIFEST.in
+++ b/testing/web-platform/harness/MANIFEST.in
@@ -1,12 +1,13 @@
 exclude MANIFEST.in
 include requirements.txt
 include wptrunner/browsers/b2g_setup/*
 include wptrunner.default.ini
 include wptrunner/testharness_runner.html
 include wptrunner/testharnessreport.js
+include wptrunner/testharnessreport-servo.js
 include wptrunner/executors/testharness_marionette.js
 include wptrunner/executors/testharness_webdriver.js
 include wptrunner/executors/reftest.js
 include wptrunner/executors/reftest-wait.js
 include wptrunner/config.json
 include wptrunner/browsers/server-locations.txt
\ No newline at end of file
--- a/testing/web-platform/harness/setup.py
+++ b/testing/web-platform/harness/setup.py
@@ -7,17 +7,17 @@ import os
 import sys
 import textwrap
 
 from setuptools import setup, find_packages
 
 here = os.path.split(__file__)[0]
 
 PACKAGE_NAME = 'wptrunner'
-PACKAGE_VERSION = '1.10'
+PACKAGE_VERSION = '1.13'
 
 # Dependencies
 with open(os.path.join(here, "requirements.txt")) as f:
     deps = f.read().splitlines()
 
 # Browser-specific requirements
 requirements_files = glob.glob(os.path.join(here, "requirements_*.txt"))
 
--- a/testing/web-platform/harness/wptrunner.default.ini
+++ b/testing/web-platform/harness/wptrunner.default.ini
@@ -1,10 +1,11 @@
 [products]
 
 [web-platform-tests]
 remote_url = https://github.com/w3c/web-platform-tests.git
 branch = master
 sync_path = %(pwd)s/sync
 
-[paths]
+[manifest:default]
 tests = %(pwd)s/tests
 metadata = %(pwd)s/meta
+url_base = /
\ No newline at end of file
--- a/testing/web-platform/harness/wptrunner/browsers/b2g.py
+++ b/testing/web-platform/harness/wptrunner/browsers/b2g.py
@@ -13,17 +13,17 @@ import mozdevice
 import moznetwork
 import mozrunner
 from marionette import expected
 from marionette.by import By
 from marionette.wait import Wait
 from mozprofile import FirefoxProfile, Preferences
 
 from .base import get_free_port, BrowserError, Browser, ExecutorBrowser
-from ..executors.executormarionette import MarionetteTestharnessExecutor, required_files
+from ..executors.executormarionette import MarionetteTestharnessExecutor
 from ..hosts import HostsFile, HostsLine
 
 here = os.path.split(__file__)[0]
 
 __wptrunner__ = {"product": "b2g",
                  "check_args": "check_args",
                  "browser": "B2GBrowser",
                  "executor": {"testharness": "B2GMarionetteTestharnessExecutor"},
@@ -36,32 +36,34 @@ def check_args(**kwargs):
     pass
 
 
 def browser_kwargs(test_environment, **kwargs):
     return {"prefs_root": kwargs["prefs_root"],
             "no_backup": kwargs.get("b2g_no_backup", False)}
 
 
-def executor_kwargs(http_server_url, **kwargs):
+def executor_kwargs(test_type, http_server_url, cache_manager, **kwargs):
     timeout_multiplier = kwargs["timeout_multiplier"]
     if timeout_multiplier is None:
         timeout_multiplier = 2
 
+    if test_type == "reftest":
+        executor_kwargs["cache_manager"] = cache_manager
+
     executor_kwargs = {"http_server_url": http_server_url,
                        "timeout_multiplier": timeout_multiplier,
                        "close_after_done": False}
     return executor_kwargs
 
 
 def env_options():
     return {"host": "web-platform.test",
             "bind_hostname": "false",
-            "test_server_port": False,
-            "required_files": required_files}
+            "test_server_port": False}
 
 
 class B2GBrowser(Browser):
     used_ports = set()
     init_timeout = 180
 
     def __init__(self, logger, prefs_root, no_backup=False):
         Browser.__init__(self, logger)
--- a/testing/web-platform/harness/wptrunner/browsers/base.py
+++ b/testing/web-platform/harness/wptrunner/browsers/base.py
@@ -108,16 +108,17 @@ class Browser(object):
         with which it should be instantiated"""
         return ExecutorBrowser, {}
 
     def log_crash(self, process, test):
         """Return a list of dictionaries containing information about crashes that happend
         in the browser, or an empty list if no crashes occurred"""
         self.logger.crash(process, test)
 
+
 class NullBrowser(Browser):
     def start(self):
         """No-op browser to use in scenarios where the TestRunnerManager shouldn't
         actually own the browser process (e.g. Servo where we start one browser
         per test)"""
         pass
 
     def stop(self):
@@ -127,16 +128,17 @@ class NullBrowser(Browser):
         return None
 
     def is_alive(self):
         return True
 
     def on_output(self, line):
         raise NotImplementedError
 
+
 class ExecutorBrowser(object):
     def __init__(self, **kwargs):
         """View of the Browser used by the Executor object.
         This is needed because the Executor runs in a child process and
         we can't ship Browser instances between processes on Windows.
 
         Typically this will have a few product-specific properties set,
         but in some cases it may have more elaborate methods for setting
--- a/testing/web-platform/harness/wptrunner/browsers/chrome.py
+++ b/testing/web-platform/harness/wptrunner/browsers/chrome.py
@@ -1,54 +1,54 @@
 # 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/.
 
 from .base import Browser, ExecutorBrowser, require_arg
 from .webdriver import ChromedriverLocalServer
-from ..executors.executorselenium import SeleniumTestharnessExecutor, required_files
+from ..executors import executor_kwargs as base_executor_kwargs
+from ..executors.executorselenium import (SeleniumTestharnessExecutor,
+                                          SeleniumRefTestExecutor)
 
 
 __wptrunner__ = {"product": "chrome",
                  "check_args": "check_args",
                  "browser": "ChromeBrowser",
-                 "executor": {"testharness": "SeleniumTestharnessExecutor"},
+                 "executor": {"testharness": "SeleniumTestharnessExecutor",
+                              "reftest": "SeleniumRefTestExecutor"},
                  "browser_kwargs": "browser_kwargs",
                  "executor_kwargs": "executor_kwargs",
                  "env_options": "env_options"}
 
 
 def check_args(**kwargs):
     require_arg(kwargs, "binary")
 
 
 def browser_kwargs(**kwargs):
     return {"binary": kwargs["binary"],
             "webdriver_binary": kwargs["webdriver_binary"]}
 
 
-def executor_kwargs(http_server_url, **kwargs):
+def executor_kwargs(test_type, http_server_url, cache_manager, **kwargs):
     from selenium.webdriver import DesiredCapabilities
 
-    timeout_multiplier = kwargs["timeout_multiplier"]
-    if timeout_multiplier is None:
-        timeout_multiplier = 1
-    binary = kwargs["binary"]
-    capabilities = dict(DesiredCapabilities.CHROME.items() +
-                        {"chromeOptions": {"binary": binary}}.items())
+    executor_kwargs = base_executor_kwargs(test_type, http_server_url,
+                                           cache_manager, **kwargs)
+    executor_kwargs["close_after_done"] = True
+    executor_kwargs["capabilities"] = dict(DesiredCapabilities.CHROME.items() +
+                                           {"chromeOptions":
+                                            {"binary": kwargs["binary"]}}.items())
 
-    return {"http_server_url": http_server_url,
-            "capabilities": capabilities,
-            "timeout_multiplier": timeout_multiplier}
+    return executor_kwargs
 
 
 def env_options():
     return {"host": "web-platform.test",
-            "bind_hostname": "true",
-            "required_files": required_files}
+            "bind_hostname": "true"}
 
 
 class ChromeBrowser(Browser):
     """Chrome is backed by chromedriver, which is supplied through
     ``browsers.webdriver.ChromedriverLocalServer``."""
 
     def __init__(self, logger, binary, webdriver_binary="chromedriver"):
         """Creates a new representation of Chrome.  The `binary` argument gives
--- a/testing/web-platform/harness/wptrunner/browsers/firefox.py
+++ b/testing/web-platform/harness/wptrunner/browsers/firefox.py
@@ -9,25 +9,25 @@ import mozinfo
 from mozprocess import ProcessHandler
 from mozprofile import FirefoxProfile, Preferences
 from mozprofile.permissions import ServerLocations
 from mozrunner import FirefoxRunner
 from mozcrash import mozcrash
 
 from .base import get_free_port, Browser, ExecutorBrowser, require_arg, cmd_arg
 from ..executors import executor_kwargs as base_executor_kwargs
-from ..executors.executormarionette import MarionetteTestharnessExecutor, MarionetteReftestExecutor, required_files
+from ..executors.executormarionette import MarionetteTestharnessExecutor, MarionetteRefTestExecutor
 
 here = os.path.join(os.path.split(__file__)[0])
 
 __wptrunner__ = {"product": "firefox",
                  "check_args": "check_args",
                  "browser": "FirefoxBrowser",
                  "executor": {"testharness": "MarionetteTestharnessExecutor",
-                              "reftest": "MarionetteReftestExecutor"},
+                              "reftest": "MarionetteRefTestExecutor"},
                  "browser_kwargs": "browser_kwargs",
                  "executor_kwargs": "executor_kwargs",
                  "env_options": "env_options"}
 
 
 def check_args(**kwargs):
     require_arg(kwargs, "binary")
     if kwargs["ssl_type"] != "none":
@@ -40,27 +40,27 @@ def browser_kwargs(**kwargs):
             "debug_args": kwargs["debug_args"],
             "interactive": kwargs["interactive"],
             "symbols_path": kwargs["symbols_path"],
             "stackwalk_binary": kwargs["stackwalk_binary"],
             "certutil_binary": kwargs["certutil_binary"],
             "ca_certificate_path": kwargs["ssl_env"].ca_cert_path()}
 
 
-def executor_kwargs(http_server_url, **kwargs):
-    executor_kwargs = base_executor_kwargs(http_server_url, **kwargs)
+def executor_kwargs(test_type, http_server_url, cache_manager, **kwargs):
+    executor_kwargs = base_executor_kwargs(test_type, http_server_url,
+                                           cache_manager, **kwargs)
     executor_kwargs["close_after_done"] = True
     return executor_kwargs
 
 
 def env_options():
     return {"host": "127.0.0.1",
             "external_host": "web-platform.test",
             "bind_hostname": "false",
-            "required_files": required_files,
             "certificate_domain": "web-platform.test",
             "encrypt_after_connect": True}
 
 
 class FirefoxBrowser(Browser):
     used_ports = set()
 
     def __init__(self, logger, binary, prefs_root, debug_args=None, interactive=None,
--- a/testing/web-platform/harness/wptrunner/browsers/servo.py
+++ b/testing/web-platform/harness/wptrunner/browsers/servo.py
@@ -1,43 +1,50 @@
 # 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/.
 
 import os
 
 from .base import NullBrowser, ExecutorBrowser, require_arg
-from ..executors import executor_kwargs
-from ..executors.executorservo import ServoTestharnessExecutor, ServoReftestExecutor
+from ..executors import executor_kwargs as base_executor_kwargs
+from ..executors.executorservo import ServoTestharnessExecutor, ServoRefTestExecutor
 
 here = os.path.join(os.path.split(__file__)[0])
 
 __wptrunner__ = {"product": "servo",
                  "check_args": "check_args",
                  "browser": "ServoBrowser",
                  "executor": {"testharness": "ServoTestharnessExecutor",
-                              "reftest": "ServoReftestExecutor"},
+                              "reftest": "ServoRefTestExecutor"},
                  "browser_kwargs": "browser_kwargs",
                  "executor_kwargs": "executor_kwargs",
                  "env_options": "env_options"}
 
 
 def check_args(**kwargs):
     require_arg(kwargs, "binary")
 
 
 def browser_kwargs(**kwargs):
     return {"binary": kwargs["binary"],
             "debug_args": kwargs["debug_args"],
             "interactive": kwargs["interactive"]}
 
 
+def executor_kwargs(test_type, http_server_url, cache_manager, **kwargs):
+    rv = base_executor_kwargs(test_type, http_server_url,
+                              cache_manager, **kwargs)
+    rv["pause_after_test"] = kwargs["pause_after_test"]
+    return rv
+
 def env_options():
     return {"host": "localhost",
-            "bind_hostname": "true"}
+            "bind_hostname": "true",
+            "testharnessreport": "testharnessreport-servo.js"}
 
 
 class ServoBrowser(NullBrowser):
     def __init__(self, logger, binary, debug_args=None, interactive=False):
         NullBrowser.__init__(self, logger)
         self.binary = binary
         self.debug_args = debug_args
         self.interactive = interactive
copy from testing/web-platform/harness/wptrunner/wptrunner.py
copy to testing/web-platform/harness/wptrunner/environment.py
--- a/testing/web-platform/harness/wptrunner/wptrunner.py
+++ b/testing/web-platform/harness/wptrunner/environment.py
@@ -1,183 +1,146 @@
 # 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/.
 
-from __future__ import unicode_literals
-
 import json
-import logging
 import os
-import shutil
+import multiprocessing
 import socket
 import sys
-import threading
 import time
-import urlparse
-from Queue import Empty
-from StringIO import StringIO
-
-from multiprocessing import Queue
 
-from mozlog.structured import (commandline, stdadapter, get_default_logger,
-                               structuredlog, handlers, formatters)
+from mozlog.structured import get_default_logger, handlers
 
-import products
-import testloader
-import wptcommandline
-import wpttest
-from testrunner import ManagerGroup
+from wptlogging import LogLevelRewriter
 
 here = os.path.split(__file__)[0]
 
-
-"""Runner for web-platform-tests
-
-The runner has several design goals:
-
-* Tests should run with no modification from upstream.
-
-* Tests should be regarded as "untrusted" so that errors, timeouts and even
-  crashes in the tests can be handled without failing the entire test run.
-
-* For performance tests can be run in multiple browsers in parallel.
-
-The upstream repository has the facility for creating a test manifest in JSON
-format. This manifest is used directly to determine which tests exist. Local
-metadata files are used to store the expected test results.
-"""
-
-logger = None
-
+serve = None
+sslutils = None
 
-def setup_logging(args, defaults):
-    global logger
-    logger = commandline.setup_logging("web-platform-tests", args, defaults)
-    setup_stdlib_logger()
-
-    for name in args.keys():
-        if name.startswith("log_"):
-            args.pop(name)
+def do_delayed_imports(logger, test_paths):
+    global serve, sslutils
 
-    return logger
-
-
-def setup_stdlib_logger():
-    logging.root.handlers = []
-    logging.root = stdadapter.std_logging_adapter(logging.root)
-
-
-def do_delayed_imports(serve_root):
-    global serve, manifest, sslutils
+    serve_root = serve_path(test_paths)
 
     sys.path.insert(0, serve_root)
-    sys.path.insert(0, str(os.path.join(serve_root, "tools")))
-    sys.path.insert(0, str(os.path.join(serve_root, "tools", "scripts")))
+
     failed = []
 
     try:
-        import serve
+        from tools.serve import serve
     except ImportError:
         failed.append("serve")
-    try:
-        import manifest
-    except ImportError:
-        failed.append("manifest")
+
     try:
         import sslutils
     except ImportError:
         raise
         failed.append("sslutils")
 
     if failed:
         logger.critical(
             "Failed to import %s. Ensure that tests path %s contains web-platform-tests" %
             (", ".join(failed), serve_root))
         sys.exit(1)
 
 
+def serve_path(test_paths):
+    return test_paths["/"]["tests_path"]
+
+
+def get_ssl_kwargs(**kwargs):
+    if kwargs["ssl_type"] == "openssl":
+        args = {"openssl_binary": kwargs["openssl_binary"]}
+    elif kwargs["ssl_type"] == "pregenerated":
+        args = {"host_key_path": kwargs["host_key_path"],
+                "host_cert_path": kwargs["host_cert_path"],
+                 "ca_cert_path": kwargs["ca_cert_path"]}
+    else:
+        args = {}
+    return args
+
+
+def ssl_env(logger, **kwargs):
+    ssl_env_cls = sslutils.environments[kwargs["ssl_type"]]
+    return ssl_env_cls(logger, **get_ssl_kwargs(**kwargs))
+
+
 class TestEnvironmentError(Exception):
     pass
 
 
-class LogLevelRewriter(object):
-    """Filter that replaces log messages at specified levels with messages
-    at a different level.
-
-    This can be used to e.g. downgrade log messages from ERROR to WARNING
-    in some component where ERRORs are not critical.
+def static_handler(path, format_args, content_type, **headers):
+    with open(path) as f:
+        data = f.read() % format_args
 
-    :param inner: Handler to use for messages that pass this filter
-    :param from_levels: List of levels which should be affected
-    :param to_level: Log level to set for the affected messages
-    """
-    def __init__(self, inner, from_levels, to_level):
-        self.inner = inner
-        self.from_levels = [item.upper() for item in from_levels]
-        self.to_level = to_level.upper()
+    resp_headers = [("Content-Type", content_type)]
+    for k, v in headers.iteritems():
+        resp_headers.append((k.replace("_", "-"), v))
 
-    def __call__(self, data):
-        if data["action"] == "log" and data["level"].upper() in self.from_levels:
-            data = data.copy()
-            data["level"] = self.to_level
-        return self.inner(data)
+    @serve.handlers.handler
+    def func(request, response):
+        return resp_headers, data
+
+    return func
 
 
 class TestEnvironment(object):
-    def __init__(self, serve_path, test_paths, ssl_env, options):
+    def __init__(self, test_paths, ssl_env, pause_after_test, options):
         """Context manager that owns the test environment i.e. the http and
         websockets servers"""
-        self.serve_path = serve_path
         self.test_paths = test_paths
         self.ssl_env = ssl_env
         self.server = None
         self.config = None
         self.external_config = None
+        self.pause_after_test = pause_after_test
         self.test_server_port = options.pop("test_server_port", True)
         self.options = options if options is not None else {}
-        self.required_files = options.pop("required_files", [])
-        self.files_to_restore = []
+
+        self.cache_manager = multiprocessing.Manager()
 
     def __enter__(self):
         self.ssl_env.__enter__()
-        self.copy_required_files()
+        self.cache_manager.__enter__()
         self.setup_server_logging()
         self.setup_routes()
         self.config = self.load_config()
         serve.set_computed_defaults(self.config)
         self.external_config, self.servers = serve.start(self.config, self.ssl_env)
         return self
 
     def __exit__(self, exc_type, exc_val, exc_tb):
+        self.cache_manager.__exit__(exc_type, exc_val, exc_tb)
         self.ssl_env.__exit__(exc_type, exc_val, exc_tb)
 
-        self.restore_files()
         for scheme, servers in self.servers.iteritems():
             for port, server in servers:
                 server.kill()
 
     def load_config(self):
-        default_config_path = os.path.join(self.serve_path, "config.default.json")
+        default_config_path = os.path.join(serve_path(self.test_paths), "config.default.json")
         local_config_path = os.path.join(here, "config.json")
 
         with open(default_config_path) as f:
             default_config = json.load(f)
 
         with open(local_config_path) as f:
             data = f.read()
             local_config = json.loads(data % self.options)
 
         #TODO: allow non-default configuration for ssl
 
         local_config["external_host"] = self.options.get("external_host", None)
         local_config["ssl"]["encrypt_after_connect"] = self.options.get("encrypt_after_connect", False)
 
         config = serve.merge_json(default_config, local_config)
-        config["doc_root"] = self.serve_path
+        config["doc_root"] = serve_path(self.test_paths)
 
         if not self.ssl_env.ssl_enabled:
             config["ports"]["https"] = [None]
 
         host = self.options.get("certificate_domain", config["host"])
         hosts = [host]
         hosts.extend("%s.%s" % (item[0], host) for item in serve.get_subdomains(host).values())
         key_file, certificate = self.ssl_env.host_cert_path(hosts)
@@ -190,21 +153,33 @@ class TestEnvironment(object):
     def setup_server_logging(self):
         server_logger = get_default_logger(component="wptserve")
         assert server_logger is not None
         log_filter = handlers.LogLevelFilter(lambda x:x, "info")
         # Downgrade errors to warnings for the server
         log_filter = LogLevelRewriter(log_filter, ["error"], "warning")
         server_logger.component_filter = log_filter
 
-        serve.logger = server_logger
-        #Set as the default logger for wptserve
-        serve.set_logger(server_logger)
+        try:
+            #Set as the default logger for wptserve
+            serve.set_logger(server_logger)
+            serve.logger = server_logger
+        except Exception:
+            # This happens if logging has already been set up for wptserve
+            pass
 
     def setup_routes(self):
+        for path, format_args, content_type, route in [
+                ("testharness_runner.html", {}, "text/html", b"/testharness_runner.html"),
+                (self.options.get("testharnessreport", "testharnessreport.js"),
+                 {"output": self.pause_after_test}, "text/javascript",
+                 b"/resources/testharnessreport.js")]:
+            handler = static_handler(os.path.join(here, path), format_args, content_type)
+            serve.routes.insert(0, (b"GET", route, handler))
+
         for url, paths in self.test_paths.iteritems():
             if url == "/":
                 continue
 
             path = paths["tests_path"]
             url = "/%s/" % url.strip("/")
 
             for (method,
@@ -219,31 +194,16 @@ class TestEnvironment(object):
                                    "*",
                                    serve.handlers.FileHandler)]:
                 route = (method, b"%s%s" % (str(url), str(suffix)), handler_cls(path, url_base=url))
                 serve.routes.insert(-3, route)
 
         if "/" not in self.test_paths:
             serve.routes = serve.routes[:-3]
 
-    def copy_required_files(self):
-        logger.info("Placing required files in server environment.")
-        for source, destination, copy_if_exists in self.required_files:
-            source_path = os.path.join(here, source)
-            dest_path = os.path.join(self.serve_path, destination, os.path.split(source)[1])
-            dest_exists = os.path.exists(dest_path)
-            if not dest_exists or copy_if_exists:
-                if dest_exists:
-                    backup_path = dest_path + ".orig"
-                    logger.info("Backing up %s to %s" % (dest_path, backup_path))
-                    self.files_to_restore.append(dest_path)
-                    shutil.copy2(dest_path, backup_path)
-                logger.info("Copying %s to %s" % (source_path, dest_path))
-                shutil.copy2(source_path, dest_path)
-
     def ensure_started(self):
         # Pause for a while to ensure that the server has a chance to start
         time.sleep(2)
         for scheme, servers in self.servers.iteritems():
             for port, server in servers:
                 if self.test_server_port:
                     s = socket.socket()
                     try:
@@ -251,253 +211,8 @@ class TestEnvironment(object):
                     except socket.error:
                         raise EnvironmentError(
                             "%s server on port %d failed to start" % (scheme, port))
                     finally:
                         s.close()
 
                 if not server.is_alive():
                     raise EnvironmentError("%s server on port %d failed to start" % (scheme, port))
-
-    def restore_files(self):
-        for path in self.files_to_restore:
-            os.unlink(path)
-            if os.path.exists(path + ".orig"):
-                os.rename(path + ".orig", path)
-
-
-class LogThread(threading.Thread):
-    def __init__(self, queue, logger, level):
-        self.queue = queue
-        self.log_func = getattr(logger, level)
-        threading.Thread.__init__(self, name="Thread-Log")
-        self.daemon = True
-
-    def run(self):
-        while True:
-            try:
-                msg = self.queue.get()
-            except (EOFError, IOError):
-                break
-            if msg is None:
-                break
-            else:
-                self.log_func(msg)
-
-
-class LoggingWrapper(StringIO):
-    """Wrapper for file like objects to redirect output to logger
-    instead"""
-
-    def __init__(self, queue, prefix=None):
-        self.queue = queue
-        self.prefix = prefix
-
-    def write(self, data):
-        if isinstance(data, str):
-            data = data.decode("utf8")
-
-        if data.endswith("\n"):
-            data = data[:-1]
-        if data.endswith("\r"):
-            data = data[:-1]
-        if not data:
-            return
-        if self.prefix is not None:
-            data = "%s: %s" % (self.prefix, data)
-        self.queue.put(data)
-
-    def flush(self):
-        pass
-
-def list_test_groups(serve_root, test_paths, test_types, product, **kwargs):
-
-    do_delayed_imports(serve_root)
-
-    run_info = wpttest.get_run_info(kwargs["run_info"], product, debug=False)
-    test_filter = testloader.TestFilter(include=kwargs["include"],
-                                        exclude=kwargs["exclude"],
-                                        manifest_path=kwargs["include_manifest"])
-    test_loader = testloader.TestLoader(test_paths,
-                                        test_types,
-                                        test_filter,
-                                        run_info)
-
-    for item in sorted(test_loader.groups(test_types)):
-        print item
-
-
-def list_disabled(serve_root, test_paths, test_types, product, **kwargs):
-    do_delayed_imports(serve_root)
-    rv = []
-    run_info = wpttest.get_run_info(kwargs["run_info"], product, debug=False)
-    test_loader = testloader.TestLoader(test_paths,
-                                        test_types,
-                                        testloader.TestFilter(),
-                                        run_info)
-
-    for test_type, tests in test_loader.disabled_tests.iteritems():
-        for test in tests:
-            rv.append({"test": test.id, "reason": test.disabled()})
-    print json.dumps(rv, indent=2)
-
-
-def get_ssl_kwargs(**kwargs):
-    if kwargs["ssl_type"] == "openssl":
-        args = {"openssl_binary": kwargs["openssl_binary"]}
-    elif kwargs["ssl_type"] == "pregenerated":
-        args = {"host_key_path": kwargs["host_key_path"],
-                "host_cert_path": kwargs["host_cert_path"],
-                 "ca_cert_path": kwargs["ca_cert_path"]}
-    else:
-        args = {}
-    return args
-
-
-def run_tests(config, serve_root, test_paths, product, **kwargs):
-    logging_queue = None
-    logging_thread = None
-    original_stdio = (sys.stdout, sys.stderr)
-    test_queues = None
-
-    try:
-        if not kwargs["no_capture_stdio"]:
-            logging_queue = Queue()
-            logging_thread = LogThread(logging_queue, logger, "info")
-            sys.stdout = LoggingWrapper(logging_queue, prefix="STDOUT")
-            sys.stderr = LoggingWrapper(logging_queue, prefix="STDERR")
-            logging_thread.start()
-
-        do_delayed_imports(serve_root)
-
-        run_info = wpttest.get_run_info(kwargs["run_info"], product, debug=False)
-
-        (check_args,
-         browser_cls, get_browser_kwargs,
-         executor_classes, get_executor_kwargs,
-         env_options) = products.load_product(config, product)
-
-        check_args(**kwargs)
-
-        ssl_env_cls = sslutils.environments[kwargs["ssl_type"]]
-        ssl_env = ssl_env_cls(logger, **get_ssl_kwargs(**kwargs))
-
-        unexpected_total = 0
-
-        if "test_loader" in kwargs:
-            test_loader = kwargs["test_loader"]
-        else:
-            test_filter = testloader.TestFilter(include=kwargs["include"],
-                                                exclude=kwargs["exclude"],
-                                                manifest_path=kwargs["include_manifest"])
-            test_loader = testloader.TestLoader(test_paths,
-                                                kwargs["test_types"],
-                                                test_filter,
-                                                run_info,
-                                                kwargs["chunk_type"],
-                                                kwargs["total_chunks"],
-                                                kwargs["this_chunk"],
-                                                kwargs["manifest_update"])
-
-        if kwargs["run_by_dir"] is False:
-            test_source_cls = testloader.SingleTestSource
-            test_source_kwargs = {}
-        else:
-            # A value of None indicates infinite depth
-            test_source_cls = testloader.PathGroupedSource
-            test_source_kwargs = {"depth": kwargs["run_by_dir"]}
-
-        logger.info("Using %i client processes" % kwargs["processes"])
-
-        with TestEnvironment(serve_root,
-                             test_paths,
-                             ssl_env,
-                             env_options) as test_environment:
-            try:
-                test_environment.ensure_started()
-            except TestEnvironmentError as e:
-                logger.critical("Error starting test environment: %s" % e.message)
-                raise
-
-            browser_kwargs = get_browser_kwargs(ssl_env=ssl_env, **kwargs)
-            base_server = "http://%s:%i" % (test_environment.external_config["host"],
-                                            test_environment.external_config["ports"]["http"][0])
-
-            repeat = kwargs["repeat"]
-            for repeat_count in xrange(repeat):
-                if repeat > 1:
-                    logger.info("Repetition %i / %i" % (repeat_count + 1, repeat))
-
-
-                unexpected_count = 0
-                logger.suite_start(test_loader.test_ids, run_info)
-                for test_type in kwargs["test_types"]:
-                    logger.info("Running %s tests" % test_type)
-
-                    for test in test_loader.disabled_tests[test_type]:
-                        logger.test_start(test.id)
-                        logger.test_end(test.id, status="SKIP")
-
-                    executor_cls = executor_classes.get(test_type)
-                    executor_kwargs = get_executor_kwargs(base_server,
-                                                          **kwargs)
-
-                    if executor_cls is None:
-                        logger.error("Unsupported test type %s for product %s" %
-                                     (test_type, product))
-                        continue
-
-
-                    with ManagerGroup("web-platform-tests",
-                                      kwargs["processes"],
-                                      test_source_cls,
-                                      test_source_kwargs,
-                                      browser_cls,
-                                      browser_kwargs,
-                                      executor_cls,
-                                      executor_kwargs,
-                                      kwargs["pause_on_unexpected"],
-                                      kwargs["debug_args"]) as manager_group:
-                        try:
-                            manager_group.run(test_type, test_loader.tests)
-                        except KeyboardInterrupt:
-                            logger.critical("Main thread got signal")
-                            manager_group.stop()
-                            raise
-                    unexpected_count += manager_group.unexpected_count()
-
-                unexpected_total += unexpected_count
-                logger.info("Got %i unexpected results" % unexpected_count)
-                logger.suite_end()
-    except KeyboardInterrupt:
-        if test_queues is not None:
-            for queue in test_queues.itervalues():
-                queue.cancel_join_thread()
-    finally:
-        if test_queues is not None:
-            for queue in test_queues.itervalues():
-                queue.close()
-        sys.stdout, sys.stderr = original_stdio
-        if not kwargs["no_capture_stdio"] and logging_queue is not None:
-            logger.info("Closing logging queue")
-            logging_queue.put(None)
-            if logging_thread is not None:
-                logging_thread.join(10)
-            logging_queue.close()
-
-    return unexpected_total == 0
-
-
-def main():
-    """Main entry point when calling from the command line"""
-    kwargs = wptcommandline.parse_args()
-
-    if kwargs["prefs_root"] is None:
-        kwargs["prefs_root"] = os.path.abspath(os.path.join(here, "prefs"))
-
-    setup_logging(kwargs, {"raw": sys.stdout})
-
-    if kwargs["list_test_groups"]:
-        list_test_groups(**kwargs)
-    elif kwargs["list_disabled"]:
-        list_disabled(**kwargs)
-    else:
-        return run_tests(**kwargs)
--- a/testing/web-platform/harness/wptrunner/executors/base.py
+++ b/testing/web-platform/harness/wptrunner/executors/base.py
@@ -1,27 +1,36 @@
 # 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/.
 
+import hashlib
 import json
 import os
+import traceback
 from abc import ABCMeta, abstractmethod
+from multiprocessing import Manager
+
+from ..testrunner import Stop
 
 here = os.path.split(__file__)[0]
 
 
-def executor_kwargs(http_server_url, **kwargs):
+def executor_kwargs(test_type, http_server_url, cache_manager, **kwargs):
     timeout_multiplier = kwargs["timeout_multiplier"]
     if timeout_multiplier is None:
         timeout_multiplier = 1
 
     executor_kwargs = {"http_server_url": http_server_url,
                        "timeout_multiplier": timeout_multiplier,
                        "debug_args": kwargs["debug_args"]}
+
+    if test_type == "reftest":
+        executor_kwargs["screenshot_cache"] = cache_manager.dict()
+
     return executor_kwargs
 
 
 class TestharnessResultConverter(object):
     harness_codes = {0: "OK",
                      1: "ERROR",
                      2: "TIMEOUT"}
 
@@ -37,22 +46,29 @@ class TestharnessResultConverter(object)
         harness_result = test.result_cls(self.harness_codes[result["status"]], result["message"])
         return (harness_result,
                 [test.subtest_result_cls(subtest["name"], self.test_codes[subtest["status"]],
                                          subtest["message"]) for subtest in result["tests"]])
 testharness_result_converter = TestharnessResultConverter()
 
 
 def reftest_result_converter(self, test, result):
-    return (test.result_cls(result["status"], result["message"]), [])
+    return (test.result_cls(result["status"], result["message"],
+                            extra=result.get("extra")), [])
+
 
+class ExecutorException(Exception):
+    def __init__(self, status, message):
+        self.status = status
+        self.message = message
 
 class TestExecutor(object):
     __metaclass__ = ABCMeta
 
+    test_type = None
     convert_result = None
 
     def __init__(self, browser, http_server_url, timeout_multiplier=1,
                  debug_args=None):
         """Abstract Base class for object that actually executes the tests in a
         specific browser. Typically there will be a different TestExecutor
         subclass for each test type and method of executing tests.
 
@@ -63,33 +79,190 @@ class TestExecutor(object):
         :param timeout_multiplier: Multiplier relative to base timeout to use
                                    when setting test timeout.
         """
         self.runner = None
         self.browser = browser
         self.http_server_url = http_server_url
         self.timeout_multiplier = timeout_multiplier
         self.debug_args = debug_args
+        self.protocol = None # This must be set in subclasses
 
     @property
     def logger(self):
         """StructuredLogger for this executor"""
         if self.runner is not None:
             return self.runner.logger
 
-    @abstractmethod
     def setup(self, runner):
         """Run steps needed before tests can be started e.g. connecting to
         browser instance
 
         :param runner: TestRunner instance that is going to run the tests"""
-        pass
+        self.runner = runner
+        self.protocol.setup(runner)
 
     def teardown(self):
         """Run cleanup steps after tests have finished"""
-        pass
+        self.protocol.teardown()
 
-    @abstractmethod
     def run_test(self, test):
         """Run a particular test.
 
         :param test: The test to run"""
+        try:
+            result = self.do_test(test)
+        except Exception as e:
+            result = self.result_from_exception(test, e)
+
+        if result is Stop:
+            return result
+
+        if result[0].status == "ERROR":
+            self.logger.debug(result[0].message)
+        self.runner.send_message("test_ended", test, result)
+
+    @abstractmethod
+    def do_test(self, test):
+        """Test-type and protocol specific implmentation of running a
+        specific test.
+
+        :param test: The test to run."""
         pass
+
+    def result_from_exception(self, test, e):
+        if hasattr(e, "status") and e.status in test.result_cls.statuses:
+            status = e.status
+        else:
+            status = "ERROR"
+        message = unicode(getattr(e, "message", ""))
+        if message:
+            message += "\n"
+        message += traceback.format_exc(e)
+        return test.result_cls(status, message), []
+
+
+class TestharnessExecutor(TestExecutor):
+    convert_result = testharness_result_converter
+
+
+class RefTestExecutor(TestExecutor):
+    convert_result = reftest_result_converter
+
+    def __init__(self, browser, http_server_url, timeout_multiplier=1, screenshot_cache=None,
+                 debug_args=None):
+        TestExecutor.__init__(self, browser, http_server_url,
+                              timeout_multiplier=timeout_multiplier,
+                              debug_args=debug_args)
+
+        self.screenshot_cache = screenshot_cache
+
+class RefTestImplementation(object):
+    def __init__(self, executor):
+        self.timeout_multiplier = executor.timeout_multiplier
+        self.executor = executor
+        # Cache of url:(screenshot hash, screenshot). Typically the
+        # screenshot is None, but we set this value if a test fails
+        # and the screenshot was taken from the cache so that we may
+        # retrieve the screenshot from the cache directly in the future
+        self.screenshot_cache = self.executor.screenshot_cache
+        self.message = None
+
+    @property
+    def logger(self):
+        return self.executor.logger
+
+    def get_hash(self, url, timeout):
+        timeout = timeout * self.timeout_multiplier
+
+        if url not in self.screenshot_cache:
+            success, data = self.executor.screenshot(url, timeout)
+
+            if not success:
+                return False, data
+
+            screenshot = data
+            hash_value = hashlib.sha1(screenshot).hexdigest()
+
+            self.screenshot_cache[url] = (hash_value, None)
+
+            rv = True, (hash_value, screenshot)
+        else:
+            rv = True, self.screenshot_cache[url]
+
+        self.message.append("%s %s" % (url, rv[1][0]))
+        return rv
+
+    def is_pass(self, lhs_hash, rhs_hash, relation):
+        assert relation in ("==", "!=")
+        self.message.append("Testing %s %s %s" % (lhs_hash, relation, rhs_hash))
+        return ((relation == "==" and lhs_hash == rhs_hash) or
+                (relation == "!=" and lhs_hash != rhs_hash))
+
+    def run_test(self, test):
+        self.message = []
+
+        # Depth-first search of reference tree, with the goal
+        # of reachings a leaf node with only pass results
+
+        stack = list(((test, item[0]), item[1]) for item in reversed(test.references))
+        while stack:
+            hashes = [None, None]
+            screenshots = [None, None]
+
+            nodes, relation = stack.pop()
+
+            for i, node in enumerate(nodes):
+                success, data = self.get_hash(node.url, node.timeout)
+                if success is False:
+                    return {"status": data[0], "message": data[1]}
+
+                hashes[i], screenshots[i] = data
+
+            if self.is_pass(hashes[0], hashes[1], relation):
+                if nodes[1].references:
+                    stack.extend(list(((nodes[1], item[0]), item[1]) for item in reversed(nodes[1].references)))
+                else:
+                    # We passed
+                    return {"status":"PASS", "message": None}
+
+        for i, (node, screenshot) in enumerate(zip(nodes, screenshots)):
+            if screenshot is None:
+                success, screenshot = self.retake_screenshot(node)
+                if success:
+                    screenshots[i] = screenshot
+
+        log_data = [{"url": nodes[0].url, "screenshot": screenshots[0]}, relation,
+                    {"url": nodes[1].url, "screenshot": screenshots[1]}]
+
+        return {"status": "FAIL",
+                "message": "\n".join(self.message),
+                "extra": {"reftest_screenshots": log_data}}
+
+    def retake_screenshot(self, node):
+        success, data = self.executor.screenshot(node.url,
+                                                 node.timeout *
+                                                 self.timeout_multiplier)
+        if not success:
+            return False, data
+
+        hash_val, _ = self.screenshot_cache[node.url]
+        self.screenshot_cache[node.url] = hash_val, data
+        return True, data
+
+class Protocol(object):
+    def __init__(self, executor, browser, http_server_url):
+        self.executor = executor
+        self.browser = browser
+        self.http_server_url = http_server_url
+
+    @property
+    def logger(self):
+        return self.executor.logger
+
+    def setup(self, runner):
+        pass
+
+    def teardown(self):
+        pass
+
+    def wait(self):
+        pass
--- a/testing/web-platform/harness/wptrunner/executors/executormarionette.py
+++ b/testing/web-platform/harness/wptrunner/executors/executormarionette.py
@@ -12,278 +12,301 @@ import traceback
 import urlparse
 import uuid
 from collections import defaultdict
 
 marionette = None
 
 here = os.path.join(os.path.split(__file__)[0])
 
-from .base import TestExecutor, testharness_result_converter, reftest_result_converter
+from .base import (ExecutorException,
+                   Protocol,
+                   RefTestExecutor,
+                   RefTestImplementation,
+                   TestExecutor,
+                   TestharnessExecutor,
+                   testharness_result_converter,
+                   reftest_result_converter)
 from ..testrunner import Stop
 
 # Extra timeout to use after internal test timeout at which the harness
 # should force a timeout
 extra_timeout = 5 # seconds
 
-required_files = [("testharness_runner.html", "", False),
-                  ("testharnessreport.js", "resources/", True)]
-
-
 def do_delayed_imports():
     global marionette
     global errors
     try:
         import marionette
         from marionette import errors
     except ImportError:
         from marionette_driver import marionette, errors
 
 
-class MarionetteTestExecutor(TestExecutor):
-    def __init__(self,
-                 browser,
-                 http_server_url,
-                 timeout_multiplier=1,
-                 debug_args=None,
-                 close_after_done=True):
+class MarionetteProtocol(Protocol):
+    def __init__(self, executor, browser, http_server_url):
         do_delayed_imports()
 
-        TestExecutor.__init__(self, browser, http_server_url, timeout_multiplier, debug_args)
-        self.marionette_port = browser.marionette_port
+        Protocol.__init__(self, executor, browser, http_server_url)
         self.marionette = None
-
-        self.timer = None
-        self.window_id = str(uuid.uuid4())
-        self.close_after_done = close_after_done
+        self.marionette_port = browser.marionette_port
 
     def setup(self, runner):
         """Connect to browser via Marionette."""
-        self.runner = runner
+        Protocol.setup(self, runner)
 
         self.logger.debug("Connecting to marionette on port %i" % self.marionette_port)
         self.marionette = marionette.Marionette(host='localhost', port=self.marionette_port)
+
         # XXX Move this timeout somewhere
         self.logger.debug("Waiting for Marionette connection")
         while True:
             success = self.marionette.wait_for_port(60)
             #When running in a debugger wait indefinitely for firefox to start
-            if success or self.debug_args is None:
+            if success or self.executor.debug_args is None:
                 break
 
         session_started = False
         if success:
             try:
                 self.logger.debug("Starting Marionette session")
                 self.marionette.start_session()
             except Exception as e:
                 self.logger.warning("Starting marionette session failed: %s" % e)
             else:
                 self.logger.debug("Marionette session started")
                 session_started = True
 
         if not success or not session_started:
             self.logger.warning("Failed to connect to Marionette")
-            self.runner.send_message("init_failed")
+            self.executor.runner.send_message("init_failed")
         else:
             try:
                 self.after_connect()
             except Exception:
                 self.logger.warning("Post-connection steps failed")
                 self.logger.error(traceback.format_exc())
-                self.runner.send_message("init_failed")
+                self.executor.runner.send_message("init_failed")
             else:
-                self.runner.send_message("init_succeeded")
+                self.executor.runner.send_message("init_succeeded")
 
     def teardown(self):
         try:
             self.marionette.delete_session()
-        except:
+        except Exception:
             # This is typically because the session never started
             pass
         del self.marionette
 
     def is_alive(self):
         """Check if the marionette connection is still active"""
         try:
             # Get a simple property over the connection
             self.marionette.current_window_handle
-        except:
+        except Exception:
             return False
         return True
 
     def after_connect(self):
         url = urlparse.urljoin(
             self.http_server_url, "/testharness_runner.html")
         self.logger.debug("Loading %s" % url)
         try:
             self.marionette.navigate(url)
-        except:
+        except Exception as e:
             self.logger.critical(
                 "Loading initial page %s failed. Ensure that the "
                 "there are no other programs bound to this port and "
                 "that your firewall rules or network setup does not "
-                "prevent access." % url)
-            raise
+                "prevent access.\e%s" % (url, traceback.format_exc(e)))
         self.marionette.execute_script(
             "document.title = '%s'" % threading.current_thread().name.replace("'", '"'))
 
-    def run_test(self, test):
-        """Run a single test.
+    def wait(self):
+        while True:
+            try:
+                self.marionette.execute_async_script("");
+            except errors.ScriptTimeoutException:
+                pass
+            except (socket.timeout, errors.InvalidResponseException, IOError):
+                break
+            except Exception as e:
+                self.logger.error(traceback.format_exc(e))
+                break
+
 
-        This method is independent of the test type, and calls
-        do_test to implement the type-sepcific testing functionality.
-        """
-        # Lock to prevent races between timeouts and other results
-        # This might not be strictly necessary if we need to deal
-        # with the result changing post-hoc anyway (e.g. due to detecting
-        # a crash after we get the data back from marionette)
-        result = None
-        result_flag = threading.Event()
-        result_lock = threading.Lock()
+class MarionetteRun(object):
+    def __init__(self, logger, func, marionette, url, timeout):
+        self.logger = logger
+        self.result = None
+        self.marionette = marionette
+        self.func = func
+        self.url = url
+        self.timeout = timeout
+        self.result_flag = threading.Event()
 
-        timeout = test.timeout * self.timeout_multiplier
-
-        def timeout_func():
-            with result_lock:
-                if not result_flag.is_set():
-                    result_flag.set()
-                    result = (test.result_cls("EXTERNAL-TIMEOUT", None), [])
-                    self.runner.send_message("test_ended", test, result)
-
-        if self.debug_args is None:
-            self.timer = threading.Timer(timeout + 2 * extra_timeout, timeout_func)
-            self.timer.start()
+    def run(self):
+        timeout = self.timeout
 
         try:
-            self.marionette.set_script_timeout((timeout + extra_timeout) * 1000)
-        except IOError, errors.InvalidResponseException:
+            if timeout is not None:
+                self.marionette.set_script_timeout((timeout + extra_timeout) * 1000)
+            else:
+                # We just want it to never time out, really, but marionette doesn't
+                # make that possible. It also seems to time out immediately if the
+                # timeout is set too high. This works at least.
+                self.marionette.set_script_timeout(2**31 - 1)
+        except (IOError, errors.InvalidResponseException):
             self.logger.error("Lost marionette connection before starting test")
             return Stop
 
+        executor = threading.Thread(target = self._run)
+        executor.start()
+
+        if timeout is not None:
+            wait_timeout = timeout + 2 * extra_timeout
+        else:
+            wait_timeout = None
+
+        flag = self.result_flag.wait(wait_timeout)
+        if self.result is None:
+            self.logger.debug("Timed out waiting for a result")
+            assert not flag
+            self.result = False, ("EXTERNAL-TIMEOUT", None)
+
+        return self.result
+
+    def _run(self):
         try:
-            result = self.convert_result(test, self.do_test(test, timeout))
+            self.result = True, self.func(self.marionette, self.url, self.timeout)
         except errors.ScriptTimeoutException:
-            with result_lock:
-                if not result_flag.is_set():
-                    result_flag.set()
-                    result = (test.result_cls("EXTERNAL-TIMEOUT", None), [])
-            # Clean up any unclosed windows
-            # This doesn't account for the possibility the browser window
-            # is totally hung. That seems less likely since we are still
-            # getting data from marionette, but it might be just as well
-            # to do a full restart in this case
-            # XXX - this doesn't work at the moment because window_handles
-            # only returns OS-level windows (see bug 907197)
-            # while True:
-            #     handles = self.marionette.window_handles
-            #     self.marionette.switch_to_window(handles[-1])
-            #     if len(handles) > 1:
-            #         self.marionette.close()
-            #     else:
-            #         break
-            # Now need to check if the browser is still responsive and restart it if not
+            self.logger.debug("Got a marionette timeout")
+            self.result = False, ("EXTERNAL-TIMEOUT", None)
         except (socket.timeout, errors.InvalidResponseException, IOError):
             # This can happen on a crash
             # Also, should check after the test if the firefox process is still running
             # and otherwise ignore any other result and set it to crash
-            with result_lock:
-                if not result_flag.is_set():
-                    result_flag.set()
-                    result = (test.result_cls("CRASH", None), [])
-        finally:
-            if self.timer is not None:
-                self.timer.cancel()
-
-        with result_lock:
-            if result:
-                self.runner.send_message("test_ended", test, result)
-
-    def do_test(self, test, timeout):
-        """Run the steps specific to a given test type for Marionette-based tests.
-
-        :param test: - the Test being run
-        :param timeout: - the timeout in seconds to give the test
-        """
-        raise NotImplementedError
+            self.result = False, ("CRASH", None)
+        except Exception as e:
+            message = getattr(e, "message", "")
+            if message:
+                message += "\n"
+            message += traceback.format_exc(e)
+            self.result = False, ("ERROR", e)
 
-class MarionetteTestharnessExecutor(MarionetteTestExecutor):
-    convert_result = testharness_result_converter
-
-    def __init__(self, *args, **kwargs):
-        """Marionette-based executor for testharness.js tests"""
-        MarionetteTestExecutor.__init__(self, *args, **kwargs)
-        self.script = open(os.path.join(here, "testharness_marionette.js")).read()
-
-    def do_test(self, test, timeout):
-        if self.close_after_done:
-            self.marionette.execute_script("if (window.wrappedJSObject.win) {window.wrappedJSObject.win.close()}")
-
-        return self.marionette.execute_async_script(
-            self.script % {"abs_url": urlparse.urljoin(self.http_server_url, test.url),
-                           "url": test.url,
-                           "window_id": self.window_id,
-                           "timeout_multiplier": self.timeout_multiplier,
-                           "timeout": timeout * 1000,
-                           "explicit_timeout": self.debug_args is not None}, new_sandbox=False)
+        finally:
+            self.result_flag.set()
 
 
-class MarionetteReftestExecutor(MarionetteTestExecutor):
-    convert_result = reftest_result_converter
+class MarionetteTestharnessExecutor(TestharnessExecutor):
+    def __init__(self, browser, http_server_url, timeout_multiplier=1, close_after_done=True,
+                 debug_args=None):
+        """Marionette-based executor for testharness.js tests"""
+        TestharnessExecutor.__init__(self, browser, http_server_url,
+                                     timeout_multiplier=timeout_multiplier,
+                                     debug_args=debug_args)
+
+        self.protocol = MarionetteProtocol(self, browser, http_server_url)
+        self.script = open(os.path.join(here, "testharness_marionette.js")).read()
+        self.close_after_done = close_after_done
+        self.window_id = str(uuid.uuid4())
+
+        if marionette is None:
+            do_delayed_imports()
+
+    def is_alive(self):
+        return self.protocol.is_alive()
+
+    def do_test(self, test):
+        timeout = (test.timeout * self.timeout_multiplier if self.debug_args is None
+                   else None)
+        success, data = MarionetteRun(self.logger,
+                                      self.do_testharness,
+                                      self.protocol.marionette,
+                                      test.url,
+                                      timeout).run()
+        if success:
+            return self.convert_result(test, data)
+
+        return (test.result_cls(*data), [])
 
-    def __init__(self, *args, **kwargs):
+    def do_testharness(self, marionette, url, timeout):
+        if self.close_after_done:
+            marionette.execute_script("if (window.wrappedJSObject.win) {window.wrappedJSObject.win.close()}")
+
+        if timeout is not None:
+            timeout_ms = str(timeout * 1000)
+        else:
+            timeout_ms = "null"
+
+        script = self.script % {"abs_url": urlparse.urljoin(self.http_server_url, url),
+                                "url": url,
+                                "window_id": self.window_id,
+                                "timeout_multiplier": self.timeout_multiplier,
+                                "timeout": timeout_ms,
+                                "explicit_timeout": timeout is None}
+
+        return marionette.execute_async_script(script, new_sandbox=False)
+
+
+class MarionetteRefTestExecutor(RefTestExecutor):
+    def __init__(self, browser, http_server_url, timeout_multiplier=1,
+                 screenshot_cache=None, close_after_done=True, debug_args=None):
         """Marionette-based executor for reftests"""
-        MarionetteTestExecutor.__init__(self, *args, **kwargs)
+        RefTestExecutor.__init__(self,
+                                 browser,
+                                 http_server_url,
+                                 screenshot_cache=screenshot_cache,
+                                 timeout_multiplier=timeout_multiplier,
+                                 debug_args=debug_args)
+        self.protocol = MarionetteProtocol(self, browser, http_server_url)
+        self.implementation = RefTestImplementation(self)
+        self.close_after_done = close_after_done
+        self.has_window = False
+
         with open(os.path.join(here, "reftest.js")) as f:
             self.script = f.read()
         with open(os.path.join(here, "reftest-wait.js")) as f:
             self.wait_script = f.read()
-        self.ref_hashes = {}
-        self.ref_urls_by_hash = defaultdict(set)
+
+    def is_alive(self):
+        return self.protocol.is_alive()
+
+    def do_test(self, test):
+        if self.close_after_done and self.has_window:
+            self.protocol.marionette.close()
+            self.protocol.marionette.switch_to_window(
+                self.protocol.marionette.window_handles[-1])
+            self.has_window = False
+
+        if not self.has_window:
+            self.protocol.marionette.execute_script(self.script)
+            self.protocol.marionette.switch_to_window(self.protocol.marionette.window_handles[-1])
+            self.has_window = True
+
+        result = self.implementation.run_test(test)
+
+        return self.convert_result(test, result)
 
-    def do_test(self, test, timeout):
-        test_url, ref_type, ref_url = test.url, test.ref_type, test.ref_url
-        hashes = {"test": None,
-                  "ref": self.ref_hashes.get(ref_url)}
-        self.marionette.execute_script(self.script)
-        self.marionette.switch_to_window(self.marionette.window_handles[-1])
-        for url_type, url in [("test", test_url), ("ref", ref_url)]:
-            if hashes[url_type] is None:
-                # Would like to do this in a new tab each time, but that isn't
-                # easy with the current state of marionette
-                full_url = urlparse.urljoin(self.http_server_url, url)
-                try:
-                    self.marionette.navigate(full_url)
-                except errors.MarionetteException:
-                    return {"status": "ERROR",
-                            "message": "Failed to load url %s" % (full_url,)}
-                if url_type == "test":
-                    self.wait()
-                screenshot = self.marionette.screenshot()
-                # strip off the data:img/png, part of the url
-                if screenshot.startswith("data:image/png;base64,"):
-                    screenshot = screenshot.split(",", 1)[1]
-                hashes[url_type] = hashlib.sha1(screenshot).hexdigest()
+    def screenshot(self, url, timeout):
+        timeout = timeout if self.debug_args is None else None
+
+        return MarionetteRun(self.logger,
+                             self._screenshot,
+                             self.protocol.marionette,
+                             url,
+                             timeout).run()
 
-        self.ref_urls_by_hash[hashes["ref"]].add(ref_url)
-        self.ref_hashes[ref_url] = hashes["ref"]
-
-        if ref_type == "==":
-            passed = hashes["test"] == hashes["ref"]
-        elif ref_type == "!=":
-            passed = hashes["test"] != hashes["ref"]
-        else:
-            raise ValueError
+    def _screenshot(self, marionette, url, timeout):
+        full_url = urlparse.urljoin(self.http_server_url, url)
+        try:
+            marionette.navigate(full_url)
+        except errors.MarionetteException:
+            raise ExecutorException("ERROR", "Failed to load url %s" % (full_url,))
 
-        return {"status": "PASS" if passed else "FAIL",
-                "message": None}
-
-    def wait(self):
-        self.marionette.execute_async_script(self.wait_script)
+        marionette.execute_async_script(self.wait_script)
 
-    def teardown(self):
-        count = 0
-        for hash_val, urls in self.ref_urls_by_hash.iteritems():
-            if len(urls) > 1:
-                self.logger.info("The following %i reference urls appear to be equivalent:\n %s" %
-                                 (len(urls), "\n  ".join(urls)))
-                count += len(urls) - 1
-        MarionetteTestExecutor.teardown(self)
+        screenshot = marionette.screenshot()
+        # strip off the data:img/png, part of the url
+        if screenshot.startswith("data:image/png;base64,"):
+            screenshot = screenshot.split(",", 1)[1]
+
+        return screenshot
--- a/testing/web-platform/harness/wptrunner/executors/executorselenium.py
+++ b/testing/web-platform/harness/wptrunner/executors/executorselenium.py
@@ -6,46 +6,49 @@ import os
 import socket
 import sys
 import threading
 import time
 import traceback
 import urlparse
 import uuid
 
-from .base import TestExecutor, testharness_result_converter
+from .base import (ExecutorException,
+                   Protocol,
+                   RefTestExecutor,
+                   RefTestImplementation,
+                   TestExecutor,
+                   TestharnessExecutor,
+                   testharness_result_converter,
+                   reftest_result_converter)
 from ..testrunner import Stop
 
 
 here = os.path.join(os.path.split(__file__)[0])
 
 webdriver = None
 exceptions = None
 
-required_files = [("testharness_runner.html", "", False),
-                  ("testharnessreport.js", "resources/", True)]
-
+extra_timeout = 5
 
 def do_delayed_imports():
     global webdriver
     global exceptions
     from selenium import webdriver
     from selenium.common import exceptions
 
 
-class SeleniumTestExecutor(TestExecutor):
-    def __init__(self, browser, http_server_url, capabilities,
-                 timeout_multiplier=1, debug_args=None, **kwargs):
+class SeleniumProtocol(Protocol):
+    def __init__(self, executor, browser, http_server_url, capabilities, **kwargs):
         do_delayed_imports()
-        TestExecutor.__init__(self, browser, http_server_url, timeout_multiplier, debug_args)
+
+        Protocol.__init__(self, executor, browser, http_server_url)
         self.capabilities = capabilities
         self.url = browser.webdriver_url
         self.webdriver = None
-        self.timer = None
-        self.window_id = str(uuid.uuid4())
 
     def setup(self, runner):
         """Connect to browser via Selenium's WebDriver implementation."""
         self.runner = runner
         self.logger.debug("Connecting to Selenium on URL: %s" % self.url)
 
         session_started = False
         try:
@@ -55,27 +58,27 @@ class SeleniumTestExecutor(TestExecutor)
             self.logger.warning(
                 "Connecting to Selenium failed:\n%s" % traceback.format_exc())
         else:
             self.logger.debug("Selenium session started")
             session_started = True
 
         if not session_started:
             self.logger.warning("Failed to connect to Selenium")
-            self.runner.send_message("init_failed")
+            self.executor.runner.send_message("init_failed")
         else:
             try:
                 self.after_connect()
             except:
                 print >> sys.stderr, traceback.format_exc()
                 self.logger.warning(
                     "Failed to connect to navigate initial page")
-                self.runner.send_message("init_failed")
+                self.executor.runner.send_message("init_failed")
             else:
-                self.runner.send_message("init_succeeded")
+                self.executor.runner.send_message("init_succeeded")
 
     def teardown(self):
         self.logger.debug("Hanging up on Selenium session")
         try:
             self.webdriver.quit()
         except:
             pass
         del self.webdriver
@@ -91,95 +94,160 @@ class SeleniumTestExecutor(TestExecutor)
 
     def after_connect(self):
         url = urlparse.urljoin(self.http_server_url, "/testharness_runner.html")
         self.logger.debug("Loading %s" % url)
         self.webdriver.get(url)
         self.webdriver.execute_script("document.title = '%s'" %
                                       threading.current_thread().name.replace("'", '"'))
 
-    def run_test(self, test):
-        """Run a single test.
-
-        This method is independent of the test type, and calls
-        do_test to implement the type-sepcific testing functionality.
-        """
-        # Lock to prevent races between timeouts and other results
-        # This might not be strictly necessary if we need to deal
-        # with the result changing post-hoc anyway (e.g. due to detecting
-        # a crash after we get the data back from webdriver)
-        result = None
-        result_flag = threading.Event()
-        result_lock = threading.Lock()
-
-        timeout = test.timeout * self.timeout_multiplier
+    def wait(self):
+        while True:
+            try:
+                self.webdriver.execute_async_script("");
+            except exceptions.TimeoutException:
+                pass
+            except (socket.timeout, exceptions.NoSuchWindowException,
+                    exceptions.ErrorInResponseException, IOError):
+                break
+            except Exception as e:
+                self.logger.error(traceback.format_exc(e))
+                break
 
-        def timeout_func():
-            with result_lock:
-                if not result_flag.is_set():
-                    result_flag.set()
-                    result = (test.result_cls("EXTERNAL-TIMEOUT", None), [])
-                    self.runner.send_message("test_ended", test, result)
 
-        self.timer = threading.Timer(timeout + 10, timeout_func)
-        self.timer.start()
+class SeleniumRun(object):
+    def __init__(self, func, webdriver, url, timeout):
+        self.func = func
+        self.result = None
+        self.webdriver = webdriver
+        self.url = url
+        self.timeout = timeout
+        self.result_flag = threading.Event()
 
-        try:
-            self.webdriver.set_script_timeout((timeout + 5) * 1000)
-        except exceptions.ErrorInResponseException:
-            self.logger.error("Lost webdriver connection")
-            self.runner.send_message("restart_test", test)
-            return Stop
+    def run(self):
+        timeout = self.timeout
 
         try:
-            result = self.convert_result(test, self.do_test(test, timeout))
+            self.webdriver.set_script_timeout((timeout + extra_timeout) * 1000)
+        except exceptions.ErrorInResponseException:
+            self.logger.error("Lost webdriver connection")
+            return Stop
+
+        executor = threading.Thread(target=self._run)
+        executor.start()
+
+        flag = self.result_flag.wait(timeout + 2 * extra_timeout)
+        if self.result is None:
+            assert not flag
+            self.result = False, ("EXTERNAL-TIMEOUT", None)
+
+        return self.result
+
+    def _run(self):
+        try:
+            self.result = True, self.func(self.webdriver, self.url, self.timeout)
         except exceptions.TimeoutException:
-            with result_lock:
-                if not result_flag.is_set():
-                    result_flag.set()
-                    result = (test.result_cls("EXTERNAL-TIMEOUT", None), [])
-            # Clean up any unclosed windows
-            # This doesn't account for the possibility the browser window
-            # is totally hung. That seems less likely since we are still
-            # getting data from marionette, but it might be just as well
-            # to do a full restart in this case
-            # XXX - this doesn't work at the moment because window_handles
-            # only returns OS-level windows (see bug 907197)
-            # while True:
-            #     handles = self.marionette.window_handles
-            #     self.marionette.switch_to_window(handles[-1])
-            #     if len(handles) > 1:
-            #         self.marionette.close()
-            #     else:
-            #         break
-            # Now need to check if the browser is still responsive and restart it if not
-
-        # TODO: try to detect crash here
+            self.result = False, ("EXTERNAL-TIMEOUT", None)
         except (socket.timeout, exceptions.ErrorInResponseException):
-            # This can happen on a crash
-            # Also, should check after the test if the firefox process is still running
-            # and otherwise ignore any other result and set it to crash
-            with result_lock:
-                if not result_flag.is_set():
-                    result_flag.set()
-                    result = (test.result_cls("CRASH", None), [])
+            self.result = False, ("CRASH", None)
+        except Exception as e:
+            message = getattr(e, "message", "")
+            if message:
+                message += "\n"
+            message += traceback.format_exc(e)
+            self.result = False, ("ERROR", e)
         finally:
-            self.timer.cancel()
-
-        with result_lock:
-            if result:
-                self.runner.send_message("test_ended", test, result)
+            self.result_flag.set()
 
 
-class SeleniumTestharnessExecutor(SeleniumTestExecutor):
-    convert_result = testharness_result_converter
+class SeleniumTestharnessExecutor(TestharnessExecutor):
+    def __init__(self, browser, http_server_url, timeout_multiplier=1,
+                 close_after_done=True, capabilities=None, debug_args=None):
+        """Selenium-based executor for testharness.js tests"""
+        TestharnessExecutor.__init__(self, browser, http_server_url,
+                                     timeout_multiplier=timeout_multiplier,
+                                     debug_args=debug_args)
+        self.protocol = SeleniumProtocol(self, browser, http_server_url, capabilities)
+        with open(os.path.join(here, "testharness_webdriver.js")) as f:
+            self.script = f.read()
+        self.close_after_done = close_after_done
+        self.window_id = str(uuid.uuid4())
 
-    def __init__(self, *args, **kwargs):
-        SeleniumTestExecutor.__init__(self, *args, **kwargs)
-        self.script = open(os.path.join(here, "testharness_webdriver.js")).read()
+    def is_alive(self):
+        return self.protocol.is_alive()
 
-    def do_test(self, test, timeout):
-        return self.webdriver.execute_async_script(
-            self.script % {"abs_url": urlparse.urljoin(self.http_server_url, test.url),
-                           "url": test.url,
+    def do_test(self, test):
+        success, data = SeleniumRun(self.do_testharness, self.protocol.webdriver,
+                                    test.url, test.timeout * self.timeout_multiplier).run()
+        if success:
+            return self.convert_result(test, data)
+
+        return (test.result_cls(*data), [])
+
+    def do_testharness(self, webdriver, url, timeout):
+        return webdriver.execute_async_script(
+            self.script % {"abs_url": urlparse.urljoin(self.http_server_url, url),
+                           "url": url,
                            "window_id": self.window_id,
                            "timeout_multiplier": self.timeout_multiplier,
                            "timeout": timeout * 1000})
+
+class SeleniumRefTestExecutor(RefTestExecutor):
+    def __init__(self, browser, http_server_url, timeout_multiplier=1,
+                 screenshot_cache=None, close_after_done=True,
+                 debug_args=None, capabilities=None):
+        """Selenium WebDriver-based executor for reftests"""
+        RefTestExecutor.__init__(self,
+                                 browser,
+                                 http_server_url,
+                                 screenshot_cache=screenshot_cache,
+                                 timeout_multiplier=timeout_multiplier,
+                                 debug_args=debug_args)
+        self.protocol = SeleniumProtocol(self, browser, http_server_url,
+                                         capabilities=capabilities)
+        self.implementation = RefTestImplementation(self)
+        self.close_after_done = close_after_done
+        self.has_window = False
+
+        with open(os.path.join(here, "reftest.js")) as f:
+            self.script = f.read()
+        with open(os.path.join(here, "reftest-wait_webdriver.js")) as f:
+            self.wait_script = f.read()
+
+    def is_alive(self):
+        return self.protocol.is_alive()
+
+    def do_test(self, test):
+        self.logger.info("Test requires OS-level window focus")
+
+        if self.close_after_done and self.has_window:
+            self.protocol.webdriver.close()
+            self.protocol.webdriver.switch_to_window(
+                self.protocol.webdriver.window_handles[-1])
+            self.has_window = False
+
+        if not self.has_window:
+            self.protocol.webdriver.execute_script(self.script)
+            self.protocol.webdriver.switch_to_window(
+                self.protocol.webdriver.window_handles[-1])
+            self.has_window = True
+
+        result = self.implementation.run_test(test)
+
+        return self.convert_result(test, result)
+
+    def screenshot(self, url, timeout):
+        return SeleniumRun(self._screenshot, self.protocol.webdriver,
+                           url, timeout).run()
+
+    def _screenshot(self, webdriver, url, timeout):
+        full_url = urlparse.urljoin(self.http_server_url, url)
+        webdriver.get(full_url)
+
+        webdriver.execute_async_script(self.wait_script)
+
+        screenshot = webdriver.get_screenshot_as_base64()
+
+        # strip off the data:img/png, part of the url
+        if screenshot.startswith("data:image/png;base64,"):
+            screenshot = screenshot.split(",", 1)[1]
+
+        return screenshot
--- a/testing/web-platform/harness/wptrunner/executors/executorservo.py
+++ b/testing/web-platform/harness/wptrunner/executors/executorservo.py
@@ -9,60 +9,84 @@ import subprocess
 import tempfile
 import threading
 import urlparse
 import uuid
 from collections import defaultdict
 
 from mozprocess import ProcessHandler
 
-from .base import testharness_result_converter, reftest_result_converter
+from .base import (ExecutorException,
+                   Protocol,
+                   RefTestImplementation,
+                   testharness_result_converter,
+                   reftest_result_converter)
 from .process import ProcessTestExecutor
 
 
 class ServoTestharnessExecutor(ProcessTestExecutor):
     convert_result = testharness_result_converter
 
-    def __init__(self, *args, **kwargs):
-        ProcessTestExecutor.__init__(self, *args, **kwargs)
+    def __init__(self, browser, http_server_url, timeout_multiplier=1, debug_args=None,
+                 pause_after_test=False):
+        ProcessTestExecutor.__init__(self, browser, http_server_url,
+                                     timeout_multiplier=timeout_multiplier,
+                                     debug_args=debug_args)
+        self.pause_after_test = pause_after_test
         self.result_data = None
         self.result_flag = None
+        self.protocol = Protocol(self, browser, http_server_url)
 
-    def run_test(self, test):
+    def do_test(self, test):
         self.result_data = None
         self.result_flag = threading.Event()
 
         self.command = [self.binary, "--cpu", "--hard-fail", "-z",
                         urlparse.urljoin(self.http_server_url, test.url)]
 
+        if self.pause_after_test:
+            self.command.remove("-z")
+
         if self.debug_args:
             self.command = list(self.debug_args) + self.command
 
 
         self.proc = ProcessHandler(self.command,
                                    processOutputLine=[self.on_output],
                                    onFinish=self.on_finish)
         self.proc.run()
 
         timeout = test.timeout * self.timeout_multiplier
 
         # Now wait to get the output we expect, or until we reach the timeout
-        self.result_flag.wait(timeout + 5)
+        if self.debug_args is None and not self.pause_after_test:
+            wait_timeout = timeout + 5
+        else:
+            wait_timeout = None
+        self.result_flag.wait(wait_timeout)
 
+        proc_is_running = True
         if self.result_flag.is_set() and self.result_data is not None:
             self.result_data["test"] = test.url
             result = self.convert_result(test, self.result_data)
-            self.proc.kill()
         else:
             if self.proc.proc.poll() is not None:
                 result = (test.result_cls("CRASH", None), [])
+                proc_is_running = False
+            else:
+                result = (test.result_cls("TIMEOUT", None), [])
+
+        if proc_is_running:
+            if self.pause_after_test:
+                self.logger.info("Pausing until the browser exits")
+                self.proc.wait()
             else:
                 self.proc.kill()
-                result = (test.result_cls("TIMEOUT", None), [])
-        self.runner.send_message("test_ended", test, result)
+
+        return result
 
     def on_output(self, line):
         prefix = "ALERT: RESULT: "
         line = line.decode("utf8", "replace")
         if line.startswith(prefix):
             self.result_data = json.loads(line[len(prefix):])
             self.result_flag.set()
         else:
@@ -88,80 +112,63 @@ class TempFilename(object):
 
     def __exit__(self, *args, **kwargs):
         try:
             os.unlink(self.path)
         except OSError:
             pass
 
 
-class ServoReftestExecutor(ProcessTestExecutor):
+class ServoRefTestExecutor(ProcessTestExecutor):
     convert_result = reftest_result_converter
 
-    def __init__(self, *args, **kwargs):
-        ProcessTestExecutor.__init__(self, *args, **kwargs)
-        self.ref_hashes = {}
-        self.ref_urls_by_hash = defaultdict(set)
+    def __init__(self, browser, http_server_url, binary=None, timeout_multiplier=1,
+                 screenshot_cache=None, debug_args=None, pause_after_test=False):
+        ProcessTestExecutor.__init__(self,
+                                     browser,
+                                     http_server_url,
+                                     timeout_multiplier=timeout_multiplier,
+                                     debug_args=debug_args)
+
+        self.protocol = Protocol(self, browser, http_server_url)
+        self.screenshot_cache = screenshot_cache
+        self.implementation = RefTestImplementation(self)
         self.tempdir = tempfile.mkdtemp()
 
     def teardown(self):
         os.rmdir(self.tempdir)
         ProcessTestExecutor.teardown(self)
 
-    def run_test(self, test):
-        test_url, ref_type, ref_url = test.url, test.ref_type, test.ref_url
-        hashes = {"test": None,
-                  "ref": self.ref_hashes.get(ref_url)}
-
-        status = None
-
-        for url_type, url in [("test", test_url), ("ref", ref_url)]:
-            if hashes[url_type] is None:
-                full_url = urlparse.urljoin(self.http_server_url, url)
+    def screenshot(self, url, timeout):
+        full_url = urlparse.urljoin(self.http_server_url, url)
 
-                with TempFilename(self.tempdir) as output_path:
-                    self.command = [self.binary, "--cpu", "--hard-fail", "--exit",
-                                    "--output=%s" % output_path, full_url]
-
-                    timeout = test.timeout * self.timeout_multiplier
-                    self.proc = ProcessHandler(self.command,
-                                               processOutputLine=[self.on_output])
-                    self.proc.run()
-                    rv = self.proc.wait(timeout=timeout)
-
-                    if rv is None:
-                        status = "EXTERNAL-TIMEOUT"
-                        self.proc.kill()
-                        break
+        with TempFilename(self.tempdir) as output_path:
+            self.command = [self.binary, "--cpu", "--hard-fail", "--exit",
+                            "--output=%s" % output_path, full_url]
 
-                    if rv < 0:
-                        status = "CRASH"
-                        break
-
-                    with open(output_path) as f:
-                        # Might need to strip variable headers or something here
-                        data = f.read()
-                        hashes[url_type] = hashlib.sha1(data).hexdigest()
-
-        if status is None:
-            self.ref_urls_by_hash[hashes["ref"]].add(ref_url)
-            self.ref_hashes[ref_url] = hashes["ref"]
+            self.proc = ProcessHandler(self.command,
+                                       processOutputLine=[self.on_output])
+            self.proc.run()
+            rv = self.proc.wait(timeout=timeout)
+            if rv is None:
+                self.proc.kill()
+                return False, ("EXTERNAL-TIMEOUT", None)
 
-            if ref_type == "==":
-                passed = hashes["test"] == hashes["ref"]
-            elif ref_type == "!=":
-                passed = hashes["test"] != hashes["ref"]
-            else:
-                raise ValueError
+            if rv < 0:
+                return False, ("CRASH", None)
 
-            status = "PASS" if passed else "FAIL"
+            with open(output_path) as f:
+                # Might need to strip variable headers or something here
+                data = f.read()
+                return True, data
 
-        result = self.convert_result(test, {"status": status, "message": None})
-        self.runner.send_message("test_ended", test, result)
+    def do_test(self, test):
+        result = self.implementation.run_test(test)
 
+        return self.convert_result(test, result)
 
     def on_output(self, line):
         line = line.decode("utf8", "replace")
         if self.interactive:
             print line
         else:
             self.logger.process_output(self.proc.pid,
                                        line,
--- a/testing/web-platform/harness/wptrunner/executors/process.py
+++ b/testing/web-platform/harness/wptrunner/executors/process.py
@@ -14,10 +14,10 @@ class ProcessTestExecutor(TestExecutor):
     def setup(self, runner):
         self.runner = runner
         self.runner.send_message("init_succeeded")
         return True
 
     def is_alive(self):
         return True
 
-    def run_test(self, test):
+    def do_test(self, test):
         raise NotImplementedError
--- a/testing/web-platform/harness/wptrunner/executors/reftest-wait.js
+++ b/testing/web-platform/harness/wptrunner/executors/reftest-wait.js
@@ -9,9 +9,14 @@ function test(x) {
     marionetteScriptFinished();
   }
 }
 
 var root = document.documentElement;
 var observer = new MutationObserver(test);
 
 observer.observe(root, {attributes: true});
-test();
+
+if (document.readyState != "complete") {
+  onload = test
+} else {
+  test();
+}
copy from testing/web-platform/harness/wptrunner/executors/reftest-wait.js
copy to testing/web-platform/harness/wptrunner/executors/reftest-wait_webdriver.js
--- a/testing/web-platform/harness/wptrunner/executors/reftest-wait.js
+++ b/testing/web-platform/harness/wptrunner/executors/reftest-wait_webdriver.js
@@ -1,17 +1,23 @@
 /* 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/. */
 
+var callback = arguments[arguments.length - 1];
+
 function test(x) {
-  log("classList: " + root.classList);
   if (!root.classList.contains("reftest-wait")) {
     observer.disconnect();
-    marionetteScriptFinished();
+    callback()
   }
 }
 
 var root = document.documentElement;
 var observer = new MutationObserver(test);
 
 observer.observe(root, {attributes: true});
-test();
+
+if (document.readyState != "complete") {
+    onload = test;
+} else {
+    test();
+}
--- a/testing/web-platform/harness/wptrunner/executors/testharness_marionette.js
+++ b/testing/web-platform/harness/wptrunner/executors/testharness_marionette.js
@@ -13,12 +13,15 @@ window.wrappedJSObject.done = function(t
   marionetteScriptFinished({test:"%(url)s",
                             tests:test_results,
                             status: status.status,
                             message: status.message});
 }
 
 window.wrappedJSObject.win = window.open("%(abs_url)s", "%(window_id)s");
 
-var timer = setTimeout(function() {
-  log("Timeout fired");
-  window.wrappedJSObject.win.timeout();
-}, %(timeout)s);
+var timer = null;
+if (%(timeout)s) {
+  timer = setTimeout(function() {
+      log("Timeout fired");
+      window.wrappedJSObject.win.timeout();
+  }, %(timeout)s);
+}
--- a/testing/web-platform/harness/wptrunner/manifestexpected.py
+++ b/testing/web-platform/harness/wptrunner/manifestexpected.py
@@ -49,17 +49,17 @@ class ExpectedManifest(ManifestItem):
         self.child_map = {}
         self.test_path = test_path
         self.url_base = url_base
 
     def append(self, child):
         """Add a test to the manifest"""
         ManifestItem.append(self, child)
         self.child_map[child.id] = child
-        assert len(self.child_map) == len(self.children)
+        #assert len(self.child_map) == len(self.children), "%r %r" % (self.child_map, self.children)
 
     def _remove_child(self, child):
         del self.child_map[child.id]
         ManifestItem.remove_child(self, child)
         assert len(self.child_map) == len(self.children)
 
     def get_test(self, test_id):
         """Get a test from the manifest by ID
@@ -84,33 +84,27 @@ class TestNode(ManifestItem):
         self.new_expected = []
         self.subtests = {}
         self.default_status = None
         self._from_file = True
 
     @property
     def is_empty(self):
         required_keys = set(["type"])
-        if self.test_type == "reftest":
-            required_keys |= set(["reftype", "refurl"])
         if set(self._data.keys()) != required_keys:
             return False
         return all(child.is_empty for child in self.children)
 
     @property
     def test_type(self):
         return self.get("type")
 
     @property
     def id(self):
-        url = urlparse.urljoin(self.parent.url, self.name)
-        if self.test_type == "reftest":
-            return (url, self.get("reftype"), self.get("refurl"))
-        else:
-            return url
+        return urlparse.urljoin(self.parent.url, self.name)
 
     def disabled(self):
         """Boolean indicating whether the test is disabled"""
         try:
             return self.get("disabled")
         except KeyError:
             return False
 
--- a/testing/web-platform/harness/wptrunner/manifestinclude.py
+++ b/testing/web-platform/harness/wptrunner/manifestinclude.py
@@ -3,16 +3,17 @@
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 """Manifest structure used to store paths that should be included in a test run.
 
 The manifest is represented by a tree of IncludeManifest objects, the root
 representing the file and each subnode representing a subdirectory that should
 be included or excluded.
 """
+import os
 
 from wptmanifest.node import DataNode
 from wptmanifest.backends import conditional
 from wptmanifest.backends.conditional import ManifestItem
 
 
 class IncludeManifest(ManifestItem):
     def __init__(self, node):
@@ -63,44 +64,51 @@ class IncludeManifest(ManifestItem):
                     # Include by default
                     return True
 
     def _get_path_components(self, test):
         test_url = test.url
         assert test_url[0] == "/"
         return [item for item in reversed(test_url.split("/")) if item]
 
-    def _add_rule(self, url, direction):
+    def _add_rule(self, test_manifests, url, direction):
+        maybe_path = os.path.abspath(os.path.join(os.curdir, url))
+        if os.path.exists(maybe_path):
+            for manifest, data in test_manifests.iteritems():
+                rel_path = os.path.relpath(maybe_path, data["tests_path"])
+                if ".." not in rel_path.split(os.sep):
+                    url = rel_path
+
         assert direction in ("include", "exclude")
         components = [item for item in reversed(url.split("/")) if item]
 
         node = self
         while components:
             component = components.pop()
             if component not in node.child_map:
                 new_node = IncludeManifest(DataNode(component))
                 node.append(new_node)
 
             node = node.child_map[component]
 
         skip = False if direction == "include" else True
         node.set("skip", str(skip))
 
-    def add_include(self, url_prefix):
+    def add_include(self, test_manifests, url_prefix):
         """Add a rule indicating that tests under a url path
         should be included in test runs
 
         :param url_prefix: The url prefix to include
         """
-        return self._add_rule(url_prefix, "include")
+        return self._add_rule(test_manifests, url_prefix, "include")
 
-    def add_exclude(self, url_prefix):
+    def add_exclude(self, test_manifests, url_prefix):
         """Add a rule indicating that tests under a url path
         should be excluded from test runs
 
         :param url_prefix: The url prefix to exclude
         """
-        return self._add_rule(url_prefix, "exclude")
+        return self._add_rule(test_manifests, url_prefix, "exclude")
 
 
 def get_manifest(manifest_path):
     with open(manifest_path) as f:
         return conditional.compile(f, data_cls_getter=lambda x, y: IncludeManifest)
--- a/testing/web-platform/harness/wptrunner/manifestupdate.py
+++ b/testing/web-platform/harness/wptrunner/manifestupdate.py
@@ -63,23 +63,23 @@ class ExpectedManifest(ManifestItem):
         self.child_map = {}
         self.test_path = test_path
         self.url_base = url_base
         assert self.url_base is not None
         self.modified = False
 
     def append(self, child):
         ManifestItem.append(self, child)
+        if child.id in self.child_map:
+            print "Warning: Duplicate heading %s" % child.id
         self.child_map[child.id] = child
-        assert len(self.child_map) == len(self.children)
 
     def _remove_child(self, child):
         del self.child_map[child.id]
         ManifestItem._remove_child(self, child)
-        assert len(self.child_map) == len(self.children)
 
     def get_test(self, test_id):
         """Return a TestNode by test id, or None if no test matches
 
         :param test_id: The id of the test to look up"""
 
         return self.child_map[test_id]
 
@@ -111,54 +111,42 @@ class TestNode(ManifestItem):
 
     @classmethod
     def create(cls, test_type, test_id):
         """Create a TestNode corresponding to a given test
 
         :param test_type: The type of the test
         :param test_id: The id of the test"""
 
-        if test_type == "reftest":
-            url = test_id[0]
-        else:
-            url = test_id
+        url = test_id
         name = url.split("/")[-1]
         node = DataNode(name)
         self = cls(node)
 
         self.set("type", test_type)
-        if test_type == "reftest":
-            self.set("reftype", test_id[1])
-            self.set("refurl", test_id[2])
         self._from_file = False
         return self
 
     @property
     def is_empty(self):
         required_keys = set(["type"])
-        if self.test_type == "reftest":
-            required_keys |= set(["reftype", "refurl"])
         if set(self._data.keys()) != required_keys:
             return False
         return all(child.is_empty for child in self.children)
 
     @property
     def test_type(self):
         """The type of the test represented by this TestNode"""
 
         return self.get("type", None)
 
     @property
     def id(self):
         """The id of the test represented by this TestNode"""
-        url = urlparse.urljoin(self.parent.url, self.name)
-        if self.test_type == "reftest":
-            return (url, self.get("reftype", None), self.get("refurl", None))
-        else:
-            return url
+        return urlparse.urljoin(self.parent.url, self.name)
 
     def disabled(self, run_info):
         """Boolean indicating whether this test is disabled when run in an
         environment with the given run_info
 
         :param run_info: Dictionary of run_info parameters"""
 
         return self.get("disabled", run_info) is not None
--- a/testing/web-platform/harness/wptrunner/metadata.py
+++ b/testing/web-platform/harness/wptrunner/metadata.py
@@ -60,20 +60,17 @@ def update_expected(test_paths, serve_ro
 
     results_changed = [item.test_path for item in expected_map.itervalues() if item.modified]
 
     return unexpected_changes(manifests, change_data, results_changed)
 
 
 def do_delayed_imports(serve_root):
     global manifest
-
-    sys.path.insert(0, os.path.join(serve_root))
-    sys.path.insert(0, os.path.join(serve_root, "tools", "scripts"))
-    import manifest
+    from manifest import manifest
 
 
 def files_in_repo(repo_root):
     return git("ls-tree", "-r", "--name-only", "HEAD").split("\n")
 
 
 def rev_range(rev_old, rev_new, symmetric=False):
     joiner = ".." if not symmetric else "..."
@@ -156,18 +153,24 @@ def update_from_logs(manifests, *log_fil
 
     return expected_map
 
 
 def write_changes(metadata_path, expected_map):
     # First write the new manifest files to a temporary directory
     temp_path = tempfile.mkdtemp(dir=os.path.split(metadata_path)[0])
     write_new_expected(temp_path, expected_map)
-    shutil.copyfile(os.path.join(metadata_path, "MANIFEST.json"),
-                    os.path.join(temp_path, "MANIFEST.json"))
+
+    # Copy all files in the root to the temporary location since
+    # these cannot be ini files
+    keep_files = [item for item in os.listdir(metadata_path) if
+                  not os.path.isdir(os.path.join(metadata_path, item))]
+    for item in keep_files:
+        shutil.copyfile(os.path.join(metadata_path, item),
+                        os.path.join(temp_path, item))
 
     # Then move the old manifest files to a new location
     temp_path_2 = metadata_path + str(uuid.uuid4())
     os.rename(metadata_path, temp_path_2)
     # Move the new files to the destination location and remove the old files
     os.rename(temp_path, metadata_path)
     shutil.rmtree(temp_path_2)
 
@@ -265,17 +268,17 @@ class ExpectedUpdater(object):
         test.set_result(self.run_info, result)
         del self.test_cache[test_id]
 
 
 def create_test_tree(metadata_path, test_manifest):
     expected_map = {}
     id_test_map = {}
     exclude_types = frozenset(["stub", "helper", "manual"])
-    include_types = set(manifest.item_types) ^ exclude_types
+    include_types = set(manifest.item_types) - exclude_types
     for test_path, tests in test_manifest.itertypes(*include_types):
         expected_data = load_expected(test_manifest, metadata_path, test_path, tests)
         if expected_data is None:
             expected_data = create_expected(test_manifest, test_path, tests)
 
         for test in tests:
             id_test_map[test.id] = (test_manifest, test)
             expected_map[test] = expected_data
copy from testing/web-platform/harness/wptrunner/executors/reftest-wait.js
copy to testing/web-platform/harness/wptrunner/testharnessreport-servo.js
--- a/testing/web-platform/harness/wptrunner/executors/reftest-wait.js
+++ b/testing/web-platform/harness/wptrunner/testharnessreport-servo.js
@@ -1,17 +1,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/. */
 
-function test(x) {
-  log("classList: " + root.classList);
-  if (!root.classList.contains("reftest-wait")) {
-    observer.disconnect();
-    marionetteScriptFinished();
-  }
-}
+var props = {output:%(output)d};
+
+setup(props);
 
-var root = document.documentElement;
-var observer = new MutationObserver(test);
-
-observer.observe(root, {attributes: true});
-test();
+add_completion_callback(function (tests, harness_status) {
+    alert("RESULT: " + JSON.stringify({
+        tests: tests.map(function(t) {
+            return { name: t.name, status: t.status, message: t.message }
+        }),
+        status: harness_status.status,
+        message: harness_status.message,
+    }));
+});
--- a/testing/web-platform/harness/wptrunner/testharnessreport.js
+++ b/testing/web-platform/harness/wptrunner/testharnessreport.js
@@ -1,18 +1,21 @@
 /* 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/. */
 
-var props = {output:false,
+var props = {output:%(output)d,
              explicit_timeout: true};
+
 if (window.opener && "timeout_multiplier" in window.opener) {
     props["timeout_multiplier"] = window.opener.timeout_multiplier;
 }
+
 if (window.opener && window.opener.explicit_timeout) {
     props["explicit_timeout"] = window.opener.explicit_timeout;
 }
+
 setup(props);
 add_completion_callback(function() {
     add_completion_callback(function(tests, status) {
         window.opener.done(tests, status)
     })
 });
--- a/testing/web-platform/harness/wptrunner/testloader.py
+++ b/testing/web-platform/harness/wptrunner/testloader.py
@@ -7,21 +7,23 @@ from collections import defaultdict, Ord
 from multiprocessing import Queue
 
 import manifestinclude
 import manifestexpected
 import wpttest
 from mozlog import structured
 
 manifest = None
+manifest_update = None
 
 def do_delayed_imports():
     # This relies on an already loaded module having set the sys.path correctly :(
-    global manifest
-    import manifest
+    global manifest, manifest_update
+    from manifest import manifest
+    from manifest import update as manifest_update
 
 class TestChunker(object):
     def __init__(self, total_chunks, chunk_number):
         self.total_chunks = total_chunks
         self.chunk_number = chunk_number
         assert self.chunk_number <= self.total_chunks
 
     def __call__(self, manifest):
@@ -182,44 +184,45 @@ class EqualTimeChunker(TestChunker):
     def __call__(self, manifest_iter):
         manifest = list(manifest_iter)
         tests = self._get_chunk(manifest)
         for item in tests:
             yield item
 
 
 class TestFilter(object):
-    def __init__(self, include=None, exclude=None, manifest_path=None):
+    def __init__(self, test_manifests, include=None, exclude=None, manifest_path=None):
+        test_manifests = test_manifests
+
         if manifest_path is not None and include is None:
             self.manifest = manifestinclude.get_manifest(manifest_path)
         else:
             self.manifest = manifestinclude.IncludeManifest.create()
 
-        if include is not None:
+        if include:
             self.manifest.set("skip", "true")
             for item in include:
-                self.manifest.add_include(item)
+                self.manifest.add_include(test_manifests, item)
 
-        if exclude is not None:
+        if exclude:
             for item in exclude:
-                self.manifest.add_exclude(item)
+                self.manifest.add_exclude(test_manifests, item)
 
     def __call__(self, manifest_iter):
         for test_path, tests in manifest_iter:
             include_tests = set()
             for test in tests:
                 if self.manifest.include(test):
                     include_tests.add(test)
 
             if include_tests:
                 yield test_path, include_tests
 
 
 class ManifestLoader(object):
-
     def __init__(self, test_paths, force_manifest_update=False):
         do_delayed_imports()
         self.test_paths = test_paths
         self.force_manifest_update = force_manifest_update
         self.logger = structured.get_default_logger()
         if self.logger is None:
             self.logger = structured.structuredlog.StructuredLogger("ManifestLoader")
 
@@ -247,51 +250,50 @@ class ManifestLoader(object):
                     json_data = json.load(f)
             except IOError:
                 #If the existing file doesn't exist just create one from scratch
                 pass
 
         if not json_data:
             manifest_file = manifest.Manifest(None, url_base)
         else:
-            manifest_file = manifest.Manifest.from_json(json_data)
+            manifest_file = manifest.Manifest.from_json(tests_path, json_data)
 
-        manifest.update(tests_path, url_base, manifest_file)
+        manifest_update.update(tests_path, url_base, manifest_file)
         manifest.write(manifest_file, manifest_path)
 
     def load_manifest(self, tests_path, metadata_path, url_base="/"):
         manifest_path = os.path.join(metadata_path, "MANIFEST.json")
         if (not os.path.exists(manifest_path) or
             self.force_manifest_update):
             self.update_manifest(manifest_path, tests_path, url_base)
-        manifest_file = manifest.load(manifest_path)
+        manifest_file = manifest.load(tests_path, manifest_path)
         if manifest_file.url_base != url_base:
             self.logger.info("Updating url_base in manifest from %s to %s" % (manifest_file.url_base,
                                                                               url_base))
             manifest_file.url_base = url_base
             manifest.write(manifest_file, manifest_path)
 
         return manifest_file
 
 class TestLoader(object):
     def __init__(self,
-                 test_paths,
+                 test_manifests,
                  test_types,
                  test_filter,
                  run_info,
                  chunk_type="none",
                  total_chunks=1,
                  chunk_number=1,
                  force_manifest_update=False):
 
-        self.test_paths = test_paths
         self.test_types = test_types
         self.test_filter = test_filter
         self.run_info = run_info
-        self.manifests = ManifestLoader(test_paths, force_manifest_update).load()
+        self.manifests = test_manifests
         self.tests = None
         self.disabled_tests = None
 
         self.chunk_type = chunk_type
         self.total_chunks = total_chunks
         self.chunk_number = chunk_number
 
         self.chunker = {"none": Unchunked,
@@ -311,16 +313,17 @@ class TestLoader(object):
                     self._test_ids += [item.id for item in test_dict[test_type]]
         return self._test_ids
 
     def get_test(self, manifest_test, expected_file):
         if expected_file is not None:
             expected = expected_file.get_test(manifest_test.id)
         else:
             expected = None
+
         return wpttest.from_manifest(manifest_test, expected)
 
     def load_expected_manifest(self, test_manifest, metadata_path, test_path):
         return manifestexpected.get_manifest(metadata_path, test_path, test_manifest.url_base, self.run_info)
 
     def iter_tests(self):
         manifest_items = []
 
--- a/testing/web-platform/harness/wptrunner/testrunner.py
+++ b/testing/web-platform/harness/wptrunner/testrunner.py
@@ -81,17 +81,18 @@ class TestRunner(object):
         self.command_queue = None
         self.browser = None
 
     def run(self):
         """Main loop accepting commands over the pipe and triggering
         the associated methods"""
         self.setup()
         commands = {"run_test": self.run_test,
-                    "stop": self.stop}
+                    "stop": self.stop,
+                    "wait": self.wait}
         while True:
             command, args = self.command_queue.get()
             try:
                 rv = commands[command](*args)
             except Exception:
                 self.send_message("error",
                                   "Error running command %s with arguments %r:\n%s" %
                                   (command, args, traceback.format_exc()))
@@ -118,16 +119,20 @@ class TestRunner(object):
         else:
             self.send_message("test_start", test)
         try:
             return self.executor.run_test(test)
         except Exception:
             self.logger.critical(traceback.format_exc())
             raise
 
+    def wait(self):
+        self.executor.protocol.wait()
+        self.send_message("after_test_ended", True)
+
     def send_message(self, command, *args):
         self.result_queue.put((command, args))
 
 
 def start_runner(test_queue, runner_command_queue, runner_result_queue,
                  executor_cls, executor_kwargs,
                  executor_browser_cls, executor_browser_kwargs,
                  stop_flag):
@@ -157,18 +162,18 @@ def next_manager_number():
     local = manager_count = manager_count + 1
     return local
 
 
 class TestRunnerManager(threading.Thread):
     init_lock = threading.Lock()
 
     def __init__(self, suite_name, test_queue, test_source_cls, browser_cls, browser_kwargs,
-                 executor_cls, executor_kwargs, stop_flag, pause_on_unexpected=False,
-                 debug_args=None):
+                 executor_cls, executor_kwargs, stop_flag, pause_after_test=False,
+                 pause_on_unexpected=False, debug_args=None):
         """Thread that owns a single TestRunner process and any processes required
         by the TestRunner (e.g. the Firefox binary).
 
         TestRunnerManagers are responsible for launching the browser process and the
         runner process, and for logging the test progress. The actual test running
         is done by the TestRunner. In particular they:
 
         * Start the binary of the program under test
@@ -194,16 +199,17 @@ class TestRunnerManager(threading.Thread
 
         self.browser = None
         self.browser_pid = None
 
         # Flags used to shut down this thread if we get a sigint
         self.parent_stop_flag = stop_flag
         self.child_stop_flag = multiprocessing.Event()
 
+        self.pause_after_test = pause_after_test
         self.pause_on_unexpected = pause_on_unexpected
         self.debug_args = debug_args
 
         self.manager_number = next_manager_number()
 
         self.command_queue = Queue()
         self.remote_queue = Queue()
 
@@ -243,16 +249,17 @@ class TestRunnerManager(threading.Thread
             try:
                 if self.init() is Stop:
                     return
                 while True:
                     commands = {"init_succeeded": self.init_succeeded,
                                 "init_failed": self.init_failed,
                                 "test_start": self.test_start,
                                 "test_ended": self.test_ended,
+                                "after_test_ended": self.after_test_ended,
                                 "restart_runner": self.restart_runner,
                                 "runner_teardown": self.runner_teardown,
                                 "log": self.log,
                                 "error": self.error}
                     try:
                         command, data = self.command_queue.get(True, 1)
                     except IOError:
                         if not self.should_stop():
@@ -323,16 +330,17 @@ class TestRunnerManager(threading.Thread
                 self.logger.debug("Setting child stop flag in init_failed")
                 self.child_stop_flag.set()
 
         with self.init_lock:
             # Guard against problems initialising the browser or the browser
             # remote control method
             if self.debug_args is None:
                 self.init_timer = threading.Timer(self.browser.init_timeout, init_failed)
+
             test_queue = self.test_source.get_queue()
             if test_queue is None:
                 self.logger.info("No more tests")
                 return Stop
 
             try:
                 if self.init_timer is not None:
                     self.init_timer.start()
@@ -502,27 +510,34 @@ class TestRunnerManager(threading.Thread
             self.unexpected_count += 1
             self.logger.debug("Unexpected count in this thread %i" % self.unexpected_count)
         if status == "CRASH":
             self.browser.log_crash(process=self.browser_pid, test=test.id)
 
         self.logger.test_end(test.id,
                              status,
                              message=file_result.message,
-                             expected=expected)
+                             expected=expected,
+                             extra=file_result.extra)
 
         self.test = None
 
-        if self.pause_on_unexpected and (subtest_unexpected or is_unexpected):
-            self.logger.info("Got an unexpected result, pausing until the browser exits")
-            self.browser.runner.process_handler.wait()
+        restart_before_next = (file_result.status in ("CRASH", "EXTERNAL-TIMEOUT") or
+                               subtest_unexpected or is_unexpected)
 
+        if (self.pause_after_test or
+            (self.pause_on_unexpected and (subtest_unexpected or is_unexpected))):
+            self.logger.info("Pausing until the browser exits")
+            self.send_message("wait")
+        else:
+            self.after_test_ended(restart_before_next)
+
+    def after_test_ended(self, restart_before_next):
         # Handle starting the next test, with a runner restart if required
-        if (file_result.status in ("CRASH", "EXTERNAL-TIMEOUT") or
-            subtest_unexpected or is_unexpected):
+        if restart_before_next:
             return self.restart_runner()
         else:
             return self.start_next_test()
 
     def restart_runner(self):
         """Stop and restart the TestRunner"""
         if self.restart_count >= self.max_restarts:
             return Stop
@@ -566,27 +581,30 @@ class TestQueue(object):
         if self.queue is not None:
             self.queue.close()
             self.queue = None
 
 
 class ManagerGroup(object):
     def __init__(self, suite_name, size, test_source_cls, test_source_kwargs,
                  browser_cls, browser_kwargs,
-                 executor_cls, executor_kwargs, pause_on_unexpected=False,
+                 executor_cls, executor_kwargs,
+                 pause_after_test=False,
+                 pause_on_unexpected=False,
                  debug_args=None):
         """Main thread object that owns all the TestManager threads."""
         self.suite_name = suite_name
         self.size = size
         self.test_source_cls = test_source_cls
         self.test_source_kwargs = test_source_kwargs
         self.browser_cls = browser_cls
         self.browser_kwargs = browser_kwargs
         self.executor_cls = executor_cls
         self.executor_kwargs = executor_kwargs
+        self.pause_after_test = pause_after_test
         self.pause_on_unexpected = pause_on_unexpected
         self.debug_args = debug_args
 
         self.pool = set()
         # Event that is polled by threads so that they can gracefully exit in the face
         # of sigint
         self.stop_flag = threading.Event()
         self.logger = structuredlog.StructuredLogger(suite_name)
@@ -614,16 +632,17 @@ class ManagerGroup(object):
                 manager = TestRunnerManager(self.suite_name,
                                             test_queue,
                                             self.test_source_cls,
                                             self.browser_cls,
                                             self.browser_kwargs,
                                             self.executor_cls,
                                             self.executor_kwargs,
                                             self.stop_flag,
+                                            self.pause_after_test,
                                             self.pause_on_unexpected,
                                             self.debug_args)
                 manager.start()
                 self.pool.add(manager)
             self.wait()
 
     def is_alive(self):
         """Boolean indicating whether any manager in the group is still alive"""
--- a/testing/web-platform/harness/wptrunner/update/metadata.py
+++ b/testing/web-platform/harness/wptrunner/update/metadata.py
@@ -47,14 +47,15 @@ class CreateMetadataPatch(Step):
         local_tree.create_patch(name, message)
 
         if not local_tree.is_clean:
             metadata_paths = [manifest_path["metadata_path"]
                               for manifest_path in state.paths.itervalues()]
             for path in metadata_paths:
                 local_tree.add_new(os.path.relpath(path, local_tree.root))
             local_tree.update_patch(include=metadata_paths)
+            local_tree.commit_patch()
 
 
 class MetadataUpdateRunner(StepRunner):
     """(Sub)Runner for updating metadata"""
     steps = [UpdateExpected,
              CreateMetadataPatch]
--- a/testing/web-platform/harness/wptrunner/update/sync.py
+++ b/testing/web-platform/harness/wptrunner/update/sync.py
@@ -123,20 +123,20 @@ class LoadManifest(Step):
         )
 
 
 class UpdateManifest(Step):
     """Update the manifest to match the tests in the sync tree checkout"""
 
     provides = ["initial_rev"]
     def create(self, state):
-        import manifest
+        from manifest import manifest, update
         test_manifest = state.test_manifest
         state.initial_rev = test_manifest.rev
-        manifest.update(state.sync["path"], "/", test_manifest)
+        update.update(state.sync["path"], "/", test_manifest)
         manifest.write(test_manifest, os.path.join(state.metadata_path, "MANIFEST.json"))
 
 
 class CopyWorkTree(Step):
     """Copy the sync tree over to the destination in the local tree"""
 
     def create(self, state):
         copy_wpt_tree(state.sync_tree,
--- a/testing/web-platform/harness/wptrunner/update/update.py
+++ b/testing/web-platform/harness/wptrunner/update/update.py
@@ -4,21 +4,22 @@
 
 import os
 import sys
 
 from metadata import MetadataUpdateRunner
 from sync import SyncFromUpstreamRunner
 from tree import GitTree, HgTree, NoVCSTree
 
+from .. import wptrunner
 from base import Step, StepRunner, exit_clean, exit_unclean
 from state import State
 
 def setup_paths(serve_root):
-    sys.path.insert(0, os.path.join(serve_root, "tools", "scripts"))
+    wptrunner.do_delayed_imports(serve_root)
 
 class LoadConfig(Step):
     """Step for loading configuration from the ini file and kwargs."""
 
     provides = ["sync", "paths", "metadata_path", "tests_path"]
 
     def create(self, state):
         state.sync = {"remote_url": state.kwargs["remote_url"],
@@ -77,19 +78,18 @@ class SyncFromUpstream(Step):
 class UpdateMetadata(Step):
     """Update the expectation metadata from a set of run logs"""
 
     def create(self, state):
         if not state.kwargs["run_log"]:
             return
 
         kwargs = state.kwargs
-        with state.push(["local_tree", "sync_tree", "paths"]):
+        with state.push(["local_tree", "sync_tree", "paths", "serve_root"]):
             state.run_log = kwargs["run_log"]
-            state.serve_root = kwargs["serve_root"]
             state.ignore_existing = kwargs["ignore_existing"]
             state.no_patch = kwargs["no_patch"]
             runner = MetadataUpdateRunner(self.logger, state)
             runner.run()
 
 
 class UpdateRunner(StepRunner):
     """Runner for doing an overall update."""
@@ -103,21 +103,19 @@ class WPTUpdate(object):
     def __init__(self, logger, runner_cls=UpdateRunner, **kwargs):
         """Object that controls the running of a whole wptupdate.
 
         :param runner_cls: Runner subclass holding the overall list of
                            steps to run.
         :param kwargs: Command line arguments
         """
         self.runner_cls = runner_cls
-        if kwargs["serve_root"] is None:
-            kwargs["serve_root"] = kwargs["test_paths"]["/"]["tests_path"]
-
+        self.serve_root = kwargs["test_paths"]["/"]["tests_path"]
         #This must be before we try to reload state
-        setup_paths(kwargs["serve_root"])
+        setup_paths(self.serve_root)
 
         self.state = State(logger)
         self.kwargs = kwargs
         self.logger = logger
 
     def run(self, **kwargs):
         if self.kwargs["abort"]:
             self.abort()
@@ -131,16 +129,18 @@ class WPTUpdate(object):
             if self.state.is_empty():
                 self.logger.error("No sync in progress?")
                 return exit_clean
 
             self.kwargs = self.state.kwargs
         else:
             self.state.kwargs = self.kwargs
 
+        self.state.serve_root = self.serve_root
+
         update_runner = self.runner_cls(self.logger, self.state)
         rv = update_runner.run()
         if rv in (exit_clean, None):
             self.state.clear()
 
         return rv
 
     def abort(self):
--- a/testing/web-platform/harness/wptrunner/wptcommandline.py
+++ b/testing/web-platform/harness/wptrunner/wptcommandline.py
@@ -49,123 +49,135 @@ def create_parser(product_choices=None):
         config_data = config.load()
         product_choices = products.products_enabled(config_data)
 
     parser = argparse.ArgumentParser(description="Runner for web-platform-tests tests.")
     parser.add_argument("--metadata", action="store", type=abs_path, dest="metadata_root",
                         help="Path to the folder containing test metadata"),
     parser.add_argument("--tests", action="store", type=abs_path, dest="tests_root",
                         help="Path to test files"),
-    parser.add_argument("--prefs-root", dest="prefs_root", action="store", type=abs_path,
-                        help="Path to the folder containing browser prefs"),
-    parser.add_argument("--serve-root", action="store", type=abs_path, dest="serve_root",
-                        help="Path to web-platform-tests checkout containing serve.py and manifest.py"
-                        " (defaults to test_root)")
     parser.add_argument("--run-info", action="store", type=abs_path,
                         help="Path to directory containing extra json files to add to run info")
     parser.add_argument("--config", action="store", type=abs_path, dest="config",
                         help="Path to config file")
 
     parser.add_argument("--manifest-update", action="store_true", default=False,
                         help="Force regeneration of the test manifest")
 
     parser.add_argument("--binary", action="store",
                         type=abs_path, help="Binary to run tests against")
     parser.add_argument("--webdriver-binary", action="store", metavar="BINARY",
                         type=abs_path, help="WebDriver server binary to use")
-    parser.add_argument("--test-types", action="store",
-                        nargs="*", default=["testharness", "reftest"],
-                        choices=["testharness", "reftest"],
-                        help="Test types to run")
     parser.add_argument("--processes", action="store", type=int, default=1,
                         help="Number of simultaneous processes to use")
-    parser.add_argument("--include", action="append", type=slash_prefixed,
-                        help="URL prefix to include")
-    parser.add_argument("--exclude", action="append", type=slash_prefixed,
-                        help="URL prefix to exclude")
-    parser.add_argument("--include-manifest", type=abs_path,
-                        help="Path to manifest listing tests to include")
 
     parser.add_argument("--run-by-dir", type=int, nargs="?", default=False,
                         help="Split run into groups by directories. With a parameter,"
                         "limit the depth of splits e.g. --run-by-dir=1 to split by top-level"
                         "directory")
 
-    parser.add_argument("--total-chunks", action="store", type=int, default=1,
-                        help="Total number of chunks to use")
-    parser.add_argument("--this-chunk", action="store", type=int, default=1,
-                        help="Chunk number to run")
-    parser.add_argument("--chunk-type", action="store", choices=["none", "equal_time", "hash"],
-                        default=None, help="Chunking type to use")
-
-    parser.add_argument("--list-test-groups", action="store_true",
-                        default=False,
-                        help="List the top level directories containing tests that will run.")
-    parser.add_argument("--list-disabled", action="store_true",
-                        default=False,
-                        help="List the tests that are disabled on the current platform")
-
     parser.add_argument("--timeout-multiplier", action="store", type=float, default=None,
                         help="Multiplier relative to standard test timeout to use")
     parser.add_argument("--repeat", action="store", type=int, default=1,
                         help="Number of times to run the tests")
 
     parser.add_argument("--no-capture-stdio", action="store_true", default=False,
                         help="Don't capture stdio and write to logging")
 
     parser.add_argument("--product", action="store", choices=product_choices,
                         default="firefox", help="Browser against which to run tests")
 
-    parser.add_argument('--debugger',
-                        help="run under a debugger, e.g. gdb or valgrind")
-    parser.add_argument('--debugger-args', help="arguments to the debugger")
-    parser.add_argument('--pause-on-unexpected', action="store_true",
-                        help="Halt the test runner when an unexpected result is encountered")
+    parser.add_argument("--list-test-groups", action="store_true",
+                        default=False,
+                        help="List the top level directories containing tests that will run.")
+    parser.add_argument("--list-disabled", action="store_true",
+                        default=False,
+                        help="List the tests that are disabled on the current platform")
+
+    test_selection_group = parser.add_argument_group("Test Selection")
+    test_selection_group.add_argument("--test-types", action="store",
+                                      nargs="*", default=["testharness", "reftest"],
+                                      choices=["testharness", "reftest"],
+                                      help="Test types to run")
+    test_selection_group.add_argument("--include", action="append", type=slash_prefixed,
+                                      help="URL prefix to include")
+    test_selection_group.add_argument("--exclude", action="append", type=slash_prefixed,
+                                      help="URL prefix to exclude")
+    test_selection_group.add_argument("--include-manifest", type=abs_path,
+                                      help="Path to manifest listing tests to include")
+
+    debugging_group = parser.add_argument_group("Debugging")
+    debugging_group.add_argument('--debugger',
+                                 help="run under a debugger, e.g. gdb or valgrind")
+    debugging_group.add_argument('--debugger-args', help="arguments to the debugger")
 
-    parser.add_argument("--symbols-path", action="store", type=url_or_path,
-                        help="Path or url to symbols file used to analyse crash minidumps.")
-    parser.add_argument("--stackwalk-binary", action="store", type=abs_path,
-                        help="Path to stackwalker program used to analyse minidumps.")
+    debugging_group.add_argument('--pause-after-test', action="store_true", default=None,
+                                 help="Halt the test runner after each test (this happens by default if only a single test is run)")
+    debugging_group.add_argument('--no-pause-after-test', dest="pause_after_test", action="store_false",
+                                 help="Don't halt the test runner irrespective of the number of tests run")
+
+    debugging_group.add_argument('--pause-on-unexpected', action="store_true",
+                                 help="Halt the test runner when an unexpected result is encountered")
 
-    parser.add_argument("--ssl-type", action="store", default=None,
+    debugging_group.add_argument("--symbols-path", action="store", type=url_or_path,
+                                 help="Path or url to symbols file used to analyse crash minidumps.")
+    debugging_group.add_argument("--stackwalk-binary", action="store", type=abs_path,
+                                 help="Path to stackwalker program used to analyse minidumps.")
+
+    chunking_group = parser.add_argument_group("Test Chunking")
+    chunking_group.add_argument("--total-chunks", action="store", type=int, default=1,
+                                help="Total number of chunks to use")
+    chunking_group.add_argument("--this-chunk", action="store", type=int, default=1,
+                                help="Chunk number to run")
+    chunking_group.add_argument("--chunk-type", action="store", choices=["none", "equal_time", "hash"],
+                                default=None, help="Chunking type to use")
+
+    ssl_group = parser.add_argument_group("SSL/TLS")
+    ssl_group.add_argument("--ssl-type", action="store", default=None,
                         choices=["openssl", "pregenerated", "none"],
                         help="Type of ssl support to enable (running without ssl may lead to spurious errors)")
 
-    parser.add_argument("--openssl-binary", action="store",
+    ssl_group.add_argument("--openssl-binary", action="store",
                         help="Path to openssl binary", default="openssl")
-    parser.add_argument("--certutil-binary", action="store",
+    ssl_group.add_argument("--certutil-binary", action="store",
                         help="Path to certutil binary for use with Firefox + ssl")
 
-
-    parser.add_argument("--ca-cert-path", action="store", type=abs_path,
+    ssl_group.add_argument("--ca-cert-path", action="store", type=abs_path,
                         help="Path to ca certificate when using pregenerated ssl certificates")
-    parser.add_argument("--host-key-path", action="store", type=abs_path,
+    ssl_group.add_argument("--host-key-path", action="store", type=abs_path,
                         help="Path to host private key when using pregenerated ssl certificates")
-    parser.add_argument("--host-cert-path", action="store", type=abs_path,
+    ssl_group.add_argument("--host-cert-path", action="store", type=abs_path,
                         help="Path to host certificate when using pregenerated ssl certificates")
 
+    gecko_group = parser.add_argument_group("Gecko-specific")
+    gecko_group.add_argument("--prefs-root", dest="prefs_root", action="store", type=abs_path,
+                             help="Path to the folder containing browser prefs")
 
-    parser.add_argument("--b2g-no-backup", action="store_true", default=False,
-                        help="Don't backup device before testrun with --product=b2g")
+    b2g_group = parser.add_argument_group("B2G-specific")
+    b2g_group.add_argument("--b2g-no-backup", action="store_true", default=False,
+                           help="Don't backup device before testrun with --product=b2g")
+
+    parser.add_argument("test_list", nargs="*",
+                        help="List of URLs for tests to run, or paths including tests to run. "
+                             "(equivalent to --include)")
 
     commandline.add_logging_group(parser)
     return parser
 
 
 def set_from_config(kwargs):
     if kwargs["config"] is None:
         config_path = config.path()
     else:
         config_path = kwargs["config"]
 
     kwargs["config_path"] = config_path
     kwargs["config"] = config.read(kwargs["config_path"])
 
-    keys = {"paths": [("serve", "serve_root", True),
-                      ("prefs", "prefs_root", True),
+    keys = {"paths": [("prefs", "prefs_root", True),
                       ("run_info", "run_info", True)],
             "web-platform-tests": [("remote_url", "remote_url", False),
                                    ("branch", "branch", False),
                                    ("sync_path", "sync_path", True)],
             "SSL": [("openssl_binary", "openssl_binary", True),
                     ("certutil_binary", "certutil_binary", True),
                     ("ca_cert_path", "ca_cert_path", True),
                     ("host_cert_path", "host_cert_path", True),
@@ -204,16 +216,19 @@ def get_test_paths(config):
             test_paths[url_base] = {
                 "tests_path": manifest_opts.get_path("tests"),
                 "metadata_path": manifest_opts.get_path("metadata")}
 
     return test_paths
 
 
 def exe_path(name):
+    if name is None:
+        return
+
     path = find_executable(name)
     if os.access(path, os.X_OK):
         return path
 
 
 def check_args(kwargs):
     from mozrunner import debugger_arguments
 
@@ -230,22 +245,21 @@ def check_args(kwargs):
             if not os.path.exists(path):
                 print "Fatal: %s path %s does not exist" % (name, path)
                 sys.exit(1)
 
             if not os.path.isdir(path):
                 print "Fatal: %s path %s is not a directory" % (name, path)
                 sys.exit(1)
 
-    if kwargs["serve_root"] is None:
-        if "/" in kwargs["test_paths"]:
-            kwargs["serve_root"] = kwargs["test_paths"]["/"]["tests_path"]
-    else:
-        print >> sys.stderr, "Unable to determine server root path"
-        sys.exit(1)
+    if kwargs["test_list"]:
+        if kwargs["include"] is not None:
+            kwargs["include"].extend(kwargs["test_list"])
+        else:
+            kwargs["include"] = kwargs["test_list"]
 
     if kwargs["run_info"] is None:
         kwargs["run_info"] = kwargs["config_path"]
 
     if kwargs["this_chunk"] > 1:
         require_arg(kwargs, "total_chunks", lambda x: x >= kwargs["this_chunk"])
 
     if kwargs["chunk_type"] is None:
@@ -308,19 +322,16 @@ def create_parser_update():
                                      description="Update script for web-platform-tests tests.")
     parser.add_argument("--config", action="store", type=abs_path, help="Path to config file")
     parser.add_argument("--metadata", action="store", type=abs_path, dest="metadata_root",
                         help="Path to the folder containing test metadata"),
     parser.add_argument("--tests", action="store", type=abs_path, dest="tests_root",
                         help="Path to web-platform-tests"),
     parser.add_argument("--sync-path", action="store", type=abs_path,
                         help="Path to store git checkout of web-platform-tests during update"),
-    parser.add_argument("--serve-root", action="store", type=abs_path, dest="serve_root",
-                        help="Path to web-platform-tests checkout containing serve.py and manifest.py"
-                        " (defaults to test_root)")
     parser.add_argument("--remote_url", action="store",
                         help="URL of web-platfrom-tests repository to sync against"),
     parser.add_argument("--branch", action="store", type=abs_path,
                         help="Remote branch to sync against")
     parser.add_argument("--rev", action="store", help="Revision to sync to")
     parser.add_argument("--no-patch", action="store_true",
                         help="Don't create an mq patch or git commit containing the changes.")
     parser.add_argument("--sync", dest="sync", action="store_true", default=False,
copy from testing/web-platform/harness/wptrunner/wptrunner.py
copy to testing/web-platform/harness/wptrunner/wptlogging.py
--- a/testing/web-platform/harness/wptrunner/wptrunner.py
+++ b/testing/web-platform/harness/wptrunner/wptlogging.py
@@ -1,109 +1,36 @@
 # 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/.
 
-from __future__ import unicode_literals
-
-import json
 import logging
-import os
-import shutil
-import socket
 import sys
 import threading
-import time
-import urlparse
-from Queue import Empty
 from StringIO import StringIO
-
 from multiprocessing import Queue
 
-from mozlog.structured import (commandline, stdadapter, get_default_logger,
-                               structuredlog, handlers, formatters)
-
-import products
-import testloader
-import wptcommandline
-import wpttest
-from testrunner import ManagerGroup
-
-here = os.path.split(__file__)[0]
-
-
-"""Runner for web-platform-tests
-
-The runner has several design goals:
+from mozlog.structured import commandline, stdadapter
 
-* Tests should run with no modification from upstream.
-
-* Tests should be regarded as "untrusted" so that errors, timeouts and even
-  crashes in the tests can be handled without failing the entire test run.
-
-* For performance tests can be run in multiple browsers in parallel.
-
-The upstream repository has the facility for creating a test manifest in JSON
-format. This manifest is used directly to determine which tests exist. Local
-metadata files are used to store the expected test results.
-"""
-
-logger = None
-
-
-def setup_logging(args, defaults):
-    global logger
+def setup(args, defaults):
     logger = commandline.setup_logging("web-platform-tests", args, defaults)
     setup_stdlib_logger()
 
     for name in args.keys():
         if name.startswith("log_"):
             args.pop(name)
 
     return logger
 
 
 def setup_stdlib_logger():
     logging.root.handlers = []
     logging.root = stdadapter.std_logging_adapter(logging.root)
 
 
-def do_delayed_imports(serve_root):
-    global serve, manifest, sslutils
-
-    sys.path.insert(0, serve_root)
-    sys.path.insert(0, str(os.path.join(serve_root, "tools")))
-    sys.path.insert(0, str(os.path.join(serve_root, "tools", "scripts")))
-    failed = []
-
-    try:
-        import serve
-    except ImportError:
-        failed.append("serve")
-    try:
-        import manifest
-    except ImportError:
-        failed.append("manifest")
-    try:
-        import sslutils
-    except ImportError:
-        raise
-        failed.append("sslutils")
-
-    if failed:
-        logger.critical(
-            "Failed to import %s. Ensure that tests path %s contains web-platform-tests" %
-            (", ".join(failed), serve_root))
-        sys.exit(1)
-
-
-class TestEnvironmentError(Exception):
-    pass
-
-
 class LogLevelRewriter(object):
     """Filter that replaces log messages at specified levels with messages
     at a different level.
 
     This can be used to e.g. downgrade log messages from ERROR to WARNING
     in some component where ERRORs are not critical.
 
     :param inner: Handler to use for messages that pass this filter
@@ -117,157 +44,16 @@ class LogLevelRewriter(object):
 
     def __call__(self, data):
         if data["action"] == "log" and data["level"].upper() in self.from_levels:
             data = data.copy()
             data["level"] = self.to_level
         return self.inner(data)
 
 
-class TestEnvironment(object):
-    def __init__(self, serve_path, test_paths, ssl_env, options):
-        """Context manager that owns the test environment i.e. the http and
-        websockets servers"""
-        self.serve_path = serve_path
-        self.test_paths = test_paths
-        self.ssl_env = ssl_env
-        self.server = None
-        self.config = None
-        self.external_config = None
-        self.test_server_port = options.pop("test_server_port", True)
-        self.options = options if options is not None else {}
-        self.required_files = options.pop("required_files", [])
-        self.files_to_restore = []
-
-    def __enter__(self):
-        self.ssl_env.__enter__()
-        self.copy_required_files()
-        self.setup_server_logging()
-        self.setup_routes()
-        self.config = self.load_config()
-        serve.set_computed_defaults(self.config)
-        self.external_config, self.servers = serve.start(self.config, self.ssl_env)
-        return self
-
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        self.ssl_env.__exit__(exc_type, exc_val, exc_tb)
-
-        self.restore_files()
-        for scheme, servers in self.servers.iteritems():
-            for port, server in servers:
-                server.kill()
-
-    def load_config(self):
-        default_config_path = os.path.join(self.serve_path, "config.default.json")
-        local_config_path = os.path.join(here, "config.json")
-
-        with open(default_config_path) as f:
-            default_config = json.load(f)
-
-        with open(local_config_path) as f:
-            data = f.read()
-            local_config = json.loads(data % self.options)
-
-        #TODO: allow non-default configuration for ssl
-
-        local_config["external_host"] = self.options.get("external_host", None)
-        local_config["ssl"]["encrypt_after_connect"] = self.options.get("encrypt_after_connect", False)
-
-        config = serve.merge_json(default_config, local_config)
-        config["doc_root"] = self.serve_path
-
-        if not self.ssl_env.ssl_enabled:
-            config["ports"]["https"] = [None]
-
-        host = self.options.get("certificate_domain", config["host"])
-        hosts = [host]
-        hosts.extend("%s.%s" % (item[0], host) for item in serve.get_subdomains(host).values())
-        key_file, certificate = self.ssl_env.host_cert_path(hosts)
-
-        config["key_file"] = key_file
-        config["certificate"] = certificate
-
-        return config
-
-    def setup_server_logging(self):
-        server_logger = get_default_logger(component="wptserve")
-        assert server_logger is not None
-        log_filter = handlers.LogLevelFilter(lambda x:x, "info")
-        # Downgrade errors to warnings for the server
-        log_filter = LogLevelRewriter(log_filter, ["error"], "warning")
-        server_logger.component_filter = log_filter
-
-        serve.logger = server_logger
-        #Set as the default logger for wptserve
-        serve.set_logger(server_logger)
-
-    def setup_routes(self):
-        for url, paths in self.test_paths.iteritems():
-            if url == "/":
-                continue
-
-            path = paths["tests_path"]
-            url = "/%s/" % url.strip("/")
-
-            for (method,
-                 suffix,
-                 handler_cls) in [(serve.any_method,
-                                   b"*.py",
-                                   serve.handlers.PythonScriptHandler),
-                                  (b"GET",
-                                   "*.asis",
-                                   serve.handlers.AsIsHandler),
-                                  (b"GET",
-                                   "*",
-                                   serve.handlers.FileHandler)]:
-                route = (method, b"%s%s" % (str(url), str(suffix)), handler_cls(path, url_base=url))
-                serve.routes.insert(-3, route)
-
-        if "/" not in self.test_paths:
-            serve.routes = serve.routes[:-3]
-
-    def copy_required_files(self):
-        logger.info("Placing required files in server environment.")
-        for source, destination, copy_if_exists in self.required_files:
-            source_path = os.path.join(here, source)
-            dest_path = os.path.join(self.serve_path, destination, os.path.split(source)[1])
-            dest_exists = os.path.exists(dest_path)
-            if not dest_exists or copy_if_exists:
-                if dest_exists:
-                    backup_path = dest_path + ".orig"
-                    logger.info("Backing up %s to %s" % (dest_path, backup_path))
-                    self.files_to_restore.append(dest_path)
-                    shutil.copy2(dest_path, backup_path)
-                logger.info("Copying %s to %s" % (source_path, dest_path))
-                shutil.copy2(source_path, dest_path)
-
-    def ensure_started(self):
-        # Pause for a while to ensure that the server has a chance to start
-        time.sleep(2)
-        for scheme, servers in self.servers.iteritems():
-            for port, server in servers:
-                if self.test_server_port:
-                    s = socket.socket()
-                    try:
-                        s.connect((self.config["host"], port))
-                    except socket.error:
-                        raise EnvironmentError(
-                            "%s server on port %d failed to start" % (scheme, port))
-                    finally:
-                        s.close()
-
-                if not server.is_alive():
-                    raise EnvironmentError("%s server on port %d failed to start" % (scheme, port))
-
-    def restore_files(self):
-        for path in self.files_to_restore:
-            os.unlink(path)
-            if os.path.exists(path + ".orig"):
-                os.rename(path + ".orig", path)
-
 
 class LogThread(threading.Thread):
     def __init__(self, queue, logger, level):
         self.queue = queue
         self.log_func = getattr(logger, level)
         threading.Thread.__init__(self, name="Thread-Log")
         self.daemon = True
 
@@ -303,201 +89,35 @@ class LoggingWrapper(StringIO):
             return
         if self.prefix is not None:
             data = "%s: %s" % (self.prefix, data)
         self.queue.put(data)
 
     def flush(self):
         pass
 
-def list_test_groups(serve_root, test_paths, test_types, product, **kwargs):
-
-    do_delayed_imports(serve_root)
-
-    run_info = wpttest.get_run_info(kwargs["run_info"], product, debug=False)
-    test_filter = testloader.TestFilter(include=kwargs["include"],
-                                        exclude=kwargs["exclude"],
-                                        manifest_path=kwargs["include_manifest"])
-    test_loader = testloader.TestLoader(test_paths,
-                                        test_types,
-                                        test_filter,
-                                        run_info)
-
-    for item in sorted(test_loader.groups(test_types)):
-        print item
-
-
-def list_disabled(serve_root, test_paths, test_types, product, **kwargs):
-    do_delayed_imports(serve_root)
-    rv = []
-    run_info = wpttest.get_run_info(kwargs["run_info"], product, debug=False)
-    test_loader = testloader.TestLoader(test_paths,
-                                        test_types,
-                                        testloader.TestFilter(),
-                                        run_info)
-
-    for test_type, tests in test_loader.disabled_tests.iteritems():
-        for test in tests:
-            rv.append({"test": test.id, "reason": test.disabled()})
-    print json.dumps(rv, indent=2)
-
-
-def get_ssl_kwargs(**kwargs):
-    if kwargs["ssl_type"] == "openssl":
-        args = {"openssl_binary": kwargs["openssl_binary"]}
-    elif kwargs["ssl_type"] == "pregenerated":
-        args = {"host_key_path": kwargs["host_key_path"],
-                "host_cert_path": kwargs["host_cert_path"],
-                 "ca_cert_path": kwargs["ca_cert_path"]}
-    else:
-        args = {}
-    return args
-
-
-def run_tests(config, serve_root, test_paths, product, **kwargs):
-    logging_queue = None
-    logging_thread = None
-    original_stdio = (sys.stdout, sys.stderr)
-    test_queues = None
-
-    try:
-        if not kwargs["no_capture_stdio"]:
-            logging_queue = Queue()
-            logging_thread = LogThread(logging_queue, logger, "info")
-            sys.stdout = LoggingWrapper(logging_queue, prefix="STDOUT")
-            sys.stderr = LoggingWrapper(logging_queue, prefix="STDERR")
-            logging_thread.start()
-
-        do_delayed_imports(serve_root)
-
-        run_info = wpttest.get_run_info(kwargs["run_info"], product, debug=False)
-
-        (check_args,
-         browser_cls, get_browser_kwargs,
-         executor_classes, get_executor_kwargs,
-         env_options) = products.load_product(config, product)
-
-        check_args(**kwargs)
-
-        ssl_env_cls = sslutils.environments[kwargs["ssl_type"]]
-        ssl_env = ssl_env_cls(logger, **get_ssl_kwargs(**kwargs))
-
-        unexpected_total = 0
-
-        if "test_loader" in kwargs:
-            test_loader = kwargs["test_loader"]
-        else:
-            test_filter = testloader.TestFilter(include=kwargs["include"],
-                                                exclude=kwargs["exclude"],
-                                                manifest_path=kwargs["include_manifest"])
-            test_loader = testloader.TestLoader(test_paths,
-                                                kwargs["test_types"],
-                                                test_filter,
-                                                run_info,
-                                                kwargs["chunk_type"],
-                                                kwargs["total_chunks"],
-                                                kwargs["this_chunk"],
-                                                kwargs["manifest_update"])
-
-        if kwargs["run_by_dir"] is False:
-            test_source_cls = testloader.SingleTestSource
-            test_source_kwargs = {}
-        else:
-            # A value of None indicates infinite depth
-            test_source_cls = testloader.PathGroupedSource
-            test_source_kwargs = {"depth": kwargs["run_by_dir"]}
+class CaptureIO(object):
+    def __init__(self, logger, do_capture):
+        self.logger = logger
+        self.do_capture = do_capture
+        self.logging_queue = None
+        self.logging_thread = None
+        self.original_stdio = None
 
-        logger.info("Using %i client processes" % kwargs["processes"])
-
-        with TestEnvironment(serve_root,
-                             test_paths,
-                             ssl_env,
-                             env_options) as test_environment:
-            try:
-                test_environment.ensure_started()
-            except TestEnvironmentError as e:
-                logger.critical("Error starting test environment: %s" % e.message)
-                raise
-
-            browser_kwargs = get_browser_kwargs(ssl_env=ssl_env, **kwargs)
-            base_server = "http://%s:%i" % (test_environment.external_config["host"],
-                                            test_environment.external_config["ports"]["http"][0])
-
-            repeat = kwargs["repeat"]
-            for repeat_count in xrange(repeat):
-                if repeat > 1:
-                    logger.info("Repetition %i / %i" % (repeat_count + 1, repeat))
-
-
-                unexpected_count = 0
-                logger.suite_start(test_loader.test_ids, run_info)
-                for test_type in kwargs["test_types"]:
-                    logger.info("Running %s tests" % test_type)
-
-                    for test in test_loader.disabled_tests[test_type]:
-                        logger.test_start(test.id)
-                        logger.test_end(test.id, status="SKIP")
-
-                    executor_cls = executor_classes.get(test_type)
-                    executor_kwargs = get_executor_kwargs(base_server,
-                                                          **kwargs)
-
-                    if executor_cls is None:
-                        logger.error("Unsupported test type %s for product %s" %
-                                     (test_type, product))
-                        continue
-
+    def __enter__(self):
+        if self.do_capture:
+            self.original_stdio = (sys.stdout, sys.stderr)
+            self.logging_queue = Queue()
+            self.logging_thread = LogThread(self.logging_queue, self.logger, "info")
+            sys.stdout = LoggingWrapper(self.logging_queue, prefix="STDOUT")
+            sys.stderr = LoggingWrapper(self.logging_queue, prefix="STDERR")
+            self.logging_thread.start()
 
-                    with ManagerGroup("web-platform-tests",
-                                      kwargs["processes"],
-                                      test_source_cls,
-                                      test_source_kwargs,
-                                      browser_cls,
-                                      browser_kwargs,
-                                      executor_cls,
-                                      executor_kwargs,
-                                      kwargs["pause_on_unexpected"],
-                                      kwargs["debug_args"]) as manager_group:
-                        try:
-                            manager_group.run(test_type, test_loader.tests)
-                        except KeyboardInterrupt:
-                            logger.critical("Main thread got signal")
-                            manager_group.stop()
-                            raise
-                    unexpected_count += manager_group.unexpected_count()
-
-                unexpected_total += unexpected_count
-                logger.info("Got %i unexpected results" % unexpected_count)
-                logger.suite_end()
-    except KeyboardInterrupt:
-        if test_queues is not None:
-            for queue in test_queues.itervalues():
-                queue.cancel_join_thread()
-    finally:
-        if test_queues is not None:
-            for queue in test_queues.itervalues():
-                queue.close()
-        sys.stdout, sys.stderr = original_stdio
-        if not kwargs["no_capture_stdio"] and logging_queue is not None:
-            logger.info("Closing logging queue")
-            logging_queue.put(None)
-            if logging_thread is not None:
-                logging_thread.join(10)
-            logging_queue.close()
-
-    return unexpected_total == 0
-
-
-def main():
-    """Main entry point when calling from the command line"""
-    kwargs = wptcommandline.parse_args()
-
-    if kwargs["prefs_root"] is None:
-        kwargs["prefs_root"] = os.path.abspath(os.path.join(here, "prefs"))
-
-    setup_logging(kwargs, {"raw": sys.stdout})
-
-    if kwargs["list_test_groups"]:
-        list_test_groups(**kwargs)
-    elif kwargs["list_disabled"]:
-        list_disabled(**kwargs)
-    else:
-        return run_tests(**kwargs)
+    def __exit__(self, *args, **kwargs):
+        if self.do_capture:
+            sys.stdout, sys.stderr = self.original_stdio
+            if self.logging_queue is not None:
+                self.logger.info("Closing logging queue")
+                self.logging_queue.put(None)
+                if self.logging_thread is not None:
+                    self.logging_thread.join(10)
+                self.logging_queue.close()
+                self.logger.info("queue closed")
--- a/testing/web-platform/harness/wptrunner/wptrunner.py
+++ b/testing/web-platform/harness/wptrunner/wptrunner.py
@@ -1,39 +1,29 @@
 # 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/.
 
 from __future__ import unicode_literals
 
 import json
-import logging
 import os
-import shutil
-import socket
 import sys
-import threading
-import time
-import urlparse
-from Queue import Empty
-from StringIO import StringIO
 
-from multiprocessing import Queue
-
-from mozlog.structured import (commandline, stdadapter, get_default_logger,
-                               structuredlog, handlers, formatters)
-
+import environment as env
 import products
 import testloader
 import wptcommandline
+import wptlogging
 import wpttest
 from testrunner import ManagerGroup
 
 here = os.path.split(__file__)[0]
 
+logger = None
 
 """Runner for web-platform-tests
 
 The runner has several design goals:
 
 * Tests should run with no modification from upstream.
 
 * Tests should be regarded as "untrusted" so that errors, timeouts and even
@@ -41,384 +31,113 @@ The runner has several design goals:
 
 * For performance tests can be run in multiple browsers in parallel.
 
 The upstream repository has the facility for creating a test manifest in JSON
 format. This manifest is used directly to determine which tests exist. Local
 metadata files are used to store the expected test results.
 """
 
-logger = None
-
-
-def setup_logging(args, defaults):
+def setup_logging(*args, **kwargs):
     global logger
-    logger = commandline.setup_logging("web-platform-tests", args, defaults)
-    setup_stdlib_logger()
-
-    for name in args.keys():
-        if name.startswith("log_"):
-            args.pop(name)
-
-    return logger
-
-
-def setup_stdlib_logger():
-    logging.root.handlers = []
-    logging.root = stdadapter.std_logging_adapter(logging.root)
-
-
-def do_delayed_imports(serve_root):
-    global serve, manifest, sslutils
-
-    sys.path.insert(0, serve_root)
-    sys.path.insert(0, str(os.path.join(serve_root, "tools")))
-    sys.path.insert(0, str(os.path.join(serve_root, "tools", "scripts")))
-    failed = []
-
-    try:
-        import serve
-    except ImportError:
-        failed.append("serve")
-    try:
-        import manifest
-    except ImportError:
-        failed.append("manifest")
-    try:
-        import sslutils
-    except ImportError:
-        raise
-        failed.append("sslutils")
-
-    if failed:
-        logger.critical(
-            "Failed to import %s. Ensure that tests path %s contains web-platform-tests" %
-            (", ".join(failed), serve_root))
-        sys.exit(1)
-
-
-class TestEnvironmentError(Exception):
-    pass
-
-
-class LogLevelRewriter(object):
-    """Filter that replaces log messages at specified levels with messages
-    at a different level.
-
-    This can be used to e.g. downgrade log messages from ERROR to WARNING
-    in some component where ERRORs are not critical.
-
-    :param inner: Handler to use for messages that pass this filter
-    :param from_levels: List of levels which should be affected
-    :param to_level: Log level to set for the affected messages
-    """
-    def __init__(self, inner, from_levels, to_level):
-        self.inner = inner
-        self.from_levels = [item.upper() for item in from_levels]
-        self.to_level = to_level.upper()
-
-    def __call__(self, data):
-        if data["action"] == "log" and data["level"].upper() in self.from_levels:
-            data = data.copy()
-            data["level"] = self.to_level
-        return self.inner(data)
-
-
-class TestEnvironment(object):
-    def __init__(self, serve_path, test_paths, ssl_env, options):
-        """Context manager that owns the test environment i.e. the http and
-        websockets servers"""
-        self.serve_path = serve_path
-        self.test_paths = test_paths
-        self.ssl_env = ssl_env
-        self.server = None
-        self.config = None
-        self.external_config = None
-        self.test_server_port = options.pop("test_server_port", True)
-        self.options = options if options is not None else {}
-        self.required_files = options.pop("required_files", [])
-        self.files_to_restore = []
-
-    def __enter__(self):
-        self.ssl_env.__enter__()
-        self.copy_required_files()
-        self.setup_server_logging()
-        self.setup_routes()
-        self.config = self.load_config()
-        serve.set_computed_defaults(self.config)
-        self.external_config, self.servers = serve.start(self.config, self.ssl_env)
-        return self
-
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        self.ssl_env.__exit__(exc_type, exc_val, exc_tb)
-
-        self.restore_files()
-        for scheme, servers in self.servers.iteritems():
-            for port, server in servers:
-                server.kill()
-
-    def load_config(self):
-        default_config_path = os.path.join(self.serve_path, "config.default.json")
-        local_config_path = os.path.join(here, "config.json")
-
-        with open(default_config_path) as f:
-            default_config = json.load(f)
-
-        with open(local_config_path) as f:
-            data = f.read()
-            local_config = json.loads(data % self.options)
-
-        #TODO: allow non-default configuration for ssl
-
-        local_config["external_host"] = self.options.get("external_host", None)
-        local_config["ssl"]["encrypt_after_connect"] = self.options.get("encrypt_after_connect", False)
-
-        config = serve.merge_json(default_config, local_config)
-        config["doc_root"] = self.serve_path
-
-        if not self.ssl_env.ssl_enabled:
-            config["ports"]["https"] = [None]
+    logger = wptlogging.setup(*args, **kwargs)
 
-        host = self.options.get("certificate_domain", config["host"])
-        hosts = [host]
-        hosts.extend("%s.%s" % (item[0], host) for item in serve.get_subdomains(host).values())
-        key_file, certificate = self.ssl_env.host_cert_path(hosts)
-
-        config["key_file"] = key_file
-        config["certificate"] = certificate
-
-        return config
-
-    def setup_server_logging(self):
-        server_logger = get_default_logger(component="wptserve")
-        assert server_logger is not None
-        log_filter = handlers.LogLevelFilter(lambda x:x, "info")
-        # Downgrade errors to warnings for the server
-        log_filter = LogLevelRewriter(log_filter, ["error"], "warning")
-        server_logger.component_filter = log_filter
-
-        serve.logger = server_logger
-        #Set as the default logger for wptserve
-        serve.set_logger(server_logger)
-
-    def setup_routes(self):
-        for url, paths in self.test_paths.iteritems():
-            if url == "/":
-                continue
-
-            path = paths["tests_path"]
-            url = "/%s/" % url.strip("/")
-
-            for (method,
-                 suffix,
-                 handler_cls) in [(serve.any_method,
-                                   b"*.py",