merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 27 Nov 2015 11:08:41 +0100
changeset 274444 47b49b0d32360fab04b11ff9120970979c426911
parent 274380 ad985ade8a2e5e58123f0a774605d273834c3ba1 (current diff)
parent 274443 48139ac411625d1ee8b21c51770b917502dba1d9 (diff)
child 274445 7883e81f3c305078353ca27a6b1adb8c769d5904
child 274448 bf0d6a7b6fa1374e52560dce73111e9de835100d
child 274479 bb512bf5a0669afa0d8158daf906a4223f4ba6ce
child 274530 2c0df58d9443dec00c684b00b111dfaf96613997
push id29730
push usercbook@mozilla.com
push dateFri, 27 Nov 2015 10:08:56 +0000
treeherdermozilla-central@47b49b0d3236 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone45.0a1
first release with
nightly linux32
47b49b0d3236 / 45.0a1 / 20151127030231 / files
nightly linux64
47b49b0d3236 / 45.0a1 / 20151127030231 / files
nightly mac
47b49b0d3236 / 45.0a1 / 20151127030231 / files
nightly win32
47b49b0d3236 / 45.0a1 / 20151127030231 / files
nightly win64
47b49b0d3236 / 45.0a1 / 20151127030231 / 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 mozilla-inbound to mozilla-central a=merge
testing/marionette/command.js
testing/web-platform/meta/dom/nodes/MutationObserver-document.html.ini
testing/web-platform/mozilla/meta/service-workers/service-worker/register-wait-forever-in-install-worker.https.html.ini
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -2156,17 +2156,17 @@ DocAccessible::MoveChild(Accessible* aCh
   MaybeNotifyOfValueChange(parent);
   FireDelayedEvent(reorderEvent);
 }
 
 void
 DocAccessible::PutChildrenBack(nsTArray<RefPtr<Accessible> >* aChildren,
                                uint32_t aStartIdx)
 {
-  nsTArray<Accessible*> containers;
+  nsTArray<RefPtr<Accessible> > containers;
   for (auto idx = aStartIdx; idx < aChildren->Length(); idx++) {
     Accessible* child = aChildren->ElementAt(idx);
 
     // If the child is in the tree then remove it from the owner.
     if (child->IsInDocument()) {
       Accessible* owner = child->Parent();
       if (!owner) {
         NS_ERROR("Cannot put the child back. No parent, a broken tree.");
@@ -2193,17 +2193,21 @@ DocAccessible::PutChildrenBack(nsTArray<
         containers.IndexOf(container) == nsTArray<Accessible*>::NoIndex) {
       containers.AppendElement(container);
     }
   }
 
   // And put it back where it belongs to.
   aChildren->RemoveElementsAt(aStartIdx, aChildren->Length() - aStartIdx);
   for (uint32_t idx = 0; idx < containers.Length(); idx++) {
-    UpdateTreeOnInsertion(containers[idx]);
+    NS_ASSERTION(containers[idx]->IsInDocument(),
+                 "A container has been destroyed.");
+    if (containers[idx]->IsInDocument()) {
+      UpdateTreeOnInsertion(containers[idx]);
+    }
   }
 }
 
 void
 DocAccessible::CacheChildrenInSubtree(Accessible* aRoot,
                                       Accessible** aFocusedAcc)
 {
   // If the accessible is focused then report a focus event after all related
--- a/browser/base/content/browser-gestureSupport.js
+++ b/browser/base/content/browser-gestureSupport.js
@@ -641,38 +641,43 @@ var gHistorySwipeAnimation = {
       // from 0.
       if (this._direction == "horizontal" || this._lastSwipeDir != "") {
         gBrowser.stop();
         this._lastSwipeDir = "RELOAD"; // just ensure that != ""
         this._canGoBack = this.canGoBack();
         this._canGoForward = this.canGoForward();
         this._handleFastSwiping();
       }
+      this.updateAnimation(0);
     }
     else {
-      this._startingIndex = gBrowser.webNavigation.sessionHistory.index;
-      this._historyIndex = this._startingIndex;
-      this._canGoBack = this.canGoBack();
-      this._canGoForward = this.canGoForward();
-      if (this.active) {
-        this._addBoxes();
-        this._takeSnapshot();
-        this._installPrevAndNextSnapshots();
-        this._lastSwipeDir = "";
+      // Get the session history from SessionStore.
+      let updateSessionHistory = sessionHistory => {
+        this._startingIndex = sessionHistory.index;
+        this._historyIndex = this._startingIndex;
+        this._canGoBack = this.canGoBack();
+        this._canGoForward = this.canGoForward();
+        if (this.active) {
+          this._addBoxes();
+          this._takeSnapshot();
+          this._installPrevAndNextSnapshots();
+          this._lastSwipeDir = "";
+        }
+        this.updateAnimation(0);
       }
+      SessionStore.getSessionHistory(gBrowser.selectedTab, updateSessionHistory);
     }
-    this.updateAnimation(0);
   },
 
   /**
    * Stops the swipe animation.
    */
   stopAnimation: function HSA_stopAnimation() {
     gHistorySwipeAnimation._removeBoxes();
-    this._historyIndex = gBrowser.webNavigation.sessionHistory.index;
+    this._historyIndex = this._getCurrentHistoryIndex();
   },
 
   /**
    * Updates the animation between two pages in history.
    *
    * @param aVal
    *        A floating point value that represents the progress of the
    *        swipe gesture.
@@ -721,16 +726,20 @@ var gHistorySwipeAnimation = {
         this._positionBox(this._nextBox, offset + aVal);
       } else {
         this._prevBox.collapsed = true;
         this._positionBox(this._curBox, aVal / dampValue);
       }
     }
   },
 
+  _getCurrentHistoryIndex: function() {
+    return SessionStore.getSessionHistory(gBrowser.selectedTab).index;
+  },
+
   /**
    * Event handler for events relevant to the history swipe animation.
    *
    * @param aEvent
    *        An event to process.
    */
   handleEvent: function HSA_handleEvent(aEvent) {
     let browser = gBrowser.selectedBrowser;
@@ -816,32 +825,36 @@ var gHistorySwipeAnimation = {
   },
 
   /**
    * Used to notify the history swipe animation that the OS sent a swipe end
    * event and that we should navigate to the page that the user swiped to, if
    * any. This will also result in the animation overlay to be torn down.
    */
   swipeEndEventReceived: function HSA_swipeEndEventReceived() {
-    if (this._lastSwipeDir != "" && this._historyIndex != this._startingIndex)
-      this._navigateToHistoryIndex();
-    else
-      this.stopAnimation();
+    // Update the session history before continuing.
+    let updateSessionHistory = sessionHistory => {
+      if (this._lastSwipeDir != "" && this._historyIndex != this._startingIndex)
+        this._navigateToHistoryIndex();
+      else
+        this.stopAnimation();
+    }
+    SessionStore.getSessionHistory(gBrowser.selectedTab, updateSessionHistory);
   },
 
   /**
    * Checks whether a particular index exists in the browser history or not.
    *
    * @param aIndex
    *        The index to check for availability for in the history.
    * @return true if the index exists in the browser history, false otherwise.
    */
   _doesIndexExistInHistory: function HSA__doesIndexExistInHistory(aIndex) {
     try {
-      gBrowser.webNavigation.sessionHistory.getEntryAtIndex(aIndex, false);
+      return SessionStore.getSessionHistory(gBrowser.selectedTab).entries[aIndex] != null;
     }
     catch(ex) {
       return false;
     }
     return true;
   },
 
   /**
@@ -954,21 +967,17 @@ var gHistorySwipeAnimation = {
 
   /**
    * Verifies that we're ready to take snapshots based on the global pref and
    * the current index in history.
    *
    * @return true if we're ready to take snapshots, false otherwise.
    */
   _readyToTakeSnapshots: function HSA__readyToTakeSnapshots() {
-    if ((this._maxSnapshots < 1) ||
-        (gBrowser.webNavigation.sessionHistory.index < 0)) {
-      return false;
-    }
-    return true;
+    return (this._maxSnapshots >= 1 && this._getCurrentHistoryIndex() >= 0);
   },
 
   /**
    * Takes a snapshot of the page the browser is currently on.
    */
   _takeSnapshot: function HSA__takeSnapshot() {
     if (!this._readyToTakeSnapshots()) {
       return;
@@ -1021,17 +1030,17 @@ var gHistorySwipeAnimation = {
    * snapshot in the list.
    *
    * @param aCanvas
    *        The snapshot to add to the list and compress.
    */
   _assignSnapshotToCurrentBrowser:
   function HSA__assignSnapshotToCurrentBrowser(aCanvas) {
     let browser = gBrowser.selectedBrowser;
-    let currIndex = browser.webNavigation.sessionHistory.index;
+    let currIndex = this._getCurrentHistoryIndex();
 
     this._removeTrackedSnapshot(currIndex, browser);
     this._addSnapshotRefToArray(currIndex, browser);
 
     if (!("snapshots" in browser))
       browser.snapshots = [];
     let snapshots = browser.snapshots;
     // Temporarily store the canvas as the compressed snapshot.
@@ -1054,17 +1063,17 @@ var gHistorySwipeAnimation = {
       // there's nothing to compress.
       return;
     }
 
     TelemetryStopwatch.start("FX_GESTURE_COMPRESS_SNAPSHOT_OF_PAGE");
     try {
       let browser = gBrowser.selectedBrowser;
       let snapshots = browser.snapshots;
-      let currIndex = browser.webNavigation.sessionHistory.index;
+      let currIndex = _getCurrentHistoryIndex();
 
       // Kick off snapshot compression.
       let canvas = snapshots[currIndex].image;
       canvas.toBlob(function(aBlob) {
           if (snapshots[currIndex]) {
             snapshots[currIndex].image = aBlob;
           }
         }, "image/png"
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -3372,27 +3372,26 @@ var E10SUINotification = {
   },
 };
 
 #else // E10S_TESTING_ONLY
 
 var E10SAccessibilityCheck = {
   init: function() {
     Services.obs.addObserver(this, "a11y-init-or-shutdown", true);
-    if (Services.appinfo.accessibilityIsBlacklistedForE10S) {
+    if (Services.appinfo.accessibilityEnabled) {
       this._showE10sAccessibilityWarning();
     }
   },
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]),
 
   observe: function(subject, topic, data) {
     if (topic == "a11y-init-or-shutdown"
-        && data == "1" &&
-        Services.appinfo.accessibilityIsBlacklistedForE10S) {
+        && data == "1") {
       this._showE10sAccessibilityWarning();
     }
   },
 
   _warnedAboutAccessibility: false,
 
   _showE10sAccessibilityWarning: function() {
     try {
--- a/configure.in
+++ b/configure.in
@@ -319,27 +319,26 @@ if test -n "$gonkdir" ; then
     AC_DEFINE(HAVE_SYS_UIO_H)
     AC_DEFINE(HAVE_PTHREADS)
     MOZ_CHROME_FILE_FORMAT=omni
     direct_nspr_config=1
     android_cxx_stl=mozstlport
 else
     if test "$COMPILE_ENVIRONMENT"; then
         MOZ_ANDROID_NDK
-    else
-        AC_DEFINE(ANDROID)
     fi # COMPILE_ENVIRONMENT
 
     case "$target" in
     *-android*|*-linuxandroid*)
         if test -z "$ANDROID_PACKAGE_NAME" ; then
             ANDROID_PACKAGE_NAME='org.mozilla.$(MOZ_APP_NAME)'
         fi
         MOZ_CHROME_FILE_FORMAT=omni
         ZLIB_DIR=yes
+        AC_DEFINE(ANDROID)
         ;;
     *-linux*)
         AC_PATH_PROG(OBJCOPY,objcopy)
         ;;
     esac
 fi
 
 case "$target" in
@@ -4213,16 +4212,17 @@ cairo-uikit)
     AC_DEFINE(MOZ_WIDGET_UIKIT)
     LDFLAGS="$LDFLAGS -framework UIKit -lobjc"
     TK_CFLAGS="-DNO_X11"
     TK_LIBS='-Wl,-framework,Foundation -Wl,-framework,CoreFoundation -Wl,-framework,CoreGraphics -Wl,-framework,CoreText -Wl,-framework,AVFoundation -Wl,-framework,AudioToolbox -Wl,-framework,CoreMedia -Wl,-framework,CoreVideo -Wl,-framework,OpenGLES -Wl,-framework,QuartzCore'
     CFLAGS="$CFLAGS $TK_CFLAGS"
     CXXFLAGS="$CXXFLAGS $TK_CFLAGS"
     MOZ_USER_DIR="Mozilla"
     MOZ_FS_LAYOUT=bundle
+    AC_DEFINE(MOZ_SINGLE_PROCESS_APZ)
     ;;
 
 cairo-android)
     AC_DEFINE(MOZ_WIDGET_ANDROID)
     MOZ_WIDGET_TOOLKIT=android
     MOZ_PDF_PRINTING=1
     MOZ_INSTRUMENT_EVENT_LOOP=1
     ;;
@@ -4796,18 +4796,21 @@ fi
 dnl ========================================================
 dnl = Enable the C++ async pan/zoom code instead of the Java version
 dnl ========================================================
 MOZ_ARG_ENABLE_BOOL(android-apz,
 [  --enable-android-apz      Switch to C++ pan/zoom code],
     MOZ_ANDROID_APZ=1,
     MOZ_ANDROID_APZ=)
 if test -n "$MOZ_ANDROID_APZ"; then
-     dnl Do this if defined in confvars.sh
-     AC_DEFINE(MOZ_ANDROID_APZ)
+    dnl Do this if defined in confvars.sh
+    AC_DEFINE(MOZ_ANDROID_APZ)
+    if test -z "$MOZ_B2GDROID"; then
+      AC_DEFINE(MOZ_SINGLE_PROCESS_APZ)
+    fi
 fi
 
 dnl ========================================================
 dnl = Disable WebSMS backend
 dnl ========================================================
 MOZ_ARG_DISABLE_BOOL(websms-backend,
 [  --disable-websms-backend
                            Disable WebSMS backend],
--- a/devtools/client/framework/gDevTools.jsm
+++ b/devtools/client/framework/gDevTools.jsm
@@ -824,17 +824,17 @@ var gDevToolsBrowser = {
     gDevToolsBrowser._trackedBrowserWindows.add(win);
     gDevToolsBrowser._addAllToolsToMenu(win.document);
 
     if (this._isFirebugInstalled()) {
       let broadcaster = win.document.getElementById("devtoolsMenuBroadcaster_DevToolbox");
       broadcaster.removeAttribute("key");
     }
 
-    let tabContainer = win.document.getElementById("tabbrowser-tabs");
+    let tabContainer = win.gBrowser.tabContainer;
     tabContainer.addEventListener("TabSelect", this, false);
     tabContainer.addEventListener("TabOpen", this, false);
     tabContainer.addEventListener("TabClose", this, false);
     tabContainer.addEventListener("TabPinned", this, false);
     tabContainer.addEventListener("TabUnpinned", this, false);
   },
 
   /**
@@ -1019,30 +1019,27 @@ var gDevToolsBrowser = {
           ref = doc.getElementById("appmenu_devtools_separator");
         }
 
         if (ref) {
           amp.insertBefore(elements.appmenuitem, ref);
         }
       }
 
-      let mp = doc.getElementById("menuWebDeveloperPopup");
-      if (mp) {
-        let ref;
+      let ref;
 
-        if (prevDef != null) {
-          let menuitem = doc.getElementById("menuitem_" + prevDef.id);
-          ref = menuitem && menuitem.nextSibling ? menuitem.nextSibling : null;
-        } else {
-          ref = doc.getElementById("menu_devtools_separator");
-        }
+      if (prevDef) {
+        let menuitem = doc.getElementById("menuitem_" + prevDef.id);
+        ref = menuitem && menuitem.nextSibling ? menuitem.nextSibling : null;
+      } else {
+        ref = doc.getElementById("menu_devtools_separator");
+      }
 
-        if (ref) {
-          mp.insertBefore(elements.menuitem, ref);
-        }
+      if (ref) {
+        ref.parentNode.insertBefore(elements.menuitem, ref);
       }
     }
 
     if (toolDefinition.id === "jsdebugger") {
       gDevToolsBrowser.setSlowScriptDebugHandler();
     }
   },
 
@@ -1082,25 +1079,25 @@ var gDevToolsBrowser = {
     let mcs = doc.getElementById("mainCommandSet");
     mcs.appendChild(fragCommands);
 
     this.attachKeybindingsToBrowser(doc, fragKeys);
 
     let mbs = doc.getElementById("mainBroadcasterSet");
     mbs.appendChild(fragBroadcasters);
 
-    let amp = doc.getElementById("appmenu_webDeveloper_popup");
-    if (amp) {
-      let amps = doc.getElementById("appmenu_devtools_separator");
-      amp.insertBefore(fragAppMenuItems, amps);
+    let amps = doc.getElementById("appmenu_devtools_separator");
+    if (amps) {
+      amps.parentNode.insertBefore(fragAppMenuItems, amps);
     }
 
-    let mp = doc.getElementById("menuWebDeveloperPopup");
     let mps = doc.getElementById("menu_devtools_separator");
-    mp.insertBefore(fragMenuItems, mps);
+    if (mps) {
+      mps.parentNode.insertBefore(fragMenuItems, mps);
+    }
   },
 
   /**
    * Add a menu entry for a tool definition
    *
    * @param {string} toolDefinition
    *        Tool definition of the tool to add a menu entry.
    * @param {XULDocument} doc
@@ -1251,17 +1248,17 @@ var gDevToolsBrowser = {
 
     // Destroy toolboxes for closed window
     for (let [target, toolbox] of gDevTools._toolboxes) {
       if (toolbox.frame && toolbox.frame.ownerDocument.defaultView == win) {
         toolbox.destroy();
       }
     }
 
-    let tabContainer = win.document.getElementById("tabbrowser-tabs");
+    let tabContainer = win.gBrowser.tabContainer;
     tabContainer.removeEventListener("TabSelect", this, false);
     tabContainer.removeEventListener("TabOpen", this, false);
     tabContainer.removeEventListener("TabClose", this, false);
     tabContainer.removeEventListener("TabPinned", this, false);
     tabContainer.removeEventListener("TabUnpinned", this, false);
   },
 
   handleEvent: function(event) {
@@ -1270,17 +1267,17 @@ var gDevToolsBrowser = {
       case "TabClose":
       case "TabPinned":
       case "TabUnpinned":
         let open = 0;
         let pinned = 0;
 
         for (let win of this._trackedBrowserWindows) {
           let tabContainer = win.gBrowser.tabContainer;
-          let numPinnedTabs = tabContainer.tabbrowser._numPinnedTabs;
+          let numPinnedTabs = win.gBrowser._numPinnedTabs || 0;
           let numTabs = tabContainer.itemCount - numPinnedTabs;
 
           open += numTabs;
           pinned += numPinnedTabs;
         }
 
         this._tabStats.histOpen.push(open);
         this._tabStats.histPinned.push(pinned);
--- a/devtools/server/actors/webbrowser.js
+++ b/devtools/server/actors/webbrowser.js
@@ -1084,16 +1084,20 @@ TabActor.prototype = {
   },
 
   onListFrames: function BTA_onListFrames(aRequest) {
     let windows = this._docShellsToWindows(this.docShells);
     return { frames: windows };
   },
 
   onListWorkers: function BTA_onListWorkers(aRequest) {
+    if (!this.attached) {
+      return { error: "wrongState" };
+    }
+
     if (this._workerActorList === null) {
       this._workerActorList = new WorkerActorList({
         type: Ci.nsIWorkerDebugger.TYPE_DEDICATED,
         window: this.window
       });
     }
 
     return this._workerActorList.getList().then((actors) => {
--- a/devtools/server/actors/worker.js
+++ b/devtools/server/actors/worker.js
@@ -141,74 +141,82 @@ function WorkerActorList(options) {
   this._onListChanged = null;
   this._mustNotify = false;
   this.onRegister = this.onRegister.bind(this);
   this.onUnregister = this.onUnregister.bind(this);
 }
 
 WorkerActorList.prototype = {
   getList: function () {
+    // Create a set of debuggers.
     let dbgs = new Set();
     let e = wdm.getWorkerDebuggerEnumerator();
     while (e.hasMoreElements()) {
       let dbg = e.getNext().QueryInterface(Ci.nsIWorkerDebugger);
       if (matchWorkerDebugger(dbg, this._options)) {
         dbgs.add(dbg);
       }
     }
 
+    // Delete each actor for which we don't have a debugger.
     for (let [dbg, ] of this._actors) {
       if (!dbgs.has(dbg)) {
         this._actors.delete(dbg);
       }
     }
 
+    // Create an actor for each debugger for which we don't have one.
     for (let dbg of dbgs) {
       if (!this._actors.has(dbg)) {
         this._actors.set(dbg, new WorkerActor(dbg));
       }
     }
 
     let actors = [];
     for (let [, actor] of this._actors) {
       actors.push(actor);
     }
 
-    this._mustNotify = true;
-    this._checkListening();
+    if (!this._mustNotify) {
+      if (this._onListChanged !== null) {
+        wdm.addListener(this);
+      }
+      this._mustNotify = true;
+    }
 
     return Promise.resolve(actors);
   },
 
   get onListChanged() {
     return this._onListChanged;
   },
 
   set onListChanged(onListChanged) {
     if (typeof onListChanged !== "function" && onListChanged !== null) {
       throw new Error("onListChanged must be either a function or null.");
     }
 
+    if (this._mustNotify) {
+      if (this._onListChanged === null && onListChanged !== null) {
+        wdm.addListener(this);
+      }
+      if (this._onListChanged !== null && onListChanged === null) {
+        wdm.removeListener(this);
+      }
+    }
     this._onListChanged = onListChanged;
-    this._checkListening();
-  },
-
-  _checkListening: function () {
-    if (this._onListChanged !== null && this._mustNotify) {
-      wdm.addListener(this);
-    } else {
-      wdm.removeListener(this);
-    }
   },
 
   _notifyListChanged: function () {
-    if (this._mustNotify) {
-      this._onListChanged();
-      this._mustNotify = false;
-    }
+     this._onListChanged();
+
+     if (this._onListChanged !== null) {
+       wdm.removeListener(this);
+     }
+     this._mustNotify = false;
   },
 
   onRegister: function (dbg) {
     if (matchWorkerDebugger(dbg, this._options)) {
       this._notifyListChanged();
     }
   },
 
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -7653,19 +7653,21 @@ nsContentUtils::GetButtonsFlagForButton(
   }
 }
 
 LayoutDeviceIntPoint
 nsContentUtils::ToWidgetPoint(const CSSPoint& aPoint,
                               const nsPoint& aOffset,
                               nsPresContext* aPresContext)
 {
-  return LayoutDeviceIntPoint::FromAppUnitsRounded(
-    CSSPoint::ToAppUnits(aPoint) + aOffset,
-    aPresContext->AppUnitsPerDevPixel());
+  nsPoint point = CSSPoint::ToAppUnits(aPoint) + aOffset;
+#if defined(MOZ_SINGLE_PROCESS_APZ)
+  point = point.ApplyResolution(aPresContext->PresShell()->GetCumulativeScaleResolution());
+#endif
+  return LayoutDeviceIntPoint::FromAppUnitsRounded(point, aPresContext->AppUnitsPerDevPixel());
 }
 
 nsView*
 nsContentUtils::GetViewToDispatchEvent(nsPresContext* presContext,
                                        nsIPresShell** presShell)
 {
   if (presContext && presShell) {
     *presShell = presContext->PresShell();
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -220,16 +220,17 @@
 #include "mozilla/dom/PopupBlockedEvent.h"
 #include "mozilla/dom/PrimitiveConversions.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "nsITabChild.h"
 #include "mozilla/dom/MediaQueryList.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/NavigatorBinding.h"
 #include "mozilla/dom/ImageBitmap.h"
+#include "mozilla/dom/ServiceWorkerRegistration.h"
 #ifdef HAVE_SIDEBAR
 #include "mozilla/dom/ExternalBinding.h"
 #endif
 
 #ifdef MOZ_WEBSPEECH
 #include "mozilla/dom/SpeechSynthesis.h"
 #endif
 
@@ -1571,16 +1572,18 @@ nsGlobalWindow::CleanUp()
   mAudioContexts.Clear();
 
   if (mIdleTimer) {
     mIdleTimer->Cancel();
     mIdleTimer = nullptr;
   }
 
   DisableTimeChangeNotifications();
+
+  mServiceWorkerRegistrationTable.Clear();
 }
 
 void
 nsGlobalWindow::ClearControllers()
 {
   if (mControllers) {
     uint32_t count;
     mControllers->GetControllerCount(&count);
@@ -1783,16 +1786,18 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mArguments)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDialogArguments)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReturnValue)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNavigator)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance)
 
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerRegistrationTable)
+
 #ifdef MOZ_WEBSPEECH
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSpeechSynthesis)
 #endif
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOuterWindow)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
 
@@ -1854,16 +1859,18 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mArguments)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mDialogArguments)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mReturnValue)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mNavigator)
 
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance)
 
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mServiceWorkerRegistrationTable)
+
 #ifdef MOZ_WEBSPEECH
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mSpeechSynthesis)
 #endif
 
   if (tmp->mOuterWindow) {
     static_cast<nsGlobalWindow*>(tmp->mOuterWindow.get())->MaybeClearInnerWindow(tmp);
     NS_IMPL_CYCLE_COLLECTION_UNLINK(mOuterWindow)
   }
@@ -10227,16 +10234,34 @@ nsGlobalWindow::GetCaches(ErrorResult& a
                                                      storageBlocked,
                                                      forceTrustedOrigin, aRv);
   }
 
   RefPtr<CacheStorage> ref = mCacheStorage;
   return ref.forget();
 }
 
+already_AddRefed<ServiceWorkerRegistrationMainThread>
+nsPIDOMWindow::GetServiceWorkerRegistration(const nsAString& aScope)
+{
+  RefPtr<ServiceWorkerRegistrationMainThread> registration;
+  if (!mServiceWorkerRegistrationTable.Get(aScope,
+                                           getter_AddRefs(registration))) {
+    registration = new ServiceWorkerRegistrationMainThread(this, aScope);
+    mServiceWorkerRegistrationTable.Put(aScope, registration);
+  }
+  return registration.forget();
+}
+
+void
+nsPIDOMWindow::InvalidateServiceWorkerRegistration(const nsAString& aScope)
+{
+  mServiceWorkerRegistrationTable.Remove(aScope);
+}
+
 void
 nsGlobalWindow::FireOfflineStatusEventIfChanged()
 {
   if (!IsCurrentInnerWindow())
     return;
 
   bool isOffline = NS_IsOffline() || NS_IsAppOffline(GetPrincipal());
 
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -10,16 +10,17 @@
 
 #include "nsIDOMWindow.h"
 
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsTArray.h"
 #include "mozilla/dom/EventTarget.h"
 #include "js/TypeDecls.h"
+#include "nsRefPtrHashtable.h"
 
 #define DOM_WINDOW_DESTROYED_TOPIC "dom-window-destroyed"
 #define DOM_WINDOW_FROZEN_TOPIC "dom-window-frozen"
 #define DOM_WINDOW_THAWED_TOPIC "dom-window-thawed"
 
 class nsIArray;
 class nsIContent;
 class nsICSSDeclaration;
@@ -32,16 +33,17 @@ class nsPerformance;
 class nsPIWindowRoot;
 class nsXBLPrototypeHandler;
 struct nsTimeout;
 
 namespace mozilla {
 namespace dom {
 class AudioContext;
 class Element;
+class ServiceWorkerRegistrationMainThread;
 } // namespace dom
 namespace gfx {
 class VRHMDInfo;
 } // namespace gfx
 } // namespace mozilla
 
 // Popup control state enum. The values in this enum must go from most
 // permissive to least permissive so that it's safe to push state in
@@ -213,16 +215,20 @@ public:
   }
 
   bool GetServiceWorkersTestingEnabled()
   {
     MOZ_ASSERT(IsOuterWindow());
     return mServiceWorkersTestingEnabled;
   }
 
+  already_AddRefed<mozilla::dom::ServiceWorkerRegistrationMainThread>
+    GetServiceWorkerRegistration(const nsAString& aScope);
+  void InvalidateServiceWorkerRegistration(const nsAString& aScope);
+
 protected:
   // Lazily instantiate an about:blank document if necessary, and if
   // we have what it takes to do so.
   void MaybeCreateDoc();
 
   float GetAudioGlobalVolumeInternal(float aVolume);
   void RefreshMediaElements();
 
@@ -850,16 +856,21 @@ protected:
   // This reference is used by the subclass nsGlobalWindow, and cleared in it's
   // DetachFromDocShell() method. This method is called by nsDocShell::Destroy(),
   // which is called before the nsDocShell is destroyed.
   nsIDocShell* MOZ_NON_OWNING_REF mDocShell;  // Weak Reference
 
   // mPerformance is only used on inner windows.
   RefPtr<nsPerformance>       mPerformance;
 
+  typedef nsRefPtrHashtable<nsStringHashKey,
+                            mozilla::dom::ServiceWorkerRegistrationMainThread>
+          ServiceWorkerRegistrationTable;
+  ServiceWorkerRegistrationTable mServiceWorkerRegistrationTable;
+
   uint32_t               mModalStateDepth;
 
   // These variables are only used on inner windows.
   nsTimeout             *mRunningTimeout;
 
   uint32_t               mMutationBits;
 
   bool                   mIsDocumentLoaded;
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/base/nsScriptLoader.cpp
@@ -964,16 +964,21 @@ nsScriptLoader::ProcessRequest(nsScriptL
     mCurrentParserInsertedScript = aRequest->mElement;
   }
 
   FireScriptAvailable(NS_OK, aRequest);
 
   // The window may have gone away by this point, in which case there's no point
   // in trying to run the script.
   nsCOMPtr<nsIDocument> master = mDocument->MasterDocument();
+  {
+    // Try to perform a microtask checkpoint
+    nsAutoMicroTask mt;
+  }
+
   nsPIDOMWindow *pwin = master->GetInnerWindow();
   bool runScript = !!pwin;
   if (runScript) {
     nsContentUtils::DispatchTrustedEvent(scriptElem->OwnerDoc(),
                                          scriptElem,
                                          NS_LITERAL_STRING("beforescriptexecute"),
                                          true, true, &runScript);
   }
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -672,16 +672,17 @@ skip-if = os == "mac" # fails intermitte
 [test_bug737612.html]
 [test_bug738108.html]
 [test_bug744830.html]
 [test_bug749367.html]
 [test_bug753278.html]
 [test_bug761120.html]
 [test_bug782342.html]
 [test_bug787778.html]
+[test_bug789315.html]
 [test_bug789856.html]
 [test_bug804395.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #bug 901343, specialpowers.wrap issue createsystemxhr
 [test_bug809003.html]
 [test_bug810494.html]
 [test_bug811701.html]
 [test_bug811701.xhtml]
 [test_bug813919.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_bug789315.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=789315
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 789315</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+    <script type="text/javascript">
+      (function() {
+        const observerConfig = {
+          childList: true,
+        };
+
+        var observer = new MutationObserver(onMutations);
+        observer.observe(document.head, observerConfig);
+
+        function onMutations(mutations) {
+          for (var i in mutations) {
+            var mutation = mutations[i];
+            for (var j in mutation.addedNodes) {
+              var addedNode = mutation.addedNodes[j];
+              addedNode.mutationObserverHasNotified = true;
+            }
+          }
+        }
+
+      })();
+    </script>
+
+    <link id="testnode" rel="localization" href="dummy"></link>
+
+    <script type="text/javascript">
+      var testNode = document.getElementById("testnode");
+      ok(testNode.mutationObserverHasNotified);
+    </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=789315">Mozilla Bug 789315</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/dom/events/Event.cpp
+++ b/dom/events/Event.cpp
@@ -922,29 +922,32 @@ Event::GetScreenCoords(nsPresContext* aP
         aEvent->mClass != eTouchEventClass &&
         aEvent->mClass != eDragEventClass &&
         aEvent->mClass != eSimpleGestureEventClass)) {
     return CSSIntPoint(0, 0);
   }
 
   // Doing a straight conversion from LayoutDeviceIntPoint to CSSIntPoint
   // seem incorrect, but it is needed to maintain legacy functionality.
-  if (!aPresContext) {
+  WidgetGUIEvent* guiEvent = aEvent->AsGUIEvent();
+  if (!aPresContext || !(guiEvent && guiEvent->widget)) {
     return CSSIntPoint(aPoint.x, aPoint.y);
   }
 
-  LayoutDeviceIntPoint offset = aPoint;
+  nsPoint pt =
+    LayoutDevicePixel::ToAppUnits(aPoint, aPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom());
 
-  WidgetGUIEvent* guiEvent = aEvent->AsGUIEvent();
-  if (guiEvent && guiEvent->widget) {
-    offset += guiEvent->widget->WidgetToScreenOffset();
+#if defined(MOZ_SINGLE_PROCESS_APZ)
+  if (aPresContext->PresShell()) {
+    pt = pt.RemoveResolution(aPresContext->PresShell()->GetCumulativeScaleResolution());
   }
+#endif
 
-  nsPoint pt =
-    LayoutDevicePixel::ToAppUnits(offset, aPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom());
+  pt += LayoutDevicePixel::ToAppUnits(guiEvent->widget->WidgetToScreenOffset(),
+                                      aPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom());
 
   return CSSPixel::FromAppUnitsRounded(pt);
 }
 
 // static
 CSSIntPoint
 Event::GetPageCoords(nsPresContext* aPresContext,
                      WidgetEvent* aEvent,
--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
+++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
@@ -16,21 +16,29 @@ interface nsIURI;
 interface nsIServiceWorkerUnregisterCallback : nsISupports
 {
   // aState is true if the unregistration succeded.
   // It's false if this ServiceWorkerRegistration doesn't exist.
   void unregisterSucceeded(in bool aState);
   void unregisterFailed();
 };
 
-[scriptable, builtinclass, uuid(1a1e71dd-0f78-4e2e-a2db-a946fe02cddf)]
+interface nsIWorkerDebugger;
+
+[scriptable, builtinclass, uuid(76e357ed-208d-4e4c-9165-1c4059707879)]
 interface nsIServiceWorkerInfo : nsISupports
 {
   readonly attribute DOMString scriptSpec;
   readonly attribute DOMString cacheName;
+
+  readonly attribute nsIWorkerDebugger debugger;
+
+  void attachDebugger();
+
+  void detachDebugger();
 };
 
 [scriptable, uuid(87e63548-d440-4b8a-b158-65ad1de0211E)]
 interface nsIServiceWorkerRegistrationInfoListener : nsISupports
 {
   void onChange();
 };
 
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -2802,32 +2802,30 @@ ContentChild::RecvOnAppThemeChanged()
     nsCOMPtr<nsIObserverService> os = services::GetObserverService();
     if (os) {
         os->NotifyObservers(nullptr, "app-theme-changed", nullptr);
     }
     return true;
 }
 
 bool
-ContentChild::RecvStartProfiler(const uint32_t& aEntries,
-                                const double& aInterval,
-                                nsTArray<nsCString>&& aFeatures,
-                                nsTArray<nsCString>&& aThreadNameFilters)
+ContentChild::RecvStartProfiler(const ProfilerInitParams& params)
 {
     nsTArray<const char*> featureArray;
-    for (size_t i = 0; i < aFeatures.Length(); ++i) {
-        featureArray.AppendElement(aFeatures[i].get());
+    for (size_t i = 0; i < params.features().Length(); ++i) {
+        featureArray.AppendElement(params.features()[i].get());
     }
 
     nsTArray<const char*> threadNameFilterArray;
-    for (size_t i = 0; i < aThreadNameFilters.Length(); ++i) {
-        threadNameFilterArray.AppendElement(aThreadNameFilters[i].get());
+    for (size_t i = 0; i < params.threadFilters().Length(); ++i) {
+        threadNameFilterArray.AppendElement(params.threadFilters()[i].get());
     }
 
-    profiler_start(aEntries, aInterval, featureArray.Elements(), featureArray.Length(),
+    profiler_start(params.entries(), params.interval(),
+                   featureArray.Elements(), featureArray.Length(),
                    threadNameFilterArray.Elements(), threadNameFilterArray.Length());
 
     return true;
 }
 
 bool
 ContentChild::RecvStopProfiler()
 {
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -419,20 +419,17 @@ public:
     virtual bool RecvOnAppThemeChanged() override;
 
     virtual bool RecvAssociatePluginId(const uint32_t& aPluginId,
                                        const base::ProcessId& aProcessId) override;
     virtual bool RecvLoadPluginResult(const uint32_t& aPluginId,
                                       const bool& aResult) override;
     virtual bool RecvUpdateWindow(const uintptr_t& aChildId) override;
 
-    virtual bool RecvStartProfiler(const uint32_t& aEntries,
-                                   const double& aInterval,
-                                   nsTArray<nsCString>&& aFeatures,
-                                   nsTArray<nsCString>&& aThreadNameFilters) override;
+    virtual bool RecvStartProfiler(const ProfilerInitParams& params) override;
     virtual bool RecvPauseProfiler(const bool& aPause) override;
     virtual bool RecvStopProfiler() override;
     virtual bool RecvGatherProfile() override;
     virtual bool RecvDomainSetChanged(const uint32_t& aSetType, const uint32_t& aChangeType,
                                       const OptionalURIParams& aDomain) override;
     virtual bool RecvShutdown() override;
 
     virtual bool
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -1552,16 +1552,31 @@ ContentParent::Init()
 
 #ifdef ACCESSIBILITY
     // If accessibility is running in chrome process then start it in content
     // process.
     if (nsIPresShell::IsAccessibilityActive()) {
         Unused << SendActivateA11y();
     }
 #endif
+
+#ifdef MOZ_ENABLE_PROFILER_SPS
+    nsCOMPtr<nsIProfiler> profiler(do_GetService("@mozilla.org/tools/profiler;1"));
+    bool profilerActive = false;
+    DebugOnly<nsresult> rv = profiler->IsActive(&profilerActive);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+    if (profilerActive) {
+        nsCOMPtr<nsIProfilerStartParams> currentProfilerParams;
+        rv = profiler->GetStartParams(getter_AddRefs(currentProfilerParams));
+        MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+        StartProfiler(currentProfilerParams);
+    }
+#endif
 }
 
 void
 ContentParent::ForwardKnownInfo()
 {
     MOZ_ASSERT(mMetamorphosed);
     if (!mMetamorphosed) {
         return;
@@ -3270,23 +3285,17 @@ ContentParent::Observe(nsISupports* aSub
     }
 #endif
     else if (!strcmp(aTopic, "app-theme-changed")) {
         Unused << SendOnAppThemeChanged();
     }
 #ifdef MOZ_ENABLE_PROFILER_SPS
     else if (!strcmp(aTopic, "profiler-started")) {
         nsCOMPtr<nsIProfilerStartParams> params(do_QueryInterface(aSubject));
-        uint32_t entries;
-        double interval;
-        params->GetEntries(&entries);
-        params->GetInterval(&interval);
-        const nsTArray<nsCString>& features = params->GetFeatures();
-        const nsTArray<nsCString>& threadFilterNames = params->GetThreadFilterNames();
-        Unused << SendStartProfiler(entries, interval, features, threadFilterNames);
+        StartProfiler(params);
     }
     else if (!strcmp(aTopic, "profiler-stopped")) {
         Unused << SendStopProfiler();
     }
     else if (!strcmp(aTopic, "profiler-paused")) {
         Unused << SendPauseProfiler(true);
     }
     else if (!strcmp(aTopic, "profiler-resumed")) {
@@ -5704,16 +5713,34 @@ ContentParent::RecvGetAndroidSystemInfo(
   nsSystemInfo::GetAndroidSystemInfo(aInfo);
   return true;
 #else
   MOZ_CRASH("wrong platform!");
   return false;
 #endif
 }
 
+void
+ContentParent::StartProfiler(nsIProfilerStartParams* aParams)
+{
+    if (NS_WARN_IF(!aParams)) {
+        return;
+    }
+
+    ProfilerInitParams ipcParams;
+
+    ipcParams.enabled() = true;
+    aParams->GetEntries(&ipcParams.entries());
+    aParams->GetInterval(&ipcParams.interval());
+    ipcParams.features() = aParams->GetFeatures();
+    ipcParams.threadFilters() = aParams->GetThreadFilterNames();
+
+    Unused << SendStartProfiler(ipcParams);
+}
+
 } // namespace dom
 } // namespace mozilla
 
 NS_IMPL_ISUPPORTS(ParentIdleListener, nsIObserver)
 
 NS_IMETHODIMP
 ParentIdleListener::Observe(nsISupports*, const char* aTopic, const char16_t* aData) {
     mozilla::Unused << mParent->SendNotifyIdleObserver(mObserver,
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -930,16 +930,17 @@ private:
                                       const uint32_t& aDropEffect) override;
 
     virtual bool RecvGetBrowserConfiguration(const nsCString& aURI, BrowserConfiguration* aConfig) override;
 
     virtual bool RecvGamepadListenerAdded() override;
     virtual bool RecvGamepadListenerRemoved() override;
     virtual bool RecvProfile(const nsCString& aProfile) override;
     virtual bool RecvGetGraphicsDeviceInitData(DeviceInitData* aOut) override;
+    void StartProfiler(nsIProfilerStartParams* aParams);
 
     virtual bool RecvGetDeviceStorageLocation(const nsString& aType,
                                               nsString* aPath) override;
 
     virtual bool RecvGetAndroidSystemInfo(AndroidSystemInfo* aInfo) override;
 
     // If you add strong pointers to cycle collected objects here, be sure to
     // release these objects in ShutDownProcess.  See the comment there for more
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -60,16 +60,17 @@ include InputStreamParams;
 include PTabContext;
 include URIParams;
 include PluginTypes;
 include ProtocolTypes;
 include PBackgroundSharedTypes;
 include PContentPermission;
 include BrowserConfiguration;
 include GraphicsMessages;
+include ProfilerTypes;
 
 // Workaround to prevent error if PContentChild.cpp & PContentBridgeParent.cpp
 // are put into different UnifiedProtocolsXX.cpp files.
 // XXX Remove this once bug 1069073 is fixed
 include "mozilla/dom/PContentBridgeParent.h";
 
 using GeoPosition from "nsGeoPositionIPCSerialiser.h";
 
@@ -646,18 +647,17 @@ child:
      * PluginModuleContentParent that the PluginModuleChromeParent's async
      * init has completed.
      */
     async LoadPluginResult(uint32_t aPluginId, bool aResult);
 
     /**
      * Control the Gecko Profiler in the child process.
      */
-    async StartProfiler(uint32_t aEntries, double aInterval, nsCString[] aFeatures,
-                        nsCString[] aThreadNameFilters);
+    async StartProfiler(ProfilerInitParams params);
     async StopProfiler();
     async PauseProfiler(bool aPause);
 
     async GatherProfile();
 
     InvokeDragSession(IPCDataTransfer[] transfers, uint32_t action);
 
     EndDragSession(bool aDoneDrag, bool aUserCancelled);
--- a/dom/media/eme/CDMProxy.cpp
+++ b/dom/media/eme/CDMProxy.cpp
@@ -13,16 +13,17 @@
 #include "nsServiceManagerUtils.h"
 #include "MainThreadUtils.h"
 #include "mozilla/EMEUtils.h"
 #include "nsIConsoleService.h"
 #include "prenv.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/CDMCallbackProxy.h"
 #include "MediaData.h"
+#include "nsPrintfCString.h"
 
 namespace mozilla {
 
 CDMProxy::CDMProxy(dom::MediaKeys* aKeys, const nsAString& aKeySystem)
   : mKeys(aKeys)
   , mKeySystem(aKeySystem)
   , mCDM(nullptr)
   , mDecryptionJobCount(0)
@@ -36,16 +37,17 @@ CDMProxy::~CDMProxy()
 {
   MOZ_COUNT_DTOR(CDMProxy);
 }
 
 void
 CDMProxy::Init(PromiseId aPromiseId,
                const nsAString& aOrigin,
                const nsAString& aTopLevelOrigin,
+               const nsAString& aGMPName,
                bool aInPrivateBrowsing)
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
 
   EME_LOG("CDMProxy::Init (%s, %s) %s",
           NS_ConvertUTF16toUTF8(aOrigin).get(),
           NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
@@ -63,20 +65,27 @@ CDMProxy::Init(PromiseId aPromiseId,
     mps->GetThread(getter_AddRefs(mGMPThread));
     if (!mGMPThread) {
       RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
                     NS_LITERAL_CSTRING("Couldn't get GMP thread CDMProxy::Init"));
       return;
     }
   }
 
+  if (aGMPName.IsEmpty()) {
+    RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+      nsPrintfCString("Unknown GMP for keysystem '%s'", NS_ConvertUTF16toUTF8(mKeySystem).get()));
+    return;
+  }
+
   nsAutoPtr<InitData> data(new InitData());
   data->mPromiseId = aPromiseId;
   data->mOrigin = aOrigin;
   data->mTopLevelOrigin = aTopLevelOrigin;
+  data->mGMPName = aGMPName;
   data->mInPrivateBrowsing = aInPrivateBrowsing;
   nsCOMPtr<nsIRunnable> task(
     NS_NewRunnableMethodWithArg<nsAutoPtr<InitData>>(this,
                                                      &CDMProxy::gmp_Init,
                                                      Move(data)));
   mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
 }
 
@@ -171,16 +180,17 @@ CDMProxy::gmp_Init(nsAutoPtr<InitData>&&
 
   // Make a copy before we transfer ownership of aData to the
   // gmp_InitGetGMPDecryptorCallback.
   InitData data(*aData);
   UniquePtr<GetNodeIdCallback> callback(
     new gmp_InitGetGMPDecryptorCallback(this, Move(aData)));
   nsresult rv = mps->GetNodeId(data.mOrigin,
                                data.mTopLevelOrigin,
+                               data.mGMPName,
                                data.mInPrivateBrowsing,
                                Move(callback));
   if (NS_FAILED(rv)) {
     RejectPromise(data.mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
                   NS_LITERAL_CSTRING("Call to GetNodeId() failed early"));
   }
 }
 
--- a/dom/media/eme/CDMProxy.h
+++ b/dom/media/eme/CDMProxy.h
@@ -52,16 +52,17 @@ public:
   CDMProxy(dom::MediaKeys* aKeys, const nsAString& aKeySystem);
 
   // Main thread only.
   // Loads the CDM corresponding to mKeySystem.
   // Calls MediaKeys::OnCDMCreated() when the CDM is created.
   void Init(PromiseId aPromiseId,
             const nsAString& aOrigin,
             const nsAString& aTopLevelOrigin,
+            const nsAString& aGMPName,
             bool aInPrivateBrowsing);
 
   // Main thread only.
   // Uses the CDM to create a key session.
   // Calls MediaKeys::OnSessionActivated() when session is created.
   // Assumes ownership of (Move()s) aInitData's contents.
   void CreateSession(uint32_t aCreateSessionToken,
                      dom::SessionType aSessionType,
@@ -181,16 +182,17 @@ public:
 private:
   friend class gmp_InitDoneCallback;
   friend class gmp_InitGetGMPDecryptorCallback;
 
   struct InitData {
     uint32_t mPromiseId;
     nsAutoString mOrigin;
     nsAutoString mTopLevelOrigin;
+    nsString mGMPName;
     bool mInPrivateBrowsing;
   };
 
   // GMP thread only.
   void gmp_Init(nsAutoPtr<InitData>&& aData);
   void gmp_InitDone(GMPDecryptorProxy* aCDM, nsAutoPtr<InitData>&& aData);
   void gmp_InitGetGMPDecryptor(nsresult aResult,
                                const nsACString& aNodeId,
--- a/dom/media/eme/EMEUtils.cpp
+++ b/dom/media/eme/EMEUtils.cpp
@@ -136,9 +136,22 @@ CopyArrayBufferViewOrArrayBufferData(con
   ArrayData data = GetArrayBufferViewOrArrayBufferData(aBufferOrView);
   aOutData.Clear();
   if (!data.IsValid()) {
     return;
   }
   aOutData.AppendElements(data.mData, data.mLength);
 }
 
+nsString
+KeySystemToGMPName(const nsAString& aKeySystem)
+{
+  if (aKeySystem.EqualsLiteral("com.adobe.primetime")) {
+    return NS_LITERAL_STRING("gmp-eme-adobe");
+  }
+  if (aKeySystem.EqualsLiteral("org.w3.clearkey")) {
+    return NS_LITERAL_STRING("gmp-clearkey");
+  }
+  MOZ_ASSERT(false, "We should only call this for known GMPs");
+  return EmptyString();
+}
+
 } // namespace mozilla
--- a/dom/media/eme/EMEUtils.h
+++ b/dom/media/eme/EMEUtils.h
@@ -99,11 +99,14 @@ struct ArrayData {
 // while the ArrayData is live, as then all bets about the data not changing
 // are off! No calls into JS, no calls into JS-implemented WebIDL or XPIDL,
 // nothing. Beware!
 //
 // Only call this on a properly initialized ArrayBufferViewOrArrayBuffer.
 ArrayData
 GetArrayBufferViewOrArrayBufferData(const dom::ArrayBufferViewOrArrayBuffer& aBufferOrView);
 
+nsString
+KeySystemToGMPName(const nsAString& aKeySystem);
+
 } // namespace mozilla
 
 #endif // EME_LOG_H_
--- a/dom/media/eme/MediaKeySystemAccess.cpp
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -438,16 +438,19 @@ GetSupportedConfig(mozIGeckoMediaPluginS
 {
   MediaKeySystemConfiguration config;
   config.mLabel = aCandidate.mLabel;
   if (aCandidate.mInitDataTypes.WasPassed()) {
     nsTArray<nsString> initDataTypes;
     for (const nsString& candidate : aCandidate.mInitDataTypes.Value()) {
       if (candidate.EqualsLiteral("cenc")) {
         initDataTypes.AppendElement(candidate);
+      } else if (candidate.EqualsLiteral("keyids") &&
+                 aKeySystem.EqualsLiteral("org.w3.clearkey")) {
+        initDataTypes.AppendElement(candidate);
       }
     }
     if (initDataTypes.IsEmpty()) {
       return false;
     }
     config.mInitDataTypes.Construct();
     config.mInitDataTypes.Value().Assign(initDataTypes);
   }
--- a/dom/media/eme/MediaKeys.cpp
+++ b/dom/media/eme/MediaKeys.cpp
@@ -363,16 +363,17 @@ MediaKeys::Init(ErrorResult& aRv)
   // here, and hold a self-reference until that promise is resolved or
   // rejected.
   MOZ_ASSERT(!mCreatePromiseId, "Should only be created once!");
   mCreatePromiseId = StorePromise(promise);
   AddRef();
   mProxy->Init(mCreatePromiseId,
                origin,
                topLevelOrigin,
+               KeySystemToGMPName(mKeySystem),
                inPrivateBrowsing);
 
   return promise.forget();
 }
 
 void
 MediaKeys::OnCDMCreated(PromiseId aId, const nsACString& aNodeId, const uint32_t aPluginId)
 {
--- a/dom/media/gmp/GMPParent.cpp
+++ b/dom/media/gmp/GMPParent.cpp
@@ -1063,13 +1063,19 @@ GMPParent::Bridge(GMPServiceParent* aGMP
 {
   if (NS_FAILED(PGMPContent::Bridge(aGMPServiceParent, this))) {
     return false;
   }
   ++mGMPContentChildCount;
   return true;
 }
 
+nsString
+GMPParent::GetPluginBaseName() const
+{
+  return NS_LITERAL_STRING("gmp-") + mName;
+}
+
 } // namespace gmp
 } // namespace mozilla
 
 #undef LOG
 #undef LOGD
--- a/dom/media/gmp/GMPParent.h
+++ b/dom/media/gmp/GMPParent.h
@@ -119,16 +119,17 @@ public:
 
   // Specifies that a GMP can only work with the specified NodeIds.
   void SetNodeId(const nsACString& aNodeId);
   const nsACString& GetNodeId() const { return mNodeId; }
 
   const nsCString& GetDisplayName() const;
   const nsCString& GetVersion() const;
   const uint32_t GetPluginId() const;
+  nsString GetPluginBaseName() const;
 
   // Returns true if a plugin can be or is being used across multiple NodeIds.
   bool CanBeSharedCrossNodeIds() const;
 
   // A GMP can be used from a NodeId if it's already been set to work with
   // that NodeId, or if it's not been set to work with any NodeId and has
   // not yet been loaded (i.e. it's not shared across NodeIds).
   bool CanBeUsedFrom(const nsACString& aNodeId) const;
--- a/dom/media/gmp/GMPServiceChild.cpp
+++ b/dom/media/gmp/GMPServiceChild.cpp
@@ -132,56 +132,61 @@ GeckoMediaPluginServiceChild::GetPluginV
   aOutVersion = version;
   return ok ? NS_OK : NS_ERROR_FAILURE;
 }
 
 class GetNodeIdDone : public GetServiceChildCallback
 {
 public:
   GetNodeIdDone(const nsAString& aOrigin, const nsAString& aTopLevelOrigin,
+                const nsAString& aGMPName,
                 bool aInPrivateBrowsing, UniquePtr<GetNodeIdCallback>&& aCallback)
     : mOrigin(aOrigin),
       mTopLevelOrigin(aTopLevelOrigin),
+      mGMPName(aGMPName),
       mInPrivateBrowsing(aInPrivateBrowsing),
       mCallback(Move(aCallback))
   {
   }
 
   virtual void Done(GMPServiceChild* aGMPServiceChild)
   {
     if (!aGMPServiceChild) {
       mCallback->Done(NS_ERROR_FAILURE, EmptyCString());
       return;
     }
 
     nsCString outId;
     if (!aGMPServiceChild->SendGetGMPNodeId(mOrigin, mTopLevelOrigin,
+                                            mGMPName,
                                             mInPrivateBrowsing, &outId)) {
       mCallback->Done(NS_ERROR_FAILURE, EmptyCString());
       return;
     }
 
     mCallback->Done(NS_OK, outId);
   }
 
 private:
   nsString mOrigin;
   nsString mTopLevelOrigin;
+  nsString mGMPName;
   bool mInPrivateBrowsing;
   UniquePtr<GetNodeIdCallback> mCallback;
 };
 
 NS_IMETHODIMP
 GeckoMediaPluginServiceChild::GetNodeId(const nsAString& aOrigin,
                                         const nsAString& aTopLevelOrigin,
+                                        const nsAString& aGMPName,
                                         bool aInPrivateBrowsing,
                                         UniquePtr<GetNodeIdCallback>&& aCallback)
 {
   UniquePtr<GetServiceChildCallback> callback(
-    new GetNodeIdDone(aOrigin, aTopLevelOrigin, aInPrivateBrowsing, Move(aCallback)));
+    new GetNodeIdDone(aOrigin, aTopLevelOrigin, aGMPName, aInPrivateBrowsing, Move(aCallback)));
   GetServiceChild(Move(callback));
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GeckoMediaPluginServiceChild::UpdateTrialCreateState(const nsAString& aKeySystem,
                                                      uint32_t aState)
 {
--- a/dom/media/gmp/GMPServiceChild.h
+++ b/dom/media/gmp/GMPServiceChild.h
@@ -42,16 +42,17 @@ public:
   static already_AddRefed<GeckoMediaPluginServiceChild> GetSingleton();
 
   NS_IMETHOD GetPluginVersionForAPI(const nsACString& aAPI,
                                     nsTArray<nsCString>* aTags,
                                     bool* aHasPlugin,
                                     nsACString& aOutVersion) override;
   NS_IMETHOD GetNodeId(const nsAString& aOrigin,
                        const nsAString& aTopLevelOrigin,
+                       const nsAString& aGMPName,
                        bool aInPrivateBrowsingMode,
                        UniquePtr<GetNodeIdCallback>&& aCallback) override;
   NS_IMETHOD UpdateTrialCreateState(const nsAString& aKeySystem,
                                     uint32_t aState) override;
 
   NS_DECL_NSIOBSERVER
 
   void SetServiceChild(UniquePtr<GMPServiceChild>&& aServiceChild);
--- a/dom/media/gmp/GMPServiceParent.cpp
+++ b/dom/media/gmp/GMPServiceParent.cpp
@@ -158,58 +158,79 @@ CloneAndAppend(nsIFile* aFile, const nsA
   rv = f->Append(aDir);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return nullptr;
   }
   return f.forget();
 }
 
 static void
-MoveAndOverwrite(nsIFile* aOldStorageDir,
-                 nsIFile* aNewStorageDir,
+MoveAndOverwrite(nsIFile* aOldParentDir,
+                 nsIFile* aNewParentDir,
                  const nsAString& aSubDir)
 {
   nsresult rv;
 
-  nsCOMPtr<nsIFile> srcDir(CloneAndAppend(aOldStorageDir, aSubDir));
+  nsCOMPtr<nsIFile> srcDir(CloneAndAppend(aOldParentDir, aSubDir));
   if (NS_WARN_IF(!srcDir)) {
     return;
   }
 
   if (!FileExists(srcDir)) {
     // No sub-directory to be migrated.
     return;
   }
 
-  nsCOMPtr<nsIFile> dstDir(CloneAndAppend(aNewStorageDir, aSubDir));
+  // Ensure destination parent directory exists.
+  rv = aNewParentDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
+  if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  nsCOMPtr<nsIFile> dstDir(CloneAndAppend(aNewParentDir, aSubDir));
   if (FileExists(dstDir)) {
     // We must have migrated before already, and then ran an old version
     // of Gecko again which created storage at the old location. Overwrite
     // the previously migrated storage.
     rv = dstDir->Remove(true);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       // MoveTo will fail.
       return;
     }
   }
 
-  rv = srcDir->MoveTo(aNewStorageDir, EmptyString());
+  rv = srcDir->MoveTo(aNewParentDir, EmptyString());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 }
 
 static void
 MigratePreGecko42StorageDir(nsIFile* aOldStorageDir,
                             nsIFile* aNewStorageDir)
 {
   MoveAndOverwrite(aOldStorageDir, aNewStorageDir, NS_LITERAL_STRING("id"));
   MoveAndOverwrite(aOldStorageDir, aNewStorageDir, NS_LITERAL_STRING("storage"));
 }
 
+static void
+MigratePreGecko45StorageDir(nsIFile* aStorageDirBase)
+{
+  nsCOMPtr<nsIFile> adobeStorageDir(CloneAndAppend(aStorageDirBase, NS_LITERAL_STRING("gmp-eme-adobe")));
+  if (NS_WARN_IF(!adobeStorageDir)) {
+    return;
+  }
+
+  // The base storage dir in pre-45 contained "id" and "storage" subdirs.
+  // We assume all storage in the base storage dir that aren't known to GMP
+  // storage are records for the Adobe GMP.
+  MoveAndOverwrite(aStorageDirBase, adobeStorageDir, NS_LITERAL_STRING("id"));
+  MoveAndOverwrite(aStorageDirBase, adobeStorageDir, NS_LITERAL_STRING("storage"));
+}
+
 static nsresult
 GMPPlatformString(nsAString& aOutPlatform)
 {
   // Append the OS and arch so that we don't reuse the storage if the profile is
   // copied or used under a different bit-ness, or copied to another platform.
   nsCOMPtr<nsIXULRuntime> runtime = do_GetService("@mozilla.org/xre/runtime;1");
   if (!runtime) {
     return NS_ERROR_FAILURE;
@@ -286,21 +307,29 @@ GeckoMediaPluginServiceParent::InitStora
     return rv;
   }
 
   rv = mStorageBaseDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
   if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)) {
     return rv;
   }
 
-  // Prior to 42, GMP storage was stored in $profile/gmp/. After 42, it's
-  // stored in $profile/gmp/$platform/. So we must migrate any old records
+  // Prior to 42, GMP storage was stored in $profileDir/gmp/. After 42, it's
+  // stored in $profileDir/gmp/$platform/. So we must migrate any old records
   // from the old location to the new location, for forwards compatibility.
   MigratePreGecko42StorageDir(gmpDirWithoutPlatform, mStorageBaseDir);
 
+  // Prior to 45, GMP storage was not separated by plugin. In 45 and after,
+  // it's stored in $profile/gmp/$platform/$gmpName. So we must migrate old
+  // records from the old location to the new location, for forwards
+  // compatibility. We assume all directories in the base storage dir that
+  // aren't known to GMP storage are records for the Adobe GMP, since it
+  // was first.
+  MigratePreGecko45StorageDir(mStorageBaseDir);
+
   return GeckoMediaPluginService::Init();
 }
 
 NS_IMETHODIMP
 GeckoMediaPluginServiceParent::Observe(nsISupports* aSubject,
                                        const char* aTopic,
                                        const char16_t* aSomeData)
 {
@@ -727,17 +756,17 @@ GeckoMediaPluginServiceParent::PathRunna
 {
   if (mOperation == ADD) {
     mService->AddOnGMPThread(mPath);
   } else {
     mService->RemoveOnGMPThread(mPath,
                                 mOperation == REMOVE_AND_DELETE_FROM_DISK,
                                 mDefer);
   }
-#ifndef MOZ_WIDGET_GONK // Bug 1214967: disabled on B2G due to inscrutable test failures.
+#ifndef MOZ_WIDGET_GONK // Bug 1214967: disabled on B2G due to inscrutable test failures.
   // For e10s, we must fire a notification so that all ContentParents notify
   // their children to update the codecs that the GMPDecoderModule can use.
   NS_DispatchToMainThread(new NotifyObserversTask("gmp-changed"), NS_DISPATCH_NORMAL);
   // For non-e10s, and for decoding in the chrome process, must update GMP
   // PDM's codecs list directly.
   NS_DispatchToMainThread(NS_NewRunnableFunction([]() -> void {
     GMPDecoderModule::UpdateUsableCodecs();
   }));
@@ -1207,16 +1236,17 @@ GeckoMediaPluginServiceParent::IsPersist
   NS_ENSURE_ARG(aOutAllowed);
   *aOutAllowed = mPersistentStorageAllowed.Get(aNodeId);
   return NS_OK;
 }
 
 nsresult
 GeckoMediaPluginServiceParent::GetNodeId(const nsAString& aOrigin,
                                          const nsAString& aTopLevelOrigin,
+                                         const nsAString& aGMPName,
                                          bool aInPrivateBrowsing,
                                          nsACString& aOutId)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
   LOGD(("%s::%s: (%s, %s), %s", __CLASS__, __FUNCTION__,
        NS_ConvertUTF16toUTF8(aOrigin).get(),
        NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
        (aInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing")));
@@ -1239,58 +1269,70 @@ GeckoMediaPluginServiceParent::GetNodeId
     mPersistentStorageAllowed.Put(salt, false);
     return NS_OK;
   }
 
   const uint32_t hash = AddToHash(HashString(aOrigin),
                                   HashString(aTopLevelOrigin));
 
   if (aInPrivateBrowsing) {
-    // For PB mode, we store the node id, indexed by the origin pair,
-    // so that if the same origin pair is opened in this session, it gets
-    // the same node id.
+    // For PB mode, we store the node id, indexed by the origin pair and GMP name,
+    // so that if the same origin pair is opened for the same GMP in this session,
+    // it gets the same node id.
+    const uint32_t pbHash = AddToHash(HashString(aGMPName), hash);
     nsCString* salt = nullptr;
-    if (!(salt = mTempNodeIds.Get(hash))) {
+    if (!(salt = mTempNodeIds.Get(pbHash))) {
       // No salt stored, generate and temporarily store some for this id.
       nsAutoCString newSalt;
       rv = GenerateRandomPathName(newSalt, NodeIdSaltLength);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
       salt = new nsCString(newSalt);
-      mTempNodeIds.Put(hash, salt);
+      mTempNodeIds.Put(pbHash, salt);
       mPersistentStorageAllowed.Put(*salt, false);
     }
     aOutId = *salt;
     return NS_OK;
   }
 
   // Otherwise, try to see if we've previously generated and stored salt
   // for this origin pair.
-  nsCOMPtr<nsIFile> path; // $profileDir/gmp/
+  nsCOMPtr<nsIFile> path; // $profileDir/gmp/$platform/
   rv = GetStorageDir(getter_AddRefs(path));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  rv = path->Append(aGMPName);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  // $profileDir/gmp/$platform/$gmpName/
+  rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700);
+  if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   rv = path->AppendNative(NS_LITERAL_CSTRING("id"));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  // $profileDir/gmp/id/
+  // $profileDir/gmp/$platform/$gmpName/id/
   rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700);
   if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   nsAutoCString hashStr;
   hashStr.AppendInt((int64_t)hash);
 
-  // $profileDir/gmp/id/$hash
+  // $profileDir/gmp/$platform/$gmpName/id/$hash
   rv = path->AppendNative(hashStr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = path->Create(nsIFile::DIRECTORY_TYPE, 0700);
   if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -1317,31 +1359,31 @@ GeckoMediaPluginServiceParent::GetNodeId
     // No stored salt for this origin. Generate salt, and store it and
     // the origin on disk.
     nsresult rv = GenerateRandomPathName(salt, NodeIdSaltLength);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     MOZ_ASSERT(salt.Length() == NodeIdSaltLength);
 
-    // $profileDir/gmp/id/$hash/salt
+    // $profileDir/gmp/$platform/$gmpName/id/$hash/salt
     rv = WriteToFile(path, NS_LITERAL_CSTRING("salt"), salt);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    // $profileDir/gmp/id/$hash/origin
+    // $profileDir/gmp/$platform/$gmpName/id/$hash/origin
     rv = WriteToFile(path,
                      NS_LITERAL_CSTRING("origin"),
                      NS_ConvertUTF16toUTF8(aOrigin));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
-    // $profileDir/gmp/id/$hash/topLevelOrigin
+    // $profileDir/gmp/$platform/$gmpName/id/$hash/topLevelOrigin
     rv = WriteToFile(path,
                      NS_LITERAL_CSTRING("topLevelOrigin"),
                      NS_ConvertUTF16toUTF8(aTopLevelOrigin));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
   } else {
@@ -1355,21 +1397,22 @@ GeckoMediaPluginServiceParent::GetNodeId
   mPersistentStorageAllowed.Put(salt, true);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 GeckoMediaPluginServiceParent::GetNodeId(const nsAString& aOrigin,
                                          const nsAString& aTopLevelOrigin,
+                                         const nsAString& aGMPName,
                                          bool aInPrivateBrowsing,
                                          UniquePtr<GetNodeIdCallback>&& aCallback)
 {
   nsCString nodeId;
-  nsresult rv = GetNodeId(aOrigin, aTopLevelOrigin, aInPrivateBrowsing, nodeId);
+  nsresult rv = GetNodeId(aOrigin, aTopLevelOrigin, aGMPName, aInPrivateBrowsing, nodeId);
   aCallback->Done(rv, nodeId);
   return rv;
 }
 
 NS_IMETHODIMP
 GeckoMediaPluginServiceParent::UpdateTrialCreateState(const nsAString& aKeySystem,
                                                       uint32_t aState)
 {
@@ -1476,99 +1519,80 @@ struct NodeFilter {
   }
 private:
   const nsTArray<nsCString>& mNodeIDs;
 };
 
 void
 GeckoMediaPluginServiceParent::ClearNodeIdAndPlugin(DirectoryFilter& aFilter)
 {
-  nsresult rv;
+  // $profileDir/gmp/$platform/
   nsCOMPtr<nsIFile> path;
-
-  // $profileDir/gmp/
-  rv = GetStorageDir(getter_AddRefs(path));
-  if (NS_FAILED(rv)) {
-    return;
-  }
-
-  // $profileDir/gmp/id/
-  rv = path->AppendNative(NS_LITERAL_CSTRING("id"));
-  if (NS_FAILED(rv)) {
-    return;
-  }
-
-  // Iterate all sub-folders of $profileDir/gmp/id/
-  nsCOMPtr<nsISimpleEnumerator> iter;
-  rv = path->GetDirectoryEntries(getter_AddRefs(iter));
+  nsresult rv = GetStorageDir(getter_AddRefs(path));
   if (NS_FAILED(rv)) {
     return;
   }
 
-  bool hasMore = false;
-  nsTArray<nsCString> nodeIDsToClear;
-  while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
-    nsCOMPtr<nsISupports> supports;
-    rv = iter->GetNext(getter_AddRefs(supports));
-    if (NS_FAILED(rv)) {
-      continue;
-    }
+  // Iterate all sub-folders of $profileDir/gmp/$platform/, i.e. the dirs in which
+  // specific GMPs store their data.
+  DirectoryEnumerator iter(path, DirectoryEnumerator::DirsOnly);
+  for (nsCOMPtr<nsIFile> pluginDir; (pluginDir = iter.Next()) != nullptr;) {
+    ClearNodeIdAndPlugin(pluginDir, aFilter);
+  }
+}
 
-    // $profileDir/gmp/id/$hash
-    nsCOMPtr<nsIFile> dirEntry(do_QueryInterface(supports, &rv));
-    if (NS_FAILED(rv)) {
-      continue;
-    }
+void
+GeckoMediaPluginServiceParent::ClearNodeIdAndPlugin(nsIFile* aPluginStorageDir,
+                                                    DirectoryFilter& aFilter)
+{
+  // $profileDir/gmp/$platform/$gmpName/id/
+  nsCOMPtr<nsIFile> path = CloneAndAppend(aPluginStorageDir, NS_LITERAL_STRING("id"));
+  if (!path) {
+    return;
+  }
 
-    // Skip non-directory files.
-    bool isDirectory = false;
-    rv = dirEntry->IsDirectory(&isDirectory);
-    if (NS_FAILED(rv) || !isDirectory) {
-      continue;
-    }
-
+  // Iterate all sub-folders of $profileDir/gmp/$platform/$gmpName/id/
+  nsTArray<nsCString> nodeIDsToClear;
+  DirectoryEnumerator iter(path, DirectoryEnumerator::DirsOnly);
+  for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) {
+    // dirEntry is the hash of origins, i.e.:
+    // $profileDir/gmp/$platform/$gmpName/id/$originHash/
     if (!aFilter(dirEntry)) {
       continue;
     }
-
     nsAutoCString salt;
     if (NS_SUCCEEDED(ReadSalt(dirEntry, salt))) {
       // Keep node IDs to clear data/plugins associated with them later.
       nodeIDsToClear.AppendElement(salt);
       // Also remove node IDs from the table.
       mPersistentStorageAllowed.Remove(salt);
     }
     // Now we can remove the directory for the origin pair.
     if (NS_FAILED(dirEntry->Remove(true))) {
       NS_WARNING("Failed to delete the directory for the origin pair");
     }
   }
 
-  // Kill plugins that have node IDs to be cleared.
+  // Kill plugin instances that have node IDs being cleared.
   KillPlugins(mPlugins, mMutex, NodeFilter(nodeIDsToClear));
 
-  // Clear all matching $profileDir/gmp/storage/$nodeId/
-  rv = GetStorageDir(getter_AddRefs(path));
-  if (NS_FAILED(rv)) {
+  // Clear all storage in $profileDir/gmp/$platform/$gmpName/storage/$nodeId/
+  path = CloneAndAppend(aPluginStorageDir, NS_LITERAL_STRING("storage"));
+  if (!path) {
     return;
   }
 
-  rv = path->AppendNative(NS_LITERAL_CSTRING("storage"));
-  if (NS_FAILED(rv)) {
-    return;
-  }
-
-  for (size_t i = 0; i < nodeIDsToClear.Length(); i++) {
+  for (const nsCString& nodeId : nodeIDsToClear) {
     nsCOMPtr<nsIFile> dirEntry;
-    rv = path->Clone(getter_AddRefs(dirEntry));
+    nsresult rv = path->Clone(getter_AddRefs(dirEntry));
     if (NS_FAILED(rv)) {
       continue;
     }
 
-    rv = dirEntry->AppendNative(nodeIDsToClear[i]);
+    rv = dirEntry->AppendNative(nodeId);
     if (NS_FAILED(rv)) {
       continue;
     }
 
     if (NS_FAILED(DeleteDir(dirEntry))) {
       NS_WARNING("Failed to delete GMP storage directory for the node");
     }
   }
@@ -1593,91 +1617,68 @@ GeckoMediaPluginServiceParent::ForgetThi
 }
 
 void
 GeckoMediaPluginServiceParent::ClearRecentHistoryOnGMPThread(PRTime aSince)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
   LOGD(("%s::%s: since=%lld", __CLASS__, __FUNCTION__, (int64_t)aSince));
 
-  nsCOMPtr<nsIFile> storagePath;
-  nsCOMPtr<nsIFile> temp;
-  if (NS_SUCCEEDED(GetStorageDir(getter_AddRefs(temp))) &&
-      NS_SUCCEEDED(temp->AppendNative(NS_LITERAL_CSTRING("storage")))) {
-    storagePath = temp.forget();
-  }
-
   struct MTimeFilter : public DirectoryFilter {
-    explicit MTimeFilter(PRTime aSince, already_AddRefed<nsIFile> aPath)
-      : mSince(aSince), mStoragePath(aPath) {}
+    explicit MTimeFilter(PRTime aSince)
+      : mSince(aSince) {}
 
     // Return true if any files under aPath is modified after |mSince|.
     bool IsModifiedAfter(nsIFile* aPath) {
       PRTime lastModified;
       nsresult rv = aPath->GetLastModifiedTime(&lastModified);
       if (NS_SUCCEEDED(rv) && lastModified >= mSince) {
         return true;
       }
-      // Check sub-directories recursively
-      nsCOMPtr<nsISimpleEnumerator> iter;
-      rv = aPath->GetDirectoryEntries(getter_AddRefs(iter));
-      if (NS_FAILED(rv)) {
-        return false;
-      }
-
-      bool hasMore = false;
-      while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
-        nsCOMPtr<nsISupports> supports;
-        rv = iter->GetNext(getter_AddRefs(supports));
-        if (NS_FAILED(rv)) {
-          continue;
-        }
-
-        nsCOMPtr<nsIFile> path(do_QueryInterface(supports, &rv));
-        if (NS_FAILED(rv)) {
-          continue;
-        }
-
-        if (IsModifiedAfter(path)) {
+      DirectoryEnumerator iter(aPath, DirectoryEnumerator::FilesAndDirs);
+      for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) {
+        if (IsModifiedAfter(dirEntry)) {
           return true;
         }
       }
       return false;
     }
 
-    // |aPath| is $profileDir/gmp/id/$hash
+    // |aPath| is $profileDir/gmp/$platform/$gmpName/id/$originHash/
     virtual bool operator()(nsIFile* aPath) {
       if (IsModifiedAfter(aPath)) {
         return true;
       }
 
       nsAutoCString salt;
-      nsresult rv = ReadSalt(aPath, salt);
-      if (NS_FAILED(rv)) {
+      if (NS_FAILED(ReadSalt(aPath, salt))) {
         return false;
       }
 
-      // $profileDir/gmp/storage/
-      if (!mStoragePath) {
+      // $profileDir/gmp/$platform/$gmpName/id/
+      nsCOMPtr<nsIFile> idDir;
+      if (NS_FAILED(aPath->GetParent(getter_AddRefs(idDir)))) {
         return false;
       }
-      // $profileDir/gmp/storage/$nodeId/
-      nsCOMPtr<nsIFile> path;
-      rv = mStoragePath->Clone(getter_AddRefs(path));
-      if (NS_FAILED(rv)) {
+      // $profileDir/gmp/$platform/$gmpName/
+      nsCOMPtr<nsIFile> temp;
+      if (NS_FAILED(idDir->GetParent(getter_AddRefs(temp)))) {
         return false;
       }
 
-      rv = path->AppendNative(salt);
-      return NS_SUCCEEDED(rv) && IsModifiedAfter(path);
+      // $profileDir/gmp/$platform/$gmpName/storage/
+      if (NS_FAILED(temp->Append(NS_LITERAL_STRING("storage")))) {
+        return false;
+      }
+      // $profileDir/gmp/$platform/$gmpName/storage/$originSalt
+      return NS_SUCCEEDED(temp->AppendNative(salt)) && IsModifiedAfter(temp);
     }
   private:
     const PRTime mSince;
-    const nsCOMPtr<nsIFile> mStoragePath;
-  } filter(aSince, storagePath.forget());
+  } filter(aSince);
 
   ClearNodeIdAndPlugin(filter);
 
   NS_DispatchToMainThread(new NotifyObserversTask("gmp-clear-storage-complete"), NS_DISPATCH_NORMAL);
 }
 
 NS_IMETHODIMP
 GeckoMediaPluginServiceParent::ForgetThisSite(const nsAString& aSite)
@@ -1696,17 +1697,17 @@ void
 GeckoMediaPluginServiceParent::ClearStorage()
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
   LOGD(("%s::%s", __CLASS__, __FUNCTION__));
 
   // Kill plugins with valid nodeIDs.
   KillPlugins(mPlugins, mMutex, &IsNodeIdValid);
 
-  nsCOMPtr<nsIFile> path; // $profileDir/gmp/
+  nsCOMPtr<nsIFile> path; // $profileDir/gmp/$platform/
   nsresult rv = GetStorageDir(getter_AddRefs(path));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
   if (NS_FAILED(DeleteDir(path))) {
     NS_WARNING("Failed to delete GMP storage directory");
   }
@@ -1741,20 +1742,21 @@ GMPServiceParent::RecvLoadGMP(const nsCS
   *aPluginId = gmp->GetPluginId();
 
   return aAlreadyBridgedTo.Contains(*aId) || gmp->Bridge(this);
 }
 
 bool
 GMPServiceParent::RecvGetGMPNodeId(const nsString& aOrigin,
                                    const nsString& aTopLevelOrigin,
+                                   const nsString& aGMPName,
                                    const bool& aInPrivateBrowsing,
                                    nsCString* aID)
 {
-  nsresult rv = mService->GetNodeId(aOrigin, aTopLevelOrigin,
+  nsresult rv = mService->GetNodeId(aOrigin, aTopLevelOrigin, aGMPName,
                                     aInPrivateBrowsing, *aID);
   return NS_SUCCEEDED(rv);
 }
 
 bool
 GMPServiceParent::RecvUpdateGMPTrialCreateState(const nsString& aKeySystem,
                                                 const uint32_t& aState)
 {
--- a/dom/media/gmp/GMPServiceParent.h
+++ b/dom/media/gmp/GMPServiceParent.h
@@ -36,16 +36,17 @@ public:
 
   // mozIGeckoMediaPluginService
   NS_IMETHOD GetPluginVersionForAPI(const nsACString& aAPI,
                                     nsTArray<nsCString>* aTags,
                                     bool* aHasPlugin,
                                     nsACString& aOutVersion) override;
   NS_IMETHOD GetNodeId(const nsAString& aOrigin,
                        const nsAString& aTopLevelOrigin,
+                       const nsAString& aGMPName,
                        bool aInPrivateBrowsingMode,
                        UniquePtr<GetNodeIdCallback>&& aCallback) override;
   NS_IMETHOD UpdateTrialCreateState(const nsAString& aKeySystem,
                                     uint32_t aState) override;
 
   NS_DECL_MOZIGECKOMEDIAPLUGINCHROMESERVICE
   NS_DECL_NSIOBSERVER
 
@@ -68,16 +69,17 @@ private:
                                 const nsCString& aAPI,
                                 const nsTArray<nsCString>& aTags);
   GMPParent* FindPluginForAPIFrom(size_t aSearchStartIndex,
                                   const nsCString& aAPI,
                                   const nsTArray<nsCString>& aTags,
                                   size_t* aOutPluginIndex);
 
   nsresult GetNodeId(const nsAString& aOrigin, const nsAString& aTopLevelOrigin,
+                     const nsAString& aGMPName,
                      bool aInPrivateBrowsing, nsACString& aOutId);
 
   void UnloadPlugins();
   void CrashPlugins();
   void NotifySyncShutdownComplete();
   void NotifyAsyncShutdownComplete();
 
   void LoadFromEnvironment();
@@ -90,17 +92,18 @@ private:
 
   nsresult SetAsyncShutdownTimeout();
 
   struct DirectoryFilter {
     virtual bool operator()(nsIFile* aPath) = 0;
     ~DirectoryFilter() {}
   };
   void ClearNodeIdAndPlugin(DirectoryFilter& aFilter);
-
+  void ClearNodeIdAndPlugin(nsIFile* aPluginStorageDir,
+                            DirectoryFilter& aFilter);
   void ForgetThisSiteOnGMPThread(const nsACString& aOrigin);
   void ClearRecentHistoryOnGMPThread(PRTime aSince);
 
 protected:
   friend class GMPParent;
   void ReAddOnGMPThread(const RefPtr<GMPParent>& aOld);
   void PluginTerminated(const RefPtr<GMPParent>& aOld);
   virtual void InitializePlugins() override;
@@ -210,16 +213,17 @@ public:
                            const nsCString& aApi,
                            nsTArray<nsCString>&& aTags,
                            nsTArray<ProcessId>&& aAlreadyBridgedTo,
                            base::ProcessId* aID,
                            nsCString* aDisplayName,
                            uint32_t* aPluginId) override;
   virtual bool RecvGetGMPNodeId(const nsString& aOrigin,
                                 const nsString& aTopLevelOrigin,
+                                const nsString& aGMPName,
                                 const bool& aInPrivateBrowsing,
                                 nsCString* aID) override;
   static bool RecvGetGMPPluginVersionForAPI(const nsCString& aAPI,
                                             nsTArray<nsCString>&& aTags,
                                             bool* aHasPlugin,
                                             nsCString* aVersion);
   virtual bool RecvUpdateGMPTrialCreateState(const nsString& aKeySystem,
                                              const uint32_t& aState) override;
--- a/dom/media/gmp/GMPStorageParent.cpp
+++ b/dom/media/gmp/GMPStorageParent.cpp
@@ -27,20 +27,22 @@ namespace mozilla {
 
 extern LogModule* GetGMPLog();
 
 #define LOGD(msg) MOZ_LOG(GetGMPLog(), mozilla::LogLevel::Debug, msg)
 #define LOG(level, msg) MOZ_LOG(GetGMPLog(), (level), msg)
 
 namespace gmp {
 
-// We store the records in files in the profile dir.
-// $profileDir/gmp/storage/$nodeId/
+// We store the records for a given GMP as files in the profile dir.
+// $profileDir/gmp/$platform/$gmpName/storage/$nodeId/
 static nsresult
-GetGMPStorageDir(nsIFile** aTempDir, const nsCString& aNodeId)
+GetGMPStorageDir(nsIFile** aTempDir,
+                 const nsString& aGMPName,
+                 const nsCString& aNodeId)
 {
   if (NS_WARN_IF(!aTempDir)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   nsCOMPtr<mozIGeckoMediaPluginChromeService> mps =
     do_GetService("@mozilla.org/gecko-media-plugin-service;1");
   if (NS_WARN_IF(!mps)) {
@@ -48,16 +50,26 @@ GetGMPStorageDir(nsIFile** aTempDir, con
   }
 
   nsCOMPtr<nsIFile> tmpFile;
   nsresult rv = mps->GetStorageDir(getter_AddRefs(tmpFile));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  rv = tmpFile->Append(aGMPName);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
+  if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   rv = tmpFile->AppendNative(NS_LITERAL_CSTRING("storage"));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700);
   if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -82,18 +94,20 @@ GetGMPStorageDir(nsIFile** aTempDir, con
 // the profile directory. The record name is a hash of the filename,
 // and we resolve hash collisions by just adding 1 to the hash code.
 // The format of records on disk is:
 //   4 byte, uint32_t $recordNameLength, in little-endian byte order,
 //   record name (i.e. $recordNameLength bytes, no null terminator)
 //   record bytes (entire remainder of file)
 class GMPDiskStorage : public GMPStorage {
 public:
-  explicit GMPDiskStorage(const nsCString& aNodeId)
+  explicit GMPDiskStorage(const nsCString& aNodeId,
+                          const nsString& aGMPName)
     : mNodeId(aNodeId)
+    , mGMPName(aGMPName)
   {
   }
 
   ~GMPDiskStorage() {
     // Close all open file handles.
     for (auto iter = mRecords.ConstIter(); !iter.Done(); iter.Next()) {
       Record* record = iter.UserData();
       if (record->mFileDesc) {
@@ -101,39 +115,23 @@ public:
         record->mFileDesc = nullptr;
       }
     }
   }
 
   nsresult Init() {
     // Build our index of records on disk.
     nsCOMPtr<nsIFile> storageDir;
-    nsresult rv = GetGMPStorageDir(getter_AddRefs(storageDir), mNodeId);
+    nsresult rv = GetGMPStorageDir(getter_AddRefs(storageDir), mGMPName, mNodeId);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return NS_ERROR_FAILURE;
     }
 
-    nsCOMPtr<nsISimpleEnumerator> iter;
-    rv = storageDir->GetDirectoryEntries(getter_AddRefs(iter));
-    if (NS_FAILED(rv)) {
-      return NS_ERROR_FAILURE;
-    }
-
-    bool hasMore;
-    while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
-      nsCOMPtr<nsISupports> supports;
-      rv = iter->GetNext(getter_AddRefs(supports));
-      if (NS_FAILED(rv)) {
-        continue;
-      }
-      nsCOMPtr<nsIFile> dirEntry(do_QueryInterface(supports, &rv));
-      if (NS_FAILED(rv)) {
-        continue;
-      }
-
+    DirectoryEnumerator iter(storageDir, DirectoryEnumerator::FilesAndDirs);
+    for (nsCOMPtr<nsIFile> dirEntry; (dirEntry = iter.Next()) != nullptr;) {
       PRFileDesc* fd = nullptr;
       if (NS_FAILED(dirEntry->OpenNSPRFileDesc(PR_RDONLY, 0, &fd))) {
         continue;
       }
       int32_t recordLength = 0;
       nsCString recordName;
       nsresult err = ReadRecordMetadata(fd, recordLength, recordName);
       PR_Close(fd);
@@ -329,17 +327,17 @@ private:
 
   // We store records in a file which is a hash of the record name.
   // If there is a hash collision, we just keep adding 1 to the hash
   // code, until we find a free slot.
   nsresult GetUnusedFilename(const nsACString& aRecordName,
                              nsString& aOutFilename)
   {
     nsCOMPtr<nsIFile> storageDir;
-    nsresult rv = GetGMPStorageDir(getter_AddRefs(storageDir), mNodeId);
+    nsresult rv = GetGMPStorageDir(getter_AddRefs(storageDir), mGMPName, mNodeId);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     uint64_t recordNameHash = HashString(PromiseFlatCString(aRecordName).get());
     for (int i = 0; i < 1000000; i++) {
       nsCOMPtr<nsIFile> f;
       rv = storageDir->Clone(getter_AddRefs(f));
@@ -374,17 +372,17 @@ private:
 
   nsresult OpenStorageFile(const nsAString& aFileLeafName,
                            const OpenFileMode aMode,
                            PRFileDesc** aOutFD)
   {
     MOZ_ASSERT(aOutFD);
 
     nsCOMPtr<nsIFile> f;
-    nsresult rv = GetGMPStorageDir(getter_AddRefs(f), mNodeId);
+    nsresult rv = GetGMPStorageDir(getter_AddRefs(f), mGMPName, mNodeId);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     f->Append(aFileLeafName);
 
     auto mode = PR_RDWR | PR_CREATE_FILE;
     if (aMode == Truncate) {
       mode |= PR_TRUNCATE;
@@ -451,17 +449,17 @@ private:
     }
 
     return NS_OK;
   }
 
   nsresult RemoveStorageFile(const nsString& aFilename)
   {
     nsCOMPtr<nsIFile> f;
-    nsresult rv = GetGMPStorageDir(getter_AddRefs(f), mNodeId);
+    nsresult rv = GetGMPStorageDir(getter_AddRefs(f), mGMPName, mNodeId);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     rv = f->Append(aFilename);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
     return f->Remove(/* bool recursive= */ false);
@@ -480,16 +478,17 @@ private:
     nsString mFilename;
     nsCString mRecordName;
     PRFileDesc* mFileDesc;
   };
 
   // Hash record name to record data.
   nsClassHashtable<nsCStringHashKey, Record> mRecords;
   const nsAutoCString mNodeId;
+  const nsString mGMPName;
 };
 
 class GMPMemoryStorage : public GMPStorage {
 public:
   GMPErr Open(const nsCString& aRecordName) override
   {
     MOZ_ASSERT(!IsOpen(aRecordName));
 
@@ -587,17 +586,18 @@ GMPStorageParent::Init()
     return NS_ERROR_FAILURE;
   }
 
   bool persistent = false;
   if (NS_WARN_IF(NS_FAILED(mps->IsPersistentStorageAllowed(mNodeId, &persistent)))) {
     return NS_ERROR_FAILURE;
   }
   if (persistent) {
-    UniquePtr<GMPDiskStorage> storage = MakeUnique<GMPDiskStorage>(mNodeId);
+    UniquePtr<GMPDiskStorage> storage =
+      MakeUnique<GMPDiskStorage>(mNodeId, mPlugin->GetPluginBaseName());
     if (NS_FAILED(storage->Init())) {
       NS_WARNING("Failed to initialize on disk GMP storage");
       return NS_ERROR_FAILURE;
     }
     mStorage = Move(storage);
   } else {
     mStorage = MakeUnique<GMPMemoryStorage>();
   }
--- a/dom/media/gmp/GMPUtils.cpp
+++ b/dom/media/gmp/GMPUtils.cpp
@@ -6,16 +6,17 @@
 
 #include "GMPUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsIFile.h"
 #include "nsCOMPtr.h"
 #include "nsLiteralString.h"
 #include "nsCRTGlue.h"
 #include "mozilla/Base64.h"
+#include "nsISimpleEnumerator.h"
 
 namespace mozilla {
 
 bool
 GetEMEVoucherPath(nsIFile** aPath)
 {
   nsCOMPtr<nsIFile> path;
   NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(path));
@@ -66,9 +67,46 @@ ToBase64(const nsTArray<uint8_t>& aBytes
 
 bool
 FileExists(nsIFile* aFile)
 {
   bool exists = false;
   return aFile && NS_SUCCEEDED(aFile->Exists(&exists)) && exists;
 }
 
+DirectoryEnumerator::DirectoryEnumerator(nsIFile* aPath, Mode aMode)
+  : mMode(aMode)
+{
+  aPath->GetDirectoryEntries(getter_AddRefs(mIter));
+}
+
+already_AddRefed<nsIFile>
+DirectoryEnumerator::Next()
+{
+  if (!mIter) {
+    return nullptr;
+  }
+  bool hasMore = false;
+  while (NS_SUCCEEDED(mIter->HasMoreElements(&hasMore)) && hasMore) {
+    nsCOMPtr<nsISupports> supports;
+    nsresult rv = mIter->GetNext(getter_AddRefs(supports));
+    if (NS_FAILED(rv)) {
+      continue;
+    }
+
+    nsCOMPtr<nsIFile> path(do_QueryInterface(supports, &rv));
+    if (NS_FAILED(rv)) {
+      continue;
+    }
+
+    if (mMode == DirsOnly) {
+      bool isDirectory = false;
+      rv = path->IsDirectory(&isDirectory);
+      if (NS_FAILED(rv) || !isDirectory) {
+        continue;
+      }
+    }
+    return path.forget();
+  }
+  return nullptr;
+}
+
 } // namespace mozilla
--- a/dom/media/gmp/GMPUtils.h
+++ b/dom/media/gmp/GMPUtils.h
@@ -3,19 +3,21 @@
  * 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 GMPUtils_h_
 #define GMPUtils_h_
 
 #include "mozilla/UniquePtr.h"
 #include "nsTArray.h"
+#include "nsCOMPtr.h"
 
 class nsIFile;
 class nsCString;
+class nsISimpleEnumerator;
 
 namespace mozilla {
 
 template<typename T>
 struct DestroyPolicy
 {
   void operator()(T* aGMPObject) const {
     aGMPObject->Destroy();
@@ -35,11 +37,29 @@ SplitAt(const char* aDelims,
         nsTArray<nsCString>& aOutTokens);
 
 nsCString
 ToBase64(const nsTArray<uint8_t>& aBytes);
 
 bool
 FileExists(nsIFile* aFile);
 
+// Enumerate directory entries for a specified path.
+class DirectoryEnumerator {
+public:
+
+  enum Mode {
+    DirsOnly, // Enumeration only includes directories.
+    FilesAndDirs // Enumeration includes directories and non-directory files.
+  };
+
+  DirectoryEnumerator(nsIFile* aPath, Mode aMode);
+
+  already_AddRefed<nsIFile> Next();
+
+private:
+  Mode mMode;
+  nsCOMPtr<nsISimpleEnumerator> mIter;
+};
+
 } // namespace mozilla
 
 #endif
--- a/dom/media/gmp/PGMPService.ipdl
+++ b/dom/media/gmp/PGMPService.ipdl
@@ -14,16 +14,17 @@ sync protocol PGMPService
 {
   parent spawns PGMP as child;
 
 parent:
   sync LoadGMP(nsCString nodeId, nsCString api, nsCString[] tags,
                ProcessId[] alreadyBridgedTo)
     returns (ProcessId id, nsCString displayName, uint32_t pluginId);
   sync GetGMPNodeId(nsString origin, nsString topLevelOrigin,
+                    nsString gmpName,
                     bool inPrivateBrowsing)
     returns (nsCString id);
 
   async UpdateGMPTrialCreateState(nsString keySystem, uint32_t status);
 };
 
 } // namespace gmp
 } // namespace mozilla
--- a/dom/media/gmp/mozIGeckoMediaPluginService.idl
+++ b/dom/media/gmp/mozIGeckoMediaPluginService.idl
@@ -47,17 +47,17 @@ public:
 
 [ptr] native TagArray(nsTArray<nsCString>);
 native GetGMPDecryptorCallback(mozilla::UniquePtr<GetGMPDecryptorCallback>&&);
 native GetGMPAudioDecoderCallback(mozilla::UniquePtr<GetGMPAudioDecoderCallback>&&);
 native GetGMPVideoDecoderCallback(mozilla::UniquePtr<GetGMPVideoDecoderCallback>&&);
 native GetGMPVideoEncoderCallback(mozilla::UniquePtr<GetGMPVideoEncoderCallback>&&);
 native GetNodeIdCallback(mozilla::UniquePtr<GetNodeIdCallback>&&);
 
-[scriptable, uuid(661492d6-726b-4ba0-8e6e-14bfaf2b62e4)]
+[scriptable, uuid(b5492915-2f0e-4973-9f91-a6fe61ac4749)]
 interface mozIGeckoMediaPluginService : nsISupports
 {
 
   /**
    * The GMP thread. Callable from any thread.
    */
   readonly attribute nsIThread thread;
 
@@ -140,16 +140,17 @@ interface mozIGeckoMediaPluginService : 
                        in GetGMPDecryptorCallback callback);
 
   /**
    * Gets the NodeId for a (origin, urlbarOrigin, isInprivateBrowsing) tuple.
    */
   [noscript]
   void getNodeId(in AString origin,
                  in AString topLevelOrigin,
+                 in AString gmpName,
                  in bool inPrivateBrowsingMode,
                  in GetNodeIdCallback callback);
 
   /**
    * Stores the result of trying to create a decoder for the given keysystem.
    */
   [noscript]
   void updateTrialCreateState(in AString keySystem, in uint32_t status);
--- a/dom/media/gtest/TestGMPCrossOrigin.cpp
+++ b/dom/media/gtest/TestGMPCrossOrigin.cpp
@@ -268,34 +268,41 @@ EnumerateDir(nsIFile* aPath, T&& aDirIte
     }
 
     aDirIter(entry);
   }
   return NS_OK;
 }
 
 /**
- * Enumerate files under $profileDir/gmp/$aDir/ (non-recursive).
+ * Enumerate files under $profileDir/gmp/$platform/gmp-fake/$aDir/ (non-recursive).
  */
 template<typename T>
 static nsresult
 EnumerateGMPStorageDir(const nsACString& aDir, T&& aDirIter)
 {
   RefPtr<GeckoMediaPluginServiceParent> service =
     GeckoMediaPluginServiceParent::GetSingleton();
   MOZ_ASSERT(service);
 
-  // $profileDir/gmp/
+  // $profileDir/gmp/$platform/
   nsCOMPtr<nsIFile> path;
   nsresult rv = service->GetStorageDir(getter_AddRefs(path));
   if (NS_FAILED(rv)) {
     return rv;
   }
 
-  // $profileDir/gmp/$aDir/
+
+  // $profileDir/gmp/$platform/gmp-fake/
+  rv = path->Append(NS_LITERAL_STRING("gmp-fake"));
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  // $profileDir/gmp/$platform/gmp-fake/$aDir/
   rv = path->AppendNative(aDir);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   return EnumerateDir(path, aDirIter);
 }
 
@@ -464,16 +471,17 @@ GetNodeId(const nsAString& aOrigin,
   nsCString nodeId;
   nsresult result;
   UniquePtr<GetNodeIdCallback> callback(new TestGetNodeIdCallback(nodeId,
                                                                   result));
   // We rely on the fact that the GetNodeId implementation for
   // GeckoMediaPluginServiceParent is synchronous.
   nsresult rv = service->GetNodeId(aOrigin,
                                    aTopLevelOrigin,
+                                   NS_LITERAL_STRING("gmp-fake"),
                                    aInPBMode,
                                    Move(callback));
   EXPECT_TRUE(NS_SUCCEEDED(rv) && NS_SUCCEEDED(result));
   return nodeId;
 }
 
 static bool
 IsGMPStorageIsEmpty()
@@ -815,20 +823,20 @@ class GMPStorageTest : public GMPDecrypt
 
     rv = EnumerateGMPStorageDir(
         NS_LITERAL_CSTRING("storage"), StorageVerifier(aSiteInfo));
     EXPECT_TRUE(NS_SUCCEEDED(rv));
   }
 
   /**
    * 1. Generate some storage data.
-   * 2. Find the max mtime |t| in $profileDir/gmp/id/.
+   * 2. Find the max mtime |t| in $profileDir/gmp/$platform/gmp-fake/id/.
    * 3. Pass |t| to clear recent history.
-   * 4. Check if all directories in $profileDir/gmp/id/ and
-   *    $profileDir/gmp/storage are removed.
+   * 4. Check if all directories in $profileDir/gmp/$platform/gmp-fake/id/ and
+   *    $profileDir/gmp/$platform/gmp-fake/storage are removed.
    */
   void TestClearRecentHistory1() {
     AssertIsOnGMPThread();
     EXPECT_TRUE(IsGMPStorageIsEmpty());
 
     // Generate storage data for some site.
     nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(
         this, &GMPStorageTest::TestClearRecentHistory1_Clear);
@@ -837,20 +845,20 @@ class GMPStorageTest : public GMPDecrypt
     CreateDecryptor(NS_LITERAL_STRING("http://example1.com"),
                     NS_LITERAL_STRING("http://example2.com"),
                     false,
                     NS_LITERAL_CSTRING("test-storage"));
 }
 
   /**
    * 1. Generate some storage data.
-   * 2. Find the max mtime |t| in $profileDir/gmp/storage/.
+   * 2. Find the max mtime |t| in $profileDir/gmp/$platform/gmp-fake/storage/.
    * 3. Pass |t| to clear recent history.
-   * 4. Check if all directories in $profileDir/gmp/id/ and
-   *    $profileDir/gmp/storage are removed.
+   * 4. Check if all directories in $profileDir/gmp/$platform/gmp-fake/id/ and
+   *    $profileDir/gmp/$platform/gmp-fake/storage are removed.
    */
   void TestClearRecentHistory2() {
     AssertIsOnGMPThread();
     EXPECT_TRUE(IsGMPStorageIsEmpty());
 
     // Generate storage data for some site.
     nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(
         this, &GMPStorageTest::TestClearRecentHistory2_Clear);
@@ -859,20 +867,20 @@ class GMPStorageTest : public GMPDecrypt
     CreateDecryptor(NS_LITERAL_STRING("http://example1.com"),
                     NS_LITERAL_STRING("http://example2.com"),
                     false,
                     NS_LITERAL_CSTRING("test-storage"));
   }
 
   /**
    * 1. Generate some storage data.
-   * 2. Find the max mtime |t| in $profileDir/gmp/storage/.
+   * 2. Find the max mtime |t| in $profileDir/gmp/$platform/gmp-fake/storage/.
    * 3. Pass |t+1| to clear recent history.
-   * 4. Check if all directories in $profileDir/gmp/id/ and
-   *    $profileDir/gmp/storage remain unchanged.
+   * 4. Check if all directories in $profileDir/gmp/$platform/gmp-fake/id/ and
+   *    $profileDir/gmp/$platform/gmp-fake/storage remain unchanged.
    */
   void TestClearRecentHistory3() {
     AssertIsOnGMPThread();
     EXPECT_TRUE(IsGMPStorageIsEmpty());
 
     // Generate storage data for some site.
     nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethod(
         this, &GMPStorageTest::TestClearRecentHistory3_Clear);
@@ -943,39 +951,39 @@ class GMPStorageTest : public GMPDecrypt
   private:
     int mCount;
   };
 
   void TestClearRecentHistory_CheckEmpty() {
     FileCounter c1;
     nsresult rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("id"), c1);
     EXPECT_TRUE(NS_SUCCEEDED(rv));
-    // There should be no files under $profileDir/gmp/id/
+    // There should be no files under $profileDir/gmp/$platform/gmp-fake/id/
     EXPECT_EQ(c1.GetCount(), 0);
 
     FileCounter c2;
     rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("storage"), c2);
     EXPECT_TRUE(NS_SUCCEEDED(rv));
-    // There should be no files under $profileDir/gmp/storage/
+    // There should be no files under $profileDir/gmp/$platform/gmp-fake/storage/
     EXPECT_EQ(c2.GetCount(), 0);
 
     SetFinished();
   }
 
   void TestClearRecentHistory_CheckNonEmpty() {
     FileCounter c1;
     nsresult rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("id"), c1);
     EXPECT_TRUE(NS_SUCCEEDED(rv));
-    // There should be one directory under $profileDir/gmp/id/
+    // There should be one directory under $profileDir/gmp/$platform/gmp-fake/id/
     EXPECT_EQ(c1.GetCount(), 1);
 
     FileCounter c2;
     rv = EnumerateGMPStorageDir(NS_LITERAL_CSTRING("storage"), c2);
     EXPECT_TRUE(NS_SUCCEEDED(rv));
-    // There should be one directory under $profileDir/gmp/storage/
+    // There should be one directory under $profileDir/gmp/$platform/gmp-fake/storage/
     EXPECT_EQ(c2.GetCount(), 1);
 
     SetFinished();
   }
 
   void TestCrossOriginStorage() {
     EXPECT_TRUE(!mDecryptor);
 
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -614,16 +614,18 @@ skip-if = (toolkit == 'android' && proce
 [test_defaultMuted.html]
 [test_delay_load.html]
 skip-if = buildapp == 'b2g' && toolkit != 'gonk' # bug 1082984
 [test_dormant_playback.html]
 skip-if = (os == 'win' && os_version == '5.1') || (os != 'win' && toolkit != 'gonk')
 [test_eme_session_callable_value.html]
 [test_eme_canvas_blocked.html]
 skip-if = toolkit == 'android' # bug 1149374
+[test_eme_key_ids_initdata.html]
+skip-if = toolkit == 'android' # bug 1149374
 [test_eme_non_mse_fails.html]
 skip-if = toolkit == 'android' # bug 1149374
 [test_eme_request_notifications.html]
 skip-if = toolkit == 'android' # bug 1149374
 [test_eme_persistent_sessions.html]
 skip-if = toolkit == 'android' # bug 1149374
 [test_eme_playback.html]
 skip-if = toolkit == 'android' || toolkit == 'gonk' # android: bug 1149374; gonk: bug 1193351
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_eme_key_ids_initdata.html
@@ -0,0 +1,111 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test Encrypted Media Extensions</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="manifest.js"></script>
+  <script type="text/javascript" src="eme.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var tests = [
+  {
+    name: "One keyId",
+    initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"]}',
+    expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"],"type":"temporary"}',
+    sessionType: 'temporary',
+    expectPass: true,
+  },
+  {
+    name: "Two keyIds",
+    initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A", "0DdtU9od-Bh5L3xbv0Xf_A"]}',
+    expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A","0DdtU9od-Bh5L3xbv0Xf_A"],"type":"temporary"}',
+    sessionType: 'temporary',
+    expectPass: true,
+  },
+  {
+    name: "Two keyIds, temporary session",
+    initData: '{"type":"temporary", "kids":["LwVHf8JLtPrv2GUXFW2v_A", "0DdtU9od-Bh5L3xbv0Xf_A"]}',
+    expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A","0DdtU9od-Bh5L3xbv0Xf_A"],"type":"temporary"}',
+    sessionType: 'temporary',
+    expectPass: true,
+  },
+  {
+    name: "Two keyIds, persistent session, type before kids",
+    initData: '{"type":"persistent", "kids":["LwVHf8JLtPrv2GUXFW2v_A", "0DdtU9od-Bh5L3xbv0Xf_A"]}',
+    expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A","0DdtU9od-Bh5L3xbv0Xf_A"],"type":"persistent"}',
+    sessionType: 'persistent',
+    expectPass: true,
+  },
+  {
+    name: "Invalid keyId",
+    initData: '{"kids":["0"]}',
+    sessionType: 'temporary',
+    expectPass: false,
+  },
+  {
+    name: "Empty keyId",
+    initData: '{"kids":[""]}',
+    sessionType: 'temporary',
+    expectPass: false,
+  },
+  {
+    name: "SessionType in license doesn't match MediaKeySession's sessionType",
+    initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"]}',
+    sessionType: 'persistent',
+    expectPass: false,
+  },
+  {
+    name: "One valid and one invalid kid",
+    initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A", "invalid"]}',
+    expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"],"type":"temporary"}',
+    sessionType: 'temporary',
+    expectPass: true,
+  },
+  {
+    name: "Invalid initData",
+    initData: 'invalid initData',
+    sessionType: 'temporary',
+    expectPass: false,
+  },
+];
+
+function Test(test) {
+  return new Promise(function(resolve, reject) {
+    navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{initDataTypes: ['keyids']}]).then(
+      (access) => access.createMediaKeys()
+      ).then(
+        (mediaKeys) => {
+          var session = mediaKeys.createSession(test.sessionType);
+          var initData = new TextEncoder().encode(test.initData);
+          session.addEventListener("message", function(event) {
+            is(event.messageType, "license-request", "'" + test.name + "' MediaKeyMessage type should be license-request.");
+            var text = new TextDecoder().decode(event.message);
+            is(text, test.expectedRequest, "'" + test.name + "' got expected response.");
+            is(text == test.expectedRequest, test.expectPass,
+               "'" + test.name + "' expected to " + (test.expectPass ? "pass" : "fail"));
+            resolve();
+          });
+          return session.generateRequest('keyids', initData);
+        }
+      ).catch((x) => {
+        ok(!test.expectPass, "'" + test.name + "' expected to fail.");
+        resolve();
+      });
+  });
+}
+
+function beginTest() {
+  Promise.all(tests.map(Test)).then(function() { SimpleTest.finish(); });
+}
+
+SimpleTest.waitForExplicitFinish();
+SetupEMEPref(beginTest);
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/plugins/ipc/PPluginModule.ipdl
+++ b/dom/plugins/ipc/PPluginModule.ipdl
@@ -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/. */
 
 include protocol PPluginInstance;
 include protocol PPluginScriptableObject;
 include protocol PCrashReporter;
 include protocol PContent;
+include ProfilerTypes;
 
 using NPError from "npapi.h";
 using NPNVariable from "npapi.h";
 using mozilla::dom::NativeThreadId from "mozilla/dom/TabMessageUtils.h";
 using class mac_plugin_interposing::NSCursorInfo from "mozilla/plugins/PluginMessageUtils.h";
 using struct nsID from "nsID.h";
 
 namespace mozilla {
@@ -88,18 +89,17 @@ child:
   async SetParentHangTimeout(uint32_t seconds);
 
   intr PCrashReporter()
     returns (NativeThreadId tid, uint32_t processType);
 
   /**
    * Control the Gecko Profiler in the plugin process.
    */
-  async StartProfiler(uint32_t aEntries, double aInterval, nsCString[] aFeatures,
-                      nsCString[] aThreadNameFilters);
+  async StartProfiler(ProfilerInitParams params);
   async StopProfiler();
 
   async GatherProfile();
 
   async SettingChanged(PluginSettings settings);
 
 parent:
   async NP_InitializeResult(NPError aError);
--- a/dom/plugins/ipc/PluginModuleChild.cpp
+++ b/dom/plugins/ipc/PluginModuleChild.cpp
@@ -2519,32 +2519,30 @@ PluginModuleChild::RecvProcessNativeEven
 #ifdef MOZ_WIDGET_COCOA
 void
 PluginModuleChild::ProcessNativeEvents() {
     CallProcessSomeEvents();    
 }
 #endif
 
 bool
-PluginModuleChild::RecvStartProfiler(const uint32_t& aEntries,
-                                     const double& aInterval,
-                                     nsTArray<nsCString>&& aFeatures,
-                                     nsTArray<nsCString>&& aThreadNameFilters)
+PluginModuleChild::RecvStartProfiler(const ProfilerInitParams& params)
 {
     nsTArray<const char*> featureArray;
-    for (size_t i = 0; i < aFeatures.Length(); ++i) {
-        featureArray.AppendElement(aFeatures[i].get());
+    for (size_t i = 0; i < params.features().Length(); ++i) {
+        featureArray.AppendElement(params.features()[i].get());
     }
 
     nsTArray<const char*> threadNameFilterArray;
-    for (size_t i = 0; i < aThreadNameFilters.Length(); ++i) {
-        threadNameFilterArray.AppendElement(aThreadNameFilters[i].get());
+    for (size_t i = 0; i < params.threadFilters().Length(); ++i) {
+        threadNameFilterArray.AppendElement(params.threadFilters()[i].get());
     }
 
-    profiler_start(aEntries, aInterval, featureArray.Elements(), featureArray.Length(),
+    profiler_start(params.entries(), params.interval(),
+                   featureArray.Elements(), featureArray.Length(),
                    threadNameFilterArray.Elements(), threadNameFilterArray.Length());
 
     return true;
 }
 
 bool
 PluginModuleChild::RecvStopProfiler()
 {
--- a/dom/plugins/ipc/PluginModuleChild.h
+++ b/dom/plugins/ipc/PluginModuleChild.h
@@ -142,20 +142,17 @@ protected:
     virtual void
     ActorDestroy(ActorDestroyReason why) override;
 
     MOZ_NORETURN void QuickExit();
 
     virtual bool
     RecvProcessNativeEventsInInterruptCall() override;
 
-    virtual bool RecvStartProfiler(const uint32_t& aEntries,
-                                   const double& aInterval,
-                                   nsTArray<nsCString>&& aFeatures,
-                                   nsTArray<nsCString>&& aThreadNameFilters) override;
+    virtual bool RecvStartProfiler(const ProfilerInitParams& params) override;
     virtual bool RecvStopProfiler() override;
     virtual bool RecvGatherProfile() override;
 
 public:
     explicit PluginModuleChild(bool aIsChrome);
     virtual ~PluginModuleChild();
 
     bool CommonInit(base::ProcessId aParentPid,
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -3145,17 +3145,25 @@ PluginProfilerObserver::Observe(nsISuppo
     if (!strcmp(aTopic, "profiler-started")) {
         nsCOMPtr<nsIProfilerStartParams> params(do_QueryInterface(aSubject));
         uint32_t entries;
         double interval;
         params->GetEntries(&entries);
         params->GetInterval(&interval);
         const nsTArray<nsCString>& features = params->GetFeatures();
         const nsTArray<nsCString>& threadFilterNames = params->GetThreadFilterNames();
-        Unused << mPmp->SendStartProfiler(entries, interval, features, threadFilterNames);
+
+        ProfilerInitParams ipcParams;
+        ipcParams.enabled() = true;
+        ipcParams.entries() = entries;
+        ipcParams.interval() = interval;
+        ipcParams.features() = features;
+        ipcParams.threadFilters() = threadFilterNames;
+
+        Unused << mPmp->SendStartProfiler(ipcParams);
     } else if (!strcmp(aTopic, "profiler-stopped")) {
         Unused << mPmp->SendStopProfiler();
     } else if (!strcmp(aTopic, "profiler-subprocess-gather")) {
         RefPtr<ProfileGatherer> gatherer = static_cast<ProfileGatherer*>(aSubject);
         mPmp->GatherAsyncProfile(gatherer);
     } else if (!strcmp(aTopic, "profiler-subprocess")) {
         nsCOMPtr<nsIProfileSaveEvent> pse = do_QueryInterface(aSubject);
         mPmp->GatheredAsyncProfile(pse);
--- a/dom/push/PushService.jsm
+++ b/dom/push/PushService.jsm
@@ -273,17 +273,17 @@ this.PushService = {
       case "perm-changed":
         this._onPermissionChange(aSubject, aData).catch(error => {
           console.error("onPermissionChange: Error updating registrations:",
             error);
         })
         break;
 
       case "clear-origin-data":
-        this._clearOriginData(data).catch(error => {
+        this._clearOriginData(aData).catch(error => {
           console.error("clearOriginData: Error clearing origin data:", error);
         });
         break;
     }
   },
 
   _clearOriginData: function(data) {
     console.log("clearOriginData()");
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -128,128 +128,176 @@ struct ServiceWorkerManager::Registratio
   nsTArray<nsCString> mOrderedScopes;
 
   // Scope to registration.
   // The scope should be a fully qualified valid URL.
   nsRefPtrHashtable<nsCStringHashKey, ServiceWorkerRegistrationInfo> mInfos;
 
   // Maps scopes to job queues.
   nsClassHashtable<nsCStringHashKey, ServiceWorkerJobQueue> mJobQueues;
-
-  nsDataHashtable<nsCStringHashKey, bool> mSetOfScopesBeingUpdated;
 };
 
 struct ServiceWorkerManager::PendingOperation final
 {
   nsCOMPtr<nsIRunnable> mRunnable;
 
   ServiceWorkerJobQueue* mQueue;
   RefPtr<ServiceWorkerJob> mJob;
 
   ServiceWorkerRegistrationData mRegistration;
 };
 
 class ServiceWorkerJob : public nsISupports
 {
+  friend class ServiceWorkerJobQueue;
+
+public:
+  NS_DECL_ISUPPORTS
+
+  enum Type
+  {
+    RegisterJob,
+    UpdateJob,
+    InstallJob,
+    UnregisterJob
+  };
+
+  virtual void Start() = 0;
+
+  bool
+  IsRegisterOrInstallJob() const
+  {
+    return mJobType == RegisterJob || mJobType == UpdateJob ||
+      mJobType == InstallJob;
+  }
+
 protected:
   // The queue keeps the jobs alive, so they can hold a rawptr back to the
   // queue.
   ServiceWorkerJobQueue* mQueue;
 
-public:
-  NS_DECL_ISUPPORTS
-
-  virtual void Start() = 0;
-
-  virtual bool
-  IsRegisterJob() const { return false; }
-
-protected:
-  explicit ServiceWorkerJob(ServiceWorkerJobQueue* aQueue)
+  Type mJobType;
+
+  explicit ServiceWorkerJob(ServiceWorkerJobQueue* aQueue, Type aJobType)
     : mQueue(aQueue)
+    , mJobType(aJobType)
   {}
 
   virtual ~ServiceWorkerJob()
   {}
 
   void
   Done(nsresult aStatus);
 };
 
 class ServiceWorkerJobQueue final
 {
   friend class ServiceWorkerJob;
 
-  nsTArray<RefPtr<ServiceWorkerJob>> mJobs;
+  struct QueueData final
+  {
+    QueueData()
+      : mPopping(false)
+    { }
+
+    ~QueueData()
+    {
+      if (!mJobs.IsEmpty()) {
+        NS_WARNING("Pending/running jobs still around on shutdown!");
+      }
+    }
+
+    nsTArray<RefPtr<ServiceWorkerJob>> mJobs;
+    bool mPopping;
+  };
+
   const nsCString mOriginAttributesSuffix;
-  bool mPopping;
+  QueueData mRegistrationJobQueue;
+  QueueData mInstallationJobQueue;
 
 public:
   explicit ServiceWorkerJobQueue(const nsACString& aScopeKey)
     : mOriginAttributesSuffix(aScopeKey)
-    , mPopping(false)
   {}
 
   ~ServiceWorkerJobQueue()
-  {
-    if (!mJobs.IsEmpty()) {
-      NS_WARNING("Pending/running jobs still around on shutdown!");
-    }
-  }
+  { }
 
   void
   Append(ServiceWorkerJob* aJob)
   {
     MOZ_ASSERT(aJob);
-    MOZ_ASSERT(!mJobs.Contains(aJob));
-    bool wasEmpty = mJobs.IsEmpty();
-    mJobs.AppendElement(aJob);
+    QueueData& queue = GetQueue(aJob->mJobType);
+    MOZ_ASSERT(!queue.mJobs.Contains(aJob));
+
+    bool wasEmpty = queue.mJobs.IsEmpty();
+    queue.mJobs.AppendElement(aJob);
     if (wasEmpty) {
       aJob->Start();
     }
   }
 
   void
   CancelJobs();
 
-  // Only used by HandleError, keep it that way!
-  ServiceWorkerJob*
-  Peek()
-  {
-    if (mJobs.IsEmpty()) {
-      return nullptr;
-    }
-    return mJobs[0];
-  }
-
 private:
   void
-  Pop()
+  CancelJobs(QueueData& aQueue);
+
+  // Internal helper function used to assign jobs to the correct queue.
+  QueueData&
+  GetQueue(ServiceWorkerJob::Type aType)
   {
-    MOZ_ASSERT(!mPopping,
+    switch (aType) {
+    case ServiceWorkerJob::Type::RegisterJob:
+    case ServiceWorkerJob::Type::UpdateJob:
+    case ServiceWorkerJob::Type::UnregisterJob:
+      return mRegistrationJobQueue;
+    case ServiceWorkerJob::Type::InstallJob:
+      return mInstallationJobQueue;
+    default:
+      MOZ_CRASH("Invalid job queue type.");
+      return mRegistrationJobQueue;
+    }
+  }
+
+  bool
+  IsEmpty()
+  {
+    return mRegistrationJobQueue.mJobs.IsEmpty() &&
+      mInstallationJobQueue.mJobs.IsEmpty();
+  }
+
+  void
+  Pop(QueueData& aQueue)
+  {
+    MOZ_ASSERT(!aQueue.mPopping,
                "Pop() called recursively, did you write a job which calls Done() synchronously from Start()?");
-    AutoRestore<bool> savePopping(mPopping);
-    mPopping = true;
-    MOZ_ASSERT(!mJobs.IsEmpty());
-    mJobs.RemoveElementAt(0);
-    if (!mJobs.IsEmpty()) {
-      mJobs[0]->Start();
-    } else {
+
+    AutoRestore<bool> savePopping(aQueue.mPopping);
+    aQueue.mPopping = true;
+    MOZ_ASSERT(!aQueue.mJobs.IsEmpty());
+    aQueue.mJobs.RemoveElementAt(0);
+    if (!aQueue.mJobs.IsEmpty()) {
+      aQueue.mJobs[0]->Start();
+    } else if (IsEmpty()) {
       RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
       MOZ_ASSERT(swm);
       swm->MaybeRemoveRegistrationInfo(mOriginAttributesSuffix);
     }
   }
 
   void
   Done(ServiceWorkerJob* aJob)
   {
-    MOZ_ASSERT(!mJobs.IsEmpty());
-    MOZ_ASSERT(mJobs[0] == aJob);
-    Pop();
+    MOZ_ASSERT(aJob);
+    QueueData& queue = GetQueue(aJob->mJobType);
+    MOZ_ASSERT(!queue.mJobs.IsEmpty());
+    MOZ_ASSERT(queue.mJobs[0] == aJob);
+    Pop(queue);
   }
 };
 
 namespace {
 
 nsresult
 PopulateRegistrationData(nsIPrincipal* aPrincipal,
                          const ServiceWorkerRegistrationInfo* aRegistration,
@@ -370,16 +418,17 @@ ServiceWorkerRegistrationInfo::Clear()
 }
 
 ServiceWorkerRegistrationInfo::ServiceWorkerRegistrationInfo(const nsACString& aScope,
                                                              nsIPrincipal* aPrincipal)
   : mControlledDocumentsCounter(0)
   , mScope(aScope)
   , mPrincipal(aPrincipal)
   , mLastUpdateCheckTime(0)
+  , mUpdating(false)
   , mPendingUninstall(false)
 {}
 
 ServiceWorkerRegistrationInfo::~ServiceWorkerRegistrationInfo()
 {
   if (IsControllingDocuments()) {
     NS_WARNING("ServiceWorkerRegistrationInfo is still controlling documents. This can be a bug or a leak in ServiceWorker API or in any other API that takes the document alive.");
   }
@@ -551,24 +600,24 @@ protected:
   {}
 
 public:
   virtual void ContinueAfterWorkerEvent(bool aSuccess) = 0;
 };
 
 NS_IMPL_ISUPPORTS0(ContinueLifecycleTask);
 
-class ServiceWorkerRegisterJob;
+class ServiceWorkerInstallJob;
 
 class ContinueInstallTask final : public ContinueLifecycleTask
 {
-  RefPtr<ServiceWorkerRegisterJob> mJob;
+  RefPtr<ServiceWorkerInstallJob> mJob;
 
 public:
-  explicit ContinueInstallTask(ServiceWorkerRegisterJob* aJob)
+  explicit ContinueInstallTask(ServiceWorkerInstallJob* aJob)
     : mJob(aJob)
   {}
 
   void ContinueAfterWorkerEvent(bool aSuccess) override;
 };
 
 class ContinueActivateTask final : public ContinueLifecycleTask
 {
@@ -626,38 +675,45 @@ public:
     : mWindow(aWindow)
     , mPromise(aPromise)
   {}
 
   void
   UpdateSucceeded(ServiceWorkerRegistrationInfo* aInfo) override
   {
     RefPtr<ServiceWorkerRegistrationMainThread> swr =
-      new ServiceWorkerRegistrationMainThread(mWindow,
-                                              NS_ConvertUTF8toUTF16(aInfo->mScope));
+      mWindow->GetServiceWorkerRegistration(NS_ConvertUTF8toUTF16(aInfo->mScope));
     mPromise->MaybeResolve(swr);
   }
 
   void
   UpdateFailed(ErrorResult& aStatus) override
   {
     mPromise->MaybeReject(aStatus);
   }
 };
 
-class ContinueUpdateRunnable final : public nsRunnable
+class ContinueUpdateRunnable final : public LifeCycleEventCallback
 {
   nsMainThreadPtrHandle<nsISupports> mJob;
+  bool mScriptEvaluationResult;
 public:
   explicit ContinueUpdateRunnable(const nsMainThreadPtrHandle<nsISupports> aJob)
     : mJob(aJob)
+    , mScriptEvaluationResult(false)
   {
     AssertIsOnMainThread();
   }
 
+  void
+  SetResult(bool aResult)
+  {
+    mScriptEvaluationResult = aResult;
+  }
+
   NS_IMETHOD Run();
 };
 
 namespace {
 
 /**
  * The spec mandates slightly different behaviors for computing the scope
  * prefix string in case a Service-Worker-Allowed header is specified versus
@@ -850,118 +906,323 @@ public:
 
 private:
   ~PropagateRemoveAllRunnable()
   {}
 };
 
 } // namespace
 
-class ServiceWorkerRegisterJob final : public ServiceWorkerJob,
+class ServiceWorkerJobBase : public ServiceWorkerJob
+{
+public:
+  ServiceWorkerJobBase(ServiceWorkerJobQueue* aQueue,
+                       ServiceWorkerJob::Type aJobType,
+                       ServiceWorkerUpdateFinishCallback* aCallback)
+    : ServiceWorkerJobBase(aQueue, aJobType, aCallback, nullptr, nullptr)
+  { }
+
+  ServiceWorkerJobBase(ServiceWorkerJobQueue* aQueue,
+                       ServiceWorkerJob::Type aJobType,
+                       ServiceWorkerUpdateFinishCallback* aCallback,
+                       ServiceWorkerRegistrationInfo* aRegistration,
+                       ServiceWorkerInfo* aServiceWorkerInfo)
+    : ServiceWorkerJob(aQueue, aJobType)
+    , mCallback(aCallback)
+    , mCanceled(false)
+    , mRegistration(aRegistration)
+    , mUpdateAndInstallInfo(aServiceWorkerInfo)
+  {
+    AssertIsOnMainThread();
+  }
+
+  void
+  Cancel()
+  {
+    mQueue = nullptr;
+    mCanceled = true;
+  }
+
+protected:
+  RefPtr<ServiceWorkerUpdateFinishCallback> mCallback;
+  bool mCanceled;
+  RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
+  RefPtr<ServiceWorkerInfo> mUpdateAndInstallInfo;
+
+  ~ServiceWorkerJobBase()
+  { }
+
+  // This MUST only be called when the job is still performing actions related
+  // to registration or update. After the spec resolves the update promise, use
+  // Done() with the failure code instead.
+  // Callers MUST hold a strong ref before calling this!
+  void
+  Fail(ErrorResult& aRv)
+  {
+    AssertIsOnMainThread();
+    MOZ_ASSERT(mRegistration);
+
+    // With cancellation support, we may only be running with one reference
+    // from another object like a stream loader or something.
+    RefPtr<ServiceWorkerJob> kungFuDeathGrip = this;
+
+    // Save off the plain error code to pass to Done() where its logged to
+    // stderr as a warning.
+    nsresult origStatus = static_cast<nsresult>(aRv.ErrorCodeAsInt());
+
+    // Ensure that we only surface SecurityErr or TypeErr to script.
+    if (aRv.Failed() && !aRv.ErrorCodeIs(NS_ERROR_DOM_SECURITY_ERR) &&
+                        !aRv.ErrorCodeIs(NS_ERROR_DOM_TYPE_ERR)) {
+
+      // Remove the old error code so we can replace it with a TypeError.
+      aRv.SuppressException();
+
+      NS_ConvertUTF8toUTF16 scriptSpec(mRegistration->mScriptSpec);
+      NS_ConvertUTF8toUTF16 scope(mRegistration->mScope);
+
+      // Throw the type error with a generic error message.
+      aRv.ThrowTypeError<MSG_SW_INSTALL_ERROR>(scriptSpec, scope);
+    }
+
+    if (mCallback) {
+      mCallback->UpdateFailed(aRv);
+      mCallback = nullptr;
+    }
+    // In case the callback does not consume the exception
+    aRv.SuppressException();
+
+    mUpdateAndInstallInfo = nullptr;
+    if (mRegistration->mInstallingWorker) {
+      nsresult rv = serviceWorkerScriptCache::PurgeCache(mRegistration->mPrincipal,
+                                                         mRegistration->mInstallingWorker->CacheName());
+      if (NS_FAILED(rv)) {
+        NS_WARNING("Failed to purge the installing worker cache.");
+      }
+    }
+
+    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+    swm->MaybeRemoveRegistration(mRegistration);
+    // Ensures that the job can't do anything useful from this point on.
+    mRegistration = nullptr;
+    Done(origStatus);
+  }
+
+  void
+  Fail(nsresult aRv)
+  {
+    ErrorResult rv(aRv);
+    Fail(rv);
+  }
+
+  void
+  Succeed()
+  {
+    AssertIsOnMainThread();
+    // We don't have a callback for soft updates.
+    if (mCallback) {
+      mCallback->UpdateSucceeded(mRegistration);
+      mCallback = nullptr;
+    }
+  }
+};
+
+class ServiceWorkerInstallJob final : public ServiceWorkerJobBase
+{
+  friend class ContinueInstallTask;
+
+public:
+  ServiceWorkerInstallJob(ServiceWorkerJobQueue* aQueue,
+                          ServiceWorkerUpdateFinishCallback* aCallback,
+                          ServiceWorkerRegistrationInfo* aRegistration,
+                          ServiceWorkerInfo* aServiceWorkerInfo)
+    : ServiceWorkerJobBase(aQueue, Type::InstallJob, aCallback,
+                           aRegistration, aServiceWorkerInfo)
+  {
+    MOZ_ASSERT(aRegistration);
+  }
+
+  void
+  Start()
+  {
+    AssertIsOnMainThread();
+    nsCOMPtr<nsIRunnable> r =
+      NS_NewRunnableMethod(this, &ServiceWorkerInstallJob::Install);
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(r)));
+  }
+
+  void
+  Install()
+  {
+    RefPtr<ServiceWorkerJob> kungFuDeathGrip = this;
+    if (mCanceled) {
+      return Fail(NS_ERROR_DOM_ABORT_ERR);
+    }
+    MOZ_ASSERT(mRegistration);
+
+    // Begin [[Install]] atomic step 3.
+    if (mRegistration->mInstallingWorker) {
+      mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Redundant);
+      mRegistration->mInstallingWorker->WorkerPrivate()->TerminateWorker();
+    }
+
+    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+    swm->InvalidateServiceWorkerRegistrationWorker(mRegistration,
+                                                   WhichServiceWorker::INSTALLING_WORKER);
+
+    mRegistration->mInstallingWorker = mUpdateAndInstallInfo.forget();
+    mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Installing);
+    mRegistration->NotifyListenersOnChange();
+
+    Succeed();
+
+    // The job should NOT call fail from this point on.
+
+    // Step 8 "Queue a task..." for updatefound.
+    nsCOMPtr<nsIRunnable> upr =
+      NS_NewRunnableMethodWithArg<ServiceWorkerRegistrationInfo*>(
+        swm,
+        &ServiceWorkerManager::FireUpdateFoundOnServiceWorkerRegistrations,
+        mRegistration);
+
+    NS_DispatchToMainThread(upr);
+
+    // Call ContinueAfterInstallEvent(false) on main thread if the SW
+    // script fails to load.
+    nsCOMPtr<nsIRunnable> failRunnable = NS_NewRunnableMethodWithArgs<bool>
+      (this, &ServiceWorkerInstallJob::ContinueAfterInstallEvent, false);
+
+    nsMainThreadPtrHandle<ContinueLifecycleTask> installTask(
+      new nsMainThreadPtrHolder<ContinueLifecycleTask>(new ContinueInstallTask(this)));
+    RefPtr<LifeCycleEventCallback> callback = new ContinueLifecycleRunnable(installTask);
+
+    // This triggers Step 4.7 "Queue a task to run the following substeps..."
+    // which sends the install event to the worker.
+    ServiceWorkerPrivate* workerPrivate =
+      mRegistration->mInstallingWorker->WorkerPrivate();
+    nsresult rv = workerPrivate->SendLifeCycleEvent(NS_LITERAL_STRING("install"),
+                                                    callback, failRunnable);
+
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      ContinueAfterInstallEvent(false /* aSuccess */);
+    }
+  }
+
+  void
+  ContinueAfterInstallEvent(bool aInstallEventSuccess)
+  {
+    if (mCanceled) {
+      return Done(NS_ERROR_DOM_ABORT_ERR);
+    }
+
+    if (!mRegistration->mInstallingWorker) {
+      NS_WARNING("mInstallingWorker was null.");
+      return Done(NS_ERROR_DOM_ABORT_ERR);
+    }
+
+    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+
+    // "If installFailed is true"
+    if (NS_WARN_IF(!aInstallEventSuccess)) {
+      mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Redundant);
+      mRegistration->mInstallingWorker = nullptr;
+      swm->InvalidateServiceWorkerRegistrationWorker(mRegistration,
+                                                     WhichServiceWorker::INSTALLING_WORKER);
+      swm->MaybeRemoveRegistration(mRegistration);
+      return Done(NS_ERROR_DOM_ABORT_ERR);
+    }
+
+    // "If registration's waiting worker is not null"
+    if (mRegistration->mWaitingWorker) {
+      mRegistration->mWaitingWorker->WorkerPrivate()->TerminateWorker();
+      mRegistration->mWaitingWorker->UpdateState(ServiceWorkerState::Redundant);
+
+      nsresult rv =
+        serviceWorkerScriptCache::PurgeCache(mRegistration->mPrincipal,
+                                             mRegistration->mWaitingWorker->CacheName());
+      if (NS_FAILED(rv)) {
+        NS_WARNING("Failed to purge the old waiting cache.");
+      }
+    }
+
+    mRegistration->mWaitingWorker = mRegistration->mInstallingWorker.forget();
+    mRegistration->mWaitingWorker->UpdateState(ServiceWorkerState::Installed);
+    mRegistration->NotifyListenersOnChange();
+    swm->InvalidateServiceWorkerRegistrationWorker(mRegistration,
+                                                   WhichServiceWorker::INSTALLING_WORKER | WhichServiceWorker::WAITING_WORKER);
+
+    // "If registration's waiting worker's skip waiting flag is set"
+    if (mRegistration->mWaitingWorker->SkipWaitingFlag()) {
+      mRegistration->PurgeActiveWorker();
+    }
+
+    Done(NS_OK);
+    // Activate() is invoked out of band of atomic.
+    mRegistration->TryToActivate();
+  }
+};
+
+class ServiceWorkerRegisterJob final : public ServiceWorkerJobBase,
                                        public serviceWorkerScriptCache::CompareCallback
 {
-  friend class ContinueInstallTask;
+  friend class ContinueUpdateRunnable;
 
   nsCString mScope;
   nsCString mScriptSpec;
-  RefPtr<ServiceWorkerRegistrationInfo> mRegistration;
-  nsTArray<RefPtr<ServiceWorkerUpdateFinishCallback>> mCallbacks;
   nsCOMPtr<nsIPrincipal> mPrincipal;
-  RefPtr<ServiceWorkerInfo> mUpdateAndInstallInfo;
   nsCOMPtr<nsILoadGroup> mLoadGroup;
 
   ~ServiceWorkerRegisterJob()
-  {}
-
-  enum
-  {
-    REGISTER_JOB = 0,
-    UPDATE_JOB = 1,
-  } mJobType;
-
-  bool mCanceled;
+  { }
 
 public:
   NS_DECL_ISUPPORTS_INHERITED
 
   // [[Register]]
   ServiceWorkerRegisterJob(ServiceWorkerJobQueue* aQueue,
                            const nsCString& aScope,
                            const nsCString& aScriptSpec,
                            ServiceWorkerUpdateFinishCallback* aCallback,
                            nsIPrincipal* aPrincipal,
                            nsILoadGroup* aLoadGroup)
-    : ServiceWorkerJob(aQueue)
+    : ServiceWorkerJobBase(aQueue, Type::RegisterJob, aCallback)
     , mScope(aScope)
     , mScriptSpec(aScriptSpec)
     , mPrincipal(aPrincipal)
     , mLoadGroup(aLoadGroup)
-    , mJobType(REGISTER_JOB)
-    , mCanceled(false)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(mLoadGroup);
     MOZ_ASSERT(aCallback);
-
-    mCallbacks.AppendElement(aCallback);
   }
 
   // [[Update]]
   ServiceWorkerRegisterJob(ServiceWorkerJobQueue* aQueue,
                            ServiceWorkerRegistrationInfo* aRegistration,
                            ServiceWorkerUpdateFinishCallback* aCallback)
-    : ServiceWorkerJob(aQueue)
-    , mRegistration(aRegistration)
-    , mJobType(UPDATE_JOB)
-    , mCanceled(false)
+    : ServiceWorkerJobBase(aQueue, Type::UpdateJob, aCallback,
+                           aRegistration, nullptr)
   {
     AssertIsOnMainThread();
-    MOZ_ASSERT(aCallback);
-
-    mCallbacks.AppendElement(aCallback);
-  }
-
-  bool
-  IsRegisterJob() const override
-  {
-    return true;
-  }
-
-  void
-  AppendCallback(ServiceWorkerUpdateFinishCallback* aCallback)
-  {
-    AssertIsOnMainThread();
-    MOZ_ASSERT(aCallback);
-    MOZ_ASSERT(!mCallbacks.Contains(aCallback));
-
-    mCallbacks.AppendElement(aCallback);
-  }
-
-  void
-  Cancel()
-  {
-    mQueue = nullptr;
-    mCanceled = true;
   }
 
   void
   Start() override
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(!mCanceled);
 
     RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
     if (!swm->HasBackgroundActor()) {
       nsCOMPtr<nsIRunnable> runnable =
         NS_NewRunnableMethod(this, &ServiceWorkerRegisterJob::Start);
       swm->AppendPendingOperation(runnable);
       return;
     }
 
-    if (mJobType == REGISTER_JOB) {
+    if (mJobType == RegisterJob) {
       mRegistration = swm->GetRegistration(mPrincipal, mScope);
 
       if (mRegistration) {
         mRegistration->mPendingUninstall = false;
         RefPtr<ServiceWorkerInfo> newest = mRegistration->Newest();
         if (newest && mScriptSpec.Equals(newest->ScriptSpec()) &&
             mScriptSpec.Equals(mRegistration->mScriptSpec)) {
           swm->StoreRegistration(mPrincipal, mRegistration);
@@ -980,21 +1241,17 @@ public:
       } else {
         mRegistration = swm->CreateNewRegistration(mScope, mPrincipal);
       }
 
       mRegistration->mScriptSpec = mScriptSpec;
       mRegistration->NotifyListenersOnChange();
       swm->StoreRegistration(mPrincipal, mRegistration);
     } else {
-      MOZ_ASSERT(mJobType == UPDATE_JOB);
-      MOZ_ASSERT(mRegistration);
-      MOZ_ASSERT(mRegistration->mUpdateJob == nullptr);
-
-      mRegistration->mUpdateJob = this;
+      MOZ_ASSERT(mJobType == UpdateJob);
     }
 
     Update();
   }
 
   void
   ComparisonResult(nsresult aStatus, bool aInCacheAndEqual,
                    const nsAString& aNewCacheName,
@@ -1066,242 +1323,95 @@ public:
       return Fail(NS_ERROR_FAILURE);
     }
 
     ServiceWorkerManager::RegistrationDataPerPrincipal* data;
     if (!swm->mRegistrationInfos.Get(scopeKey, &data)) {
       return Fail(NS_ERROR_FAILURE);
     }
 
-    nsAutoString cacheName;
-    // We have to create a ServiceWorker here simply to ensure there are no
-    // errors. Ideally we should just pass this worker on to ContinueInstall.
-    MOZ_ASSERT(!data->mSetOfScopesBeingUpdated.Contains(mRegistration->mScope));
-    data->mSetOfScopesBeingUpdated.Put(mRegistration->mScope, true);
-
-    // Call FailScopeUpdate on main thread if the SW script load fails below.
-    nsCOMPtr<nsIRunnable> failRunnable = NS_NewRunnableMethodWithArgs
-      <StorensRefPtrPassByPtr<ServiceWorkerManager>, nsCString>
-      (this, &ServiceWorkerRegisterJob::FailScopeUpdate, swm, scopeKey);
-
     MOZ_ASSERT(!mUpdateAndInstallInfo);
     mUpdateAndInstallInfo =
       new ServiceWorkerInfo(mRegistration, mRegistration->mScriptSpec,
                             aNewCacheName);
 
     RefPtr<ServiceWorkerJob> upcasted = this;
     nsMainThreadPtrHandle<nsISupports> handle(
         new nsMainThreadPtrHolder<nsISupports>(upcasted));
-    RefPtr<nsRunnable> callback = new ContinueUpdateRunnable(handle);
+    RefPtr<LifeCycleEventCallback> callback = new ContinueUpdateRunnable(handle);
 
     ServiceWorkerPrivate* workerPrivate =
       mUpdateAndInstallInfo->WorkerPrivate();
-    rv = workerPrivate->ContinueOnSuccessfulScriptEvaluation(callback);
+    rv = workerPrivate->CheckScriptEvaluation(callback);
 
     if (NS_WARN_IF(NS_FAILED(rv))) {
-      return FailScopeUpdate(swm, scopeKey);
+      Fail(NS_ERROR_DOM_ABORT_ERR);
     }
   }
 
+private:
+  // This will perform steps 27 and 28 from [[Update]]
+  // Remove the job from the registration queue and invoke [[Install]]
   void
-  FailScopeUpdate(ServiceWorkerManager* aSwm, const nsACString& aScopeKey)
-  {
-    AssertIsOnMainThread();
-    MOZ_ASSERT(aSwm);
-    ServiceWorkerManager::RegistrationDataPerPrincipal* data;
-    if (aSwm->mRegistrationInfos.Get(aScopeKey, &data)) {
-      data->mSetOfScopesBeingUpdated.Remove(aScopeKey);
-    }
-    Fail(NS_ERROR_DOM_ABORT_ERR);
-  }
-
-  // This MUST only be called when the job is still performing actions related
-  // to registration or update. After the spec resolves the update promise, use
-  // Done() with the failure code instead.
-  // Callers MUST hold a strong ref before calling this!
-  void
-  Fail(ErrorResult& aRv)
+  ContinueInstall(bool aScriptEvaluationResult)
   {
     AssertIsOnMainThread();
-    MOZ_ASSERT(mCallbacks.Length());
-
-    // With cancellation support, we may only be running with one reference
-    // from another object like a stream loader or something.
-    RefPtr<ServiceWorkerRegisterJob> kungFuDeathGrip = this;
-
-    // Save off the plain error code to pass to Done() where its logged to
-    // stderr as a warning.
-    nsresult origStatus = static_cast<nsresult>(aRv.ErrorCodeAsInt());
-
-    // Ensure that we only surface SecurityErr or TypeErr to script.
-    if (aRv.Failed() && !aRv.ErrorCodeIs(NS_ERROR_DOM_SECURITY_ERR) &&
-                        !aRv.ErrorCodeIs(NS_ERROR_DOM_TYPE_ERR)) {
-
-      // Remove the old error code so we can replace it with a TypeError.
-      aRv.SuppressException();
-
-      // Depending on how the job was created and where we are in the
-      // state machine the spec and scope may be stored in different ways.
-      // Extract the current scope and script spec.
-      nsString scriptSpec;
-      nsString scope;
-      if (mRegistration) {
-        CopyUTF8toUTF16(mRegistration->mScriptSpec, scriptSpec);
-        CopyUTF8toUTF16(mRegistration->mScope, scope);
-      } else {
-        CopyUTF8toUTF16(mScriptSpec, scriptSpec);
-        CopyUTF8toUTF16(mScope, scope);
-      }
-
-      // Throw the type error with a generic error message.
-      aRv.ThrowTypeError<MSG_SW_INSTALL_ERROR>(scriptSpec, scope);
-    }
-
-    for (uint32_t i = 1; i < mCallbacks.Length(); ++i) {
-      ErrorResult rv;
-      aRv.CloneTo(rv);
-      mCallbacks[i]->UpdateFailed(rv);
-      rv.SuppressException();
-    }
-
-    mCallbacks[0]->UpdateFailed(aRv);
-
-    // In case the callback does not consume the exception
-    aRv.SuppressException();
-
-    mUpdateAndInstallInfo = nullptr;
-    if (mRegistration->mInstallingWorker) {
-      nsresult rv = serviceWorkerScriptCache::PurgeCache(mRegistration->mPrincipal,
-                                                         mRegistration->mInstallingWorker->CacheName());
-      if (NS_FAILED(rv)) {
-        NS_WARNING("Failed to purge the installing worker cache.");
-      }
-    }
-
-    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
-    swm->MaybeRemoveRegistration(mRegistration);
-    // Ensures that the job can't do anything useful from this point on.
-    mRegistration->mUpdateJob = nullptr;
-    mRegistration = nullptr;
-    Done(origStatus);
-  }
-
-  void
-  Fail(nsresult aRv)
-  {
-    ErrorResult rv(aRv);
-    Fail(rv);
-  }
-
-  // Public so our error handling code can continue with a successful worker.
-  void
-  ContinueInstall()
-  {
-    // mRegistration will be null if we have already Fail()ed.
-    if (!mRegistration) {
-      return;
-    }
-
-    // Even if we are canceled, ensure integrity of mSetOfScopesBeingUpdated
-    // first.
-    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
-
-    nsAutoCString scopeKey;
-    nsresult rv = swm->PrincipalToScopeKey(mRegistration->mPrincipal, scopeKey);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return Fail(NS_ERROR_FAILURE);
-    }
-
-    ServiceWorkerManager::RegistrationDataPerPrincipal* data;
-    if (!swm->mRegistrationInfos.Get(scopeKey, &data)) {
-      return Fail(NS_ERROR_FAILURE);
-    }
-
-    MOZ_ASSERT(data->mSetOfScopesBeingUpdated.Contains(mRegistration->mScope));
-    data->mSetOfScopesBeingUpdated.Remove(mRegistration->mScope);
-    // This is effectively the end of Step 4.3 of the [[Update]] algorithm.
-    // The invocation of [[Install]] is not part of the atomic block.
+    MOZ_ASSERT(mRegistration);
+    mRegistration->mUpdating = false;
 
     RefPtr<ServiceWorkerRegisterJob> kungFuDeathGrip = this;
     if (mCanceled) {
       return Fail(NS_ERROR_DOM_ABORT_ERR);
     }
 
-    // Begin [[Install]] atomic step 4.
-    if (mRegistration->mInstallingWorker) {
-      mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Redundant);
-      mRegistration->mInstallingWorker->WorkerPrivate()->TerminateWorker();
+    if (NS_WARN_IF(!aScriptEvaluationResult)) {
+      ErrorResult error;
+
+      NS_ConvertUTF8toUTF16 scriptSpec(mRegistration->mScriptSpec);
+      NS_ConvertUTF8toUTF16 scope(mRegistration->mScope);
+      error.ThrowTypeError<MSG_SW_SCRIPT_THREW>(scriptSpec, scope);
+      return Fail(error);
     }
 
-    swm->InvalidateServiceWorkerRegistrationWorker(mRegistration,
-                                                   WhichServiceWorker::INSTALLING_WORKER);
-
-    mRegistration->mInstallingWorker = mUpdateAndInstallInfo.forget();
-    mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Installing);
-    mRegistration->NotifyListenersOnChange();
-
-    Succeed();
-    // The job should NOT call fail from this point on.
-
-    // Step 4.6 "Queue a task..." for updatefound.
-    nsCOMPtr<nsIRunnable> upr =
-      NS_NewRunnableMethodWithArg<ServiceWorkerRegistrationInfo*>(
-        swm,
-        &ServiceWorkerManager::FireUpdateFoundOnServiceWorkerRegistrations,
-        mRegistration);
-
-    NS_DispatchToMainThread(upr);
-
-    // Call ContinueAfterInstallEvent(false) on main thread if the SW
-    // script fails to load.
-    nsCOMPtr<nsIRunnable> failRunnable = NS_NewRunnableMethodWithArgs<bool>
-      (this, &ServiceWorkerRegisterJob::ContinueAfterInstallEvent, false);
-
-    nsMainThreadPtrHandle<ContinueLifecycleTask> installTask(
-      new nsMainThreadPtrHolder<ContinueLifecycleTask>(new ContinueInstallTask(this)));
-    RefPtr<LifeCycleEventCallback> callback = new ContinueLifecycleRunnable(installTask);
-
-    // This triggers Step 4.7 "Queue a task to run the following substeps..."
-    // which sends the install event to the worker.
-    ServiceWorkerPrivate* workerPrivate =
-      mRegistration->mInstallingWorker->WorkerPrivate();
-    rv = workerPrivate->SendLifeCycleEvent(NS_LITERAL_STRING("install"),
-                                           callback, failRunnable);
-
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      ContinueAfterInstallEvent(false /* aSuccess */);
-    }
-  }
-
-private:
+    RefPtr<ServiceWorkerInstallJob> job =
+      new ServiceWorkerInstallJob(mQueue, mCallback,
+                                  mRegistration, mUpdateAndInstallInfo);
+    mQueue->Append(job);
+    Done(NS_OK);
+  }
+
   void
   Update()
   {
+    AssertIsOnMainThread();
+
     // Since Update() is called synchronously from Start(), we can assert this.
     MOZ_ASSERT(!mCanceled);
     MOZ_ASSERT(mRegistration);
     nsCOMPtr<nsIRunnable> r =
       NS_NewRunnableMethod(this, &ServiceWorkerRegisterJob::ContinueUpdate);
     NS_DispatchToMainThread(r);
+
+    mRegistration->mUpdating = true;
   }
 
   // Aspects of (actually the whole algorithm) of [[Update]] after
   // "Run the following steps in parallel."
   void
   ContinueUpdate()
   {
     AssertIsOnMainThread();
     RefPtr<ServiceWorkerRegisterJob> kungFuDeathGrip = this;
     if (mCanceled) {
       return Fail(NS_ERROR_DOM_ABORT_ERR);
     }
 
     if (mRegistration->mInstallingWorker) {
       mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Redundant);
-      // This will terminate the installing worker thread.
+      mRegistration->mInstallingWorker->WorkerPrivate()->TerminateWorker();
       mRegistration->mInstallingWorker = nullptr;
     }
 
     RefPtr<ServiceWorkerInfo> workerInfo = mRegistration->Newest();
     nsAutoString cacheName;
 
     // 9.2.20 If newestWorker is not null, and newestWorker's script url is
     // equal to registration's registering script url and response is a
@@ -1315,123 +1425,67 @@ private:
                                         NS_ConvertUTF8toUTF16(mRegistration->mScriptSpec),
                                         this, mLoadGroup);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return Fail(rv);
     }
   }
 
   void
-  Succeed()
+  Done(nsresult aStatus)
   {
     AssertIsOnMainThread();
-    MOZ_ASSERT(mCallbacks.Length());
-
-    for (uint32_t i = 0; i < mCallbacks.Length(); ++i) {
-      mCallbacks[i]->UpdateSucceeded(mRegistration);
-    }
-    mCallbacks.Clear();
-  }
-
-  void
-  ContinueAfterInstallEvent(bool aInstallEventSuccess)
-  {
-    if (mCanceled) {
-      return Done(NS_ERROR_DOM_ABORT_ERR);
-    }
-
-    if (!mRegistration->mInstallingWorker) {
-      NS_WARNING("mInstallingWorker was null.");
-      return Done(NS_ERROR_DOM_ABORT_ERR);
-    }
-
-    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
-
-    // "If installFailed is true"
-    if (NS_WARN_IF(!aInstallEventSuccess)) {
-      mRegistration->mInstallingWorker->UpdateState(ServiceWorkerState::Redundant);
-      mRegistration->mInstallingWorker = nullptr;
-      swm->InvalidateServiceWorkerRegistrationWorker(mRegistration,
-                                                     WhichServiceWorker::INSTALLING_WORKER);
-      swm->MaybeRemoveRegistration(mRegistration);
-      return Done(NS_ERROR_DOM_ABORT_ERR);
+
+    if (mRegistration) {
+      mRegistration->mUpdating = false;
     }
 
-    // "If registration's waiting worker is not null"
-    if (mRegistration->mWaitingWorker) {
-      mRegistration->mWaitingWorker->WorkerPrivate()->TerminateWorker();
-      mRegistration->mWaitingWorker->UpdateState(ServiceWorkerState::Redundant);
-
-      nsresult rv = serviceWorkerScriptCache::PurgeCache(mRegistration->mPrincipal,
-                                                         mRegistration->mWaitingWorker->CacheName());
-      if (NS_FAILED(rv)) {
-        NS_WARNING("Failed to purge the old waiting cache.");
-      }
-    }
-
-    mRegistration->mWaitingWorker = mRegistration->mInstallingWorker.forget();
-    mRegistration->mWaitingWorker->UpdateState(ServiceWorkerState::Installed);
-    mRegistration->NotifyListenersOnChange();
-    swm->InvalidateServiceWorkerRegistrationWorker(mRegistration,
-                                                   WhichServiceWorker::INSTALLING_WORKER | WhichServiceWorker::WAITING_WORKER);
-
-    // "If registration's waiting worker's skip waiting flag is set"
-    if (mRegistration->mWaitingWorker->SkipWaitingFlag()) {
-      mRegistration->PurgeActiveWorker();
-    }
-
-    Done(NS_OK);
-    // Activate() is invoked out of band of atomic.
-    mRegistration->TryToActivate();
-  }
-
-  void
-  Done(nsresult aStatus)
-  {
     ServiceWorkerJob::Done(aStatus);
-
-    if (mJobType == UPDATE_JOB && mRegistration) {
-      MOZ_ASSERT(NS_IsMainThread());
-      MOZ_ASSERT(mRegistration->mUpdateJob);
-      mRegistration->mUpdateJob = nullptr;
-    }
   }
 };
 
 NS_IMPL_ISUPPORTS_INHERITED0(ServiceWorkerRegisterJob, ServiceWorkerJob);
 
 void
 ServiceWorkerJobQueue::CancelJobs()
 {
-  if (mJobs.IsEmpty()) {
+  // The order doesn't matter. Cancel() just sets a flag on these jobs.
+  CancelJobs(mRegistrationJobQueue);
+  CancelJobs(mInstallationJobQueue);
+}
+
+void
+ServiceWorkerJobQueue::CancelJobs(QueueData& aQueue)
+{
+  if (aQueue.mJobs.IsEmpty()) {
     return;
   }
 
   // We have to treat the first job specially. It is the running job and needs
   // to be notified correctly.
-  RefPtr<ServiceWorkerJob> runningJob = mJobs[0];
+  RefPtr<ServiceWorkerJob> runningJob = aQueue.mJobs[0];
   // We can just let an Unregister job run to completion.
-  if (runningJob->IsRegisterJob()) {
-    ServiceWorkerRegisterJob* job = static_cast<ServiceWorkerRegisterJob*>(runningJob.get());
+  if (runningJob->IsRegisterOrInstallJob()) {
+    ServiceWorkerJobBase* job = static_cast<ServiceWorkerJobBase*>(runningJob.get());
     job->Cancel();
   }
 
   // Get rid of everything. Non-main thread objects may still be holding a ref
   // to the running register job. Since we called Cancel() on it, the job's
   // main thread functions will just exit.
-  mJobs.Clear();
+  aQueue.mJobs.Clear();
 }
 
 NS_IMETHODIMP
 ContinueUpdateRunnable::Run()
 {
   AssertIsOnMainThread();
   RefPtr<ServiceWorkerJob> job = static_cast<ServiceWorkerJob*>(mJob.get());
   RefPtr<ServiceWorkerRegisterJob> upjob = static_cast<ServiceWorkerRegisterJob*>(job.get());
-  upjob->ContinueInstall();
+  upjob->ContinueInstall(mScriptEvaluationResult);
   return NS_OK;
 }
 
 void
 ContinueInstallTask::ContinueAfterWorkerEvent(bool aSuccess)
 {
   // This does not start the job immediately if there are other jobs in the
   // queue, which captures the "atomic" behaviour we want.
@@ -1800,17 +1854,17 @@ public:
 
       rv = principal->CheckMayLoad(scopeURI, true /* report */,
                                    false /* allowIfInheritsPrincipal */);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         continue;
       }
 
       RefPtr<ServiceWorkerRegistrationMainThread> swr =
-        new ServiceWorkerRegistrationMainThread(mWindow, scope);
+        mWindow->GetServiceWorkerRegistration(scope);
 
       array.AppendElement(swr);
     }
 
     mPromise->MaybeResolve(array);
     return NS_OK;
   }
 };
@@ -1907,17 +1961,17 @@ public:
 
     if (!registration) {
       mPromise->MaybeResolve(JS::UndefinedHandleValue);
       return NS_OK;
     }
 
     NS_ConvertUTF8toUTF16 scope(registration->mScope);
     RefPtr<ServiceWorkerRegistrationMainThread> swr =
-      new ServiceWorkerRegistrationMainThread(mWindow, scope);
+      mWindow->GetServiceWorkerRegistration(scope);
     mPromise->MaybeResolve(swr);
 
     return NS_OK;
   }
 };
 
 // If we return an error code here, the ServiceWorkerContainer will
 // automatically reject the Promise.
@@ -2169,17 +2223,17 @@ ServiceWorkerManager::CheckReadyPromise(
   MOZ_ASSERT(principal);
 
   RefPtr<ServiceWorkerRegistrationInfo> registration =
     GetServiceWorkerRegistrationInfo(principal, aURI);
 
   if (registration && registration->mActiveWorker) {
     NS_ConvertUTF8toUTF16 scope(registration->mScope);
     RefPtr<ServiceWorkerRegistrationMainThread> swr =
-      new ServiceWorkerRegistrationMainThread(aWindow, scope);
+      aWindow->GetServiceWorkerRegistration(scope);
     aPromise->MaybeResolve(swr);
     return true;
   }
 
   return false;
 }
 
 ServiceWorkerInfo*
@@ -2227,17 +2281,17 @@ class ServiceWorkerUnregisterJob final :
   ~ServiceWorkerUnregisterJob()
   {}
 
 public:
   ServiceWorkerUnregisterJob(ServiceWorkerJobQueue* aQueue,
                              const nsACString& aScope,
                              nsIServiceWorkerUnregisterCallback* aCallback,
                              nsIPrincipal* aPrincipal)
-    : ServiceWorkerJob(aQueue)
+    : ServiceWorkerJob(aQueue, Type::UnregisterJob)
     , mScope(aScope)
     , mCallback(aCallback)
     , mPrincipal(aPrincipal)
   {
     AssertIsOnMainThread();
   }
 
   void
@@ -2582,38 +2636,16 @@ ServiceWorkerManager::HandleError(JSCont
     return;
   }
 
   ServiceWorkerManager::RegistrationDataPerPrincipal* data;
   if (NS_WARN_IF(!mRegistrationInfos.Get(scopeKey, &data))) {
     return;
   }
 
-  // If this is a failure, we may need to cancel an in-progress registration.
-  if (!JSREPORT_IS_WARNING(aFlags) &&
-      data->mSetOfScopesBeingUpdated.Contains(aScope)) {
-
-    data->mSetOfScopesBeingUpdated.Remove(aScope);
-
-    ServiceWorkerJobQueue* queue = data->mJobQueues.Get(aScope);
-    MOZ_ASSERT(queue);
-
-    ServiceWorkerJob* job = queue->Peek();
-    if (job) {
-      MOZ_ASSERT(job->IsRegisterJob());
-      RefPtr<ServiceWorkerRegisterJob> regJob =
-        static_cast<ServiceWorkerRegisterJob*>(job);
-
-      ErrorResult rv;
-      NS_ConvertUTF8toUTF16 scope(aScope);
-      rv.ThrowTypeError<MSG_SW_SCRIPT_THREW>(aWorkerURL, scope);
-      regJob->Fail(rv);
-    }
-  }
-
   // Always report any uncaught exceptions or errors to the console of
   // each client.
   ReportToAllClients(aScope, aMessage, aFilename, aLine, aLineNumber,
                      aColumnNumber, aFlags);
 }
 
 void
 ServiceWorkerRegistrationInfo::FinishActivate(bool aSuccess)
@@ -2659,33 +2691,16 @@ void
 ServiceWorkerRegistrationInfo::NotifyListenersOnChange()
 {
   nsTArray<nsCOMPtr<nsIServiceWorkerRegistrationInfoListener>> listeners(mListeners);
   for (size_t index = 0; index < listeners.Length(); ++index) {
     listeners[index]->OnChange();
   }
 }
 
-bool
-ServiceWorkerRegistrationInfo::IsUpdating() const
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  return mUpdateJob != nullptr;
-}
-
-void
-ServiceWorkerRegistrationInfo::AppendUpdateCallback(ServiceWorkerUpdateFinishCallback* aCallback)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aCallback);
-  MOZ_ASSERT(mUpdateJob);
-
-  mUpdateJob->AppendCallback(aCallback);
-}
-
 void
 ServiceWorkerManager::LoadRegistration(
                              const ServiceWorkerRegistrationData& aRegistration)
 {
   AssertIsOnMainThread();
 
   nsCOMPtr<nsIPrincipal> principal =
     PrincipalInfoToPrincipal(aRegistration.principal());
@@ -3010,16 +3025,17 @@ ServiceWorkerManager::RemoveScopeAndRegi
   RefPtr<ServiceWorkerRegistrationInfo> info;
   data->mInfos.Get(aRegistration->mScope, getter_AddRefs(info));
 
   data->mInfos.Remove(aRegistration->mScope);
   data->mOrderedScopes.RemoveElement(aRegistration->mScope);
   swm->NotifyListenersOnUnregister(info);
 
   swm->MaybeRemoveRegistrationInfo(scopeKey);
+  swm->NotifyServiceWorkerRegistrationRemoved(aRegistration);
 }
 
 void
 ServiceWorkerManager::MaybeRemoveRegistrationInfo(const nsACString& aScopeKey)
 {
   RegistrationDataPerPrincipal* data;
   if (!mRegistrationInfos.Get(aScopeKey, &data)) {
     return;
@@ -3522,65 +3538,44 @@ ServiceWorkerManager::InvalidateServiceW
 
     if (utf8Scope.Equals(aRegistration->mScope)) {
       target->InvalidateWorkers(aWhichOnes);
     }
   }
 }
 
 void
-ServiceWorkerManager::SoftUpdate(nsIPrincipal* aPrincipal,
-                                 const nsACString& aScope,
-                                 ServiceWorkerUpdateFinishCallback* aCallback)
+ServiceWorkerManager::NotifyServiceWorkerRegistrationRemoved(ServiceWorkerRegistrationInfo* aRegistration)
 {
-  MOZ_ASSERT(aPrincipal);
-
-  nsAutoCString scopeKey;
-  nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return;
-  }
-
-  SoftUpdate(scopeKey, aScope, aCallback);
+  AssertIsOnMainThread();
+  nsTObserverArray<ServiceWorkerRegistrationListener*>::ForwardIterator it(mServiceWorkerRegistrationListeners);
+  while (it.HasMore()) {
+    RefPtr<ServiceWorkerRegistrationListener> target = it.GetNext();
+    nsAutoString regScope;
+    target->GetScope(regScope);
+    MOZ_ASSERT(!regScope.IsEmpty());
+
+    NS_ConvertUTF16toUTF8 utf8Scope(regScope);
+
+    if (utf8Scope.Equals(aRegistration->mScope)) {
+      target->RegistrationRemoved();
+    }
+  }
 }
 
 void
-ServiceWorkerManager::SoftUpdate(const PrincipalOriginAttributes& aOriginAttributes,
-                                 const nsACString& aScope,
-                                 ServiceWorkerUpdateFinishCallback* aCallback)
+ServiceWorkerManager::SoftUpdate(const OriginAttributes& aOriginAttributes,
+                                 const nsACString& aScope)
 {
+  AssertIsOnMainThread();
   nsAutoCString scopeKey;
   aOriginAttributes.CreateSuffix(scopeKey);
-  SoftUpdate(scopeKey, aScope, aCallback);
-}
-
-namespace {
-
-// Empty callback.  Only use when you really want to ignore errors.
-class EmptyUpdateFinishCallback final : public ServiceWorkerUpdateFinishCallback
-{
-public:
-  void
-  UpdateSucceeded(ServiceWorkerRegistrationInfo* aInfo) override
-  {}
-
-  void
-  UpdateFailed(ErrorResult& aStatus) override
-  {}
-};
-
-} // anonymous namespace
-
-void
-ServiceWorkerManager::SoftUpdate(const nsACString& aScopeKey,
-                                 const nsACString& aScope,
-                                 ServiceWorkerUpdateFinishCallback* aCallback)
-{
+
   RefPtr<ServiceWorkerRegistrationInfo> registration =
-    GetRegistration(aScopeKey, aScope);
+    GetRegistration(scopeKey, aScope);
   if (NS_WARN_IF(!registration)) {
     return;
   }
 
   // "If registration's uninstalling flag is set, abort these steps."
   if (registration->mPendingUninstall) {
     return;
   }
@@ -3596,38 +3591,81 @@ ServiceWorkerManager::SoftUpdate(const n
   RefPtr<ServiceWorkerInfo> newest = registration->Newest();
   if (!newest) {
     return;
   }
 
   // "Set registration's registering script url to newestWorker's script url."
   registration->mScriptSpec = newest->ScriptSpec();
 
+  // "If the registration queue for registration is empty, invoke Update algorithm,
+  // or its equivalent, with client, registration as its argument."
+  // TODO(catalinb): We don't implement the force bypass cache flag.
+  // See: https://github.com/slightlyoff/ServiceWorker/issues/759
+  if (!registration->mUpdating) {
+    ServiceWorkerJobQueue* queue = GetOrCreateJobQueue(scopeKey, aScope);
+    MOZ_ASSERT(queue);
+
+    RefPtr<ServiceWorkerRegisterJob> job =
+      new ServiceWorkerRegisterJob(queue, registration, nullptr);
+    queue->Append(job);
+  }
+}
+
+void
+ServiceWorkerManager::Update(nsIPrincipal* aPrincipal,
+                             const nsACString& aScope,
+                             ServiceWorkerUpdateFinishCallback* aCallback)
+{
+  MOZ_ASSERT(aPrincipal);
+  MOZ_ASSERT(aCallback);
+
+  nsAutoCString scopeKey;
+  nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  RefPtr<ServiceWorkerRegistrationInfo> registration =
+    GetRegistration(scopeKey, aScope);
+  if (NS_WARN_IF(!registration)) {
+    return;
+  }
+
+  // "If registration's uninstalling flag is set, abort these steps."
+  if (registration->mPendingUninstall) {
+    return;
+  }
+
+  // "Let newestWorker be the result of running Get Newest Worker algorithm
+  // passing registration as its argument.
+  // If newestWorker is null, return a promise rejected with "InvalidStateError"
+  RefPtr<ServiceWorkerInfo> newest = registration->Newest();
+  if (!newest) {
+    ErrorResult error(NS_ERROR_DOM_INVALID_STATE_ERR);
+    aCallback->UpdateFailed(error);
+
+    // In case the callback does not consume the exception
+    error.SuppressException();
+
+    return;
+  }
+
+  // "Set registration's registering script url to newestWorker's script url."
+  registration->mScriptSpec = newest->ScriptSpec();
+
   ServiceWorkerJobQueue* queue =
-    GetOrCreateJobQueue(aScopeKey, aScope);
+    GetOrCreateJobQueue(scopeKey, aScope);
   MOZ_ASSERT(queue);
 
-  RefPtr<ServiceWorkerUpdateFinishCallback> cb(aCallback);
-  if (!cb) {
-    cb = new EmptyUpdateFinishCallback();
-  }
-
   // "Invoke Update algorithm, or its equivalent, with client, registration as
   // its argument."
-  if (registration->IsUpdating()) {
-    // This is used to reduce burst of update events. If there is an update
-    // job in queue when we try to create a new one, drop current one and
-    // merge the callback function to existing update job.
-    // See. https://github.com/slightlyoff/ServiceWorker/issues/759
-    registration->AppendUpdateCallback(cb);
-  } else {
-    RefPtr<ServiceWorkerRegisterJob> job =
-      new ServiceWorkerRegisterJob(queue, registration, cb);
-    queue->Append(job);
-  }
+  RefPtr<ServiceWorkerRegisterJob> job =
+    new ServiceWorkerRegisterJob(queue, registration, aCallback);
+  queue->Append(job);
 }
 
 namespace {
 
 static void
 FireControllerChangeOnDocument(nsIDocument* aDocument)
 {
   AssertIsOnMainThread();
@@ -3753,30 +3791,31 @@ ServiceWorkerManager::ClaimClients(nsIPr
 
 nsresult
 ServiceWorkerManager::SetSkipWaitingFlag(nsIPrincipal* aPrincipal,
                                          const nsCString& aScope,
                                          uint64_t aServiceWorkerID)
 {
   RefPtr<ServiceWorkerRegistrationInfo> registration =
     GetRegistration(aPrincipal, aScope);
-  if (!registration) {
+  if (NS_WARN_IF(!registration)) {
     return NS_ERROR_FAILURE;
   }
 
   if (registration->mInstallingWorker &&
       (registration->mInstallingWorker->ID() == aServiceWorkerID)) {
     registration->mInstallingWorker->SetSkipWaitingFlag();
   } else if (registration->mWaitingWorker &&
              (registration->mWaitingWorker->ID() == aServiceWorkerID)) {
     registration->mWaitingWorker->SetSkipWaitingFlag();
     if (registration->mWaitingWorker->State() == ServiceWorkerState::Installed) {
       registration->TryToActivate();
     }
   } else {
+    NS_WARNING("Failed to set skipWaiting flag, no matching worker.");
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 void
 ServiceWorkerManager::FireControllerChange(ServiceWorkerRegistrationInfo* aRegistration)
@@ -4528,16 +4567,38 @@ ServiceWorkerInfo::GetScriptSpec(nsAStri
 NS_IMETHODIMP
 ServiceWorkerInfo::GetCacheName(nsAString& aCacheName)
 {
   AssertIsOnMainThread();
   aCacheName = mCacheName;
   return NS_OK;
 }
 
+NS_IMETHODIMP
+ServiceWorkerInfo::GetDebugger(nsIWorkerDebugger** aResult)
+{
+  if (NS_WARN_IF(!aResult)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return mServiceWorkerPrivate->GetDebugger(aResult);
+}
+
+NS_IMETHODIMP
+ServiceWorkerInfo::AttachDebugger()
+{
+  return mServiceWorkerPrivate->AttachDebugger();
+}
+
+NS_IMETHODIMP
+ServiceWorkerInfo::DetachDebugger()
+{
+  return mServiceWorkerPrivate->DetachDebugger();
+}
+
 void
 ServiceWorkerInfo::AppendWorker(ServiceWorker* aWorker)
 {
   MOZ_ASSERT(aWorker);
 #ifdef DEBUG
   nsAutoString workerURL;
   aWorker->GetScriptURL(workerURL);
   MOZ_ASSERT(workerURL.Equals(NS_ConvertUTF8toUTF16(mScriptSpec)));
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -42,21 +42,19 @@ namespace dom {
 class ServiceWorkerRegistrationListener;
 
 namespace workers {
 
 class ServiceWorker;
 class ServiceWorkerClientInfo;
 class ServiceWorkerInfo;
 class ServiceWorkerJob;
-class ServiceWorkerRegisterJob;
 class ServiceWorkerJobQueue;
 class ServiceWorkerManagerChild;
 class ServiceWorkerPrivate;
-class ServiceWorkerUpdateFinishCallback;
 
 class ServiceWorkerRegistrationInfo final
   : public nsIServiceWorkerRegistrationInfo
 {
   uint32_t mControlledDocumentsCounter;
 
   virtual ~ServiceWorkerRegistrationInfo();
 
@@ -74,17 +72,21 @@ public:
   RefPtr<ServiceWorkerInfo> mActiveWorker;
   RefPtr<ServiceWorkerInfo> mWaitingWorker;
   RefPtr<ServiceWorkerInfo> mInstallingWorker;
 
   nsTArray<nsCOMPtr<nsIServiceWorkerRegistrationInfoListener>> mListeners;
 
   uint64_t mLastUpdateCheckTime;
 
-  RefPtr<ServiceWorkerRegisterJob> mUpdateJob;
+  // According to the spec, Soft Update shouldn't queue an update job
+  // if the registration queue is not empty. Because our job queue
+  // works slightly different, we use a flag to determine if the registration
+  // is already updating.
+  bool mUpdating;
 
   // When unregister() is called on a registration, it is not immediately
   // removed since documents may be controlled. It is marked as
   // pendingUninstall and when all controlling documents go away, removed.
   bool mPendingUninstall;
 
   ServiceWorkerRegistrationInfo(const nsACString& aScope,
                                 nsIPrincipal* aPrincipal);
@@ -144,22 +146,16 @@ public:
   void
   RefreshLastUpdateCheckTime();
 
   bool
   IsLastUpdateCheckTimeOverOneDay() const;
 
   void
   NotifyListenersOnChange();
-
-  bool
-  IsUpdating() const;
-
-  void
-  AppendUpdateCallback(ServiceWorkerUpdateFinishCallback* aCallback);
 };
 
 class ServiceWorkerUpdateFinishCallback
 {
 protected:
   virtual ~ServiceWorkerUpdateFinishCallback()
   {}
 
@@ -311,17 +307,19 @@ class ServiceWorkerManager final
   : public nsIServiceWorkerManager
   , public nsIIPCBackgroundChildCreateCallback
   , public nsIObserver
 {
   friend class GetReadyPromiseRunnable;
   friend class GetRegistrationsRunnable;
   friend class GetRegistrationRunnable;
   friend class ServiceWorkerJobQueue;
+  friend class ServiceWorkerInstallJob;
   friend class ServiceWorkerRegisterJob;
+  friend class ServiceWorkerJobBase;
   friend class ServiceWorkerRegistrationInfo;
   friend class ServiceWorkerUnregisterJob;
 
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSISERVICEWORKERMANAGER
   NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
   NS_DECL_NSIOBSERVER
@@ -368,24 +366,23 @@ public:
                     ErrorResult& aRv);
 
   void
   DispatchPreparedFetchEvent(nsIInterceptedChannel* aChannel,
                              nsIRunnable* aPreparedRunnable,
                              ErrorResult& aRv);
 
   void
-  SoftUpdate(nsIPrincipal* aPrincipal,
-             const nsACString& aScope,
-             ServiceWorkerUpdateFinishCallback* aCallback = nullptr);
+  Update(nsIPrincipal* aPrincipal,
+         const nsACString& aScope,
+         ServiceWorkerUpdateFinishCallback* aCallback);
 
   void
-  SoftUpdate(const PrincipalOriginAttributes& aOriginAttributes,
-             const nsACString& aScope,
-             ServiceWorkerUpdateFinishCallback* aCallback = nullptr);
+  SoftUpdate(const OriginAttributes& aOriginAttributes,
+             const nsACString& aScope);
 
   void
   PropagateSoftUpdate(const PrincipalOriginAttributes& aOriginAttributes,
                       const nsAString& aScope);
 
   void
   PropagateRemove(const nsACString& aHost);
 
@@ -484,21 +481,16 @@ private:
 
   ServiceWorkerJobQueue*
   GetOrCreateJobQueue(const nsACString& aOriginSuffix,
                       const nsACString& aScope);
 
   void
   MaybeRemoveRegistrationInfo(const nsACString& aScopeKey);
 
-  void
-  SoftUpdate(const nsACString& aScopeKey,
-             const nsACString& aScope,
-             ServiceWorkerUpdateFinishCallback* aCallback = nullptr);
-
   already_AddRefed<ServiceWorkerRegistrationInfo>
   GetRegistration(const nsACString& aScopeKey,
                   const nsACString& aScope) const;
 
   void
   AbortCurrentUpdate(ServiceWorkerRegistrationInfo* aRegistration);
 
   nsresult
@@ -521,16 +513,19 @@ private:
   ServiceWorkerInfo*
   GetActiveWorkerInfoForDocument(nsIDocument* aDocument);
 
   void
   InvalidateServiceWorkerRegistrationWorker(ServiceWorkerRegistrationInfo* aRegistration,
                                             WhichServiceWorker aWhichOnes);
 
   void
+  NotifyServiceWorkerRegistrationRemoved(ServiceWorkerRegistrationInfo* aRegistration);
+
+  void
   StartControllingADocument(ServiceWorkerRegistrationInfo* aRegistration,
                             nsIDocument* aDoc);
 
   void
   StopControllingADocument(ServiceWorkerRegistrationInfo* aRegistration);
 
   already_AddRefed<ServiceWorkerRegistrationInfo>
   GetServiceWorkerRegistrationInfo(nsPIDOMWindow* aWindow);
--- a/dom/workers/ServiceWorkerManagerChild.cpp
+++ b/dom/workers/ServiceWorkerManagerChild.cpp
@@ -37,17 +37,17 @@ ServiceWorkerManagerChild::RecvNotifySof
 {
   if (mShuttingDown) {
     return true;
   }
 
   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
   MOZ_ASSERT(swm);
 
-  swm->SoftUpdate(aOriginAttributes, NS_ConvertUTF16toUTF8(aScope), nullptr);
+  swm->SoftUpdate(aOriginAttributes, NS_ConvertUTF16toUTF8(aScope));
   return true;
 }
 
 bool
 ServiceWorkerManagerChild::RecvNotifyUnregister(const PrincipalInfo& aPrincipalInfo,
                                                 const nsString& aScope)
 {
   if (mShuttingDown) {
--- a/dom/workers/ServiceWorkerPrivate.cpp
+++ b/dom/workers/ServiceWorkerPrivate.cpp
@@ -56,16 +56,17 @@ private:
   RefPtr<ServiceWorkerPrivate> mPrivate;
 };
 
 NS_IMPL_ISUPPORTS0(KeepAliveToken)
 
 ServiceWorkerPrivate::ServiceWorkerPrivate(ServiceWorkerInfo* aInfo)
   : mInfo(aInfo)
   , mIsPushWorker(false)
+  , mDebuggerCount(0)
   , mTokenCount(0)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aInfo);
 
   mIdleWorkerTimer = do_CreateInstance(NS_TIMER_CONTRACTID);
   MOZ_ASSERT(mIdleWorkerTimer);
 }
@@ -98,56 +99,74 @@ ServiceWorkerPrivate::SendMessageEvent(J
   return rv.StealNSResult();
 }
 
 namespace {
 
 class CheckScriptEvaluationWithCallback final : public WorkerRunnable
 {
   nsMainThreadPtrHandle<KeepAliveToken> mKeepAliveToken;
-  RefPtr<nsRunnable> mCallback;
+  RefPtr<LifeCycleEventCallback> mCallback;
+  DebugOnly<bool> mDone;
 
 public:
   CheckScriptEvaluationWithCallback(WorkerPrivate* aWorkerPrivate,
                                     KeepAliveToken* aKeepAliveToken,
-                                    nsRunnable* aCallback)
+                                    LifeCycleEventCallback* aCallback)
     : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
     , mKeepAliveToken(new nsMainThreadPtrHolder<KeepAliveToken>(aKeepAliveToken))
     , mCallback(aCallback)
+    , mDone(false)
   {
     AssertIsOnMainThread();
   }
 
+  ~CheckScriptEvaluationWithCallback()
+  {
+    MOZ_ASSERT(mDone);
+  }
+
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     aWorkerPrivate->AssertIsOnWorkerThread();
-    if (aWorkerPrivate->WorkerScriptExecutedSuccessfully()) {
-      nsresult rv = NS_DispatchToMainThread(mCallback);
-      if (NS_FAILED(rv)) {
-        NS_WARNING("Failed to dispatch CheckScriptEvaluation callback.");
-      }
-    }
+    Done(aWorkerPrivate->WorkerScriptExecutedSuccessfully());
 
     return true;
   }
+
+  NS_IMETHOD
+  Cancel() override
+  {
+    Done(false);
+    return WorkerRunnable::Cancel();
+  }
+
+private:
+  void
+  Done(bool aResult)
+  {
+    mDone = true;
+    mCallback->SetResult(aResult);
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(mCallback)));
+  }
 };
 
 } // anonymous namespace
 
 nsresult
-ServiceWorkerPrivate::ContinueOnSuccessfulScriptEvaluation(nsRunnable* aCallback)
+ServiceWorkerPrivate::CheckScriptEvaluation(LifeCycleEventCallback* aCallback)
 {
   nsresult rv = SpawnWorkerIfNeeded(LifeCycleEvent, nullptr);
   NS_ENSURE_SUCCESS(rv, rv);
 
   MOZ_ASSERT(mKeepAliveToken);
   RefPtr<WorkerRunnable> r = new CheckScriptEvaluationWithCallback(mWorkerPrivate,
-                                                                     mKeepAliveToken,
-                                                                     aCallback);
+                                                                   mKeepAliveToken,
+                                                                   aCallback);
   AutoJSAPI jsapi;
   jsapi.Init();
   if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
@@ -347,78 +366,151 @@ public:
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     MOZ_ASSERT(aWorkerPrivate);
     return DispatchLifecycleEvent(aCx, aWorkerPrivate);
   }
 
+  NS_IMETHOD
+  Cancel() override
+  {
+    mCallback->SetResult(false);
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(mCallback)));
+
+    return WorkerRunnable::Cancel();
+  }
+
 private:
   bool
   DispatchLifecycleEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate);
 
 };
 
 /*
- * Used to handle ExtendableEvent::waitUntil() and proceed with
- * installation/activation.
+ * Used to handle ExtendableEvent::waitUntil() and catch abnormal worker
+ * termination during the execution of life cycle events. It is responsible
+ * with advancing the job queue for install/activate tasks.
  */
-class LifecycleEventPromiseHandler final : public PromiseNativeHandler
+class LifeCycleEventWatcher final : public PromiseNativeHandler,
+                                    public WorkerFeature
 {
+  WorkerPrivate* mWorkerPrivate;
   RefPtr<LifeCycleEventCallback> mCallback;
+  bool mDone;
 
-  virtual
-  ~LifecycleEventPromiseHandler()
-  { }
+  ~LifeCycleEventWatcher()
+  {
+    if (mDone) {
+      return;
+    }
+
+    MOZ_ASSERT(GetCurrentThreadWorkerPrivate() == mWorkerPrivate);
+    // XXXcatalinb: If all the promises passed to waitUntil go out of scope,
+    // the resulting Promise.all will be cycle collected and it will drop its
+    // native handlers (including this object). Instead of waiting for a timeout
+    // we report the failure now.
+    JSContext* cx = mWorkerPrivate->GetJSContext();
+    ReportResult(cx, false);
+  }
 
 public:
   NS_DECL_ISUPPORTS
 
-  explicit LifecycleEventPromiseHandler(LifeCycleEventCallback* aCallback)
-    : mCallback(aCallback)
+  LifeCycleEventWatcher(WorkerPrivate* aWorkerPrivate,
+                        LifeCycleEventCallback* aCallback)
+    : mWorkerPrivate(aWorkerPrivate)
+    , mCallback(aCallback)
+    , mDone(false)
+  {
+    MOZ_ASSERT(aWorkerPrivate);
+    aWorkerPrivate->AssertIsOnWorkerThread();
+  }
+
+  bool
+  Init()
   {
-    MOZ_ASSERT(!NS_IsMainThread());
+    MOZ_ASSERT(mWorkerPrivate);
+    mWorkerPrivate->AssertIsOnWorkerThread();
+    JSContext* cx = mWorkerPrivate->GetJSContext();
+
+    // We need to listen for worker termination in case the event handler
+    // never completes or never resolves the waitUntil promise. There are
+    // two possible scenarios:
+    // 1. The keepAlive token expires and the worker is terminated, in which
+    //    case the registration/update promise will be rejected
+    // 2. A new service worker is registered which will terminate the current
+    //    installing worker.
+    if (NS_WARN_IF(!mWorkerPrivate->AddFeature(cx, this))) {
+      NS_WARNING("LifeCycleEventWatcher failed to add feature.");
+      ReportResult(cx, false);
+      return false;
+    }
+
+    return true;
+  }
+
+  bool
+  Notify(JSContext* aCx, Status aStatus) override
+  {
+    if (aStatus < Terminating) {
+      return true;
+    }
+
+    MOZ_ASSERT(GetCurrentThreadWorkerPrivate() == mWorkerPrivate);
+    ReportResult(aCx, false);
+
+    return true;
+  }
+
+  void
+  ReportResult(JSContext* aCx, bool aResult)
+  {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+
+    if (mDone) {
+      return;
+    }
+    mDone = true;
+
+    mCallback->SetResult(aResult);
+    nsresult rv = NS_DispatchToMainThread(mCallback);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      NS_RUNTIMEABORT("Failed to dispatch life cycle event handler.");
+    }
+
+    mWorkerPrivate->RemoveFeature(aCx, this);
   }
 
   void
   ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
   {
-    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
-    MOZ_ASSERT(workerPrivate);
-    workerPrivate->AssertIsOnWorkerThread();
+    MOZ_ASSERT(GetCurrentThreadWorkerPrivate() == mWorkerPrivate);
+    mWorkerPrivate->AssertIsOnWorkerThread();
 
-    mCallback->SetResult(true);
-    nsresult rv = NS_DispatchToMainThread(mCallback);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      NS_RUNTIMEABORT("Failed to dispatch life cycle event handler.");
-    }
+    ReportResult(aCx, true);
   }
 
   void
   RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
   {
-    WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
-    MOZ_ASSERT(workerPrivate);
-    workerPrivate->AssertIsOnWorkerThread();
+    MOZ_ASSERT(GetCurrentThreadWorkerPrivate() == mWorkerPrivate);
+    mWorkerPrivate->AssertIsOnWorkerThread();
 
-    mCallback->SetResult(false);
-    nsresult rv = NS_DispatchToMainThread(mCallback);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      NS_RUNTIMEABORT("Failed to dispatch life cycle event handler.");
-    }
+    ReportResult(aCx, false);
 
     // Note, all WaitUntil() rejections are reported to client consoles
     // by the WaitUntilHandler in ServiceWorkerEvents.  This ensures that
     // errors in non-lifecycle events like FetchEvent and PushEvent are
     // reported properly.
   }
 };
 
-NS_IMPL_ISUPPORTS0(LifecycleEventPromiseHandler)
+NS_IMPL_ISUPPORTS0(LifeCycleEventWatcher)
 
 bool
 LifecycleEventWorkerRunnable::DispatchLifecycleEvent(JSContext* aCx,
                                                      WorkerPrivate* aWorkerPrivate)
 {
   aWorkerPrivate->AssertIsOnWorkerThread();
   MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
 
@@ -431,26 +523,33 @@ LifecycleEventWorkerRunnable::DispatchLi
     init.mCancelable = false;
     event = ExtendableEvent::Constructor(target, mEventName, init);
   } else {
     MOZ_CRASH("Unexpected lifecycle event");
   }
 
   event->SetTrusted(true);
 
+  // It is important to initialize the watcher before actually dispatching
+  // the event in order to catch worker termination while the event handler
+  // is still executing. This can happen with infinite loops, for example.
+  RefPtr<LifeCycleEventWatcher> watcher =
+    new LifeCycleEventWatcher(aWorkerPrivate, mCallback);
+
+  if (!watcher->Init()) {
+    return true;
+  }
+
   RefPtr<Promise> waitUntil;
   DispatchExtendableEventOnWorkerScope(aCx, aWorkerPrivate->GlobalScope(),
                                        event, getter_AddRefs(waitUntil));
   if (waitUntil) {
-    RefPtr<LifecycleEventPromiseHandler> handler =
-      new LifecycleEventPromiseHandler(mCallback);
-    waitUntil->AppendNativeHandler(handler);
+    waitUntil->AppendNativeHandler(watcher);
   } else {
-    mCallback->SetResult(false);
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(mCallback)));
+    watcher->ReportResult(aCx, false);
   }
 
   return true;
 }
 
 } // anonymous namespace
 
 nsresult
@@ -1326,17 +1425,17 @@ ServiceWorkerPrivate::SpawnWorkerIfNeede
   // XXXcatalinb: We need to have a separate load group that's linked to
   // an existing tab child to pass security checks on b2g.
   // This should be fixed in bug 1125961, but for now we enforce updating
   // the overriden load group when intercepting a fetch.
   MOZ_ASSERT_IF(aWhy == FetchEvent, aLoadGroup);
 
   if (mWorkerPrivate) {
     mWorkerPrivate->UpdateOverridenLoadGroup(aLoadGroup);
-    ResetIdleTimeout(aWhy);
+    RenewKeepAliveToken(aWhy);
 
     return NS_OK;
   }
 
   // Sanity check: mSupportsArray should be empty if we're about to
   // spin up a new worker.
   MOZ_ASSERT(mSupportsArray.IsEmpty());
 
@@ -1406,17 +1505,17 @@ ServiceWorkerPrivate::SpawnWorkerIfNeede
                                               scriptSpec,
                                               false, WorkerTypeService,
                                               mInfo->Scope(), &info, error);
   if (NS_WARN_IF(error.Failed())) {
     return error.StealNSResult();
   }
 
   mIsPushWorker = false;
-  ResetIdleTimeout(aWhy);
+  RenewKeepAliveToken(aWhy);
 
   return NS_OK;
 }
 
 void
 ServiceWorkerPrivate::StoreISupports(nsISupports* aSupports)
 {
   AssertIsOnMainThread();
@@ -1472,17 +1571,17 @@ ServiceWorkerPrivate::NoteDeadServiceWor
   mInfo = nullptr;
   TerminateWorker();
 }
 
 void
 ServiceWorkerPrivate::NoteStoppedControllingDocuments()
 {
   AssertIsOnMainThread();
-  if (mIsPushWorker) {
+  if (mIsPushWorker || mDebuggerCount) {
     return;
   }
 
   TerminateWorker();
 }
 
 void
 ServiceWorkerPrivate::Activated()
@@ -1502,16 +1601,78 @@ ServiceWorkerPrivate::Activated()
     AutoJSAPI jsapi;
     jsapi.Init();
     if (NS_WARN_IF(!r->Dispatch(jsapi.cx()))) {
       NS_WARNING("Failed to dispatch pending functional event!");
     }
   }
 }
 
+nsresult
+ServiceWorkerPrivate::GetDebugger(nsIWorkerDebugger** aResult)
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(aResult);
+
+  if (!mDebuggerCount) {
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(mWorkerPrivate);
+
+  nsCOMPtr<nsIWorkerDebugger> debugger = do_QueryInterface(mWorkerPrivate->Debugger());
+  debugger.forget(aResult);
+
+  return NS_OK;
+}
+
+nsresult
+ServiceWorkerPrivate::AttachDebugger()
+{
+  AssertIsOnMainThread();
+
+  // When the first debugger attaches to a worker, we spawn a worker if needed,
+  // and cancel the idle timeout. The idle timeout should not be reset until
+  // the last debugger detached from the worker.
+  if (!mDebuggerCount) {
+    nsresult rv = SpawnWorkerIfNeeded(AttachEvent, nullptr);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    mIdleWorkerTimer->Cancel();
+  }
+
+  ++mDebuggerCount;
+
+  return NS_OK;
+}
+
+nsresult
+ServiceWorkerPrivate::DetachDebugger()
+{
+  AssertIsOnMainThread();
+
+  if (!mDebuggerCount) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  --mDebuggerCount;
+
+  // When the last debugger detaches from a worker, we either reset the idle
+  // timeout, or terminate the worker if there are no more active tokens.
+  if (!mDebuggerCount) {
+    if (mTokenCount) {
+      ResetIdleTimeout();
+    } else {
+      TerminateWorker();
+    }
+  }
+
+  return NS_OK;
+}
+
 /* static */ void
 ServiceWorkerPrivate::NoteIdleWorkerCallback(nsITimer* aTimer, void* aPrivate)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aPrivate);
 
   RefPtr<ServiceWorkerPrivate> swp = static_cast<ServiceWorkerPrivate*>(aPrivate);
 
@@ -1545,34 +1706,46 @@ ServiceWorkerPrivate::TerminateWorkerCal
 
   MOZ_ASSERT(aTimer == serviceWorkerPrivate->mIdleWorkerTimer,
       "Invalid timer!");
 
   serviceWorkerPrivate->TerminateWorker();
 }
 
 void
-ServiceWorkerPrivate::ResetIdleTimeout(WakeUpReason aWhy)
+ServiceWorkerPrivate::RenewKeepAliveToken(WakeUpReason aWhy)
 {
-  // We should have an active worker if we're reseting the idle timeout
+  // We should have an active worker if we're renewing the keep alive token.
   MOZ_ASSERT(mWorkerPrivate);
 
   if (aWhy == PushEvent || aWhy == PushSubscriptionChangeEvent) {
     mIsPushWorker = true;
   }
 
+  // If there is at least one debugger attached to the worker, the idle worker
+  // timeout was canceled when the first debugger attached to the worker. It
+  // should not be reset until the last debugger detaches from the worker.
+  if (!mDebuggerCount) {
+    ResetIdleTimeout();
+  }
+
+  if (!mKeepAliveToken) {
+    mKeepAliveToken = new KeepAliveToken(this);
+  }
+}
+
+void
+ServiceWorkerPrivate::ResetIdleTimeout()
+{
   uint32_t timeout = Preferences::GetInt("dom.serviceWorkers.idle_timeout");
   DebugOnly<nsresult> rv =
     mIdleWorkerTimer->InitWithFuncCallback(ServiceWorkerPrivate::NoteIdleWorkerCallback,
                                            this, timeout,
                                            nsITimer::TYPE_ONE_SHOT);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
-  if (!mKeepAliveToken) {
-    mKeepAliveToken = new KeepAliveToken(this);
-  }
 }
 
 void
 ServiceWorkerPrivate::AddToken()
 {
   AssertIsOnMainThread();
   ++mTokenCount;
 }
--- a/dom/workers/ServiceWorkerPrivate.h
+++ b/dom/workers/ServiceWorkerPrivate.h
@@ -67,21 +67,19 @@ public:
   explicit ServiceWorkerPrivate(ServiceWorkerInfo* aInfo);
 
   nsresult
   SendMessageEvent(JSContext* aCx, JS::Handle<JS::Value> aMessage,
                    const Optional<Sequence<JS::Value>>& aTransferable,
                    UniquePtr<ServiceWorkerClientInfo>&& aClientInfo);
 
   // This is used to validate the worker script and continue the installation
-  // process. Note that the callback is dispatched to the main thread
-  // ONLY if the evaluation was successful. Failure is handled by the JS
-  // exception handler which will call ServiceWorkerManager::HandleError.
+  // process.
   nsresult
-  ContinueOnSuccessfulScriptEvaluation(nsRunnable* aCallback);
+  CheckScriptEvaluation(LifeCycleEventCallback* aCallback);
 
   nsresult
   SendLifeCycleEvent(const nsAString& aEventType,
                      LifeCycleEventCallback* aCallback,
                      nsIRunnable* aLoadFailure);
 
   nsresult
   SendPushEvent(const Maybe<nsTArray<uint8_t>>& aData,
@@ -132,35 +130,48 @@ public:
   GetWorkerPrivate() const
   {
     return mWorkerPrivate;
   }
 
   void
   Activated();
 
+  nsresult
+  GetDebugger(nsIWorkerDebugger** aResult);
+
+  nsresult
+  AttachDebugger();
+
+  nsresult
+  DetachDebugger();
+
 private:
   enum WakeUpReason {
     FetchEvent = 0,
     PushEvent,
     PushSubscriptionChangeEvent,
     MessageEvent,
     NotificationClickEvent,
-    LifeCycleEvent
+    LifeCycleEvent,
+    AttachEvent
   };
 
   // Timer callbacks
   static void
   NoteIdleWorkerCallback(nsITimer* aTimer, void* aPrivate);
 
   static void
   TerminateWorkerCallback(nsITimer* aTimer, void *aPrivate);
 
   void
-  ResetIdleTimeout(WakeUpReason aWhy);
+  RenewKeepAliveToken(WakeUpReason aWhy);
+
+  void
+  ResetIdleTimeout();
 
   void
   AddToken();
 
   void
   ReleaseToken();
 
   // |aLoadFailedRunnable| is a runnable dispatched to the main thread
@@ -188,16 +199,18 @@ private:
   // woken up. The flag is reset to false every time a new WorkerPrivate
   // is created.
   bool mIsPushWorker;
 
   // We keep a token for |dom.serviceWorkers.idle_timeout| seconds to give the
   // worker a grace period after each event.
   RefPtr<KeepAliveToken> mKeepAliveToken;
 
+  uint64_t mDebuggerCount;
+
   uint64_t mTokenCount;
 
   // Meant for keeping objects alive while handling requests from the worker
   // on the main thread. Access to this array is provided through
   // |StoreISupports| and |RemoveISupports|. Note that the array is also
   // cleared whenever the worker is terminated.
   nsTArray<nsCOMPtr<nsISupports>> mSupportsArray;
 
--- a/dom/workers/ServiceWorkerRegistration.cpp
+++ b/dom/workers/ServiceWorkerRegistration.cpp
@@ -245,34 +245,46 @@ ServiceWorkerRegistrationMainThread::Inv
 
   if (aWhichOnes & WhichServiceWorker::WAITING_WORKER) {
     mWaitingWorker = nullptr;
   }
 
   if (aWhichOnes & WhichServiceWorker::ACTIVE_WORKER) {
     mActiveWorker = nullptr;
   }
+
+}
+
+void
+ServiceWorkerRegistrationMainThread::RegistrationRemoved()
+{
+  // If the registration is being removed completely, remove it from the
+  // window registration hash table so that a new registration would get a new
+  // wrapper JS object.
+  nsCOMPtr<nsPIDOMWindow> window = GetOwner();
+  if (window) {
+    window->InvalidateServiceWorkerRegistration(mScope);
+  }
 }
 
 namespace {
 
 void
 UpdateInternal(nsIPrincipal* aPrincipal,
                const nsAString& aScope,
                ServiceWorkerUpdateFinishCallback* aCallback)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aPrincipal);
   MOZ_ASSERT(aCallback);
 
   RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
   MOZ_ASSERT(swm);
 
-  // The spec defines ServiceWorkerRegistration.update() exactly as Soft Update.
-  swm->SoftUpdate(aPrincipal, NS_ConvertUTF16toUTF8(aScope), aCallback);
+  swm->Update(aPrincipal, NS_ConvertUTF16toUTF8(aScope), aCallback);
 }
 
 class MainThreadUpdateCallback final : public ServiceWorkerUpdateFinishCallback
 {
   RefPtr<Promise> mPromise;
 
   ~MainThreadUpdateCallback()
   { }
@@ -388,24 +400,32 @@ public:
   {}
 
   NS_IMETHOD
   Run() override
   {
     AssertIsOnMainThread();
     ErrorResult result;
 
-    MutexAutoLock lock(mPromiseProxy->Lock());
-    if (mPromiseProxy->CleanedUp()) {
-      return NS_OK;
+    nsCOMPtr<nsIPrincipal> principal;
+    // UpdateInternal may try to reject the promise synchronously leading
+    // to a deadlock.
+    {
+      MutexAutoLock lock(mPromiseProxy->Lock());
+      if (mPromiseProxy->CleanedUp()) {
+        return NS_OK;
+      }
+
+      principal = mPromiseProxy->GetWorkerPrivate()->GetPrincipal();
     }
+    MOZ_ASSERT(principal);
 
     RefPtr<WorkerThreadUpdateCallback> cb =
       new WorkerThreadUpdateCallback(mPromiseProxy);
-    UpdateInternal(mPromiseProxy->GetWorkerPrivate()->GetPrincipal(), mScope, cb);
+    UpdateInternal(principal, mScope, cb);
     return NS_OK;
   }
 
 private:
   ~UpdateRunnable()
   {}
 
   RefPtr<PromiseWorkerProxy> mPromiseProxy;
@@ -853,16 +873,22 @@ public:
   void
   InvalidateWorkers(WhichServiceWorker aWhichOnes) override
   {
     AssertIsOnMainThread();
     // FIXME(nsm);
   }
 
   void
+  RegistrationRemoved() override
+  {
+    AssertIsOnMainThread();
+  }
+
+  void
   GetScope(nsAString& aScope) const override
   {
     aScope = mScope;
   }
 
   ServiceWorkerRegistrationWorkerThread*
   GetRegistration() const
   {
--- a/dom/workers/ServiceWorkerRegistration.h
+++ b/dom/workers/ServiceWorkerRegistration.h
@@ -61,16 +61,19 @@ public:
 
   virtual void
   UpdateFound() = 0;
 
   virtual void
   InvalidateWorkers(WhichServiceWorker aWhichOnes) = 0;
 
   virtual void
+  RegistrationRemoved() = 0;
+
+  virtual void
   GetScope(nsAString& aScope) const = 0;
 };
 
 class ServiceWorkerRegistrationBase : public DOMEventTargetHelper
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
 
@@ -101,19 +104,16 @@ protected:
 class ServiceWorkerRegistrationMainThread final : public ServiceWorkerRegistrationBase,
                                                   public ServiceWorkerRegistrationListener
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ServiceWorkerRegistrationMainThread,
                                            ServiceWorkerRegistrationBase)
 
-  ServiceWorkerRegistrationMainThread(nsPIDOMWindow* aWindow,
-                                      const nsAString& aScope);
-
   already_AddRefed<Promise>
   Update(ErrorResult& aRv);
 
   already_AddRefed<Promise>
   Unregister(ErrorResult& aRv);
 
   JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
@@ -150,22 +150,28 @@ public:
   // ServiceWorkerRegistrationListener
   void
   UpdateFound() override;
 
   void
   InvalidateWorkers(WhichServiceWorker aWhichOnes) override;
 
   void
+  RegistrationRemoved() override;
+
+  void
   GetScope(nsAString& aScope) const override
   {
     aScope = mScope;
   }
 
 private:
+  friend nsPIDOMWindow;
+  ServiceWorkerRegistrationMainThread(nsPIDOMWindow* aWindow,
+                                      const nsAString& aScope);
   ~ServiceWorkerRegistrationMainThread();
 
   already_AddRefed<workers::ServiceWorker>
   GetWorkerReference(WhichServiceWorker aWhichOne);
 
   void
   StartListeningForEvents();
 
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -588,17 +588,19 @@ public:
     swm->SetSkipWaitingFlag(workerPrivate->GetPrincipal(), mScope,
                             workerPrivate->ServiceWorkerID());
 
     RefPtr<SkipWaitingResultRunnable> runnable =
       new SkipWaitingResultRunnable(workerPrivate, mPromiseProxy);
 
     AutoJSAPI jsapi;
     jsapi.Init();
-    runnable->Dispatch(jsapi.cx());
+    if (!runnable->Dispatch(jsapi.cx())) {
+      NS_WARNING("Failed to dispatch SkipWaitingResultRunnable to the worker.");
+    }
     return NS_OK;
   }
 };
 
 } // namespace
 
 already_AddRefed<Promise>
 ServiceWorkerGlobalScope::SkipWaiting(ErrorResult& aRv)
--- a/dom/workers/test/serviceworkers/chrome.ini
+++ b/dom/workers/test/serviceworkers/chrome.ini
@@ -1,17 +1,19 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g' || os == 'android'
 support-files =
   app/*
   app2/*
   chrome_helpers.js
+  serviceworkerinfo_iframe.html
   serviceworkermanager_iframe.html
   serviceworkerregistrationinfo_iframe.html
   worker.js
   worker2.js
 
 [test_aboutserviceworkers.html]
 skip-if = true #bug 1193319
 [test_app_installation.html]
 [test_privateBrowsing.html]
+[test_serviceworkerinfo.xul]
 [test_serviceworkermanager.xul]
 [test_serviceworkerregistrationinfo.xul]
--- a/dom/workers/test/serviceworkers/chrome_helpers.js
+++ b/dom/workers/test/serviceworkers/chrome_helpers.js
@@ -52,8 +52,23 @@ function waitForServiceWorkerRegistratio
           callback();
         }
         resolve(callback ? callback() : undefined);
       }
     };
     registration.addListener(listener);
   });
 }
+
+function waitForServiceWorkerShutdown() {
+  return new Promise(function (resolve) {
+    let observer = {
+      observe: function (subject, topic, data) {
+        if (topic !== "service-worker-shutdown") {
+          return;
+        }
+        SpecialPowers.removeObserver(observer, "service-worker-shutdown");
+        resolve();
+      }
+    };
+    SpecialPowers.addObserver(observer, "service-worker-shutdown", false);
+  });
+}
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/serviceworkerinfo_iframe.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <script>
+      window.onmessage = function (event) {
+        if (event.data !== "register") {
+          return;
+        }
+        var promise = navigator.serviceWorker.register("worker.js");
+        window.onmessage = function (event) {
+          if (event.data !== "unregister") {
+            return;
+          }
+          promise.then(function (registration) {
+            registration.unregister();
+          });
+          window.onmessage = null;
+        };
+      };
+    </script>
+  </head>
+  <body>
+    This is a test page.
+  </body>
+<html>
--- a/dom/workers/test/serviceworkers/test_install_event.html
+++ b/dom/workers/test/serviceworkers/test_install_event.html
@@ -19,23 +19,36 @@
     var p = navigator.serviceWorker.register("worker.js", { scope: "./install_event" });
     return p;
   }
 
   function nextRegister(reg) {
     ok(reg instanceof ServiceWorkerRegistration, "reg should be a ServiceWorkerRegistration");
     var p = navigator.serviceWorker.register("install_event_worker.js", { scope: "./install_event" });
     return p.then(function(swr) {
-      ok(reg.scope === swr.scope, "Scope for registrations should match.");
-      return new Promise(function(resolve, reject) {
+      ok(reg === swr, "register should resolve to the same registration object");
+      var update_found_promise = new Promise(function(resolve, reject) {
         swr.addEventListener('updatefound', function(e) {
           ok(true, "Received onupdatefound");
           resolve();
         });
       });
+
+      var worker_activating = new Promise(function(res, reject) {
+        ok(swr.installing instanceof ServiceWorker, "There should be an installing worker if promise resolves.");
+        ok(swr.installing.state == "installing", "Installing worker's state should be 'installing'");
+        swr.installing.onstatechange = function(e) {
+          if (e.target.state == "activating") {
+            e.target.onstatechange = null;
+            res();
+          }
+        }
+      });
+
+      return Promise.all([update_found_promise, worker_activating]);
     }, function(e) {
       ok(false, "Unexpected Error in nextRegister! " + e);
     });
   }
 
   function installError() {
     // Silence worker errors so they don't cause the test to fail.
     window.onerror = function(e) {}
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_serviceworkerinfo.xul
@@ -0,0 +1,101 @@
+<?xml version="1.0"?>
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<window title="Test for ServiceWorkerInfo"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="test();">
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+  <script type="application/javascript" src="chrome_helpers.js"/>
+  <script type="application/javascript">
+  <![CDATA[
+
+    let IFRAME_URL = EXAMPLE_URL + "serviceworkerinfo_iframe.html";
+
+    function wait_for_active_worker(registration) {
+      ok(registration, "Registration is valid.");
+      return new Promise(function(res, rej) {
+        if (registration.activeWorker) {
+          res(registration);
+          return;
+        }
+        let listener = {
+          onChange: function() {
+            if (registration.activeWorker) {
+              registration.removeListener(listener);
+              res(registration);
+            }
+          }
+        }
+        registration.addListener(listener);
+      });
+    }
+
+    function test() {
+      SimpleTest.waitForExplicitFinish();
+
+      SpecialPowers.pushPrefEnv({'set': [
+        ["dom.serviceWorkers.enabled", true],
+        ["dom.serviceWorkers.idle_extended_timeout", 1000000],
+        ["dom.serviceWorkers.idle_timeout", 0],
+        ["dom.serviceWorkers.testing.enabled", true],
+      ]}, function () {
+        Task.spawn(function *() {
+          let iframe = $("iframe");
+          let promise = new Promise(function (resolve) {
+            iframe.onload = function () {
+              resolve();
+            };
+          });
+          iframe.src = IFRAME_URL;
+          yield promise;
+
+          info("Check that a service worker eventually shuts down.");
+          promise = Promise.all([
+            waitForRegister(EXAMPLE_URL),
+            waitForServiceWorkerShutdown()
+          ]);
+          iframe.contentWindow.postMessage("register", "*");
+          let [registration] = yield promise;
+
+          // Make sure the worker is active.
+          registration = yield wait_for_active_worker(registration);
+
+          let activeWorker = registration.activeWorker;
+          ok(activeWorker !== null, "Worker is not active!");
+          ok(activeWorker.debugger === null);
+
+          info("Attach a debugger to the service worker, and check that the " +
+               "service worker is restarted.");
+          activeWorker.attachDebugger();
+          ok(activeWorker.debugger !== null);
+
+          info("Detach the debugger from the service worker, and check that " +
+               "the service worker eventually shuts down again.");
+          promise = waitForServiceWorkerShutdown();
+          activeWorker.detachDebugger();
+          yield promise;
+          ok(activeWorker.debugger === null);
+
+          promise = waitForUnregister(EXAMPLE_URL);
+          iframe.contentWindow.postMessage("unregister", "*");
+          registration = yield promise;
+
+          SimpleTest.finish();
+        });
+      });
+    }
+
+  ]]>
+  </script>
+
+  <body xmlns="http://www.w3.org/1999/xhtml">
+    <p id="display"></p>
+    <div id="content" style="display:none;"></div>
+    <pre id="test"></pre>
+    <iframe id="iframe"></iframe>
+  </body>
+  <label id="test-result"/>
+</window>
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -417,44 +417,60 @@ APZCCallbackHelper::GetRootContentDocume
 CSSPoint
 APZCCallbackHelper::ApplyCallbackTransform(const CSSPoint& aInput,
                                            const ScrollableLayerGuid& aGuid)
 {
     CSSPoint input = aInput;
     if (aGuid.mScrollId == FrameMetrics::NULL_SCROLL_ID) {
         return input;
     }
+
     nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aGuid.mScrollId);
     if (!content) {
         return input;
     }
 
+#if !defined(MOZ_SINGLE_PROCESS_APZ)
     // First, scale inversely by the root content document's pres shell
     // resolution to cancel the scale-to-resolution transform that the
     // compositor adds to the layer with the pres shell resolution. The points
     // sent to Gecko by APZ don't have this transform unapplied (unlike other
     // compositor-side transforms) because APZ doesn't know about it.
     if (nsIPresShell* shell = GetRootContentDocumentPresShellForContent(content)) {
         input = input / shell->GetResolution();
     }
+#endif
 
-    // Now apply the callback-transform.
+    // Apply the callback-transform.
     // XXX: technically we need to walk all the way up the layer tree from the layer
     // represented by |aGuid.mScrollId| up to the root of the layer tree and apply
     // the input transforms at each level in turn. However, it is quite difficult
     // to do this given that the structure of the layer tree may be different from
     // the structure of the content tree. Also it may be impossible to do correctly
     // at this point because there are other CSS transforms and such interleaved in
     // between so applying the inputTransforms all in a row at the end may leave
     // some things transformed improperly. In practice we should rarely hit scenarios
     // where any of this matters, so I'm skipping it for now and just doing the single
     // transform for the layer that the input hit.
+
     void* property = content->GetProperty(nsGkAtoms::apzCallbackTransform);
     if (property) {
         CSSPoint delta = (*static_cast<CSSPoint*>(property));
+
+#if defined(MOZ_SINGLE_PROCESS_APZ)
+        // The delta is in root content document coordinate space while the
+        // aInput point is in root document coordinate space so convert the
+        // delta to root document space before adding it to the aInput point.
+        float resolution = 1.0f;
+        if (nsIPresShell* shell = GetRootContentDocumentPresShellForContent(content)) {
+            resolution = shell->GetResolution();
+        }
+        delta.x = delta.x * resolution;
+        delta.y = delta.y * resolution;
+#endif // MOZ_SINGLE_PROCESS_APZ
         input += delta;
     }
     return input;
 }
 
 LayoutDeviceIntPoint
 APZCCallbackHelper::ApplyCallbackTransform(const LayoutDeviceIntPoint& aPoint,
                                            const ScrollableLayerGuid& aGuid,
--- a/gfx/layers/apz/util/ChromeProcessController.cpp
+++ b/gfx/layers/apz/util/ChromeProcessController.cpp
@@ -142,16 +142,26 @@ ChromeProcessController::HandleDoubleTap
   }
 
   nsCOMPtr<nsIDocument> document = GetRootContentDocument(aGuid.mScrollId);
   if (!document.get()) {
     return;
   }
 
   CSSPoint point = APZCCallbackHelper::ApplyCallbackTransform(aPoint, aGuid);
+#if defined(MOZ_SINGLE_PROCESS_APZ)
+  // CalculateRectToZoomTo performs a hit test on the frame associated with the
+  // Root Content Document. Unfortunately that frame does not know about the
+  // resolution of the document and so we must remove it before calculating
+  // the zoomToRect.
+  nsIPresShell* presShell = document->GetShell();
+  const float resolution = presShell->ScaleToResolution() ? presShell->GetResolution () : 1.0f;
+  point.x = point.x / resolution;
+  point.y = point.y / resolution;
+#endif // MOZ_SINGLE_PROCESS_APZ
   CSSRect zoomToRect = CalculateRectToZoomTo(document, point);
 
   uint32_t presShellId;
   FrameMetrics::ViewID viewId;
   if (APZCCallbackHelper::GetOrCreateScrollIdentifiers(
       document->GetDocumentElement(), &presShellId, &viewId)) {
     mAPZCTreeManager->ZoomToRect(
       ScrollableLayerGuid(aGuid.mLayersId, presShellId, viewId), zoomToRect);
--- a/gfx/layers/basic/BasicLayerManager.cpp
+++ b/gfx/layers/basic/BasicLayerManager.cpp
@@ -123,16 +123,26 @@ BasicLayerManager::PushGroupForLayer(gfx
       group.mMaskSurface = GetMaskForLayer(aLayer, &group.mMaskTransform);
       return group;
     }
   }
 
   Matrix maskTransform;
   RefPtr<SourceSurface> maskSurf = GetMaskForLayer(aLayer, &maskTransform);
 
+  if (maskSurf) {
+    // The returned transform will transform the mask to device space on the
+    // destination. Since the User->Device space transform will be applied
+    // to the mask by PopGroupAndBlend we need to adjust the transform to
+    // transform the mask to user space.
+    Matrix currentTransform = ToMatrix(group.mFinalTarget->CurrentMatrix());
+    currentTransform.Invert();
+    maskTransform = maskTransform * currentTransform;
+  }
+
   if (aLayer->CanUseOpaqueSurface() &&
       ((didCompleteClip && aRegion.GetNumRects() == 1) ||
        !aContext->CurrentMatrix().HasNonIntegerTranslation())) {
     // If the layer is opaque in its visible region we can push a gfxContentType::COLOR
     // group. We need to make sure that only pixels inside the layer's visible
     // region are copied back to the destination. Remember if we've already
     // clipped precisely to the visible region.
     group.mNeedsClipToVisibleRegion = !didCompleteClip || aRegion.GetNumRects() > 1;
@@ -167,18 +177,22 @@ BasicLayerManager::PopGroupForLayer(Push
 
   DrawTarget* dt = group.mFinalTarget->GetDrawTarget();
   RefPtr<DrawTarget> sourceDT = group.mGroupTarget->GetDrawTarget();
   group.mGroupTarget = nullptr;
 
   RefPtr<SourceSurface> src = sourceDT->Snapshot();
 
   if (group.mMaskSurface) {
-    dt->SetTransform(group.mMaskTransform * Matrix::Translation(-group.mFinalTarget->GetDeviceOffset()));
-    dt->MaskSurface(SurfacePattern(src, ExtendMode::CLAMP, Matrix::Translation(group.mGroupOffset.x, group.mGroupOffset.y)),
+    Point finalOffset = group.mFinalTarget->GetDeviceOffset();
+    dt->SetTransform(group.mMaskTransform * Matrix::Translation(-finalOffset));
+    Matrix surfTransform = group.mMaskTransform;
+    surfTransform.Invert();
+    dt->MaskSurface(SurfacePattern(src, ExtendMode::CLAMP, surfTransform *
+                                                           Matrix::Translation(group.mGroupOffset.x, group.mGroupOffset.y)),
                     group.mMaskSurface, Point(0, 0), DrawOptions(group.mOpacity, group.mOperator));
   } else {
     // For now this is required since our group offset is in device space of the final target,
     // context but that may still have its own device offset. Once PushGroup/PopGroup logic is
     // migrated to DrawTargets this can go as gfxContext::GetDeviceOffset will essentially
     // always become null.
     dt->SetTransform(Matrix::Translation(-group.mFinalTarget->GetDeviceOffset()));
     dt->DrawSurface(src, Rect(group.mGroupOffset.x, group.mGroupOffset.y, src->GetSize().width, src->GetSize().height),
--- a/gfx/layers/ipc/CompositorParent.cpp
+++ b/gfx/layers/ipc/CompositorParent.cpp
@@ -1659,21 +1659,23 @@ CompositorParent::SetControllerForLayerT
                                                  aLayersId,
                                                  aController));
 }
 
 /*static*/ APZCTreeManager*
 CompositorParent::GetAPZCTreeManager(uint64_t aLayersId)
 {
   EnsureLayerTreeMapReady();
-  const CompositorParent::LayerTreeState* state = CompositorParent::GetIndirectShadowTree(aLayersId);
-  if (state && state->mParent) {
-    return state->mParent->mApzcTreeManager;
+  MonitorAutoLock lock(*sIndirectLayerTreesLock);
+  LayerTreeMap::iterator cit = sIndirectLayerTrees.find(aLayersId);
+  if (sIndirectLayerTrees.end() == cit) {
+    return nullptr;
   }
-  return nullptr;
+  LayerTreeState* lts = &cit->second;
+  return (lts->mParent ? lts->mParent->mApzcTreeManager.get() : nullptr);
 }
 
 float
 CompositorParent::ComputeRenderIntegrity()
 {
   if (mLayerManager) {
     return mLayerManager->ComputeRenderIntegrity();
   }
--- a/gfx/src/nsPoint.h
+++ b/gfx/src/nsPoint.h
@@ -32,16 +32,21 @@ struct nsPoint : public mozilla::gfx::Ba
 
   /**
    * Return this point scaled to a different appunits per pixel (APP) ratio.
    * @param aFromAPP the APP to scale from
    * @param aToAPP the APP to scale to
    */
   MOZ_WARN_UNUSED_RESULT inline nsPoint
     ScaleToOtherAppUnits(int32_t aFromAPP, int32_t aToAPP) const;
+
+  MOZ_WARN_UNUSED_RESULT inline nsPoint
+    RemoveResolution(const float resolution) const;
+  MOZ_WARN_UNUSED_RESULT inline nsPoint
+    ApplyResolution(const float resolution) const;
 };
 
 inline nsPoint ToAppUnits(const nsIntPoint& aPoint, nscoord aAppUnitsPerPixel);
 
 inline nsIntPoint
 nsPoint::ScaleToNearestPixels(float aXScale, float aYScale,
                               nscoord aAppUnitsPerPixel) const
 {
@@ -63,16 +68,38 @@ nsPoint::ScaleToOtherAppUnits(int32_t aF
     nsPoint point;
     point.x = NSToCoordRound(NSCoordScale(x, aFromAPP, aToAPP));
     point.y = NSToCoordRound(NSCoordScale(y, aFromAPP, aToAPP));
     return point;
   }
   return *this;
 }
 
+inline nsPoint
+nsPoint::RemoveResolution(const float resolution) const {
+  if (resolution != 1.0f) {
+    nsPoint point;
+    point.x = NSToCoordRound(NSCoordToFloat(x) / resolution);
+    point.y = NSToCoordRound(NSCoordToFloat(y) / resolution);
+    return point;
+  }
+  return *this;
+}
+
+inline nsPoint
+nsPoint::ApplyResolution(const float resolution) const {
+  if (resolution != 1.0f) {
+    nsPoint point;
+    point.x = NSToCoordRound(NSCoordToFloat(x) * resolution);
+    point.y = NSToCoordRound(NSCoordToFloat(y) * resolution);
+    return point;
+  }
+  return *this;
+}
+
 // app units are integer multiples of pixels, so no rounding needed
 inline nsPoint
 ToAppUnits(const nsIntPoint& aPoint, nscoord aAppUnitsPerPixel)
 {
   return nsPoint(NSIntPixelsToAppUnits(aPoint.x, aAppUnitsPerPixel),
                  NSIntPixelsToAppUnits(aPoint.y, aAppUnitsPerPixel));
 }
 
--- a/gfx/src/nsRect.h
+++ b/gfx/src/nsRect.h
@@ -166,16 +166,18 @@ struct nsRect :
   MOZ_WARN_UNUSED_RESULT inline mozilla::gfx::IntRect
   ToInsidePixels(nscoord aAppUnitsPerPixel) const;
 
   // This is here only to keep IPDL-generated code happy. DO NOT USE.
   bool operator==(const nsRect& aRect) const
   {
     return IsEqualEdges(aRect);
   }
+
+  MOZ_WARN_UNUSED_RESULT inline nsRect RemoveResolution(const float aResolution) const;
 };
 
 /*
  * App Unit/Pixel conversions
  */
 
 inline nsRect
 nsRect::ScaleToOtherAppUnitsRoundOut(int32_t aFromAPP, int32_t aToAPP) const
@@ -274,16 +276,35 @@ nsRect::ToOutsidePixels(nscoord aAppUnit
 }
 
 inline mozilla::gfx::IntRect
 nsRect::ToInsidePixels(nscoord aAppUnitsPerPixel) const
 {
   return ScaleToInsidePixels(1.0f, 1.0f, aAppUnitsPerPixel);
 }
 
+inline nsRect
+nsRect::RemoveResolution(const float aResolution) const
+{
+  MOZ_ASSERT(aResolution > 0.0f);
+  nsRect rect;
+  rect.x = NSToCoordRound(NSCoordToFloat(x) / aResolution);
+  rect.y = NSToCoordRound(NSCoordToFloat(y) / aResolution);
+  // A 1x1 rect indicates we are just hit testing a point, so pass down a 1x1
+  // rect as well instead of possibly rounding the width or height to zero.
+  if (width == 1 && height == 1) {
+    rect.width = rect.height = 1;
+  } else {
+    rect.width = NSToCoordCeil(NSCoordToFloat(width) / aResolution);
+    rect.height = NSToCoordCeil(NSCoordToFloat(height) / aResolution);
+  }
+
+  return rect;
+}
+
 const mozilla::gfx::IntRect& GetMaxSizedIntRect();
 
 // app units are integer multiples of pixels, so no rounding needed
 nsRect
 ToAppUnits(const mozilla::gfx::IntRect& aRect, nscoord aAppUnitsPerPixel);
 
 #ifdef DEBUG
 // Diagnostics
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -11,16 +11,17 @@
 #include "nsNetUtil.h"
 #include "nsIJARChannel.h"
 #include "nsIProtocolHandler.h"
 #include "nsIPrincipal.h"
 #include "nsIZipReader.h"
 #include "gfxFontConstants.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
 #include "mozilla/gfx/2D.h"
 #include "gfxPlatformFontList.h"
 
 #include "opentype-sanitiser.h"
 #include "ots-memory-stream.h"
 
 using namespace mozilla;
 
@@ -422,16 +423,18 @@ gfxUserFontEntry::LoadNextSrc()
                 fe->mFamilyName = mFamilyName;
                 // For src:local(), we don't care whether the request is from
                 // a private window as there's no issue of caching resources;
                 // local fonts are just available all the time.
                 StoreUserFontData(fe, false, nsString(), nullptr, 0,
                                   gfxUserFontData::kUnknownCompression);
                 mPlatformFontEntry = fe;
                 SetLoadState(STATUS_LOADED);
+                Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE,
+                                      currSrc.mSourceType + 1);
                 return;
             } else {
                 LOG(("userfonts (%p) [src %d] failed local: (%s) for (%s)\n",
                      mFontSet, mSrcIndex,
                      NS_ConvertUTF16toUTF8(currSrc.mLocalName).get(),
                      NS_ConvertUTF16toUTF8(mFamilyName).get()));
             }
         }
@@ -452,16 +455,24 @@ gfxUserFontEntry::LoadNextSrc()
                         gfxFontEntry* fe = gfxUserFontSet::
                             UserFontCache::GetFont(currSrc.mURI,
                                                    principal,
                                                    this,
                                                    mFontSet->GetPrivateBrowsing());
                         if (fe) {
                             mPlatformFontEntry = fe;
                             SetLoadState(STATUS_LOADED);
+                            if (LOG_ENABLED()) {
+                                nsAutoCString fontURI;
+                                currSrc.mURI->GetSpec(fontURI);
+                                LOG(("userfonts (%p) [src %d] "
+                                     "loaded uri from cache: (%s) for (%s)\n",
+                                     mFontSet, mSrcIndex, fontURI.get(),
+                                     NS_ConvertUTF16toUTF8(mFamilyName).get()));
+                            }
                             return;
                         }
                     }
 
                     // record the principal returned by CheckFontLoad,
                     // for use when creating a channel
                     // and when caching the loaded entry
                     mPrincipal = principal;
@@ -477,16 +488,18 @@ gfxUserFontEntry::LoadNextSrc()
 
                         // sync load font immediately
                         rv = mFontSet->SyncLoadFontData(this, &currSrc, buffer,
                                                         bufferLength);
 
                         if (NS_SUCCEEDED(rv) &&
                             LoadPlatformFont(buffer, bufferLength)) {
                             SetLoadState(STATUS_LOADED);
+                            Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE,
+                                                  currSrc.mSourceType + 1);
                             return;
                         } else {
                             mFontSet->LogMessage(this,
                                                  "font load failed",
                                                  nsIScriptError::errorFlag,
                                                  rv);
                         }
 
@@ -531,16 +544,18 @@ gfxUserFontEntry::LoadNextSrc()
             uint32_t bufferLength = 0;
 
             // sync load font immediately
             currSrc.mBuffer->TakeBuffer(buffer, bufferLength);
             if (buffer && LoadPlatformFont(buffer, bufferLength)) {
                 // LoadPlatformFont takes ownership of the buffer, so no need
                 // to free it here.
                 SetLoadState(STATUS_LOADED);
+                Telemetry::Accumulate(Telemetry::WEBFONT_SRCTYPE,
+                                      currSrc.mSourceType + 1);
                 return;
             } else {
                 mFontSet->LogMessage(this,
                                      "font load failed",
                                      nsIScriptError::errorFlag);
             }
         }
 
@@ -572,34 +587,47 @@ gfxUserFontEntry::LoadPlatformFont(const
                   mUserFontLoadState == STATUS_LOADING) &&
                  mFontDataLoadingState < LOADING_FAILED,
                  "attempting to load a font that has either completed or failed");
 
     gfxFontEntry* fe = nullptr;
 
     gfxUserFontType fontType =
         gfxFontUtils::DetermineFontDataType(aFontData, aLength);
+    Telemetry::Accumulate(Telemetry::WEBFONT_FONTTYPE, uint32_t(fontType));
 
     // Unwrap/decompress/sanitize or otherwise munge the downloaded data
     // to make a usable sfnt structure.
 
     // Because platform font activation code may replace the name table
     // in the font with a synthetic one, we save the original name so that
     // it can be reported via the nsIDOMFontFace API.
     nsAutoString originalFullName;
 
     // Call the OTS sanitizer; this will also decode WOFF to sfnt
     // if necessary. The original data in aFontData is left unchanged.
     uint32_t saneLen;
+    uint32_t fontCompressionRatio = 0;
     const uint8_t* saneData =
         SanitizeOpenTypeData(aFontData, aLength, saneLen, fontType);
     if (!saneData) {
         mFontSet->LogMessage(this, "rejected by sanitizer");
     }
     if (saneData) {
+        if (saneLen) {
+            fontCompressionRatio = uint32_t(100.0 * aLength / saneLen + 0.5);
+            if (fontType == GFX_USERFONT_WOFF ||
+                fontType == GFX_USERFONT_WOFF2) {
+                Telemetry::Accumulate(fontType == GFX_USERFONT_WOFF ?
+                                      Telemetry::WEBFONT_COMPRESSION_WOFF :
+                                      Telemetry::WEBFONT_COMPRESSION_WOFF2,
+                                      fontCompressionRatio);
+                }
+        }
+
         // The sanitizer ensures that we have a valid sfnt and a usable
         // name table, so this should never fail unless we're out of
         // memory, and GetFullNameFromSFNT is not directly exposed to
         // arbitrary/malicious data from the web.
         gfxFontUtils::GetFullNameFromSFNT(saneData, saneLen,
                                           originalFullName);
         // Here ownership of saneData is passed to the platform,
         // which will delete it when no longer required
@@ -636,21 +664,21 @@ gfxUserFontEntry::LoadPlatformFont(const
         fe->mFeatureSettings.AppendElements(mFeatureSettings);
         fe->mLanguageOverride = mLanguageOverride;
         fe->mFamilyName = mFamilyName;
         StoreUserFontData(fe, mFontSet->GetPrivateBrowsing(), originalFullName,
                           &metadata, metaOrigLen, compression);
         if (LOG_ENABLED()) {
             nsAutoCString fontURI;
             mSrcList[mSrcIndex].mURI->GetSpec(fontURI);
-            LOG(("userfonts (%p) [src %d] loaded uri: (%s) for (%s) (%p) gen: %8.8x\n",
+            LOG(("userfonts (%p) [src %d] loaded uri: (%s) for (%s) "
+                 "(%p) gen: %8.8x compress: %d%%\n",
                  mFontSet, mSrcIndex, fontURI.get(),
                  NS_ConvertUTF16toUTF8(mFamilyName).get(),
-                 this,
-                 uint32_t(mFontSet->mGeneration)));
+                 this, uint32_t(mFontSet->mGeneration), fontCompressionRatio));
         }
         mPlatformFontEntry = fe;
         SetLoadState(STATUS_LOADED);
         gfxUserFontSet::UserFontCache::CacheFont(fe);
     } else {
         if (LOG_ENABLED()) {
             nsAutoCString fontURI;
             mSrcList[mSrcIndex].mURI->GetSpec(fontURI);
@@ -733,17 +761,20 @@ gfxUserFontEntry::FontDataDownloadComple
 void
 gfxUserFontEntry::GetUserFontSets(nsTArray<gfxUserFontSet*>& aResult)
 {
     aResult.Clear();
     aResult.AppendElement(mFontSet);
 }
 
 gfxUserFontSet::gfxUserFontSet()
-    : mFontFamilies(4), mLocalRulesUsed(false)
+    : mFontFamilies(4),
+      mLocalRulesUsed(false),
+      mDownloadCount(0),
+      mDownloadSize(0)
 {
     IncrementGeneration(true);
     gfxPlatformFontList* fp = gfxPlatformFontList::PlatformFontList();
     if (fp) {
         fp->AddUserFontSet(this);
     }
 }
 
--- a/gfx/thebes/gfxUserFontSet.h
+++ b/gfx/thebes/gfxUserFontSet.h
@@ -463,16 +463,25 @@ public:
     };
 
     void SetLocalRulesUsed() {
         mLocalRulesUsed = true;
     }
 
     static mozilla::LogModule* GetUserFontsLog();
 
+    // record statistics about font completion
+    virtual void RecordFontLoadDone(uint32_t aFontSize,
+                                    mozilla::TimeStamp aDoneTime) {}
+
+    void GetLoadStatistics(uint32_t& aLoadCount, uint64_t& aLoadSize) const {
+        aLoadCount = mDownloadCount;
+        aLoadSize = mDownloadSize;
+    }
+
 protected:
     // Protected destructor, to discourage deletion outside of Release():
     virtual ~gfxUserFontSet();
 
     // Return whether the font set is associated with a private-browsing tab.
     virtual bool GetPrivateBrowsing() = 0;
 
     // parse data for a data URL
@@ -508,16 +517,20 @@ protected:
     // font families defined by @font-face rules
     nsRefPtrHashtable<nsStringHashKey, gfxUserFontFamily> mFontFamilies;
 
     uint64_t        mGeneration;        // bumped on any font load change
     uint64_t        mRebuildGeneration; // only bumped on rebuilds
 
     // true when local names have been looked up, false otherwise
     bool mLocalRulesUsed;
+
+    // performance stats
+    uint32_t mDownloadCount;
+    uint64_t mDownloadSize;
 };
 
 // acts a placeholder until the real font is downloaded
 
 class gfxUserFontEntry : public gfxFontEntry {
     friend class gfxUserFontSet;
     friend class nsUserFontSet;
     friend class nsFontFaceLoader;
--- a/intl/unicharutil/tools/genUnicodePropertyData.pl
+++ b/intl/unicharutil/tools/genUnicodePropertyData.pl
@@ -14,16 +14,17 @@
 # (1) Download the current Unicode data files from
 #
 #         http://www.unicode.org/Public/UNIDATA/
 #
 #     NB: not all the files are actually needed; currently, we require
 #       - UnicodeData.txt
 #       - Scripts.txt
 #       - BidiMirroring.txt
+#       - BidiBrackets.txt
 #       - HangulSyllableType.txt
 #       - ReadMe.txt (to record version/date of the UCD)
 #       - Unihan_Variants.txt (from Unihan.zip)
 #     though this may change if we find a need for additional properties.
 #
 #     The Unicode data files listed above should be together in one directory.
 #
 #     We also require the file
@@ -329,28 +330,30 @@ my %verticalOrientationCode = (
   'Tr' => 3  #   Tr - Transformed typographically, with fallback to Rotated
 );
 
 # initialize default properties
 my @script;
 my @category;
 my @combining;
 my @mirror;
+my @pairedBracketType;
 my @hangul;
 my @casemap;
 my @xidmod;
 my @numericvalue;
 my @hanVariant;
 my @bidicategory;
 my @fullWidth;
 my @verticalOrientation;
 for (my $i = 0; $i < 0x110000; ++$i) {
     $script[$i] = $scriptCode{"UNKNOWN"};
     $category[$i] = $catCode{"UNASSIGNED"};
     $combining[$i] = 0;
+    $pairedBracketType[$i] = 0;
     $casemap[$i] = 0;
     $xidmod[$i] = $xidmodCode{"not-chars"};
     $numericvalue[$i] = -1;
     $hanVariant[$i] = 0;
     $bidicategory[$i] = $bidicategoryCode{"L"};
     $fullWidth[$i] = 0;
     $verticalOrientation[$i] = 1; # default for unlisted codepoints is 'R'
 }
@@ -518,23 +521,48 @@ while (<FH>) {
     chomp;
     push @versionInfo, $_;
     last if /Date:/;
 }
 while (<FH>) {
     s/#.*//;
     if (m/([0-9A-F]{4,6});\s*([0-9A-F]{4,6})/) {
         my $mirrorOffset = hex("0x$2") - hex("0x$1");
-	my $offsetIndex = first { $offsets[$_] eq $mirrorOffset } 0..$#offsets;
-	if ($offsetIndex == undef) {
+        my $offsetIndex = first { $offsets[$_] eq $mirrorOffset } 0..$#offsets;
+        if ($offsetIndex == undef) {
             die "too many offset codes\n" if scalar @offsets == 31;
             push @offsets, $mirrorOffset;
-	    $offsetIndex = $#offsets;
+            $offsetIndex = $#offsets;
         }
-	$mirror[hex "0x$1"] = $offsetIndex;
+        $mirror[hex "0x$1"] = $offsetIndex;
+    }
+}
+close FH;
+
+# read BidiBrackets.txt
+my %pairedBracketTypeCode = (
+  'N' => 0,
+  'O' => 1,
+  'C' => 2
+);
+open FH, "< $ARGV[1]/BidiBrackets.txt" or die "can't open UCD file BidiBrackets.txt\n";
+push @versionInfo, "";
+while (<FH>) {
+    chomp;
+    push @versionInfo, $_;
+    last if /Date:/;
+}
+while (<FH>) {
+    s/#.*//;
+    if (m/([0-9A-F]{4,6});\s*([0-9A-F]{4,6});\s*(.)/) {
+        my $mirroredChar = $offsets[$mirror[hex "0x$1"]] + hex "0x$1";
+        die "bidi bracket does not match mirrored char\n" unless $mirroredChar == hex "0x$2";
+        my $pbt = uc($3);
+        warn "unknown Bidi Bracket type" unless exists $pairedBracketTypeCode{$pbt};
+        $pairedBracketType[hex "0x$1"] = $pairedBracketTypeCode{$pbt};
     }
 }
 close FH;
 
 # read HangulSyllableType.txt
 my %hangulType = (
   'L'   => 0x01,
   'V'   => 0x02,
@@ -731,37 +759,37 @@ my $type = q/
 struct nsCharProps1 {
   unsigned char mMirrorOffsetIndex:5;
   unsigned char mHangulType:3;
   unsigned char mCombiningClass:8;
 };
 /;
 print DATA_TABLES "#ifndef ENABLE_INTL_API\n";
 &genTables("CharProp1", $type, "nsCharProps1", 11, 5, \&sprintCharProps1, 1, 2, 1);
-+print DATA_TABLES "#endif\n\n";
+print DATA_TABLES "#endif\n\n";
 
 sub sprintCharProps2
 {
   my $usv = shift;
   return sprintf("{%d,%d,%d,%d,%d,%d,%d},",
-                 $script[$usv], 0, $category[$usv],
+                 $script[$usv], $pairedBracketType[$usv], $category[$usv],
                  $bidicategory[$usv], $xidmod[$usv], $numericvalue[$usv],
                  $verticalOrientation[$usv]);
 }
-$type = q/
+$type = q|
 struct nsCharProps2 {
   unsigned char mScriptCode:8;
-  unsigned char mUnused:3;
+  unsigned char mPairedBracketType:3; // only 2 bits actually needed
   unsigned char mCategory:5;
   unsigned char mBidiCategory:5;
   unsigned char mXidmod:4;
   signed char   mNumericValue:5;
   unsigned char mVertOrient:2;
 };
-/;
+|;
 &genTables("CharProp2", $type, "nsCharProps2", 11, 5, \&sprintCharProps2, 16, 4, 1);
 
 print HEADER "#pragma pack()\n\n";
 
 sub sprintHanVariants
 {
   my $baseUsv = shift;
   my $varShift = 0;
--- a/intl/unicharutil/util/nsUnicodeProperties.cpp
+++ b/intl/unicharutil/util/nsUnicodeProperties.cpp
@@ -165,16 +165,36 @@ GetScriptTagForCode(int32_t aScriptCode)
 {
     // this will safely return 0 for negative script codes, too :)
     if (uint32_t(aScriptCode) > ArrayLength(sScriptCodeToTag)) {
         return 0;
     }
     return sScriptCodeToTag[aScriptCode];
 }
 
+PairedBracketType GetPairedBracketType(uint32_t aCh)
+{
+#if ENABLE_INTL_API
+  return PairedBracketType
+           (u_getIntPropertyValue(aCh, UCHAR_BIDI_PAIRED_BRACKET_TYPE));
+#else
+  return PairedBracketType(GetCharProps2(aCh).mPairedBracketType);
+#endif
+}
+
+uint32_t GetPairedBracket(uint32_t aCh)
+{
+#if ENABLE_INTL_API
+  return u_getBidiPairedBracket(aCh);
+#else
+  return GetPairedBracketType(aCh) != PAIRED_BRACKET_TYPE_NONE
+         ? GetMirroredChar(aCh) : aCh;
+#endif
+}
+
 static inline uint32_t
 GetCaseMapValue(uint32_t aCh)
 {
     if (aCh < UNICODE_BMP_LIMIT) {
         return sCaseMapValues[sCaseMapPages[0][aCh >> kCaseMapCharBits]]
                              [aCh & ((1 << kCaseMapCharBits) - 1)];
     }
     if (aCh < (kCaseMapMaxPlane + 1) * 0x10000) {
--- a/intl/unicharutil/util/nsUnicodeProperties.h
+++ b/intl/unicharutil/util/nsUnicodeProperties.h
@@ -52,16 +52,26 @@ enum VerticalOrientation {
   VERTICAL_ORIENTATION_Tu = 2,
   VERTICAL_ORIENTATION_Tr = 3
 };
 
 inline VerticalOrientation GetVerticalOrientation(uint32_t aCh) {
   return VerticalOrientation(GetCharProps2(aCh).mVertOrient);
 }
 
+/* This MUST match the values assigned by genUnicodePropertyData.pl! */
+enum PairedBracketType {
+  PAIRED_BRACKET_TYPE_NONE = 0,
+  PAIRED_BRACKET_TYPE_OPEN = 1,
+  PAIRED_BRACKET_TYPE_CLOSE = 2
+};
+
+PairedBracketType GetPairedBracketType(uint32_t aCh);
+uint32_t GetPairedBracket(uint32_t aCh);
+
 enum XidmodType {
   XIDMOD_RECOMMENDED,
   XIDMOD_INCLUSION,
   XIDMOD_UNCOMMON_USE,
   XIDMOD_TECHNICAL,
   XIDMOD_OBSOLETE,
   XIDMOD_ASPIRATIONAL,
   XIDMOD_LIMITED_USE,
--- a/intl/unicharutil/util/nsUnicodePropertyData.cpp
+++ b/intl/unicharutil/util/nsUnicodePropertyData.cpp
@@ -6,17 +6,17 @@
 
 /*
  * Derived from the Unicode Character Database by genUnicodePropertyData.pl
  *
  * For Unicode terms of use, see http://www.unicode.org/terms_of_use.html
  */
 
 /*
- * Created on Mon Sep  7 02:52:23 2015 from UCD data files with version info:
+ * Created on Tue Nov 17 07:34:16 2015 from UCD data files with version info:
  *
 
 # Date: 2015-06-16, 20:24:00 GMT [KW]
 #
 # Unicode Character Database
 # Copyright (c) 1991-2015 Unicode, Inc.
 # For terms of use, see http://www.unicode.org/terms_of_use.html
 #
@@ -33,16 +33,19 @@ Standard.
 
 
 # Scripts-8.0.0.txt
 # Date: 2015-03-11, 22:29:42 GMT [MD]
 
 # BidiMirroring-8.0.0.txt
 # Date: 2015-01-20, 18:30:00 GMT [KW, LI]
 
+# BidiBrackets-8.0.0.txt
+# Date: 2015-01-20, 19:00:00 GMT [AG, LI, KW]
+
 # HangulSyllableType-8.0.0.txt
 # Date: 2014-12-16, 23:07:45 GMT [MD]
 
 # File: xidmodifications.txt
 # Version: 8.0.0
 # Generated: 2015-05-17, 03:09:04 GMT
 
 #
@@ -406,19 +409,19 @@ static const uint16_t sCharProp2Pages[7]
   {320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,320,639},
   {110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110},
   {640,641,641,642,110,110,110,110,643,643,643,643,643,643,643,644,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110,110},
   {377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,377,645}
 };
 
 static const nsCharProps2 sCharProp2Values[646][32] = {
   {{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,8,8,-1,1},{0,0,0,7,8,-1,1},{0,0,0,8,8,-1,1},{0,0,0,9,8,-1,1},{0,0,0,7,8,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,7,12,-1,1},{0,0,0,7,12,-1,1},{0,0,0,7,12,-1,1},{0,0,0,8,12,-1,1}},
-  {{0,0,29,9,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,21,4,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,1,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,25,3,8,-1,1},{0,0,21,6,8,-1,1},{0,0,17,3,1,-1,1},{0,0,21,6,1,-1,1},{0,0,21,6,8,-1,1},{0,0,13,2,0,0,1},{0,0,13,2,0,1,1},{0,0,13,2,0,2,1},{0,0,13,2,0,3,1},{0,0,13,2,0,4,1},{0,0,13,2,0,5,1},{0,0,13,2,0,6,1},{0,0,13,2,0,7,1},{0,0,13,2,0,8,1},{0,0,13,2,0,9,1},{0,0,21,6,1,-1,1},{0,0,21,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,21,10,8,-1,1}},
-  {{0,0,21,10,8,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{0,0,22,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,24,10,8,-1,1},{0,0,16,10,0,-1,1}},
-  {{0,0,24,10,8,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{0,0,22,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,0,18,12,-1,1}},
+  {{0,0,29,9,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,21,4,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,1,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,25,3,8,-1,1},{0,0,21,6,8,-1,1},{0,0,17,3,1,-1,1},{0,0,21,6,1,-1,1},{0,0,21,6,8,-1,1},{0,0,13,2,0,0,1},{0,0,13,2,0,1,1},{0,0,13,2,0,2,1},{0,0,13,2,0,3,1},{0,0,13,2,0,4,1},{0,0,13,2,0,5,1},{0,0,13,2,0,6,1},{0,0,13,2,0,7,1},{0,0,13,2,0,8,1},{0,0,13,2,0,9,1},{0,0,21,6,1,-1,1},{0,0,21,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,21,10,8,-1,1}},
+  {{0,0,21,10,8,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{0,1,22,10,8,-1,1},{0,0,21,10,8,-1,1},{0,2,18,10,8,-1,1},{0,0,24,10,8,-1,1},{0,0,16,10,0,-1,1}},
+  {{0,0,24,10,8,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{0,1,22,10,8,-1,1},{0,0,25,10,8,-1,1},{0,2,18,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,0,18,12,-1,1}},
   {{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,7,8,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1},{0,0,0,18,12,-1,1}},
   {{0,0,29,6,9,-1,1},{0,0,21,10,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,26,10,8,-1,1},{0,0,21,10,8,-1,0},{0,0,24,10,9,-1,1},{0,0,26,10,8,-1,0},{25,0,7,0,9,-1,1},{0,0,20,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,1,18,10,-1,1},{0,0,26,10,8,-1,0},{0,0,24,10,9,-1,1},{0,0,26,4,8,-1,1},{0,0,25,4,8,-1,0},{0,0,15,2,9,2,1},{0,0,15,2,9,3,1},{0,0,24,10,9,-1,1},{0,0,5,0,9,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,1,-1,1},{0,0,24,10,9,-1,1},{0,0,15,2,9,1,1},{25,0,7,0,9,-1,1},{0,0,19,10,8,-1,1},{0,0,15,10,9,-1,0},{0,0,15,10,9,-1,0},{0,0,15,10,9,-1,0},{0,0,21,10,8,-1,1}},
   {{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{0,0,25,10,8,-1,0},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1}},
   {{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{0,0,25,10,8,-1,0},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1}},
   {{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1}},
   {{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,9,-1,1},{25,0,5,0,9,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,9,-1,1}},
   {{25,0,5,0,9,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,11,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1}},
   {{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,9,0,0,-1,1},{25,0,5,0,0,-1,1},{25,0,5,0,9,-1,1}},
@@ -520,17 +523,17 @@ static const nsCharProps2 sCharProp2Valu
   {{61,0,2,0,12,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1}},
   {{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,12,17,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,9,-1,1},{38,0,12,17,0,-1,1},{38,0,12,17,0,-1,1},{38,0,12,17,0,-1,1},{38,0,12,17,0,-1,1},{38,0,12,17,0,-1,1},{38,0,12,17,0,-1,1},{38,0,12,17,0,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{0,0,23,4,8,-1,1}},
   {{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,7,0,0,-1,1},{38,0,6,0,0,-1,1},{38,0,12,17,0,-1,1},{38,0,12,17,0,-1,1},{38,0,12,17,0,-1,1},{38,0,12,17,0,-1,1},{38,0,12,17,0,-1,1},{38,0,12,17,0,-1,1},{38,0,12,17,0,-1,1},{38,0,12,17,0,-1,1},{38,0,21,0,8,-1,1},{38,0,13,0,0,0,1},{38,0,13,0,0,1,1},{38,0,13,0,0,2,1},{38,0,13,0,0,3,1},{38,0,13,0,0,4,1},{38,0,13,0,0,5,1},{38,0,13,0,0,6,1},{38,0,13,0,0,7,1},{38,0,13,0,0,8,1},{38,0,13,0,0,9,1},{38,0,21,0,8,-1,1},{38,0,21,0,8,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{61,0,2,0,12,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1},{61,0,2,0,12,-1,1},{24,0,7,0,0,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1},{61,0,2,0,12,-1,1},{24,0,7,0,0,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{24,0,7,0,0,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1},{61,0,2,0,12,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1}},
   {{61,0,2,0,12,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1},{61,0,2,0,12,-1,1},{24,0,7,0,0,-1,1},{61,0,2,0,12,-1,1},{24,0,7,0,0,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1},{61,0,2,0,12,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1},{24,0,12,17,0,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,9,-1,1},{24,0,12,17,0,-1,1},{24,0,12,17,0,-1,1},{24,0,12,17,0,-1,1},{24,0,12,17,0,-1,1},{24,0,12,17,0,-1,1},{24,0,12,17,0,-1,1},{61,0,2,0,12,-1,1},{24,0,12,17,0,-1,1},{24,0,12,17,0,-1,1},{24,0,7,0,0,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1},{61,0,2,0,12,-1,1},{24,0,6,0,0,-1,1},{61,0,2,0,12,-1,1},{24,0,12,17,0,-1,1},{24,0,12,17,0,-1,1},{24,0,12,17,0,-1,1},{24,0,12,17,0,-1,1},{24,0,12,17,0,-1,1},{24,0,12,17,0,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{24,0,13,0,0,0,1},{24,0,13,0,0,1,1},{24,0,13,0,0,2,1},{24,0,13,0,0,3,1},{24,0,13,0,0,4,1},{24,0,13,0,0,5,1},{24,0,13,0,0,6,1},{24,0,13,0,0,7,1},{24,0,13,0,0,8,1},{24,0,13,0,0,9,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{24,0,7,0,9,-1,1},{24,0,7,0,9,-1,1},{24,0,7,0,0,-1,1},{24,0,7,0,0,-1,1}},
   {{39,0,7,0,0,-1,1},{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,21,0,8,-1,1},{39,0,21,0,8,-1,1},{39,0,21,0,8,-1,1},{39,0,21,0,8,-1,1},{39,0,21,0,8,-1,1},{39,0,21,0,8,-1,1},{39,0,21,0,8,-1,1},{39,0,21,0,1,-1,1},{39,0,21,0,9,-1,1},{39,0,21,0,8,-1,1},{39,0,21,0,8,-1,1},{39,0,21,0,8,-1,1},{39,0,21,0,8,-1,1},{39,0,21,0,8,-1,1},{39,0,21,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,21,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,12,17,3,-1,1},{39,0,12,17,3,-1,1},{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1}},
-  {{39,0,13,0,0,0,1},{39,0,13,0,0,1,1},{39,0,13,0,0,2,1},{39,0,13,0,0,3,1},{39,0,13,0,0,4,1},{39,0,13,0,0,5,1},{39,0,13,0,0,6,1},{39,0,13,0,0,7,1},{39,0,13,0,0,8,1},{39,0,13,0,0,9,1},{39,0,15,0,8,-1,1},{39,0,15,0,8,-1,1},{39,0,15,0,8,-1,1},{39,0,15,0,8,-1,1},{39,0,15,0,8,-1,1},{39,0,15,0,8,-1,1},{39,0,15,0,8,-1,1},{39,0,15,0,8,-1,1},{39,0,15,0,8,-1,1},{39,0,15,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,12,17,0,-1,1},{39,0,26,0,8,-1,1},{39,0,12,17,0,-1,1},{39,0,26,0,8,-1,1},{39,0,12,17,2,-1,1},{39,0,22,10,8,-1,1},{39,0,18,10,8,-1,1},{39,0,22,10,8,-1,1},{39,0,18,10,8,-1,1},{39,0,10,0,0,-1,1},{39,0,10,0,0,-1,1}},
+  {{39,0,13,0,0,0,1},{39,0,13,0,0,1,1},{39,0,13,0,0,2,1},{39,0,13,0,0,3,1},{39,0,13,0,0,4,1},{39,0,13,0,0,5,1},{39,0,13,0,0,6,1},{39,0,13,0,0,7,1},{39,0,13,0,0,8,1},{39,0,13,0,0,9,1},{39,0,15,0,8,-1,1},{39,0,15,0,8,-1,1},{39,0,15,0,8,-1,1},{39,0,15,0,8,-1,1},{39,0,15,0,8,-1,1},{39,0,15,0,8,-1,1},{39,0,15,0,8,-1,1},{39,0,15,0,8,-1,1},{39,0,15,0,8,-1,1},{39,0,15,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,12,17,0,-1,1},{39,0,26,0,8,-1,1},{39,0,12,17,0,-1,1},{39,0,26,0,8,-1,1},{39,0,12,17,2,-1,1},{39,1,22,10,8,-1,1},{39,2,18,10,8,-1,1},{39,1,22,10,8,-1,1},{39,2,18,10,8,-1,1},{39,0,10,0,0,-1,1},{39,0,10,0,0,-1,1}},
   {{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,9,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{61,0,2,0,12,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,9,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,9,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,9,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,9,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1}},
   {{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,9,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,9,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,9,-1,1},{39,0,12,17,9,-1,1},{39,0,12,17,11,-1,1},{39,0,12,17,9,-1,1},{39,0,12,17,11,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,10,0,0,-1,1}},
   {{39,0,12,17,0,-1,1},{39,0,12,17,9,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,21,0,8,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,7,0,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,9,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{61,0,2,0,12,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,9,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1}},
   {{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,9,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,9,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,9,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,9,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{39,0,12,17,0,-1,1},{61,0,2,0,12,-1,1},{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1}},
   {{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,12,17,0,-1,1},{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1},{61,0,2,0,12,-1,1},{39,0,26,0,8,-1,1},{39,0,26,0,8,-1,1},{39,0,21,0,8,-1,1},{39,0,21,0,8,-1,1},{39,0,21,0,8,-1,1},{39,0,21,0,8,-1,1},{39,0,21,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{39,0,21,0,8,-1,1},{39,0,21,0,8,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1}},
   {{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,10,0,0,-1,1},{28,0,10,0,0,-1,1},{28,0,12,17,0,-1,1},{28,0,12,17,0,-1,1},{28,0,12,17,0,-1,1},{28,0,12,17,0,-1,1},{28,0,10,0,0,-1,1},{28,0,12,17,0,-1,1},{28,0,12,17,0,-1,1},{28,0,12,17,0,-1,1},{28,0,12,17,0,-1,1},{28,0,12,17,0,-1,1},{28,0,12,17,0,-1,1},{28,0,10,0,0,-1,1},{28,0,12,17,0,-1,1},{28,0,12,17,0,-1,1},{28,0,10,0,0,-1,1},{28,0,10,0,0,-1,1},{28,0,12,17,0,-1,1},{28,0,12,17,0,-1,1},{28,0,7,0,0,-1,1}},
   {{28,0,13,0,0,0,1},{28,0,13,0,0,1,1},{28,0,13,0,0,2,1},{28,0,13,0,0,3,1},{28,0,13,0,0,4,1},{28,0,13,0,0,5,1},{28,0,13,0,0,6,1},{28,0,13,0,0,7,1},{28,0,13,0,0,8,1},{28,0,13,0,0,9,1},{28,0,21,0,8,-1,1},{28,0,21,0,8,-1,1},{28,0,21,0,8,-1,1},{28,0,21,0,8,-1,1},{28,0,21,0,8,-1,1},{28,0,21,0,8,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,10,0,0,-1,1},{28,0,10,0,0,-1,1},{28,0,12,17,0,-1,1},{28,0,12,17,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,7,0,0,-1,1},{28,0,12,17,0,-1,1},{28,0,12,17,0,-1,1}},
@@ -551,17 +554,17 @@ static const nsCharProps2 sCharProp2Valu
   {{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{11,0,12,17,0,-1,1},{11,0,12,17,0,-1,1},{11,0,12,17,0,-1,1}},
   {{11,0,21,0,8,-1,1},{11,0,21,0,8,-1,1},{11,0,21,0,8,-1,1},{11,0,21,0,8,-1,1},{11,0,21,0,8,-1,1},{11,0,21,0,8,-1,1},{11,0,21,0,8,-1,1},{11,0,21,0,8,-1,1},{11,0,21,0,8,-1,1},{11,0,15,0,4,1,1},{11,0,15,0,4,2,1},{11,0,15,0,4,3,1},{11,0,15,0,4,4,1},{11,0,15,0,4,5,1},{11,0,15,0,4,6,1},{11,0,15,0,4,7,1},{11,0,15,0,4,8,1},{11,0,15,0,4,9,1},{11,0,15,0,8,-1,1},{11,0,15,0,8,-1,1},{11,0,15,0,8,-1,1},{11,0,15,0,8,-1,1},{11,0,15,0,8,-1,1},{11,0,15,0,8,-1,1},{11,0,15,0,8,-1,1},{11,0,15,0,8,-1,1},{11,0,15,0,8,-1,1},{11,0,15,0,8,-1,1},{11,0,15,0,8,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,26,10,8,-1,1},{11,0,26,10,8,-1,1},{11,0,26,10,8,-1,1},{11,0,26,10,8,-1,1},{11,0,26,10,8,-1,1},{11,0,26,10,8,-1,1},{11,0,26,10,8,-1,1},{11,0,26,10,8,-1,1},{11,0,26,10,8,-1,1},{11,0,26,10,8,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1}},
   {{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{6,0,9,0,6,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{6,0,5,0,6,-1,1},{6,0,5,0,6,-1,1},{6,0,5,0,6,-1,1},{6,0,5,0,6,-1,1},{6,0,5,0,6,-1,1},{6,0,5,0,6,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{40,0,17,10,8,-1,1},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0}},
   {{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0}},
   {{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,21,0,8,-1,0},{40,0,21,0,8,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0},{40,0,7,0,5,-1,0}},
-  {{29,0,29,9,8,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,22,10,8,-1,1},{29,0,18,10,8,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
+  {{29,0,29,9,8,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,0,7,0,7,-1,1},{29,1,22,10,8,-1,1},{29,2,18,10,8,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1}},
   {{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{0,0,21,0,8,-1,1},{0,0,21,0,8,-1,1},{0,0,21,0,8,-1,1},{32,0,14,0,7,-1,1},{32,0,14,0,7,-1,1},{32,0,14,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{32,0,7,0,7,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{42,0,7,0,7,-1,1},{42,0,7,0,7,-1,1},{42,0,7,0,7,-1,1},{42,0,7,0,7,-1,1},{42,0,7,0,7,-1,1},{42,0,7,0,7,-1,1},{42,0,7,0,7,-1,1},{42,0,7,0,7,-1,1},{42,0,7,0,7,-1,1},{42,0,7,0,7,-1,1},{42,0,7,0,7,-1,1},{42,0,7,0,7,-1,1},{42,0,7,0,7,-1,1},{61,0,2,0,12,-1,1},{42,0,7,0,7,-1,1},{42,0,7,0,7,-1,1},{42,0,7,0,7,-1,1},{42,0,7,0,7,-1,1},{42,0,12,17,7,-1,1},{42,0,12,17,7,-1,1},{42,0,12,17,7,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{43,0,7,0,7,-1,1},{43,0,7,0,7,-1,1},{43,0,7,0,7,-1,1},{43,0,7,0,7,-1,1},{43,0,7,0,7,-1,1},{43,0,7,0,7,-1,1},{43,0,7,0,7,-1,1},{43,0,7,0,7,-1,1},{43,0,7,0,7,-1,1},{43,0,7,0,7,-1,1},{43,0,7,0,7,-1,1},{43,0,7,0,7,-1,1},{43,0,7,0,7,-1,1},{43,0,7,0,7,-1,1},{43,0,7,0,7,-1,1},{43,0,7,0,7,-1,1},{43,0,7,0,7,-1,1},{43,0,7,0,7,-1,1},{43,0,12,17,7,-1,1},{43,0,12,17,7,-1,1},{43,0,12,17,7,-1,1},{0,0,21,0,8,-1,1},{0,0,21,0,8,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{44,0,7,0,7,-1,1},{44,0,7,0,7,-1,1},{44,0,7,0,7,-1,1},{44,0,7,0,7,-1,1},{44,0,7,0,7,-1,1},{44,0,7,0,7,-1,1},{44,0,7,0,7,-1,1},{44,0,7,0,7,-1,1},{44,0,7,0,7,-1,1},{44,0,7,0,7,-1,1},{44,0,7,0,7,-1,1},{44,0,7,0,7,-1,1},{44,0,7,0,7,-1,1},{44,0,7,0,7,-1,1},{44,0,7,0,7,-1,1},{44,0,7,0,7,-1,1},{44,0,7,0,7,-1,1},{44,0,7,0,7,-1,1},{44,0,12,17,7,-1,1},{44,0,12,17,7,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{45,0,7,0,7,-1,1},{45,0,7,0,7,-1,1},{45,0,7,0,7,-1,1},{45,0,7,0,7,-1,1},{45,0,7,0,7,-1,1},{45,0,7,0,7,-1,1},{45,0,7,0,7,-1,1},{45,0,7,0,7,-1,1},{45,0,7,0,7,-1,1},{45,0,7,0,7,-1,1},{45,0,7,0,7,-1,1},{45,0,7,0,7,-1,1},{45,0,7,0,7,-1,1},{61,0,2,0,12,-1,1},{45,0,7,0,7,-1,1},{45,0,7,0,7,-1,1},{45,0,7,0,7,-1,1},{61,0,2,0,12,-1,1},{45,0,12,17,7,-1,1},{45,0,12,17,7,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1}},
   {{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,11,-1,1},{23,0,7,0,11,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,4,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,7,0,0,-1,1},{23,0,12,17,10,-1,1},{23,0,12,17,10,-1,1},{23,0,10,0,0,-1,1},{23,0,12,17,0,-1,1},{23,0,12,17,0,-1,1},{23,0,12,17,0,-1,1},{23,0,12,17,0,-1,1},{23,0,12,17,0,-1,1},{23,0,12,17,0,-1,1},{23,0,12,17,0,-1,1},{23,0,10,0,0,-1,1},{23,0,10,0,0,-1,1}},
@@ -616,35 +619,35 @@ static const nsCharProps2 sCharProp2Valu
   {{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,0,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{61,0,2,0,12,-1,1},{14,0,9,0,0,-1,1},{61,0,2,0,12,-1,1},{14,0,9,0,0,-1,1},{61,0,2,0,12,-1,1},{14,0,9,0,0,-1,1},{61,0,2,0,12,-1,1},{14,0,9,0,0,-1,1}},
   {{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,9,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,9,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,9,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,9,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,9,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,9,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,9,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1}},
   {{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,8,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{61,0,2,0,12,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,9,-1,1},{14,0,8,0,0,-1,1},{14,0,24,10,9,-1,1},{14,0,5,0,9,-1,1},{14,0,24,10,9,-1,1}},
   {{14,0,24,10,9,-1,1},{14,0,24,10,9,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{61,0,2,0,12,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,9,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,9,-1,1},{14,0,8,0,0,-1,1},{14,0,24,10,9,-1,1},{14,0,24,10,9,-1,1},{14,0,24,10,9,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,9,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,9,-1,1},{61,0,2,0,12,-1,1},{14,0,24,10,9,-1,1},{14,0,24,10,9,-1,1},{14,0,24,10,9,-1,1}},
   {{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,9,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,9,-1,1},{14,0,9,0,0,-1,1},{14,0,24,10,9,-1,1},{14,0,24,10,9,-1,1},{14,0,24,10,9,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{61,0,2,0,12,-1,1},{14,0,5,0,0,-1,1},{14,0,5,0,0,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,9,-1,1},{14,0,9,0,0,-1,1},{14,0,9,0,9,-1,1},{14,0,8,0,0,-1,1},{14,0,24,10,9,-1,1},{14,0,24,10,9,-1,1},{61,0,2,0,12,-1,1}},
   {{0,0,29,9,9,-1,1},{0,0,29,9,9,-1,1},{0,0,29,9,9,-1,1},{0,0,29,9,9,-1,1},{0,0,29,9,9,-1,1},{0,0,29,9,9,-1,1},{0,0,29,9,9,-1,1},{0,0,29,9,9,-1,1},{0,0,29,9,9,-1,1},{0,0,29,9,9,-1,1},{0,0,29,9,9,-1,1},{0,0,1,18,10,-1,1},{1,0,1,18,1,-1,1},{1,0,1,18,1,-1,1},{0,0,1,0,10,-1,1},{0,0,1,1,10,-1,1},{0,0,17,10,1,-1,1},{0,0,17,10,9,-1,1},{0,0,17,10,8,-1,1},{0,0,17,10,8,-1,1},{0,0,17,10,8,-1,1},{0,0,17,10,8,-1,1},{0,0,21,10,8,-1,0},{0,0,21,10,9,-1,1},{0,0,20,10,8,-1,1},{0,0,19,10,1,-1,1},{0,0,22,10,8,-1,1},{0,0,20,10,8,-1,1},{0,0,20,10,8,-1,1},{0,0,19,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,20,10,8,-1,1}},
   {{0,0,21,10,8,-1,0},{0,0,21,10,8,-1,0},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,9,-1,1},{0,0,21,10,9,-1,1},{0,0,21,10,9,-1,1},{0,0,21,10,1,-1,1},{0,0,27,9,8,-1,1},{0,0,28,7,8,-1,1},{0,0,1,11,10,-1,1},{0,0,1,14,10,-1,1},{0,0,1,16,10,-1,1},{0,0,1,12,10,-1,1},{0,0,1,15,10,-1,1},{0,0,29,6,9,-1,1},{0,0,21,4,8,-1,0},{0,0,21,4,8,-1,0},{0,0,21,4,8,-1,1},{0,0,21,4,9,-1,1},{0,0,21,4,9,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,9,-1,1},{0,0,21,10,9,-1,1},{0,0,21,10,8,-1,1},{0,0,20,10,8,-1,1},{0,0,19,10,8,-1,1},{0,0,21,10,8,-1,0},{0,0,21,10,9,-1,0},{0,0,21,10,8,-1,1},{0,0,21,10,9,-1,1},{0,0,16,10,3,-1,1}},
-  {{0,0,16,10,3,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,0},{0,0,21,10,8,-1,1},{0,0,25,6,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,21,10,9,-1,0},{0,0,21,10,9,-1,0},{0,0,21,10,9,-1,0},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,0},{0,0,25,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,16,10,2,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,9,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,29,9,9,-1,1}},
-  {{0,0,1,18,10,-1,1},{0,0,1,18,10,-1,1},{0,0,1,18,10,-1,1},{0,0,1,18,10,-1,1},{0,0,1,18,10,-1,1},{61,0,2,0,12,-1,0},{0,0,1,19,10,-1,1},{0,0,1,20,10,-1,1},{0,0,1,21,10,-1,1},{0,0,1,22,10,-1,1},{0,0,1,18,11,-1,1},{0,0,1,18,11,-1,1},{0,0,1,18,11,-1,1},{0,0,1,18,11,-1,1},{0,0,1,18,11,-1,1},{0,0,1,18,11,-1,1},{0,0,15,2,9,0,1},{25,0,6,0,9,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{0,0,15,2,9,4,1},{0,0,15,2,9,5,1},{0,0,15,2,9,6,1},{0,0,15,2,9,7,1},{0,0,15,2,9,8,1},{0,0,15,2,9,9,1},{0,0,25,3,9,-1,1},{0,0,25,3,9,-1,1},{0,0,25,10,9,-1,1},{0,0,22,10,9,-1,1},{0,0,18,10,9,-1,1},{25,0,6,0,9,-1,1}},
-  {{0,0,15,2,9,0,1},{0,0,15,2,9,1,1},{0,0,15,2,9,2,1},{0,0,15,2,9,3,1},{0,0,15,2,9,4,1},{0,0,15,2,9,5,1},{0,0,15,2,9,6,1},{0,0,15,2,9,7,1},{0,0,15,2,9,8,1},{0,0,15,2,9,9,1},{0,0,25,3,9,-1,1},{0,0,25,3,9,-1,1},{0,0,25,10,9,-1,1},{0,0,22,10,9,-1,1},{0,0,18,10,9,-1,1},{61,0,2,0,12,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
+  {{0,0,16,10,3,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,0},{0,0,21,10,8,-1,1},{0,0,25,6,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,0,21,10,9,-1,0},{0,0,21,10,9,-1,0},{0,0,21,10,9,-1,0},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,0},{0,0,25,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,16,10,2,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,9,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,29,9,9,-1,1}},
+  {{0,0,1,18,10,-1,1},{0,0,1,18,10,-1,1},{0,0,1,18,10,-1,1},{0,0,1,18,10,-1,1},{0,0,1,18,10,-1,1},{61,0,2,0,12,-1,0},{0,0,1,19,10,-1,1},{0,0,1,20,10,-1,1},{0,0,1,21,10,-1,1},{0,0,1,22,10,-1,1},{0,0,1,18,11,-1,1},{0,0,1,18,11,-1,1},{0,0,1,18,11,-1,1},{0,0,1,18,11,-1,1},{0,0,1,18,11,-1,1},{0,0,1,18,11,-1,1},{0,0,15,2,9,0,1},{25,0,6,0,9,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{0,0,15,2,9,4,1},{0,0,15,2,9,5,1},{0,0,15,2,9,6,1},{0,0,15,2,9,7,1},{0,0,15,2,9,8,1},{0,0,15,2,9,9,1},{0,0,25,3,9,-1,1},{0,0,25,3,9,-1,1},{0,0,25,10,9,-1,1},{0,1,22,10,9,-1,1},{0,2,18,10,9,-1,1},{25,0,6,0,9,-1,1}},
+  {{0,0,15,2,9,0,1},{0,0,15,2,9,1,1},{0,0,15,2,9,2,1},{0,0,15,2,9,3,1},{0,0,15,2,9,4,1},{0,0,15,2,9,5,1},{0,0,15,2,9,6,1},{0,0,15,2,9,7,1},{0,0,15,2,9,8,1},{0,0,15,2,9,9,1},{0,0,25,3,9,-1,1},{0,0,25,3,9,-1,1},{0,0,25,10,9,-1,1},{0,1,22,10,9,-1,1},{0,2,18,10,9,-1,1},{61,0,2,0,12,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{25,0,6,0,9,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,9,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{0,0,23,4,8,-1,1},{61,0,2,4,12,-1,1}},
   {{61,0,2,4,12,-1,1},{61,0,2,4,12,-1,1},{61,0,2,4,12,-1,1},{61,0,2,4,12,-1,1},{61,0,2,4,12,-1,1},{61,0,2,4,12,-1,1},{61,0,2,4,12,-1,1},{61,0,2,4,12,-1,1},{61,0,2,4,12,-1,1},{61,0,2,4,12,-1,1},{61,0,2,4,12,-1,1},{61,0,2,4,12,-1,1},{61,0,2,4,12,-1,1},{61,0,2,4,12,-1,1},{61,0,2,4,12,-1,1},{61,0,2,4,12,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,11,17,8,-1,0},{1,0,11,17,8,-1,0},{1,0,11,17,8,-1,0}},
   {{1,0,11,17,8,-1,0},{1,0,12,17,7,-1,1},{1,0,11,17,8,-1,0},{1,0,11,17,8,-1,0},{1,0,11,17,8,-1,0},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{1,0,12,17,7,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{0,0,26,10,9,-1,0},{0,0,26,10,9,-1,0},{0,0,9,0,9,-1,1},{0,0,26,10,9,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,9,-1,0},{0,0,26,10,9,-1,0},{0,0,9,0,9,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,9,-1,0},{0,0,5,0,9,-1,1},{0,0,9,0,9,-1,1},{0,0,9,0,9,-1,1},{0,0,9,0,9,-1,1},{0,0,5,0,9,-1,1},{0,0,5,0,9,-1,0},{0,0,9,0,9,-1,1},{0,0,9,0,9,-1,1},{0,0,9,0,9,-1,1},{0,0,5,0,9,-1,0},{0,0,26,10,8,-1,0},{0,0,9,0,9,-1,1},{0,0,26,10,9,-1,0},{0,0,26,10,8,-1,0},{0,0,25,10,3,-1,1},{0,0,9,0,9,-1,1},{0,0,9,0,9,-1,1},{0,0,9,0,9,-1,1},{0,0,9,0,9,-1,1},{0,0,9,0,9,-1,1},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0}},
   {{0,0,26,10,9,-1,0},{0,0,26,10,9,-1,0},{0,0,26,10,9,-1,0},{0,0,26,10,8,-1,0},{0,0,9,0,9,-1,1},{0,0,26,10,8,-1,0},{14,0,9,0,9,-1,1},{0,0,26,10,8,-1,0},{0,0,9,0,9,-1,1},{0,0,26,10,8,-1,0},{25,0,9,0,9,-1,1},{25,0,9,0,9,-1,1},{0,0,9,0,9,-1,1},{0,0,9,0,9,-1,1},{0,0,26,4,3,-1,0},{0,0,5,0,9,-1,1},{0,0,9,0,9,-1,1},{0,0,9,0,9,-1,1},{25,0,9,0,4,-1,1},{0,0,9,0,9,-1,1},{0,0,5,0,9,-1,1},{0,0,7,0,9,-1,0},{0,0,7,0,9,-1,0},{0,0,7,0,9,-1,0},{0,0,7,0,9,-1,0},{0,0,5,0,9,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,9,-1,0},{0,0,5,0,9,-1,0},{0,0,5,0,9,-1,0},{0,0,9,0,9,-1,0},{0,0,9,0,9,-1,0}},
   {{0,0,25,10,9,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,9,0,9,-1,0},{0,0,5,0,9,-1,0},{0,0,5,0,9,-1,0},{0,0,5,0,9,-1,0},{0,0,5,0,9,-1,0},{0,0,26,10,8,-1,0},{0,0,25,10,8,-1,1},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{25,0,5,0,4,-1,1},{0,0,26,0,8,-1,0},{0,0,15,10,9,-1,0},{0,0,15,10,9,-1,0},{0,0,15,10,9,-1,0},{0,0,15,10,9,-1,0},{0,0,15,10,9,-1,0},{0,0,15,10,9,-1,0},{0,0,15,10,9,-1,0},{0,0,15,10,9,-1,0},{0,0,15,10,9,-1,0},{0,0,15,10,9,-1,0},{0,0,15,10,9,-1,0},{0,0,15,10,9,-1,0},{0,0,15,10,9,-1,0},{0,0,15,10,9,-1,0},{0,0,15,10,9,-1,0},{0,0,15,10,9,-1,0}},
   {{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0},{25,0,14,0,9,-1,0}},
   {{25,0,14,0,4,-1,0},{25,0,14,0,4,-1,0},{25,0,14,0,4,-1,0},{25,0,9,0,4,-1,0},{25,0,5,0,4,-1,0},{25,0,14,0,4,-1,0},{25,0,14,0,4,-1,0},{25,0,14,0,4,-1,0},{25,0,14,0,4,-1,0},{0,0,15,10,9,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1}},
   {{0,0,25,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1}},
   {{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1}},
   {{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1}},
   {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,3,8,-1,1},{0,0,25,4,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,0},{0,0,25,10,8,-1,1}},
   {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,9,-1,1},{0,0,25,10,9,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,9,-1,1},{0,0,25,10,9,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,0},{0,0,25,10,8,-1,0},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1}},
   {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1}},
-  {{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0}},
-  {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,22,10,11,-1,3},{0,0,18,10,11,-1,3},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1}},
+  {{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0}},
+  {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,1,22,10,11,-1,3},{0,2,18,10,11,-1,3},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1}},
   {{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1}},
   {{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,0,8,-1,1},{0,0,26,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0}},
   {{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,0,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1}},
   {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0}},
   {{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1}},
   {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0}},
   {{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0}},
   {{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0}},
@@ -655,24 +658,24 @@ static const nsCharProps2 sCharProp2Valu
   {{0,0,26,0,9,-1,0},{0,0,26,0,9,-1,0},{0,0,26,0,9,-1,0},{0,0,26,0,9,-1,0},{0,0,26,0,9,-1,0},{0,0,26,0,9,-1,0},{0,0,26,0,9,-1,0},{0,0,26,0,9,-1,0},{0,0,26,0,9,-1,0},{0,0,26,0,9,-1,0},{0,0,15,10,9,0,0},{0,0,15,10,8,-1,0},{0,0,15,10,8,-1,0},{0,0,15,10,8,-1,0},{0,0,15,10,8,-1,0},{0,0,15,10,8,-1,0},{0,0,15,10,8,-1,0},{0,0,15,10,8,-1,0},{0,0,15,10,8,-1,0},{0,0,15,10,8,-1,0},{0,0,15,10,8,-1,0},{0,0,15,10,8,1,0},{0,0,15,10,8,2,0},{0,0,15,10,8,3,0},{0,0,15,10,8,4,0},{0,0,15,10,8,5,0},{0,0,15,10,8,6,0},{0,0,15,10,8,7,0},{0,0,15,10,8,8,0},{0,0,15,10,8,9,0},{0,0,15,10,8,-1,0},{0,0,15,10,8,0,0}},
   {{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1}},
   {{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,25,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0}},
   {{0,0,26,10,8,-1,0},{0,0,25,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0}},
   {{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,25,10,8,-1,0},{0,0,25,10,8,-1,0},{0,0,25,10,8,-1,0},{0,0,25,10,8,-1,0},{0,0,25,10,8,-1,0},{0,0,25,10,8,-1,0},{0,0,25,10,8,-1,0},{0,0,25,10,8,-1,0}},
   {{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1}},
   {{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,25,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0}},
   {{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,0,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0}},
-  {{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,15,10,8,1,0},{0,0,15,10,8,2,0},{0,0,15,10,8,3,0},{0,0,15,10,8,4,0},{0,0,15,10,8,5,0},{0,0,15,10,8,6,0},{0,0,15,10,8,7,0},{0,0,15,10,8,8,0},{0,0,15,10,8,9,0},{0,0,15,10,8,-1,0}},
+  {{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,0,15,10,8,1,0},{0,0,15,10,8,2,0},{0,0,15,10,8,3,0},{0,0,15,10,8,4,0},{0,0,15,10,8,5,0},{0,0,15,10,8,6,0},{0,0,15,10,8,7,0},{0,0,15,10,8,8,0},{0,0,15,10,8,9,0},{0,0,15,10,8,-1,0}},
   {{0,0,15,10,8,1,0},{0,0,15,10,8,2,0},{0,0,15,10,8,3,0},{0,0,15,10,8,4,0},{0,0,15,10,8,5,0},{0,0,15,10,8,6,0},{0,0,15,10,8,7,0},{0,0,15,10,8,8,0},{0,0,15,10,8,9,0},{0,0,15,10,8,-1,0},{0,0,15,10,8,1,0},{0,0,15,10,8,2,0},{0,0,15,10,8,3,0},{0,0,15,10,8,4,0},{0,0,15,10,8,5,0},{0,0,15,10,8,6,0},{0,0,15,10,8,7,0},{0,0,15,10,8,8,0},{0,0,15,10,8,9,0},{0,0,15,10,8,-1,0},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1}},
-  {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1}},
-  {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1}},
+  {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1}},
+  {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1}},
   {{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1},{46,0,26,0,8,-1,1}},
-  {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1}},
-  {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1}},
-  {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1}},
+  {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1}},
+  {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1}},
+  {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1}},
   {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,9,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1}},
   {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,9,-1,1},{0,0,25,10,9,-1,1},{0,0,25,10,9,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1}},
   {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,9,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1}},
   {{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0}},
   {{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1}},
   {{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,25,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1}},
   {{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1}},
   {{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1},{0,0,26,10,8,-1,1}},
@@ -688,25 +691,25 @@ static const nsCharProps2 sCharProp2Valu
   {{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1}},
   {{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{12,0,5,0,4,-1,1},{61,0,2,0,12,-1,1},{12,0,5,0,0,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{12,0,5,0,0,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1}},
   {{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1}},
   {{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{57,0,7,0,5,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{57,0,6,0,9,-1,1},{57,0,21,0,8,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{57,0,12,17,5,-1,1}},
   {{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{61,0,2,0,12,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{61,0,2,0,12,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{61,0,2,0,12,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{11,0,7,0,0,-1,1},{61,0,2,0,12,-1,1}},
   {{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1},{8,0,12,17,4,-1,1}},
   {{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,20,10,8,-1,1},{0,0,19,10,8,-1,1},{0,0,20,10,8,-1,1},{0,0,19,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,20,10,8,-1,1},{0,0,19,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,20,10,8,-1,1},{0,0,19,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,17,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,17,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,20,10,8,-1,1},{0,0,19,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1}},
-  {{0,0,20,10,8,-1,1},{0,0,19,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1},{0,0,18,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,6,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,17,10,8,-1,1},{0,0,17,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1}},
+  {{0,0,20,10,8,-1,1},{0,0,19,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,1,22,10,8,-1,1},{0,2,18,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,6,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,17,10,8,-1,1},{0,0,17,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,21,10,8,-1,1}},
   {{0,0,17,10,8,-1,1},{0,0,21,10,8,-1,1},{0,0,22,10,8,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{61,0,2,0,12,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,9,-1,0}},
   {{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0}},
   {{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,8,-1,0},{17,0,26,10,9,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0}},
   {{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0}},
   {{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{17,0,26,10,9,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0}},
   {{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0}},
-  {{0,0,29,9,9,-1,0},{0,0,21,10,8,-1,2},{0,0,21,10,8,-1,2},{0,0,21,10,8,-1,0},{0,0,26,10,8,-1,0},{17,0,6,0,0,-1,0},{0,0,7,0,0,-1,0},{17,0,14,0,0,-1,0},{0,0,22,10,8,-1,3},{0,0,18,10,8,-1,3},{0,0,22,10,8,-1,3},{0,0,18,10,8,-1,3},{0,0,22,10,8,-1,3},{0,0,18,10,8,-1,3},{0,0,22,10,8,-1,3},{0,0,18,10,8,-1,3},{0,0,22,10,8,-1,3},{0,0,18,10,8,-1,3},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,22,10,8,-1,3},{0,0,18,10,8,-1,3},{0,0,22,10,8,-1,3},{0,0,18,10,8,-1,3},{0,0,22,10,8,-1,3},{0,0,18,10,8,-1,3},{0,0,22,10,8,-1,3},{0,0,18,10,8,-1,3},{0,0,17,10,8,-1,3},{0,0,22,10,8,-1,3},{0,0,18,10,8,-1,3},{0,0,18,10,8,-1,3}},
+  {{0,0,29,9,9,-1,0},{0,0,21,10,8,-1,2},{0,0,21,10,8,-1,2},{0,0,21,10,8,-1,0},{0,0,26,10,8,-1,0},{17,0,6,0,0,-1,0},{0,0,7,0,0,-1,0},{17,0,14,0,0,-1,0},{0,1,22,10,8,-1,3},{0,2,18,10,8,-1,3},{0,1,22,10,8,-1,3},{0,2,18,10,8,-1,3},{0,1,22,10,8,-1,3},{0,2,18,10,8,-1,3},{0,1,22,10,8,-1,3},{0,2,18,10,8,-1,3},{0,1,22,10,8,-1,3},{0,2,18,10,8,-1,3},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{0,1,22,10,8,-1,3},{0,2,18,10,8,-1,3},{0,1,22,10,8,-1,3},{0,2,18,10,8,-1,3},{0,1,22,10,8,-1,3},{0,2,18,10,8,-1,3},{0,1,22,10,8,-1,3},{0,2,18,10,8,-1,3},{0,0,17,10,8,-1,3},{0,0,22,10,8,-1,3},{0,0,18,10,8,-1,3},{0,0,18,10,8,-1,3}},
   {{0,0,26,10,8,-1,0},{17,0,14,0,3,-1,0},{17,0,14,0,3,-1,0},{17,0,14,0,3,-1,0},{17,0,14,0,3,-1,0},{17,0,14,0,3,-1,0},{17,0,14,0,3,-1,0},{17,0,14,0,3,-1,0},{17,0,14,0,3,-1,0},{17,0,14,0,3,-1,0},{1,0,12,17,3,-1,0},{1,0,12,17,3,-1,0},{1,0,12,17,3,-1,0},{1,0,12,17,3,-1,0},{18,0,10,0,4,-1,0},{18,0,10,0,4,-1,0},{0,0,17,10,8,-1,3},{0,0,6,0,3,-1,0},{0,0,6,0,3,-1,0},{0,0,6,0,3,-1,0},{0,0,6,0,3,-1,0},{0,0,6,0,3,-1,0},{0,0,26,10,9,-1,0},{0,0,26,10,8,-1,0},{17,0,14,0,9,-1,0},{17,0,14,0,9,-1,0},{17,0,14,0,9,-1,0},{17,0,6,0,3,-1,0},{0,0,7,0,3,-1,0},{0,0,21,10,8,-1,0},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0}},
   {{61,0,2,0,12,-1,0},{20,0,7,0,0,-1,2},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,2},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,2},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,2},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,2},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0}},
   {{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,2},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0}},
   {{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,2},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,2},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,2},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,2},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,0},{20,0,7,0,0,-1,2},{20,0,7,0,0,-1,2},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{1,0,12,17,0,-1,0},{1,0,12,17,0,-1,0},{0,0,24,10,9,-1,2},{0,0,24,10,9,-1,2},{20,0,6,0,0,-1,0},{20,0,6,0,0,-1,0},{20,0,7,0,9,-1,0}},
   {{0,0,17,10,1,-1,3},{22,0,7,0,0,-1,2},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,2},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,2},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,2},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,2},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0}},
   {{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,2},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0}},
   {{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,2},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,2},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,2},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,2},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,2},{22,0,7,0,0,-1,2},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{22,0,7,0,0,-1,0},{0,0,21,10,1,-1,0},{0,0,6,0,0,-1,3},{22,0,6,0,0,-1,0},{22,0,6,0,0,-1,0},{22,0,7,0,9,-1,0}},
   {{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0},{5,0,7,0,0,-1,0}},
@@ -801,23 +804,23 @@ static const nsCharProps2 sCharProp2Valu
   {{2,0,24,13,8,-1,1},{2,0,24,13,8,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1}},
   {{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{0,0,18,10,8,-1,1},{0,0,22,10,8,-1,1}},
   {{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1}},
   {{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1}},
   {{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,23,13,9,-1,1},{2,0,26,10,8,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1}},
   {{1,0,12,17,10,-1,1},{1,0,12,17,10,-1,1},{1,0,12,17,10,-1,1},{1,0,12,17,10,-1,1},{1,0,12,17,10,-1,1},{1,0,12,17,10,-1,1},{1,0,12,17,10,-1,1},{1,0,12,17,10,-1,1},{1,0,12,17,10,-1,1},{1,0,12,17,10,-1,1},{1,0,12,17,10,-1,1},{1,0,12,17,10,-1,1},{1,0,12,17,10,-1,1},{1,0,12,17,10,-1,1},{1,0,12,17,10,-1,1},{1,0,12,17,10,-1,1},{0,0,21,10,9,-1,0},{0,0,21,10,9,-1,0},{0,0,21,10,9,-1,0},{0,0,21,10,9,-1,0},{0,0,21,10,9,-1,0},{0,0,21,10,9,-1,0},{0,0,21,10,9,-1,0},{0,0,22,10,9,-1,0},{0,0,18,10,9,-1,0},{0,0,21,10,9,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0}},
   {{1,0,12,17,3,-1,1},{1,0,12,17,3,-1,1},{1,0,12,17,3,-1,1},{1,0,12,17,3,-1,1},{1,0,12,17,3,-1,1},{1,0,12,17,3,-1,1},{1,0,12,17,3,-1,1},{1,0,12,17,3,-1,1},{1,0,12,17,3,-1,1},{1,0,12,17,3,-1,1},{1,0,12,17,3,-1,1},{1,0,12,17,3,-1,1},{1,0,12,17,3,-1,1},{1,0,12,17,3,-1,1},{8,0,12,17,3,-1,1},{8,0,12,17,3,-1,1},{0,0,21,10,9,-1,0},{0,0,17,10,9,-1,0},{0,0,17,10,9,-1,0},{0,0,16,10,9,-1,0},{0,0,16,10,9,-1,0},{0,0,22,10,9,-1,0},{0,0,18,10,9,-1,0},{0,0,22,10,9,-1,0},{0,0,18,10,9,-1,0},{0,0,22,10,9,-1,0},{0,0,18,10,9,-1,0},{0,0,22,10,9,-1,0},{0,0,18,10,9,-1,0},{0,0,22,10,9,-1,0},{0,0,18,10,9,-1,0},{0,0,22,10,9,-1,0}},
-  {{0,0,18,10,9,-1,0},{0,0,22,10,9,-1,0},{0,0,18,10,9,-1,0},{0,0,22,10,9,-1,0},{0,0,18,10,9,-1,0},{0,0,21,10,8,-1,0},{0,0,21,10,8,-1,0},{0,0,22,10,9,-1,0},{0,0,18,10,9,-1,0},{0,0,21,10,9,-1,1},{0,0,21,10,9,-1,1},{0,0,21,10,9,-1,1},{0,0,21,10,9,-1,1},{0,0,16,10,9,-1,1},{0,0,16,10,9,-1,1},{0,0,16,10,9,-1,1},{0,0,21,6,9,-1,2},{0,0,21,10,9,-1,2},{0,0,21,6,9,-1,2},{61,0,2,0,12,-1,0},{0,0,21,10,9,-1,0},{0,0,21,6,9,-1,0},{0,0,21,10,9,-1,0},{0,0,21,10,9,-1,0},{0,0,17,10,9,-1,1},{0,0,22,10,9,-1,3},{0,0,18,10,9,-1,3},{0,0,22,10,9,-1,3},{0,0,18,10,9,-1,3},{0,0,22,10,9,-1,3},{0,0,18,10,9,-1,3},{0,0,21,4,9,-1,0}},
+  {{0,0,18,10,9,-1,0},{0,0,22,10,9,-1,0},{0,0,18,10,9,-1,0},{0,0,22,10,9,-1,0},{0,0,18,10,9,-1,0},{0,0,21,10,8,-1,0},{0,0,21,10,8,-1,0},{0,0,22,10,9,-1,0},{0,0,18,10,9,-1,0},{0,0,21,10,9,-1,1},{0,0,21,10,9,-1,1},{0,0,21,10,9,-1,1},{0,0,21,10,9,-1,1},{0,0,16,10,9,-1,1},{0,0,16,10,9,-1,1},{0,0,16,10,9,-1,1},{0,0,21,6,9,-1,2},{0,0,21,10,9,-1,2},{0,0,21,6,9,-1,2},{61,0,2,0,12,-1,0},{0,0,21,10,9,-1,0},{0,0,21,6,9,-1,0},{0,0,21,10,9,-1,0},{0,0,21,10,9,-1,0},{0,0,17,10,9,-1,1},{0,1,22,10,9,-1,3},{0,2,18,10,9,-1,3},{0,1,22,10,9,-1,3},{0,2,18,10,9,-1,3},{0,1,22,10,9,-1,3},{0,2,18,10,9,-1,3},{0,0,21,4,9,-1,0}},
   {{0,0,21,10,9,-1,0},{0,0,21,10,9,-1,0},{0,0,25,3,9,-1,0},{0,0,17,3,9,-1,1},{0,0,25,10,9,-1,1},{0,0,25,10,9,-1,1},{0,0,25,10,9,-1,1},{61,0,2,0,12,-1,0},{0,0,21,10,9,-1,0},{0,0,23,4,9,-1,0},{0,0,21,4,9,-1,0},{0,0,21,10,9,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,3,-1,1},{2,0,7,13,9,-1,1},{61,0,2,13,12,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1}},
   {{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{2,0,7,13,9,-1,1},{61,0,2,13,12,-1,1},{61,0,2,13,12,-1,1},{0,0,1,18,10,-1,1}},
-  {{61,0,2,0,12,-1,1},{0,0,21,10,9,-1,2},{0,0,21,10,9,-1,0},{0,0,21,4,9,-1,0},{0,0,23,4,9,-1,0},{0,0,21,4,9,-1,0},{0,0,21,10,9,-1,0},{0,0,21,10,9,-1,0},{0,0,22,10,9,-1,3},{0,0,18,10,9,-1,3},{0,0,21,10,9,-1,0},{0,0,25,3,9,-1,0},{0,0,21,6,9,-1,2},{0,0,17,3,9,-1,1},{0,0,21,6,9,-1,2},{0,0,21,6,9,-1,0},{0,0,13,2,9,0,0},{0,0,13,2,9,1,0},{0,0,13,2,9,2,0},{0,0,13,2,9,3,0},{0,0,13,2,9,4,0},{0,0,13,2,9,5,0},{0,0,13,2,9,6,0},{0,0,13,2,9,7,0},{0,0,13,2,9,8,0},{0,0,13,2,9,9,0},{0,0,21,6,9,-1,3},{0,0,21,10,9,-1,3},{0,0,25,10,9,-1,1},{0,0,25,10,9,-1,1},{0,0,25,10,9,-1,1},{0,0,21,10,9,-1,2}},
-  {{0,0,21,10,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{0,0,22,10,9,-1,3},{0,0,21,10,9,-1,0},{0,0,18,10,9,-1,3},{0,0,24,10,9,-1,0},{0,0,16,10,9,-1,3}},
-  {{0,0,24,10,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{0,0,22,10,9,-1,3},{0,0,25,10,9,-1,3},{0,0,18,10,9,-1,3},{0,0,25,10,9,-1,3},{0,0,22,10,9,-1,3}},
-  {{0,0,18,10,9,-1,3},{0,0,21,10,9,-1,1},{0,0,22,10,9,-1,1},{0,0,18,10,9,-1,1},{0,0,21,10,9,-1,1},{0,0,21,10,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{0,0,6,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1}},
+  {{61,0,2,0,12,-1,1},{0,0,21,10,9,-1,2},{0,0,21,10,9,-1,0},{0,0,21,4,9,-1,0},{0,0,23,4,9,-1,0},{0,0,21,4,9,-1,0},{0,0,21,10,9,-1,0},{0,0,21,10,9,-1,0},{0,1,22,10,9,-1,3},{0,2,18,10,9,-1,3},{0,0,21,10,9,-1,0},{0,0,25,3,9,-1,0},{0,0,21,6,9,-1,2},{0,0,17,3,9,-1,1},{0,0,21,6,9,-1,2},{0,0,21,6,9,-1,0},{0,0,13,2,9,0,0},{0,0,13,2,9,1,0},{0,0,13,2,9,2,0},{0,0,13,2,9,3,0},{0,0,13,2,9,4,0},{0,0,13,2,9,5,0},{0,0,13,2,9,6,0},{0,0,13,2,9,7,0},{0,0,13,2,9,8,0},{0,0,13,2,9,9,0},{0,0,21,6,9,-1,3},{0,0,21,10,9,-1,3},{0,0,25,10,9,-1,1},{0,0,25,10,9,-1,1},{0,0,25,10,9,-1,1},{0,0,21,10,9,-1,2}},
+  {{0,0,21,10,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{25,0,9,0,9,-1,0},{0,1,22,10,9,-1,3},{0,0,21,10,9,-1,0},{0,2,18,10,9,-1,3},{0,0,24,10,9,-1,0},{0,0,16,10,9,-1,3}},
+  {{0,0,24,10,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{25,0,5,0,9,-1,0},{0,1,22,10,9,-1,3},{0,0,25,10,9,-1,3},{0,2,18,10,9,-1,3},{0,0,25,10,9,-1,3},{0,1,22,10,9,-1,3}},
+  {{0,2,18,10,9,-1,3},{0,0,21,10,9,-1,1},{0,1,22,10,9,-1,1},{0,2,18,10,9,-1,1},{0,0,21,10,9,-1,1},{0,0,21,10,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{0,0,6,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1}},
   {{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{22,0,7,0,9,-1,1},{0,0,6,0,9,-1,1},{0,0,6,0,9,-1,1}},
   {{18,0,7,0,10,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{61,0,2,0,12,-1,1}},
   {{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{18,0,7,0,9,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{0,0,23,4,9,-1,0},{0,0,23,4,9,-1,0},{0,0,25,10,9,-1,0},{0,0,24,10,9,-1,3},{0,0,26,10,9,-1,0},{0,0,23,4,9,-1,0},{0,0,23,4,9,-1,0},{61,0,2,0,12,-1,0},{0,0,26,10,9,-1,1},{0,0,25,10,9,-1,1},{0,0,25,10,9,-1,1},{0,0,25,10,9,-1,1},{0,0,25,10,9,-1,1},{0,0,26,10,9,-1,1},{0,0,26,10,9,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{61,0,2,0,12,-1,0},{0,0,1,10,8,-1,1},{0,0,1,10,8,-1,1},{0,0,1,10,8,-1,1},{0,0,26,10,8,-1,0},{0,0,26,10,8,-1,0},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{61,0,2,0,12,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1}},
   {{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{61,0,2,0,12,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{61,0,2,0,12,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{61,0,2,0,12,-1,1},{51,0,7,0,7,-1,1}},
   {{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{61,0,2,0,12,-1,1},{61,0,2,0,12,-1,1}},
   {{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1},{51,0,7,0,7,-1,1}},
--- a/intl/unicharutil/util/nsUnicodeScriptCodes.h
+++ b/intl/unicharutil/util/nsUnicodeScriptCodes.h
@@ -6,17 +6,17 @@
 
 /*
  * Derived from the Unicode Character Database by genUnicodePropertyData.pl
  *
  * For Unicode terms of use, see http://www.unicode.org/terms_of_use.html
  */
 
 /*
- * Created on Thu Sep  3 14:42:28 2015 from UCD data files with version info:
+ * Created on Tue Nov 17 07:34:16 2015 from UCD data files with version info:
  *
 
 # Date: 2015-06-16, 20:24:00 GMT [KW]
 #
 # Unicode Character Database
 # Copyright (c) 1991-2015 Unicode, Inc.
 # For terms of use, see http://www.unicode.org/terms_of_use.html
 #
@@ -33,16 +33,19 @@ Standard.
 
 
 # Scripts-8.0.0.txt
 # Date: 2015-03-11, 22:29:42 GMT [MD]
 
 # BidiMirroring-8.0.0.txt
 # Date: 2015-01-20, 18:30:00 GMT [KW, LI]
 
+# BidiBrackets-8.0.0.txt
+# Date: 2015-01-20, 19:00:00 GMT [AG, LI, KW]
+
 # HangulSyllableType-8.0.0.txt
 # Date: 2014-12-16, 23:07:45 GMT [MD]
 
 # File: xidmodifications.txt
 # Version: 8.0.0
 # Generated: 2015-05-17, 03:09:04 GMT
 
 #
@@ -67,17 +70,17 @@ struct nsCharProps1 {
   unsigned char mHangulType:3;
   unsigned char mCombiningClass:8;
 };
 
 
 
 struct nsCharProps2 {
   unsigned char mScriptCode:8;
-  unsigned char mUnused:3;
+  unsigned char mPairedBracketType:3; // only 2 bits actually needed
   unsigned char mCategory:5;
   unsigned char mBidiCategory:5;
   unsigned char mXidmod:4;
   signed char   mNumericValue:5;
   unsigned char mVertOrient:2;
 };
 
 
--- a/js/src/builtin/ModuleObject.h
+++ b/js/src/builtin/ModuleObject.h
@@ -172,16 +172,17 @@ class ModuleNamespaceObject : public Pro
         bool get(JSContext* cx, HandleObject proxy, HandleValue receiver,
                  HandleId id, MutableHandleValue vp) const override;
         bool set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v,
                  HandleValue receiver, ObjectOpResult& result) const override;
 
         static const char family;
     };
 
+  public:
     static const ProxyHandler proxyHandler;
 };
 
 typedef Rooted<ModuleNamespaceObject*> RootedModuleNamespaceObject;
 typedef Handle<ModuleNamespaceObject*> HandleModuleNamespaceObject;
 
 struct FunctionDeclaration
 {
@@ -311,9 +312,16 @@ class MOZ_STACK_CLASS ModuleBuilder
 };
 
 JSObject* InitModuleClass(JSContext* cx, HandleObject obj);
 JSObject* InitImportEntryClass(JSContext* cx, HandleObject obj);
 JSObject* InitExportEntryClass(JSContext* cx, HandleObject obj);
 
 } // namespace js
 
+template<>
+inline bool
+JSObject::is<js::ModuleNamespaceObject>() const
+{
+    return js::IsDerivedProxyObject(this, &js::ModuleNamespaceObject::proxyHandler);
+}
+
 #endif /* builtin_ModuleObject_h */
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/lib/dummyModuleResolveHook.js
@@ -0,0 +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/. */
+
+// A dummy implementation of the module resolve hook used by module tests. This
+// implements the bare minimum necessary to allow modules to refer to each
+// other.
+
+let moduleRepo = {};
+setModuleResolveHook(function(module, specifier) {
+    if (specifier in moduleRepo)
+        return moduleRepo[specifier];
+    throw "Module '" + specifier + "' not found";
+});
--- a/js/src/jit-test/tests/modules/ambiguous-star-export.js
+++ b/js/src/jit-test/tests/modules/ambiguous-star-export.js
@@ -1,38 +1,31 @@
 // Test ambigious export * statements.
 
 "use strict";
 
 load(libdir + "asserts.js");
+load(libdir + "dummyModuleResolveHook.js");
 
 function checkModuleEval(source, result) {
     let m = parseModule(source);
     m.declarationInstantiation();
     assertEq(m.evaluation(), result);
 }
 
 function checkModuleSyntaxError(source) {
     let m = parseModule(source);
     assertThrowsInstanceOf(() => m.declarationInstantiation(), SyntaxError);
 }
 
-let moduleRepo = new Map();
-setModuleResolveHook(function(module, specifier) {
-    if (specifier in moduleRepo)
-        return moduleRepo[specifier];
-    throw "Module " + specifier + " not found";
-});
-
 let a = moduleRepo['a'] = parseModule("export var a = 1; export var b = 2;");
 let b = moduleRepo['b'] = parseModule("export var b = 3; export var c = 4;");
 let c = moduleRepo['c'] = parseModule("export * from 'a'; export * from 'b';");
-let ms = [a, b, c];
-ms.map((m) => m.declarationInstantiation());
-ms.map((m) => m.evaluation(), moduleRepo.values());
+c.declarationInstantiation();
+c.evaluation();
 
 // Check importing/exporting non-ambiguous name works.
 checkModuleEval("import { a } from 'c'; a;", 1);
 checkModuleEval("export { a } from 'c';", undefined);
 
 // Check importing/exporting ambiguous name is a syntax error.
 checkModuleSyntaxError("import { b } from 'c';");
 checkModuleSyntaxError("export { b } from 'c';");
--- a/js/src/jit-test/tests/modules/bug1210391.js
+++ b/js/src/jit-test/tests/modules/bug1210391.js
@@ -1,11 +1,8 @@
-let moduleRepo = new Map();
-setModuleResolveHook(function(module, specifier) {
-        return moduleRepo[specifier];
-});
+load(libdir + "dummyModuleResolveHook.js");
 let a = moduleRepo['a'] = parseModule("export var a = 1; export var b = 2;");
 let b = moduleRepo['b'] = parseModule("export var b = 3; export var c = 4;");
 let c = moduleRepo['c'] = parseModule("export * from 'a'; export * from 'b';");
 let d = moduleRepo['d'] = parseModule("import { a } from 'c'; a;");
 d.declarationInstantiation();
 d.evaluation();
 
--- a/js/src/jit-test/tests/modules/debugger-frames.js
+++ b/js/src/jit-test/tests/modules/debugger-frames.js
@@ -52,21 +52,21 @@ dbg.onDebuggerStatement = function (fram
     // followed by the global.
     assertEq(env.parent.type, 'declarative');
     assertEq(env.parent.parent.type, 'object');
     assertEq(env.parent.parent.parent, null);
 };
 
 f = g2.eval(
 `
-    let moduleRepo = new Map();
+    let moduleRepo = {};
     setModuleResolveHook(function(module, specifier) {
         if (specifier in moduleRepo)
             return moduleRepo[specifier];
-        throw "Module " + specifier + " not found";
+        throw "Module '" + specifier + "' not found";
     });
 
     // Set up a module to import from.
     a = moduleRepo['a'] = parseModule(
     \`
         export var a = 1;
         export let b = 2;
         export const c = 3;
--- a/js/src/jit-test/tests/modules/import-namespace.js
+++ b/js/src/jit-test/tests/modules/import-namespace.js
@@ -1,21 +1,15 @@
 // Test importing module namespaces
 
 "use strict";
 
 load(libdir + "asserts.js");
 load(libdir + "iteration.js");
-
-let moduleRepo = new Map();
-setModuleResolveHook(function(module, specifier) {
-    if (specifier in moduleRepo)
-        return moduleRepo[specifier];
-    throw "Module " + specifier + " not found";
-});
+load(libdir + "dummyModuleResolveHook.js");
 
 function parseAndEvaluate(source) {
     let m = parseModule(source);
     m.declarationInstantiation();
     return m.evaluation();
 }
 
 function testHasNames(names, expected) {
--- a/js/src/jit-test/tests/modules/many-exports.js
+++ b/js/src/jit-test/tests/modules/many-exports.js
@@ -1,19 +1,14 @@
 // Test many exports.
 
+load(libdir + "dummyModuleResolveHook.js");
+
 const count = 1024;
 
-let moduleRepo = {};
-setModuleResolveHook(function(module, specifier) {
-    if (specifier in moduleRepo)
-        return moduleRepo[specifier];
-    throw "Module " + specifier + " not found";
-});
-
 let s = "";
 for (let i = 0; i < count; i++)
     s += "export let e" + i + " = " + (i * i) + ";\n";
 let a = moduleRepo['a'] = parseModule(s);
 
 let b = moduleRepo['b'] = parseModule("import * as ns from 'a'");
 
 b.declarationInstantiation();
--- a/js/src/jit-test/tests/modules/many-imports.js
+++ b/js/src/jit-test/tests/modules/many-imports.js
@@ -1,19 +1,14 @@
 // Test importing an import many times.
 
+load(libdir + "dummyModuleResolveHook.js");
+
 const count = 1024;
 
-let moduleRepo = {};
-setModuleResolveHook(function(module, specifier) {
-    if (specifier in moduleRepo)
-        return moduleRepo[specifier];
-    throw "Module " + specifier + " not found";
-});
-
 let a = moduleRepo['a'] = parseModule("export let a = 1;");
 
 let s = "";
 for (let i = 0; i < count; i++) {
     s += "import { a as i" + i + " } from 'a';\n";
     s += "assertEq(i" + i + ", 1);\n";
 }
 let b = moduleRepo['b'] = parseModule(s);
--- a/js/src/jit-test/tests/modules/many-namespace-imports.js
+++ b/js/src/jit-test/tests/modules/many-namespace-imports.js
@@ -1,19 +1,14 @@
 // Test importing a namespace many times.
 
+load(libdir + "dummyModuleResolveHook.js");
+
 const count = 1024;
 
-let moduleRepo = {};
-setModuleResolveHook(function(module, specifier) {
-    if (specifier in moduleRepo)
-        return moduleRepo[specifier];
-    throw "Module " + specifier + " not found";
-});
-
 let a = moduleRepo['a'] = parseModule("export let a = 1;");
 
 let s = "";
 for (let i = 0; i < count; i++) {
     s += "import * as ns" + i + " from 'a';\n";
     s += "assertEq(ns" + i + ".a, 1);\n";
 }
 let b = moduleRepo['b'] = parseModule(s);
--- a/js/src/jit-test/tests/modules/module-declaration-instantiation.js
+++ b/js/src/jit-test/tests/modules/module-declaration-instantiation.js
@@ -1,30 +1,25 @@
 // Exercise ModuleDeclarationInstantiation() operation.
 
+load(libdir + "dummyModuleResolveHook.js");
+
 function testModuleEnvironment(module, expected) {
     var actual = getModuleEnvironmentNames(module).sort();
     assertEq(actual.length, expected.length);
     for (var i = 0; i < actual.length; i++) {
         assertEq(actual[i], expected[i]);
     }
 }
 
 // Check the environment of an empty module.
 let m = parseModule("");
 m.declarationInstantiation();
 testModuleEnvironment(m, []);
 
-let moduleRepo = new Map();
-setModuleResolveHook(function(module, specifier) {
-    if (specifier in moduleRepo)
-        return moduleRepo[specifier];
-    throw "Module " + specifier + " not found";
-});
-
 let a = moduleRepo['a'] = parseModule("var x = 1; export { x };");
 let b = moduleRepo['b'] = parseModule("import { x as y } from 'a';");
 
 a.declarationInstantiation();
 b.declarationInstantiation();
 
 testModuleEnvironment(a, ['x']);
 testModuleEnvironment(b, ['y']);
--- a/js/src/jit-test/tests/modules/module-evaluation.js
+++ b/js/src/jit-test/tests/modules/module-evaluation.js
@@ -1,18 +1,12 @@
 // Exercise ModuleEvaluation() concrete method.
 
 load(libdir + "asserts.js");
-
-let moduleRepo = new Map();
-setModuleResolveHook(function(module, specifier) {
-    if (specifier in moduleRepo)
-        return moduleRepo[specifier];
-    throw "Module " + specifier + " not found";
-});
+load(libdir + "dummyModuleResolveHook.js");
 
 function parseAndEvaluate(source) {
     let m = parseModule(source);
     m.declarationInstantiation();
     return m.evaluation();
 }
 
 // Check the evaluation of an empty module succeeds.
--- a/js/src/jsprototypes.h
+++ b/js/src/jsprototypes.h
@@ -83,17 +83,17 @@
     real(Uint8Array,            23,     InitViaClassSpec,       TYPED_ARRAY_CLASP(Uint8)) \
     real(Int16Array,            24,     InitViaClassSpec,       TYPED_ARRAY_CLASP(Int16)) \
     real(Uint16Array,           25,     InitViaClassSpec,       TYPED_ARRAY_CLASP(Uint16)) \
     real(Int32Array,            26,     InitViaClassSpec,       TYPED_ARRAY_CLASP(Int32)) \
     real(Uint32Array,           27,     InitViaClassSpec,       TYPED_ARRAY_CLASP(Uint32)) \
     real(Float32Array,          28,     InitViaClassSpec,       TYPED_ARRAY_CLASP(Float32)) \
     real(Float64Array,          29,     InitViaClassSpec,       TYPED_ARRAY_CLASP(Float64)) \
     real(Uint8ClampedArray,     30,     InitViaClassSpec,       TYPED_ARRAY_CLASP(Uint8Clamped)) \
-    real(Proxy,                 31,     InitProxyClass,         OCLASP(Proxy)) \
+    real(Proxy,                 31,     InitProxyClass,         js::ProxyClassPtr) \
     real(WeakMap,               32,     InitWeakMapClass,       OCLASP(WeakMap)) \
     real(Map,                   33,     InitMapClass,           OCLASP(Map)) \
     real(Set,                   34,     InitSetClass,           OCLASP(Set)) \
     real(DataView,              35,     InitDataViewClass,      OCLASP(DataView)) \
     real(Symbol,                36,     InitSymbolClass,        OCLASP(Symbol)) \
 IF_SAB(real,imaginary)(SharedArrayBuffer,       37,     InitSharedArrayBufferClass, &js::SharedArrayBufferObject::protoClass) \
 IF_INTL(real,imaginary) (Intl,                  38,     InitIntlClass,          CLASP(Intl)) \
 IF_BDATA(real,imaginary)(TypedObject,           39,     InitTypedObjectModuleObject,   OCLASP(TypedObjectModule)) \
--- a/js/src/proxy/DeadObjectProxy.cpp
+++ b/js/src/proxy/DeadObjectProxy.cpp
@@ -146,15 +146,13 @@ DeadObjectProxy::regexp_toShared(JSConte
 {
     ReportDead(cx);
     return false;
 }
 
 const char DeadObjectProxy::family = 0;
 const DeadObjectProxy DeadObjectProxy::singleton;
 
-
 bool
 js::IsDeadProxyObject(JSObject* obj)
 {
-    return obj->is<ProxyObject>() &&
-           obj->as<ProxyObject>().handler() == &DeadObjectProxy::singleton;
+    return IsDerivedProxyObject(obj, &DeadObjectProxy::singleton);
 }
--- a/js/src/proxy/Proxy.cpp
+++ b/js/src/proxy/Proxy.cpp
@@ -712,20 +712,20 @@ js::proxy_GetElements(JSContext* cx, Han
 }
 
 JSString*
 js::proxy_FunToString(JSContext* cx, HandleObject proxy, unsigned indent)
 {
     return Proxy::fun_toString(cx, proxy, indent);
 }
 
-const Class js::ProxyObject::class_ =
+const Class js::ProxyObject::proxyClass =
     PROXY_CLASS_DEF("Proxy", JSCLASS_HAS_CACHED_PROTO(JSProto_Proxy));
 
-const Class* const js::ProxyClassPtr = &js::ProxyObject::class_;
+const Class* const js::ProxyClassPtr = &js::ProxyObject::proxyClass;
 
 JS_FRIEND_API(JSObject*)
 js::NewProxyObject(JSContext* cx, const BaseProxyHandler* handler, HandleValue priv, JSObject* proto_,
                    const ProxyOptions& options)
 {
     if (options.lazyProto()) {
         MOZ_ASSERT(!proto_);
         proto_ = TaggedProto::LazyProto;
@@ -733,17 +733,17 @@ js::NewProxyObject(JSContext* cx, const 
 
     return ProxyObject::New(cx, handler, priv, TaggedProto(proto_), options);
 }
 
 void
 ProxyObject::renew(JSContext* cx, const BaseProxyHandler* handler, Value priv)
 {
     MOZ_ASSERT_IF(IsCrossCompartmentWrapper(this), IsDeadProxyObject(this));
-    MOZ_ASSERT(getClass() == &ProxyObject::class_);
+    MOZ_ASSERT(getClass() == &ProxyObject::proxyClass);
     MOZ_ASSERT(!IsWindowProxy(this));
     MOZ_ASSERT(hasLazyPrototype());
 
     setHandler(handler);
     setCrossCompartmentPrivate(priv);
     setExtra(0, UndefinedValue());
     setExtra(1, UndefinedValue());
 }
--- a/js/src/vm/ProxyObject.h
+++ b/js/src/vm/ProxyObject.h
@@ -99,27 +99,35 @@ class ProxyObject : public JSObject
     static unsigned grayLinkExtraSlot(JSObject* obj);
 
     void renew(JSContext* cx, const BaseProxyHandler* handler, Value priv);
 
     static void trace(JSTracer* trc, JSObject* obj);
 
     void nuke(const BaseProxyHandler* handler);
 
-    static const Class class_;
+    // There is no class_ member to force specialization of JSObject::is<T>().
+    // The implementation in JSObject is incorrect for proxies since it doesn't
+    // take account of the handler type.
+    static const Class proxyClass;
 };
 
+bool IsDerivedProxyObject(const JSObject* obj, const js::BaseProxyHandler* handler);
+
 } // namespace js
 
-// Note: the following |JSObject::is<T>| methods are implemented in terms of
-// the Is*Proxy() friend API functions to ensure the implementations are tied
-// together.  The exception is |JSObject::is<js::OuterWindowProxyObject>()
-// const|, which uses the standard template definition, because there is no
-// IsOuterWindowProxy() function in the friend API.
-
 template<>
 inline bool
 JSObject::is<js::ProxyObject>() const
 {
+    // Note: this method is implemented in terms of the IsProxy() friend API
+    // functions to ensure the implementations are tied together.
+    // Note 2: this specialization isn't used for subclasses of ProxyObject
+    // which must supply their own implementation.
     return js::IsProxy(const_cast<JSObject*>(this));
 }
 
+inline bool
+js::IsDerivedProxyObject(const JSObject* obj, const js::BaseProxyHandler* handler) {
+    return obj->is<js::ProxyObject>() && obj->as<js::ProxyObject>().handler() == handler;
+}
+
 #endif /* vm_ProxyObject_h */
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -2239,16 +2239,23 @@ class DebugScopeProxy : public BaseProxy
                  ObjectOpResult& result) const override
     {
         return result.fail(JSMSG_CANT_DELETE);
     }
 };
 
 } /* anonymous namespace */
 
+template<>
+bool
+JSObject::is<js::DebugScopeObject>() const
+{
+    return IsDerivedProxyObject(this, &DebugScopeProxy::singleton);
+}
+
 const char DebugScopeProxy::family = 0;
 const DebugScopeProxy DebugScopeProxy::singleton;
 
 /* static */ DebugScopeObject*
 DebugScopeObject::create(JSContext* cx, ScopeObject& scope, HandleObject enclosing)
 {
     MOZ_ASSERT(scope.compartment() == cx->compartment());
     MOZ_ASSERT(!enclosing->is<ScopeObject>());
@@ -2328,23 +2335,16 @@ DebugScopeObject::isOptimizedOut() const
         return !s.as<CallObject>().isForEval() &&
                !s.as<CallObject>().callee().needsCallObject() &&
                !maybeSnapshot();
     }
 
     return false;
 }
 
-bool
-js::IsDebugScopeSlow(ProxyObject* proxy)
-{
-    MOZ_ASSERT(proxy->hasClass(&ProxyObject::class_));
-    return proxy->handler() == &DebugScopeProxy::singleton;
-}
-
 /*****************************************************************************/
 
 DebugScopes::DebugScopes(JSContext* cx)
  : proxiedScopes(cx),
    missingScopes(cx->runtime()),
    liveScopes(cx->runtime())
 {}
 
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -1260,19 +1260,16 @@ class DebugScopes
     static void onPopCall(AbstractFramePtr frame, JSContext* cx);
     static void onPopBlock(JSContext* cx, const ScopeIter& si);
     static void onPopBlock(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc);
     static void onPopWith(AbstractFramePtr frame);
     static void onPopStrictEvalScope(AbstractFramePtr frame);
     static void onCompartmentUnsetIsDebuggee(JSCompartment* c);
 };
 
-extern bool
-IsDebugScopeSlow(ProxyObject* proxy);
-
 }  /* namespace js */
 
 template<>
 inline bool
 JSObject::is<js::NestedScopeObject>() const
 {
     return is<js::BlockObject>() ||
            is<js::StaticWithObject>() ||
@@ -1294,23 +1291,18 @@ JSObject::is<js::ScopeObject>() const
     return is<js::LexicalScopeBase>() ||
            is<js::DeclEnvObject>() ||
            is<js::NestedScopeObject>() ||
            is<js::RuntimeLexicalErrorObject>() ||
            is<js::NonSyntacticVariablesObject>();
 }
 
 template<>
-inline bool
-JSObject::is<js::DebugScopeObject>() const
-{
-    // Note: don't use is<ProxyObject>() here -- it also matches subclasses!
-    return hasClass(&js::ProxyObject::class_) &&
-           IsDebugScopeSlow(&const_cast<JSObject*>(this)->as<js::ProxyObject>());
-}
+bool
+JSObject::is<js::DebugScopeObject>() const;
 
 template<>
 inline bool
 JSObject::is<js::ClonedBlockObject>() const
 {
     return is<js::BlockObject>() && !!getProto();
 }
 
--- a/layout/base/nsBidi.cpp
+++ b/layout/base/nsBidi.cpp
@@ -36,28 +36,41 @@ enum {
     RLO = eCharType_RightToLeftOverride,
     PDF = eCharType_PopDirectionalFormat,
     NSM = eCharType_DirNonSpacingMark,
     BN =  eCharType_BoundaryNeutral,
     LRI = eCharType_LeftToRightIsolate,
     RLI = eCharType_RightToLeftIsolate,
     FSI = eCharType_FirstStrongIsolate,
     PDI = eCharType_PopDirectionalIsolate,
+    ENL,    /* EN after W7 */           /* 23 */
+    ENR,    /* EN not subject to W7 */  /* 24 */
     dirPropCount
 };
 
+#define IS_STRONG_TYPE(dirProp) ((dirProp) <= R || (dirProp) == AL)
+
 /* to avoid some conditional statements, use tiny constant arrays */
 static Flags flagLR[2]={ DIRPROP_FLAG(L), DIRPROP_FLAG(R) };
 static Flags flagE[2]={ DIRPROP_FLAG(LRE), DIRPROP_FLAG(RLE) };
 static Flags flagO[2]={ DIRPROP_FLAG(LRO), DIRPROP_FLAG(RLO) };
 
 #define DIRPROP_FLAG_LR(level) flagLR[(level)&1]
 #define DIRPROP_FLAG_E(level) flagE[(level)&1]
 #define DIRPROP_FLAG_O(level) flagO[(level)&1]
 
+#define NO_OVERRIDE(level)  ((level)&~NSBIDI_LEVEL_OVERRIDE)
+
+static inline uint8_t
+DirFromStrong(uint8_t aDirProp)
+{
+  MOZ_ASSERT(IS_STRONG_TYPE(aDirProp));
+  return aDirProp == L ? L : R;
+}
+
 /*
  * General implementation notes:
  *
  * Throughout the implementation, there are comments like (W2) that refer to
  * rules of the Bidi algorithm in its version 5, in this example to the second
  * rule of the resolution of weak types.
  *
  * For handling surrogate pairs, where two UChar's form one "abstract" (or UTF-32)
@@ -133,19 +146,16 @@ static Flags flagO[2]={ DIRPROP_FLAG(LRO
  * the flags variable.
  *
  * If there are no White Space types in the paragraph, then
  * (L1) is not necessary in AdjustWSLevels().
  */
 nsBidi::nsBidi()
 {
   Init();
-
-  mMayAllocateText=true;
-  mMayAllocateRuns=true;
 }
 
 nsBidi::~nsBidi()
 {
   Free();
 }
 
 void nsBidi::Init()
@@ -169,57 +179,49 @@ void nsBidi::Init()
   mLevels=nullptr;
   mRuns=nullptr;
   mIsolates=nullptr;
 
   mDirPropsMemory=nullptr;
   mLevelsMemory=nullptr;
   mRunsMemory=nullptr;
   mIsolatesMemory=nullptr;
-
-  mMayAllocateText=false;
-  mMayAllocateRuns=false;
 }
 
 /*
- * We are allowed to allocate memory if aMemory==nullptr or
- * aMayAllocate==true for each array that we need.
+ * We are allowed to allocate memory if aMemory==nullptr
+ * for each array that we need.
  * We also try to grow and shrink memory as needed if we
  * allocate it.
  *
  * Assume aSizeNeeded>0.
  * If *aMemory!=nullptr, then assume *aSize>0.
  *
  * ### this realloc() may unnecessarily copy the old data,
  * which we know we don't need any more;
  * is this the best way to do this??
  */
-bool nsBidi::GetMemory(void **aMemory, size_t *aSize, bool aMayAllocate, size_t aSizeNeeded)
+/*static*/
+bool
+nsBidi::GetMemory(void **aMemory, size_t *aSize, size_t aSizeNeeded)
 {
   /* check for existing memory */
   if(*aMemory==nullptr) {
     /* we need to allocate memory */
-    if(!aMayAllocate) {
-      return false;
+    *aMemory=malloc(aSizeNeeded);
+    if (*aMemory!=nullptr) {
+      *aSize=aSizeNeeded;
+      return true;
     } else {
-      *aMemory=malloc(aSizeNeeded);
-      if (*aMemory!=nullptr) {
-        *aSize=aSizeNeeded;
-        return true;
-      } else {
-        *aSize=0;
-        return false;
-      }
+      *aSize=0;
+      return false;
     }
   } else {
     /* there is some memory, is it enough or too much? */
-    if(aSizeNeeded>*aSize && !aMayAllocate) {
-      /* not enough memory, and we must not allocate */
-      return false;
-    } else if(aSizeNeeded!=*aSize && aMayAllocate) {
+    if(aSizeNeeded!=*aSize) {
       /* we may try to grow or shrink */
       void *memory=realloc(*aMemory, aSizeNeeded);
 
       if(memory!=nullptr) {
         *aMemory=memory;
         *aSize=aSizeNeeded;
         return true;
       } else {
@@ -243,17 +245,17 @@ void nsBidi::Free()
   mRunsMemory = nullptr;
   free(mIsolatesMemory);
   mIsolatesMemory = nullptr;
 }
 
 /* SetPara ------------------------------------------------------------ */
 
 nsresult nsBidi::SetPara(const char16_t *aText, int32_t aLength,
-                         nsBidiLevel aParaLevel, nsBidiLevel *aEmbeddingLevels)
+                         nsBidiLevel aParaLevel)
 {
   nsBidiDirection direction;
 
   /* check the argument values */
   if(aText==nullptr ||
      ((NSBIDI_MAX_EXPLICIT_LEVEL<aParaLevel) && !IS_DEFAULT_LEVEL(aParaLevel)) ||
      aLength<-1
     ) {
@@ -297,42 +299,32 @@ nsresult nsBidi::SetPara(const char16_t 
    */
   if(GETDIRPROPSMEMORY(aLength)) {
     mDirProps=mDirPropsMemory;
     GetDirProps(aText);
   } else {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
-  /* are explicit levels specified? */
-  if(aEmbeddingLevels==nullptr) {
-    /* no: determine explicit levels according to the (Xn) rules */\
-    if(GETLEVELSMEMORY(aLength)) {
-      mLevels=mLevelsMemory;
-      ResolveExplicitLevels(&direction);
-    } else {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
+  /* determine explicit levels according to the (Xn) rules */
+  if(GETLEVELSMEMORY(aLength)) {
+    mLevels=mLevelsMemory;
+    ResolveExplicitLevels(&direction, aText);
   } else {
-    /* set BN for all explicit codes, check that all levels are aParaLevel..NSBIDI_MAX_EXPLICIT_LEVEL */
-    mLevels=aEmbeddingLevels;
-    nsresult rv = CheckExplicitLevels(&direction);
-    if(NS_FAILED(rv)) {
-      return rv;
-    }
+    return NS_ERROR_OUT_OF_MEMORY;
   }
 
   /* allocate isolate memory */
   if (mIsolateCount <= SIMPLE_ISOLATES_SIZE) {
     mIsolates = mSimpleIsolates;
   } else {
     if (mIsolateCount * sizeof(Isolate) <= mIsolatesSize) {
       mIsolates = mIsolatesMemory;
     } else {
-      if (GETINITIALISOLATESMEMORY(mIsolateCount)) {
+      if (GETISOLATESMEMORY(mIsolateCount)) {
         mIsolates = mIsolatesMemory;
       } else {
         return NS_ERROR_OUT_OF_MEMORY;
       }
     }
   }
   mIsolateCount = -1;  /* current isolates stack entry == none */
 
@@ -363,17 +355,17 @@ nsresult nsBidi::SetPara(const char16_t 
        * then we can treat the entire paragraph as one run.
        * Otherwise, we need to perform the following rules on runs of
        * the text with the same embedding levels. (X10)
        * "Significant" explicit level codes are ones that actually
        * affect non-BN characters.
        * Examples for "insignificant" ones are empty embeddings
        * LRE-PDF, LRE-RLE-PDF-PDF, etc.
        */
-      if(aEmbeddingLevels==nullptr && !(mFlags&DIRPROP_FLAG_MULTI_RUNS)) {
+      if(!(mFlags&DIRPROP_FLAG_MULTI_RUNS)) {
         ResolveImplicitLevels(0, aLength,
                     GET_LR_FROM_LEVEL(mParaLevel),
                     GET_LR_FROM_LEVEL(mParaLevel));
       } else {
         /* sor, eor: start and end types of same-level-run */
         nsBidiLevel *levels=mLevels;
         int32_t start, limit=0;
         nsBidiLevel level, nextLevel;
@@ -580,16 +572,344 @@ void nsBidi::GetDirProps(const char16_t 
     stackLast--;
   }
 
   flags|=DIRPROP_FLAG_LR(mParaLevel);
 
   mFlags = flags;
 }
 
+/* Functions for handling paired brackets ----------------------------------- */
+
+/* In the mIsoRuns array, the first entry is used for text outside of any
+   isolate sequence.  Higher entries are used for each more deeply nested
+   isolate sequence.
+   mIsoRunLast is the index of the last used entry.
+   The mOpenings array is used to note the data of opening brackets not yet
+   matched by a closing bracket, or matched but still susceptible to change
+   level.
+   Each isoRun entry contains the index of the first and
+   one-after-last openings entries for pending opening brackets it
+   contains.  The next mOpenings entry to use is the one-after-last of the
+   most deeply nested isoRun entry.
+   mIsoRuns entries also contain their current embedding level and the bidi
+   class of the last-encountered strong character, since these will be needed
+   to resolve the level of paired brackets.  */
+
+nsBidi::BracketData::BracketData(const nsBidi *aBidi)
+{
+  mIsoRunLast = 0;
+  mIsoRuns[0].start = 0;
+  mIsoRuns[0].limit = 0;
+  mIsoRuns[0].level = aBidi->mParaLevel;
+  mIsoRuns[0].lastStrong = mIsoRuns[0].lastBase = mIsoRuns[0].contextDir =
+    GET_LR_FROM_LEVEL(aBidi->mParaLevel);
+  mIsoRuns[0].contextPos = 0;
+  mOpenings = mSimpleOpenings;
+  mOpeningsCount = SIMPLE_OPENINGS_COUNT;
+  mOpeningsMemory = nullptr;
+}
+
+nsBidi::BracketData::~BracketData()
+{
+  free(mOpeningsMemory);
+}
+
+/* LRE, LRO, RLE, RLO, PDF */
+void
+nsBidi::BracketData::ProcessBoundary(int32_t aLastDirControlCharPos,
+                                     nsBidiLevel aContextLevel,
+                                     nsBidiLevel aEmbeddingLevel,
+                                     const DirProp* aDirProps)
+{
+  IsoRun& lastIsoRun = mIsoRuns[mIsoRunLast];
+  if (DIRPROP_FLAG(aDirProps[aLastDirControlCharPos]) & MASK_ISO) { /* after an isolate */
+    return;
+  }
+  if (NO_OVERRIDE(aEmbeddingLevel) > NO_OVERRIDE(aContextLevel)) {  /* not PDF */
+    aContextLevel = aEmbeddingLevel;
+  }
+  lastIsoRun.limit = lastIsoRun.start;
+  lastIsoRun.level = aEmbeddingLevel;
+  lastIsoRun.lastStrong = lastIsoRun.lastBase = lastIsoRun.contextDir =
+    GET_LR_FROM_LEVEL(aContextLevel);
+  lastIsoRun.contextPos = aLastDirControlCharPos;
+}
+
+/* LRI or RLI */
+void
+nsBidi::BracketData::ProcessLRI_RLI(nsBidiLevel aLevel)
+{
+  MOZ_ASSERT(mIsoRunLast <= NSBIDI_MAX_EXPLICIT_LEVEL);
+  IsoRun& lastIsoRun = mIsoRuns[mIsoRunLast];
+  lastIsoRun.lastBase = O_N;
+  IsoRun& currIsoRun = mIsoRuns[++mIsoRunLast];
+  currIsoRun.start = currIsoRun.limit = lastIsoRun.limit;
+  currIsoRun.level = aLevel;
+  currIsoRun.lastStrong = currIsoRun.lastBase = currIsoRun.contextDir =
+    GET_LR_FROM_LEVEL(aLevel);
+  currIsoRun.contextPos = 0;
+}
+
+/* PDI */
+void
+nsBidi::BracketData::ProcessPDI()
+{
+  mIsoRuns[mIsoRunLast].lastBase = O_N;
+}
+
+/* newly found opening bracket: create an openings entry */
+bool                            /* return true if success */
+nsBidi::BracketData::AddOpening(char16_t aMatch, int32_t aPosition)
+{
+  IsoRun& lastIsoRun = mIsoRuns[mIsoRunLast];
+  if (lastIsoRun.limit >= mOpeningsCount) {  /* no available new entry */
+    if (!GETOPENINGSMEMORY(lastIsoRun.limit * 2)) {
+      return false;
+    }
+    if (mOpenings == mSimpleOpenings) {
+      memcpy(mOpeningsMemory, mSimpleOpenings,
+             SIMPLE_OPENINGS_COUNT * sizeof(Opening));
+    }
+    mOpenings = mOpeningsMemory;     /* may have changed */
+    mOpeningsCount = mOpeningsSize / sizeof(Opening);
+  }
+  Opening& o = mOpenings[lastIsoRun.limit];
+  o.position = aPosition;
+  o.match = aMatch;
+  o.contextDir = lastIsoRun.contextDir;
+  o.contextPos = lastIsoRun.contextPos;
+  o.flags = 0;
+  lastIsoRun.limit++;
+  return true;
+}
+
+/* change N0c1 to N0c2 when a preceding bracket is assigned the embedding level */
+void
+nsBidi::BracketData::FixN0c(int32_t aOpeningIndex, int32_t aNewPropPosition,
+                            DirProp aNewProp, DirProp* aDirProps)
+{
+  /* This function calls itself recursively */
+  IsoRun& lastIsoRun = mIsoRuns[mIsoRunLast];
+  for (int32_t k = aOpeningIndex + 1; k < lastIsoRun.limit; k++) {
+    Opening& o = mOpenings[k];
+    if (o.match >= 0) {     /* not an N0c match */
+      continue;
+    }
+    if (aNewPropPosition < o.contextPos) {
+      break;
+    }
+    int32_t openingPosition = o.position;
+    if (aNewPropPosition >= openingPosition) {
+      continue;
+    }
+    if (aNewProp == o.contextDir) {
+      break;
+    }
+    aDirProps[openingPosition] = aNewProp;
+    int32_t closingPosition = -(o.match);
+    aDirProps[closingPosition] = aNewProp;
+    o.match = 0;                    /* prevent further changes */
+    FixN0c(k, openingPosition, aNewProp, aDirProps);
+    FixN0c(k, closingPosition, aNewProp, aDirProps);
+  }
+}
+
+/* process closing bracket */
+DirProp              /* return L or R if N0b or N0c, ON if N0d */
+nsBidi::BracketData::ProcessClosing(int32_t aOpenIdx, int32_t aPosition,
+                                    DirProp* aDirProps)
+{
+  IsoRun& lastIsoRun = mIsoRuns[mIsoRunLast];
+  Opening& o = mOpenings[aOpenIdx];
+  DirProp newProp;
+  DirProp direction = GET_LR_FROM_LEVEL(lastIsoRun.level);
+  bool stable = true; // assume stable until proved otherwise
+
+  /* The stable flag is set when brackets are paired and their
+     level is resolved and cannot be changed by what will be
+     found later in the source string.
+     An unstable match can occur only when applying N0c, where
+     the resolved level depends on the preceding context, and
+     this context may be affected by text occurring later.
+     Example: RTL paragraph containing:  abc[(latin) HEBREW]
+     When the closing parenthesis is encountered, it appears
+     that N0c1 must be applied since 'abc' sets an opposite
+     direction context and both parentheses receive level 2.
+     However, when the closing square bracket is processed,
+     N0b applies because of 'HEBREW' being included within the
+     brackets, thus the square brackets are treated like R and
+     receive level 1. However, this changes the preceding
+     context of the opening parenthesis, and it now appears
+     that N0c2 must be applied to the parentheses rather than
+     N0c1. */
+
+  if ((direction == 0 && o.flags & FOUND_L) ||
+      (direction == 1 && o.flags & FOUND_R)) { /* N0b */
+    newProp = direction;
+  } else if (o.flags & (FOUND_L|FOUND_R)) {    /* N0c */
+    /* it is stable if there is no containing pair or in
+       conditions too complicated and not worth checking */
+    stable = (aOpenIdx == lastIsoRun.start);
+    if (direction != o.contextDir) {
+      newProp = o.contextDir;           /* N0c1 */
+    } else {
+      newProp = direction;                     /* N0c2 */
+    }
+  } else {
+    /* forget this and any brackets nested within this pair */
+    lastIsoRun.limit = aOpenIdx;
+    return O_N;                                 /* N0d */
+  }
+  aDirProps[o.position] = newProp;
+  aDirProps[aPosition] = newProp;
+  /* Update nested N0c pairs that may be affected */
+  FixN0c(aOpenIdx, o.position, newProp, aDirProps);
+  if (stable) {
+    /* forget any brackets nested within this pair */
+    lastIsoRun.limit = aOpenIdx;
+  } else {
+    int32_t k;
+    o.match = -aPosition;
+    /* neutralize any unmatched opening between the current pair */
+    for (k = aOpenIdx + 1; k < lastIsoRun.limit; k++) {
+      Opening& oo = mOpenings[k];
+      if (oo.position > aPosition) {
+        break;
+      }
+      if (oo.match > 0) {
+        oo.match = 0;
+      }
+    }
+  }
+  return newProp;
+}
+
+static inline bool
+IsMatchingCloseBracket(char16_t aCh1, char16_t aCh2)
+{
+  // U+232A RIGHT-POINTING ANGLE BRACKET and U+3009 RIGHT ANGLE BRACKET
+  // are canonical equivalents, so we special-case them here.
+  return (aCh1 == aCh2) ||
+         (aCh1 == 0x232A && aCh2 == 0x3009) ||
+         (aCh2 == 0x232A && aCh1 == 0x3009);
+}
+
+/* Handle strong characters, digits and candidates for closing brackets. */
+/* Returns true if success. (The only failure mode is an OOM when trying
+   to allocate memory for the Openings array.) */
+bool
+nsBidi::BracketData::ProcessChar(int32_t aPosition, char16_t aCh,
+                                 DirProp* aDirProps, nsBidiLevel* aLevels)
+{
+  IsoRun& lastIsoRun = mIsoRuns[mIsoRunLast];
+  DirProp newProp;
+  DirProp dirProp = aDirProps[aPosition];
+  nsBidiLevel level = aLevels[aPosition];
+  if (dirProp == O_N) {
+    /* First see if it is a matching closing bracket. Hopefully, this is
+       more efficient than checking if it is a closing bracket at all */
+    for (int32_t idx = lastIsoRun.limit - 1; idx >= lastIsoRun.start; idx--) {
+      if (!IsMatchingCloseBracket(aCh, mOpenings[idx].match)) {
+        continue;
+      }
+      /* We have a match */
+      newProp = ProcessClosing(idx, aPosition, aDirProps);
+      if (newProp == O_N) {           /* N0d */
+        aCh = 0;        /* prevent handling as an opening */
+        break;
+      }
+      lastIsoRun.lastBase = O_N;
+      lastIsoRun.contextDir = newProp;
+      lastIsoRun.contextPos = aPosition;
+      if (level & NSBIDI_LEVEL_OVERRIDE) {    /* X4, X5 */
+        newProp = GET_LR_FROM_LEVEL(level);
+        lastIsoRun.lastStrong = newProp;
+        uint16_t flag = DIRPROP_FLAG(newProp);
+        for (int32_t i = lastIsoRun.start; i < idx; i++) {
+          mOpenings[i].flags |= flag;
+        }
+        /* matching brackets are not overridden by LRO/RLO */
+        aLevels[aPosition] &= ~NSBIDI_LEVEL_OVERRIDE;
+      }
+      /* matching brackets are not overridden by LRO/RLO */
+      aLevels[mOpenings[idx].position] &= ~NSBIDI_LEVEL_OVERRIDE;
+      return true;
+    }
+    /* We get here only if the ON character is not a matching closing
+       bracket or it is a case of N0d */
+    /* Now see if it is an opening bracket */
+    char16_t match = GetPairedBracket(aCh);
+    if (match != aCh &&                 /* has a matching char */
+        GetPairedBracketType(aCh) == PAIRED_BRACKET_TYPE_OPEN) { /* opening bracket */
+      if (!AddOpening(match, aPosition)) {
+        return false;
+      }
+    }
+  }
+  if (level & NSBIDI_LEVEL_OVERRIDE) {    /* X4, X5 */
+    newProp = GET_LR_FROM_LEVEL(level);
+    if (dirProp != S && dirProp != WS && dirProp != O_N) {
+      aDirProps[aPosition] = newProp;
+    }
+    lastIsoRun.lastBase = newProp;
+    lastIsoRun.lastStrong = newProp;
+    lastIsoRun.contextDir = newProp;
+    lastIsoRun.contextPos = aPosition;
+  } else if (IS_STRONG_TYPE(dirProp)) {
+    newProp = DirFromStrong(dirProp);
+    lastIsoRun.lastBase = dirProp;
+    lastIsoRun.lastStrong = dirProp;
+    lastIsoRun.contextDir = newProp;
+    lastIsoRun.contextPos = aPosition;
+  } else if (dirProp == EN) {
+    lastIsoRun.lastBase = EN;
+    if (lastIsoRun.lastStrong == L) {
+      newProp = L;                  /* W7 */
+      aDirProps[aPosition] = ENL;
+      lastIsoRun.contextDir = L;
+      lastIsoRun.contextPos = aPosition;
+    } else {
+      newProp = R;                  /* N0 */
+      if (lastIsoRun.lastStrong == AL) {
+        aDirProps[aPosition] = AN;  /* W2 */
+      } else {
+        aDirProps[aPosition] = ENR;
+      }
+      lastIsoRun.contextDir = R;
+      lastIsoRun.contextPos = aPosition;
+    }
+  } else if (dirProp == AN) {
+    newProp = R;                      /* N0 */
+    lastIsoRun.lastBase = AN;
+    lastIsoRun.contextDir = R;
+    lastIsoRun.contextPos = aPosition;
+  } else if (dirProp == NSM) {
+    /* if the last real char was ON, change NSM to ON so that it
+       will stay ON even if the last real char is a bracket which
+       may be changed to L or R */
+    newProp = lastIsoRun.lastBase;
+    if (newProp == O_N) {
+      aDirProps[aPosition] = newProp;
+    }
+  } else {
+    newProp = dirProp;
+    lastIsoRun.lastBase = dirProp;
+  }
+  if (IS_STRONG_TYPE(newProp)) {
+    uint16_t flag = DIRPROP_FLAG(DirFromStrong(newProp));
+    for (int32_t i = lastIsoRun.start; i < lastIsoRun.limit; i++) {
+      if (aPosition > mOpenings[i].position) {
+        mOpenings[i].flags |= flag;
+      }
+    }
+  }
+  return true;
+}
+
 /* perform (X1)..(X9) ------------------------------------------------------- */
 
 /*
  * Resolve the explicit levels as specified by explicit embedding codes.
  * Recalculate the flags to have them reflect the real properties
  * after taking the explicit embeddings into account.
  *
  * The Bidi algorithm is designed to result in the same behavior whether embedding
@@ -630,17 +950,17 @@ void nsBidi::GetDirProps(const char16_t 
  *
  * In order to have a correct push-pop semantics even in the case of overflows,
  * overflow counters and a valid isolate counter are used as described in UAX#9
  * section 3.3.2 "Explicit Levels and Direction".
  *
  * This implementation assumes that NSBIDI_MAX_EXPLICIT_LEVEL is odd.
  */
 
-void nsBidi::ResolveExplicitLevels(nsBidiDirection *aDirection)
+void nsBidi::ResolveExplicitLevels(nsBidiDirection *aDirection, const char16_t *aText)
 {
   DirProp *dirProps=mDirProps;
   nsBidiLevel *levels=mLevels;
 
   int32_t i=0, length=mLength;
   Flags flags=mFlags;       /* collect all directionalities in the text */
   DirProp dirProp;
   nsBidiLevel level=mParaLevel;
@@ -650,268 +970,237 @@ void nsBidi::ResolveExplicitLevels(nsBid
 
   /* determine if the text is mixed-directional or single-directional */
   direction=DirectionFromFlags(flags);
 
   /* we may not need to resolve any explicit levels */
   if(direction!=NSBIDI_MIXED) {
     /* not mixed directionality: levels don't matter - trailingWSStart will be 0 */
   } else if(!(flags&(MASK_EXPLICIT|MASK_ISO))) {
+    BracketData bracketData(this);
     /* no embeddings, set all levels to the paragraph level */
     for(i=0; i<length; ++i) {
       levels[i]=level;
+      if (dirProps[i] == BN) {
+        continue;
+      }
+      if (!bracketData.ProcessChar(i, aText[i], mDirProps, mLevels)) {
+        NS_WARNING("BracketData::ProcessChar failed, out of memory?");
+        // Ran out of memory for deeply-nested openings; give up and
+        // return LTR. This could presumably result in incorrect display,
+        // but in practice it won't happen except in some artificially-
+        // constructed torture test -- which is just as likely to die
+        // altogether with an OOM failure.
+        *aDirection = NSBIDI_LTR;
+        return;
+      }
     }
   } else {
     /* continue to perform (Xn) */
 
     /* (X1) level is set for all codes, embeddingLevel keeps track of the push/pop operations */
     /* both variables may carry the NSBIDI_LEVEL_OVERRIDE flag to indicate the override status */
     nsBidiLevel embeddingLevel = level, newLevel;
     nsBidiLevel previousLevel = level;     /* previous level for regular (not CC) characters */
+    int32_t lastDirControlCharPos = 0;     /* index of last effective LRx,RLx, PDx */
 
     uint16_t stack[NSBIDI_MAX_EXPLICIT_LEVEL + 2];   /* we never push anything >=NSBIDI_MAX_EXPLICIT_LEVEL
                                                         but we need one more entry as base */
     int32_t stackLast = 0;
     int32_t overflowIsolateCount = 0;
     int32_t overflowEmbeddingCount = 0;
     int32_t validIsolateCount = 0;
 
+    BracketData bracketData(this);
+
     stack[0] = level;
 
     /* recalculate the flags */
     flags=0;
 
     /* since we assume that this is a single paragraph, we ignore (X8) */
     for(i=0; i<length; ++i) {
       dirProp=dirProps[i];
       switch(dirProp) {
         case LRE:
         case RLE:
         case LRO:
         case RLO:
           /* (X2, X3, X4, X5) */
           flags |= DIRPROP_FLAG(BN);
+          levels[i] = previousLevel;
           if (dirProp == LRE || dirProp == LRO) {
             newLevel = (embeddingLevel + 2) & ~(NSBIDI_LEVEL_OVERRIDE | 1);    /* least greater even level */
           } else {
             newLevel = ((embeddingLevel & ~NSBIDI_LEVEL_OVERRIDE) + 1) | 1;    /* least greater odd level */
           }
           if(newLevel <= NSBIDI_MAX_EXPLICIT_LEVEL && overflowIsolateCount == 0 && overflowEmbeddingCount == 0) {
+            lastDirControlCharPos = i;
             embeddingLevel = newLevel;
             if (dirProp == LRO || dirProp == RLO) {
               embeddingLevel |= NSBIDI_LEVEL_OVERRIDE;
             }
             stackLast++;
             stack[stackLast] = embeddingLevel;
-            /* we don't need to set UBIDI_LEVEL_OVERRIDE off for LRE and RLE
+            /* we don't need to set NSBIDI_LEVEL_OVERRIDE off for LRE and RLE
                since this has already been done for newLevel which is
                the source for embeddingLevel.
              */
           } else {
-            dirProps[i] |= IGNORE_CC;
             if (overflowIsolateCount == 0) {
               overflowEmbeddingCount++;
             }
           }
           break;
 
         case PDF:
           /* (X7) */
           flags |= DIRPROP_FLAG(BN);
+          levels[i] = previousLevel;
           /* handle all the overflow cases first */
           if (overflowIsolateCount) {
-            dirProps[i] |= IGNORE_CC;
             break;
           }
           if (overflowEmbeddingCount) {
-            dirProps[i] |= IGNORE_CC;
             overflowEmbeddingCount--;
             break;
           }
           if (stackLast > 0 && stack[stackLast] < ISOLATE) {   /* not an isolate entry */
+            lastDirControlCharPos = i;
             stackLast--;
             embeddingLevel = stack[stackLast];
-          } else {
-            dirProps[i] |= IGNORE_CC;
           }
           break;
 
         case LRI:
         case RLI:
-          if (embeddingLevel != previousLevel) {
-            previousLevel = embeddingLevel;
+          flags |= DIRPROP_FLAG(O_N) | DIRPROP_FLAG_LR(embeddingLevel);
+          levels[i] = NO_OVERRIDE(embeddingLevel);
+          if (NO_OVERRIDE(embeddingLevel) != NO_OVERRIDE(previousLevel)) {
+            bracketData.ProcessBoundary(lastDirControlCharPos, previousLevel,
+                                        embeddingLevel, mDirProps);
+            flags |= DIRPROP_FLAG_MULTI_RUNS;
           }
+          previousLevel = embeddingLevel;
           /* (X5a, X5b) */
-          flags |= DIRPROP_FLAG(O_N) | DIRPROP_FLAG(BN) | DIRPROP_FLAG_LR(embeddingLevel);
-          level = embeddingLevel;
           if (dirProp == LRI) {
             newLevel = (embeddingLevel + 2) & ~(NSBIDI_LEVEL_OVERRIDE | 1); /* least greater even level */
           } else {
             newLevel = ((embeddingLevel & ~NSBIDI_LEVEL_OVERRIDE) + 1) | 1;  /* least greater odd level */
           }
           if (newLevel <= NSBIDI_MAX_EXPLICIT_LEVEL && overflowIsolateCount == 0 && overflowEmbeddingCount == 0) {
+            flags |= DIRPROP_FLAG(dirProp);
+            lastDirControlCharPos = i;
             previousLevel = embeddingLevel;
             validIsolateCount++;
             if (validIsolateCount > mIsolateCount) {
               mIsolateCount = validIsolateCount;
             }
             embeddingLevel = newLevel;
             stackLast++;
             stack[stackLast] = embeddingLevel + ISOLATE;
+            bracketData.ProcessLRI_RLI(embeddingLevel);
           } else {
-            dirProps[i] |= IGNORE_CC;
+            /* make it so that it is handled by AdjustWSLevels() */
+            dirProps[i] = WS;
             overflowIsolateCount++;
           }
           break;
 
         case PDI:
+          if (NO_OVERRIDE(embeddingLevel) != NO_OVERRIDE(previousLevel)) {
+            bracketData.ProcessBoundary(lastDirControlCharPos, previousLevel,
+                                        embeddingLevel, mDirProps);
+            flags |= DIRPROP_FLAG_MULTI_RUNS;
+          }
           /* (X6a) */
           if (overflowIsolateCount) {
-            dirProps[i] |= IGNORE_CC;
             overflowIsolateCount--;
+            /* make it so that it is handled by AdjustWSLevels() */
+            dirProps[i] = WS;
           } else if (validIsolateCount) {
+            flags |= DIRPROP_FLAG(PDI);
+            lastDirControlCharPos = i;
             overflowEmbeddingCount = 0;
             while (stack[stackLast] < ISOLATE) {
               /* pop embedding entries        */
               /* until the last isolate entry */
               stackLast--;
 
               // Since validIsolateCount is true, there must be an isolate entry
               // on the stack, so the stack is guaranteed to not be empty.
               // Still, to eliminate a warning from coverity, we use an assertion.
               MOZ_ASSERT(stackLast > 0);
             }
             stackLast--;  /* pop also the last isolate entry */
             MOZ_ASSERT(stackLast >= 0);  // For coverity
             validIsolateCount--;
+            bracketData.ProcessPDI();
           } else {
-            dirProps[i] |= IGNORE_CC;
+            /* make it so that it is handled by AdjustWSLevels() */
+            dirProps[i] = WS;
           }
           embeddingLevel = stack[stackLast] & ~ISOLATE;
-          previousLevel = level = embeddingLevel;
-          flags |= DIRPROP_FLAG(O_N) | DIRPROP_FLAG(BN) | DIRPROP_FLAG_LR(embeddingLevel);
+          flags |= DIRPROP_FLAG(O_N) | DIRPROP_FLAG_LR(embeddingLevel);
+          previousLevel = embeddingLevel;
+          levels[i] = NO_OVERRIDE(embeddingLevel);
           break;
 
         case B:
           /*
            * We do not expect to see a paragraph separator (B),
            */
           NS_NOTREACHED("Unexpected paragraph separator");
           break;
 
         case BN:
           /* BN, LRE, RLE, and PDF are supposed to be removed (X9) */
           /* they will get their levels set correctly in AdjustWSLevels() */
-          flags|=DIRPROP_FLAG(BN);
+          levels[i] = previousLevel;
+          flags |= DIRPROP_FLAG(BN);
           break;
 
         default:
           /* all other types get the "real" level */
-          level = embeddingLevel;
-          if(embeddingLevel != previousLevel) {
-            previousLevel = embeddingLevel;
+          if (NO_OVERRIDE(embeddingLevel) != NO_OVERRIDE(previousLevel)) {
+            bracketData.ProcessBoundary(lastDirControlCharPos, previousLevel,
+                                        embeddingLevel, mDirProps);
+            flags |= DIRPROP_FLAG_MULTI_RUNS;
+            if (embeddingLevel & NSBIDI_LEVEL_OVERRIDE) {
+              flags |= DIRPROP_FLAG_O(embeddingLevel);
+            } else {
+              flags |= DIRPROP_FLAG_E(embeddingLevel);
+            }
           }
-
-          if (level & NSBIDI_LEVEL_OVERRIDE) {
-            flags |= DIRPROP_FLAG_LR(level);
-          } else {
-            flags |= DIRPROP_FLAG(dirProp);
+          previousLevel = embeddingLevel;
+          levels[i] = embeddingLevel;
+          if (!bracketData.ProcessChar(i, aText[i], mDirProps, mLevels)) {
+            NS_WARNING("BracketData::ProcessChar failed, out of memory?");
+            *aDirection = NSBIDI_LTR;
+            return;
           }
+          flags |= DIRPROP_FLAG(dirProps[i]);
           break;
       }
-
-      /*
-       * We need to set reasonable levels even on BN codes and
-       * explicit codes because we will later look at same-level runs (X10).
-       */
-      levels[i]=level;
-      if (i > 0 && levels[i - 1] != level) {
-        flags |= DIRPROP_FLAG_MULTI_RUNS;
-        if (level & NSBIDI_LEVEL_OVERRIDE) {
-          flags |= DIRPROP_FLAG_O(level);
-        } else {
-          flags |= DIRPROP_FLAG_E(level);
-        }
-      }
-      if (DIRPROP_FLAG(dirProp) & MASK_ISO) {
-        level = embeddingLevel;
-      }
     }
 
     if(flags&MASK_EMBEDDING) {
       flags|=DIRPROP_FLAG_LR(mParaLevel);
     }
 
     /* subsequently, ignore the explicit codes and BN (X9) */
 
     /* again, determine if the text is mixed-directional or single-directional */
     mFlags=flags;
     direction=DirectionFromFlags(flags);
   }
 
   *aDirection = direction;
 }
 
-/*
- * Use a pre-specified embedding levels array:
- *
- * Adjust the directional properties for overrides (->LEVEL_OVERRIDE),
- * ignore all explicit codes (X9),
- * and check all the preset levels.
- *
- * Recalculate the flags to have them reflect the real properties
- * after taking the explicit embeddings into account.
- */
-nsresult nsBidi::CheckExplicitLevels(nsBidiDirection *aDirection)
-{
-  const DirProp *dirProps=mDirProps;
-  DirProp dirProp;
-  nsBidiLevel *levels=mLevels;
-  int32_t isolateCount = 0;
-
-  int32_t i, length=mLength;
-  Flags flags=0;  /* collect all directionalities in the text */
-  nsBidiLevel level, paraLevel=mParaLevel;
-  mIsolateCount = 0;
-
-  for(i=0; i<length; ++i) {
-    level=levels[i];
-    dirProp = dirProps[i];
-    if (dirProp == LRI || dirProp == RLI) {
-      isolateCount++;
-      if (isolateCount > mIsolateCount) {
-        mIsolateCount = isolateCount;
-      }
-    } else if (dirProp == PDI) {
-      isolateCount--;
-    }
-    if(level&NSBIDI_LEVEL_OVERRIDE) {
-      /* keep the override flag in levels[i] but adjust the flags */
-      level&=~NSBIDI_LEVEL_OVERRIDE;     /* make the range check below simpler */
-      flags|=DIRPROP_FLAG_O(level);
-    } else {
-      /* set the flags */
-      flags|=DIRPROP_FLAG_E(level)|DIRPROP_FLAG(dirProp);
-    }
-    if(level<paraLevel || NSBIDI_MAX_EXPLICIT_LEVEL<level) {
-      /* level out of bounds */
-      *aDirection = NSBIDI_LTR;
-      return NS_ERROR_INVALID_ARG;
-    }
-  }
-  if(flags&MASK_EMBEDDING) {
-    flags|=DIRPROP_FLAG_LR(mParaLevel);
-  }
-
-  /* determine if the text is mixed-directional or single-directional */
-  mFlags=flags;
-  *aDirection = DirectionFromFlags(flags);
-  return NS_OK;
-}
-
 /* determine if the text is mixed-directional or single-directional */
 nsBidiDirection nsBidi::DirectionFromFlags(Flags aFlags)
 {
   /* if the text contains AN and neutrals, then some neutrals may become RTL */
   if(!(aFlags&MASK_RTL || (aFlags&DIRPROP_FLAG(AN) && aFlags&MASK_POSSIBLE_N))) {
     return NSBIDI_LTR;
   } else if(!(aFlags&MASK_LTR)) {
     return NSBIDI_RTL;
@@ -1191,31 +1480,31 @@ void nsBidi::ResolveImplicitLevels(int32
   const DirProp *dirProps = mDirProps;
   DirProp dirProp;
   LevState levState;
   int32_t i, start1, start2;
   uint16_t oldStateImp, stateImp, actionImp;
   uint8_t gprop, resProp, cell;
 
   /* initialize for property and levels state tables */
-  levState.startON = -1;
   levState.runStart = aStart;
   levState.runLevel = mLevels[aStart];
   levState.pImpTab = impTab[levState.runLevel & 1];
   levState.pImpAct = impAct0;
 
   /* The isolates[] entries contain enough information to
      resume the bidi algorithm in the same state as it was
      when it was interrupted by an isolate sequence. */
-  if (dirProps[aStart] == PDI) {
+  if (dirProps[aStart] == PDI && mIsolateCount >= 0) {
     start1 = mIsolates[mIsolateCount].start1;
     stateImp = mIsolates[mIsolateCount].stateImp;
     levState.state = mIsolates[mIsolateCount].state;
     mIsolateCount--;
   } else {
+    levState.startON = -1;
     start1 = aStart;
     if (dirProps[aStart] == NSM) {
       stateImp = 1 + aSOR;
     } else {
       stateImp = 0;
     }
     levState.state = 0;
     ProcessPropertySeq(&levState, aSOR, aStart, aStart);
@@ -1228,17 +1517,17 @@ void nsBidi::ResolveImplicitLevels(int32
         dirProp = mDirProps[aLimit - 1];
         if (dirProp == LRI || dirProp == RLI) {
           break;  /* no forced closing for sequence ending with LRI/RLI */
         }
       }
       gprop = aEOR;
     } else {
       DirProp prop;
-      prop = PURE_DIRPROP(dirProps[i]);
+      prop = dirProps[i];
       gprop = groupProp[prop];
     }
     oldStateImp = stateImp;
     cell = impTabProps[oldStateImp][gprop];
     stateImp = GET_STATEPROPS(cell);      /* isolate the new state */
     actionImp = GET_ACTIONPROPS(cell);    /* isolate the action */
     if ((i == aLimit) && (actionImp == 0)) {
       /* there is an unprocessed sequence if its property == eor   */
@@ -1299,24 +1588,24 @@ void nsBidi::AdjustWSLevels()
 
   if(mFlags&MASK_WS) {
     nsBidiLevel paraLevel=mParaLevel;
     Flags flag;
 
     i=mTrailingWSStart;
     while(i>0) {
       /* reset a sequence of WS/BN before eop and B/S to the paragraph paraLevel */
-      while (i > 0 && DIRPROP_FLAG(PURE_DIRPROP(dirProps[--i])) & MASK_WS) {
+      while (i > 0 && DIRPROP_FLAG(dirProps[--i]) & MASK_WS) {
         levels[i]=paraLevel;
       }
 
       /* reset BN to the next character's paraLevel until B/S, which restarts above loop */
       /* here, i+1 is guaranteed to be <length */
       while(i>0) {
-        flag = DIRPROP_FLAG(PURE_DIRPROP(dirProps[--i]));
+        flag = DIRPROP_FLAG(dirProps[--i]);
         if(flag&MASK_BN_EXPLICIT) {
           levels[i]=levels[i+1];
         } else if(flag&MASK_B_S) {
           levels[i]=paraLevel;
           break;
         }
       }
     }
@@ -1329,262 +1618,16 @@ nsresult nsBidi::GetDirection(nsBidiDire
   return NS_OK;
 }
 
 nsresult nsBidi::GetParaLevel(nsBidiLevel* aParaLevel)
 {
   *aParaLevel = mParaLevel;
   return NS_OK;
 }
-#ifdef FULL_BIDI_ENGINE
-
-/* -------------------------------------------------------------------------- */
-
-nsresult nsBidi::GetLength(int32_t* aLength)
-{
-  *aLength = mLength;
-  return NS_OK;
-}
-
-/*
- * General remarks about the functions in this section:
- *
- * These functions deal with the aspects of potentially mixed-directional
- * text in a single paragraph or in a line of a single paragraph
- * which has already been processed according to
- * the Unicode 6.3 Bidi algorithm as defined in
- * http://www.unicode.org/unicode/reports/tr9/ , version 28,
- * also described in The Unicode Standard, Version 6.3.0 .
- *
- * This means that there is a nsBidi object with a levels
- * and a dirProps array.
- * paraLevel and direction are also set.
- * Only if the length of the text is zero, then levels==dirProps==nullptr.
- *
- * The overall directionality of the paragraph
- * or line is used to bypass the reordering steps if possible.
- * Even purely RTL text does not need reordering there because
- * the getLogical/VisualIndex() functions can compute the
- * index on the fly in such a case.
- *
- * The implementation of the access to same-level-runs and of the reordering
- * do attempt to provide better performance and less memory usage compared to
- * a direct implementation of especially rule (L2) with an array of
- * one (32-bit) integer per text character.
- *
- * Here, the levels array is scanned as soon as necessary, and a vector of
- * same-level-runs is created. Reordering then is done on this vector.
- * For each run of text positions that were resolved to the same level,
- * only 8 bytes are stored: the first text position of the run and the visual
- * position behind the run after reordering.
- * One sign bit is used to hold the directionality of the run.
- * This is inefficient if there are many very short runs. If the average run
- * length is <2, then this uses more memory.
- *
- * In a further attempt to save memory, the levels array is never changed
- * after all the resolution rules (Xn, Wn, Nn, In).
- * Many functions have to consider the field trailingWSStart:
- * if it is less than length, then there is an implicit trailing run
- * at the paraLevel,
- * which is not reflected in the levels array.
- * This allows a line nsBidi object to use the same levels array as
- * its paragraph parent object.
- *
- * When a nsBidi object is created for a line of a paragraph, then the
- * paragraph's levels and dirProps arrays are reused by way of setting
- * a pointer into them, not by copying. This again saves memory and forbids to
- * change the now shared levels for (L1).
- */
-nsresult nsBidi::SetLine(const nsBidi* aParaBidi, int32_t aStart, int32_t aLimit)
-{
-  nsBidi* pParent = (nsBidi*)aParaBidi;
-  int32_t length;
-
-  /* check the argument values */
-  if(pParent==nullptr) {
-    return NS_ERROR_INVALID_POINTER;
-  } else if(aStart < 0 || aStart >= aLimit || aLimit > pParent->mLength) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  /* set members from our aParaBidi parent */
-  length = mLength = aLimit - aStart;
-  mParaLevel=pParent->mParaLevel;
-
-  mRuns=nullptr;
-  mFlags=0;
-
-  mDirProps=pParent->mDirProps+aStart;
-  mLevels=pParent->mLevels+aStart;
-  mRunCount=-1;
-
-  if(pParent->mDirection!=NSBIDI_MIXED) {
-    /* the parent is already trivial */
-    mDirection=pParent->mDirection;
-
-    /*
-     * The parent's levels are all either
-     * implicitly or explicitly ==paraLevel;
-     * do the same here.
-     */
-    if(pParent->mTrailingWSStart<=aStart) {
-      mTrailingWSStart=0;
-    } else if(pParent->mTrailingWSStart<aLimit) {
-      mTrailingWSStart=pParent->mTrailingWSStart-aStart;
-    } else {
-      mTrailingWSStart=length;
-    }
-  } else {
-    const nsBidiLevel *levels=mLevels;
-    int32_t i, trailingWSStart;
-    nsBidiLevel level;
-
-    SetTrailingWSStart();
-    trailingWSStart=mTrailingWSStart;
-
-    /* recalculate pLineBidi->direction */
-    if(trailingWSStart==0) {
-      /* all levels are at paraLevel */
-      mDirection=(nsBidiDirection)(mParaLevel&1);
-   } else {
-      /* get the level of the first character */
-      level=levels[0]&1;
-
-      /* if there is anything of a different level, then the line is mixed */
-      if(trailingWSStart<length && (mParaLevel&1)!=level) {
-        /* the trailing WS is at paraLevel, which differs from levels[0] */
-        mDirection=NSBIDI_MIXED;
-      } else {
-        /* see if levels[1..trailingWSStart-1] have the same direction as levels[0] and paraLevel */
-        i=1;
-        for(;;) {
-          if(i==trailingWSStart) {
-            /* the direction values match those in level */
-            mDirection=(nsBidiDirection)level;
-            break;
-          } else if((levels[i]&1)!=level) {
-            mDirection=NSBIDI_MIXED;
-            break;
-          }
-          ++i;
-        }
-      }
-    }
-
-    switch(mDirection) {
-      case NSBIDI_LTR:
-        /* make sure paraLevel is even */
-        mParaLevel=(mParaLevel+1)&~1;
-
-        /* all levels are implicitly at paraLevel (important for GetLevels()) */
-        mTrailingWSStart=0;
-      break;
-      case NSBIDI_RTL:
-        /* make sure paraLevel is odd */
-        mParaLevel|=1;
-
-        /* all levels are implicitly at paraLevel (important for GetLevels()) */
-        mTrailingWSStart=0;
-        break;
-      default:
-        break;
-    }
-  }
-  return NS_OK;
-}
-
-/* handle trailing WS (L1) -------------------------------------------------- */
-
-/*
- * SetTrailingWSStart() sets the start index for a trailing
- * run of WS in the line. This is necessary because we do not modify
- * the paragraph's levels array that we just point into.
- * Using trailingWSStart is another form of performing (L1).
- *
- * To make subsequent operations easier, we also include the run
- * before the WS if it is at the paraLevel - we merge the two here.
- */
-void nsBidi::SetTrailingWSStart() {
-  /* mDirection!=NSBIDI_MIXED */
-
-  const DirProp *dirProps=mDirProps;
-  nsBidiLevel *levels=mLevels;
-  int32_t start=mLength;
-  nsBidiLevel paraLevel=mParaLevel;
-
-  /* go backwards across all WS, BN, explicit codes */
-  while(start>0 && DIRPROP_FLAG(dirProps[start-1])&MASK_WS) {
-    --start;
-  }
-
-  /* if the WS run can be merged with the previous run then do so here */
-  while(start>0 && levels[start-1]==paraLevel) {
-    --start;
-  }
-
-  mTrailingWSStart=start;
-}
-
-nsresult nsBidi::GetLevelAt(int32_t aCharIndex, nsBidiLevel* aLevel)
-{
-  /* return paraLevel if in the trailing WS run, otherwise the real level */
-  if(aCharIndex<0 || mLength<=aCharIndex) {
-    *aLevel = 0;
-  } else if(mDirection!=NSBIDI_MIXED || aCharIndex>=mTrailingWSStart) {
-    *aLevel = mParaLevel;
-  } else {
-    *aLevel = mLevels[aCharIndex];
-  }
-  return NS_OK;
-}
-
-nsresult nsBidi::GetLevels(nsBidiLevel** aLevels)
-{
-  int32_t start, length;
-
-  length = mLength;
-  if(length<=0) {
-    *aLevels = nullptr;
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  start = mTrailingWSStart;
-  if(start==length) {
-    /* the current levels array reflects the WS run */
-    *aLevels = mLevels;
-    return NS_OK;
-  }
-
-  /*
-   * After the previous if(), we know that the levels array
-   * has an implicit trailing WS run and therefore does not fully
-   * reflect itself all the levels.
-   * This must be a nsBidi object for a line, and
-   * we need to create a new levels array.
-   */
-
-  if(GETLEVELSMEMORY(length)) {
-    nsBidiLevel *levels=mLevelsMemory;
-
-    if(start>0 && levels!=mLevels) {
-      memcpy(levels, mLevels, start);
-    }
-    memset(levels+start, mParaLevel, length-start);
-
-    /* this new levels array is set for the line and reflects the WS run */
-    mTrailingWSStart=length;
-    *aLevels=mLevels=levels;
-    return NS_OK;
-  } else {
-    /* out of memory */
-    *aLevels = nullptr;
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-}
-#endif // FULL_BIDI_ENGINE
 
 nsresult nsBidi::GetCharTypeAt(int32_t aCharIndex, nsCharType* pType)
 {
   if(aCharIndex<0 || mLength<=aCharIndex) {
     return NS_ERROR_INVALID_ARG;
   }
   *pType = (nsCharType)mDirProps[aCharIndex];
   return NS_OK;
@@ -2033,459 +2076,8 @@ bool nsBidi::PrepareReorder(const nsBidi
   /* initialize the index map */
   for(start=aLength; start>0;) {
     --start;
     aIndexMap[start]=start;
   }
 
   return true;
 }
-
-#ifdef FULL_BIDI_ENGINE
-/* API functions for logical<->visual mapping ------------------------------- */
-
-nsresult nsBidi::GetVisualIndex(int32_t aLogicalIndex, int32_t* aVisualIndex) {
-  int32_t visualIndex = NSBIDI_MAP_NOWHERE;
-
-  if(aLogicalIndex<0 || mLength<=aLogicalIndex) {
-    return NS_ERROR_INVALID_ARG;
-  } else {
-    /* we can do the trivial cases without the runs array */
-    switch(mDirection) {
-    case NSBIDI_LTR:
-      *aVisualIndex = aLogicalIndex;
-      return NS_OK;
-    case NSBIDI_RTL:
-      *aVisualIndex = mLength-aLogicalIndex-1;
-      return NS_OK;
-    default:
-      if(mRunCount<0 && !GetRuns()) {
-        return NS_ERROR_OUT_OF_MEMORY;
-      } else {
-        Run *runs=mRuns;
-        int32_t i, visualStart=0, offset, length;
-
-        /* linear search for the run, search on the visual runs */
-        for (i = 0; i < mRunCount; ++i) {
-          length=runs[i].visualLimit-visualStart;
-          offset=aLogicalIndex-GET_INDEX(runs[i].logicalStart);
-          if(offset>=0 && offset<length) {
-            if(IS_EVEN_RUN(runs[i].logicalStart)) {
-              /* LTR */
-              visualIndex = visualStart + offset;
-            } else {
-              /* RTL */
-              visualIndex = visualStart + length - offset - 1;
-            }
-            break;
-          }
-          visualStart+=length;
-        }
-        if (i >= mRunCount) {
-          *aVisualIndex = NSBIDI_MAP_NOWHERE;
-          return NS_OK;
-        }
-      }
-    }
-  }
-
-  *aVisualIndex = visualIndex;
-  return NS_OK;
-}
-
-nsresult nsBidi::GetLogicalIndex(int32_t aVisualIndex, int32_t *aLogicalIndex)
-{
-  if(aVisualIndex<0 || mLength<=aVisualIndex) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  /* we can do the trivial cases without the runs array */
-  if (mDirection == NSBIDI_LTR) {
-    *aLogicalIndex = aVisualIndex;
-    return NS_OK;
-  } else if (mDirection == NSBIDI_RTL) {
-    *aLogicalIndex = mLength - aVisualIndex - 1;
-    return NS_OK;
-  }
-
-  if(mRunCount<0 && !GetRuns()) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-
-  Run *runs=mRuns;
-  int32_t i, runCount=mRunCount, start;
-
-  if(runCount<=10) {
-    /* linear search for the run */
-    for(i=0; aVisualIndex>=runs[i].visualLimit; ++i) {}
-  } else {
-    /* binary search for the run */
-    int32_t start=0, limit=runCount;
-
-    /* the middle if() will guaranteed find the run, we don't need a loop limit */
-    for(;;) {
-      i=(start+limit)/2;
-      if(aVisualIndex>=runs[i].visualLimit) {
-        start=i+1;
-      } else if(i==0 || aVisualIndex>=runs[i-1].visualLimit) {
-        break;
-      } else {
-        limit=i;
-      }
-    }
-  }
-
-  start=runs[i].logicalStart;
-  if(IS_EVEN_RUN(start)) {
-    /* LTR */
-    /* the offset in runs[i] is aVisualIndex-runs[i-1].visualLimit */
-    if(i>0) {
-      aVisualIndex-=runs[i-1].visualLimit;
-    }
-    *aLogicalIndex = GET_INDEX(start)+aVisualIndex;
-    return NS_OK;
-  } else {
-    /* RTL */
-    *aLogicalIndex = GET_INDEX(start)+runs[i].visualLimit-aVisualIndex-1;
-    return NS_OK;
-  }
-}
-
-nsresult nsBidi::GetLogicalMap(int32_t *aIndexMap)
-{
-  nsresult rv;
-
-  /* CountRuns() checks for VALID_PARA_OR_LINE */
-  rv = CountRuns(nullptr);
-  if(NS_FAILED(rv)) {
-    return rv;
-  } else if(aIndexMap==nullptr) {
-    return NS_ERROR_INVALID_ARG;
-  } else {
-    /* fill a logical-to-visual index map using the runs[] */
-    int32_t visualStart, visualLimit, j;
-    int32_t logicalStart;
-    Run *runs = mRuns;
-    if (mLength <= 0) {
-      return NS_OK;
-    }
-
-    visualStart = 0;
-    for (j = 0; j < mRunCount; ++j) {
-      logicalStart = GET_INDEX(runs[j].logicalStart);
-      visualLimit = runs[j].visualLimit;
-      if (IS_EVEN_RUN(runs[j].logicalStart)) {
-        do { /* LTR */
-          aIndexMap[logicalStart++] = visualStart++;
-        } while (visualStart < visualLimit);
-      } else {
-        logicalStart += visualLimit-visualStart;  /* logicalLimit */
-        do { /* RTL */
-          aIndexMap[--logicalStart] = visualStart++;
-        } while (visualStart < visualLimit);
-      }
-      /* visualStart==visualLimit; */
-    }
-  }
-  return NS_OK;
-}
-
-nsresult nsBidi::GetVisualMap(int32_t *aIndexMap)
-{
-  int32_t* runCount=nullptr;
-  nsresult rv;
-
-  if(aIndexMap==nullptr) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  /* CountRuns() checks all of its and our arguments */
-  rv = CountRuns(runCount);
-  if(NS_FAILED(rv)) {
-    return rv;
-  } else {
-    /* fill a visual-to-logical index map using the runs[] */
-    Run *runs=mRuns, *runsLimit=runs+mRunCount;
-    int32_t logicalStart, visualStart, visualLimit;
-
-    visualStart=0;
-    for(; runs<runsLimit; ++runs) {
-      logicalStart=runs->logicalStart;
-      visualLimit=runs->visualLimit;
-      if(IS_EVEN_RUN(logicalStart)) {
-        do { /* LTR */
-          *aIndexMap++ = logicalStart++;
-        } while(++visualStart<visualLimit);
-      } else {
-        REMOVE_ODD_BIT(logicalStart);
-        logicalStart+=visualLimit-visualStart;  /* logicalLimit */
-        do { /* RTL */
-          *aIndexMap++ = --logicalStart;
-        } while(++visualStart<visualLimit);
-      }
-      /* visualStart==visualLimit; */
-    }
-    return NS_OK;
-  }
-}
-
-/* reorder a line based on a levels array (L2) ------------------------------ */
-
-nsresult nsBidi::ReorderLogical(const nsBidiLevel *aLevels, int32_t aLength, int32_t *aIndexMap)
-{
-  int32_t start, limit, sumOfSosEos;
-  nsBidiLevel minLevel, maxLevel;
-
-  if(aIndexMap==nullptr ||
-     !PrepareReorder(aLevels, aLength, aIndexMap, &minLevel, &maxLevel)) {
-    return NS_OK;
-  }
-
-  /* nothing to do? */
-  if(minLevel==maxLevel && (minLevel&1)==0) {
-    return NS_OK;
-  }
-
-  /* reorder only down to the lowest odd level */
-  minLevel|=1;
-
-  /* loop maxLevel..minLevel */
-  do {
-    start=0;
-
-    /* loop for all sequences of levels to reorder at the current maxLevel */
-    for(;;) {
-      /* look for a sequence of levels that are all at >=maxLevel */
-      /* look for the first index of such a sequence */
-      while(start<aLength && aLevels[start]<maxLevel) {
-        ++start;
-      }
-      if(start>=aLength) {
-        break;  /* no more such sequences */
-      }
-
-      /* look for the limit of such a sequence (the index behind it) */
-      for(limit=start; ++limit<aLength && aLevels[limit]>=maxLevel;) {}
-
-      /*
-       * sos=start of sequence, eos=end of sequence
-       *
-       * The closed (inclusive) interval from sos to eos includes all the logical
-       * and visual indexes within this sequence. They are logically and
-       * visually contiguous and in the same range.
-       *
-       * For each run, the new visual index=sos+eos-old visual index;
-       * we pre-add sos+eos into sumOfSosEos ->
-       * new visual index=sumOfSosEos-old visual index;
-       */
-      sumOfSosEos=start+limit-1;
-
-      /* reorder each index in the sequence */
-      do {
-        aIndexMap[start]=sumOfSosEos-aIndexMap[start];
-      } while(++start<limit);
-
-      /* start==limit */
-      if(limit==aLength) {
-        break;  /* no more such sequences */
-      } else {
-        start=limit+1;
-      }
-    }
-  } while(--maxLevel>=minLevel);
-
-  return NS_OK;
-}
-
-nsresult nsBidi::InvertMap(const int32_t *aSrcMap, int32_t *aDestMap, int32_t aLength)
-{
-  if(aSrcMap!=nullptr && aDestMap!=nullptr && aLength > 0) {
-    const int32_t *pi;
-    int32_t destLength = -1, count = 0;
-    /* find highest value and count positive indexes in srcMap */
-    pi = aSrcMap + aLength;
-    while (pi > aSrcMap) {
-      if (*--pi > destLength) {
-        destLength = *pi;
-      }
-      if (*pi >= 0) {
-        count++;
-      }
-    }
-    destLength++;  /* add 1 for origin 0 */
-    if (count < destLength) {
-      /* we must fill unmatched destMap entries with -1 */
-      memset(aDestMap, 0xFF, destLength * sizeof(int32_t));
-    }
-    pi = aSrcMap + aLength;
-    while (aLength > 0) {
-      if (*--pi >= 0) {
-        aDestMap[*pi] = --aLength;
-      } else {
-        --aLength;
-      }
-    }
-  }
-  return NS_OK;
-}
-
-int32_t nsBidi::doWriteReverse(const char16_t *src, int32_t srcLength,
-                               char16_t *dest, uint16_t options) {
-  /*
-   * RTL run -
-   *
-   * RTL runs need to be copied to the destination in reverse order
-   * of code points, not code units, to keep Unicode characters intact.
-   *
-   * The general strategy for this is to read the source text
-   * in backward order, collect all code units for a code point
-   * (and optionally following combining characters, see below),
-   * and copy all these code units in ascending order
-   * to the destination for this run.
-   *
-   * Several options request whether combining characters
-   * should be kept after their base characters,
-   * whether Bidi control characters should be removed, and
-   * whether characters should be replaced by their mirror-image
-   * equivalent Unicode characters.
-   */
-  int32_t i, j, destSize;
-  uint32_t c;
-
-  /* optimize for several combinations of options */
-  switch(options&(NSBIDI_REMOVE_BIDI_CONTROLS|NSBIDI_DO_MIRRORING|NSBIDI_KEEP_BASE_COMBINING)) {
-    case 0:
-        /*
-         * With none of the "complicated" options set, the destination
-         * run will have the same length as the source run,
-         * and there is no mirroring and no keeping combining characters
-         * with their base characters.
-         */
-      destSize=srcLength;
-
-    /* preserve character integrity */
-      do {
-      /* i is always after the last code unit known to need to be kept in this segment */
-        i=srcLength;
-
-      /* collect code units for one base character */
-        UTF_BACK_1(src, 0, srcLength);
-
-      /* copy this base character */
-        j=srcLength;
-        do {
-          *dest++=src[j++];
-        } while(j<i);
-      } while(srcLength>0);
-      break;
-    case NSBIDI_KEEP_BASE_COMBINING:
-    /*
-         * Here, too, the destination
-         * run will have the same length as the source run,
-         * and there is no mirroring.
-         * We do need to keep combining characters with their base characters.
-         */
-      destSize=srcLength;
-
-    /* preserve character integrity */
-      do {
-      /* i is always after the last code unit known to need to be kept in this segment */
-        i=srcLength;
-
-      /* collect code units and modifier letters for one base character */
-        do {
-          UTF_PREV_CHAR(src, 0, srcLength, c);
-        } while(srcLength>0 && GetBidiCat(c) == eCharType_DirNonSpacingMark);
-
-      /* copy this "user character" */
-        j=srcLength;
-        do {
-          *dest++=src[j++];
-        } while(j<i);
-      } while(srcLength>0);
-      break;
-    default:
-    /*
-         * With several "complicated" options set, this is the most
-         * general and the slowest copying of an RTL run.
-         * We will do mirroring, remove Bidi controls, and
-         * keep combining characters with their base characters
-         * as requested.
-         */
-      if(!(options&NSBIDI_REMOVE_BIDI_CONTROLS)) {
-        i=srcLength;
-      } else {
-      /* we need to find out the destination length of the run,
-               which will not include the Bidi control characters */
-        int32_t length=srcLength;
-        char16_t ch;
-
-        i=0;
-        do {
-          ch=*src++;
-          if (!IsBidiControl((uint32_t)ch)) {
-            ++i;
-          }
-        } while(--length>0);
-        src-=srcLength;
-      }
-      destSize=i;
-
-    /* preserve character integrity */
-      do {
-      /* i is always after the last code unit known to need to be kept in this segment */
-        i=srcLength;
-
-      /* collect code units for one base character */
-        UTF_PREV_CHAR(src, 0, srcLength, c);
-        if(options&NSBIDI_KEEP_BASE_COMBINING) {
-        /* collect modifier letters for this base character */
-          while(srcLength>0 && GetBidiCat(c) == eCharType_DirNonSpacingMark) {
-            UTF_PREV_CHAR(src, 0, srcLength, c);
-          }
-        }
-
-        if(options&NSBIDI_REMOVE_BIDI_CONTROLS && IsBidiControl(c)) {
-        /* do not copy this Bidi control character */
-          continue;
-        }
-
-      /* copy this "user character" */
-        j=srcLength;
-        if(options&NSBIDI_DO_MIRRORING) {
-          /* mirror only the base character */
-          c = GetMirroredChar(c);
-
-          int32_t k=0;
-          UTF_APPEND_CHAR_UNSAFE(dest, k, c);
-          dest+=k;
-          j+=k;
-        }
-        while(j<i) {
-          *dest++=src[j++];
-        }
-      } while(srcLength>0);
-      break;
-  } /* end of switch */
-  return destSize;
-}
-
-nsresult nsBidi::WriteReverse(const char16_t *aSrc, int32_t aSrcLength, char16_t *aDest, uint16_t aOptions, int32_t *aDestSize)
-{
-  if( aSrc==nullptr || aSrcLength<0 ||
-      aDest==nullptr
-    ) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  /* do input and output overlap? */
-  if( aSrc>=aDest && aSrc<aDest+aSrcLength ||
-      aDest>=aSrc && aDest<aSrc+aSrcLength
-    ) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  if(aSrcLength>0) {
-    *aDestSize = doWriteReverse(aSrc, aSrcLength, aDest, aOptions);
-  }
-  return NS_OK;
-}
-#endif // FULL_BIDI_ENGINE
--- a/layout/base/nsBidi.h
+++ b/layout/base/nsBidi.h
@@ -44,24 +44,16 @@
  */
 
 /**
  * nsBidiLevel is the type of the level values in this
  * Bidi implementation.
  * It holds an embedding level and indicates the visual direction
  * by its bit 0 (even/odd value).<p>
  *
- * It can also hold non-level values for the
- * <code>aParaLevel</code> and <code>aEmbeddingLevels</code>
- * arguments of <code>SetPara</code>; there:
- * <ul>
- * <li>bit 7 of an <code>aEmbeddingLevels[]</code>
- * value indicates whether the using application is
- * specifying the level of a character to <i>override</i> whatever the
- * Bidi implementation would resolve it to.</li>
  * <li><code>aParaLevel</code> can be set to the
  * pseudo-level values <code>NSBIDI_DEFAULT_LTR</code>
  * and <code>NSBIDI_DEFAULT_RTL</code>.</li></ul>
  *
  * @see nsBidi::SetPara
  *
  * <p>The related constants are not real, valid level values.
  * <code>NSBIDI_DEFAULT_XXX</code> can be used to specify
@@ -122,74 +114,38 @@ enum nsBidiDirection {
   /** All left-to-right text This is a 0 value. */
   NSBIDI_LTR,
   /** All right-to-left text This is a 1 value. */
   NSBIDI_RTL,
   /** Mixed-directional text. */
   NSBIDI_MIXED
 };
 
-typedef enum nsBidiDirection nsBidiDirection;
-
 /* miscellaneous definitions ------------------------------------------------ */
-/** option flags for WriteReverse() */
-/**
- * option bit for WriteReverse():
- * keep combining characters after their base characters in RTL runs
- *
- * @see WriteReverse
- */
-#define NSBIDI_KEEP_BASE_COMBINING       1
-
-/**
- * option bit for WriteReverse():
- * replace characters with the "mirrored" property in RTL runs
- * by their mirror-image mappings
- *
- * @see WriteReverse
- */
-#define NSBIDI_DO_MIRRORING              2
-
-/**
- * option bit for WriteReverse():
- * remove Bidi control characters
- *
- * @see WriteReverse
- */
-#define NSBIDI_REMOVE_BIDI_CONTROLS      8
 
 /* helper macros for each allocated array member */
-#define GETDIRPROPSMEMORY(length) \
-                                  GetMemory((void **)&mDirPropsMemory, &mDirPropsSize, \
-                                  mMayAllocateText, (length))
+#define GETDIRPROPSMEMORY(length) nsBidi::GetMemory((void **)&mDirPropsMemory, \
+                                                    &mDirPropsSize, \
+                                                    (length))
 
-#define GETLEVELSMEMORY(length) \
-                                GetMemory((void **)&mLevelsMemory, &mLevelsSize, \
-                                mMayAllocateText, (length))
-
-#define GETRUNSMEMORY(length) \
-                              GetMemory((void **)&mRunsMemory, &mRunsSize, \
-                              mMayAllocateRuns, (length)*sizeof(Run))
+#define GETLEVELSMEMORY(length) nsBidi::GetMemory((void **)&mLevelsMemory, \
+                                                  &mLevelsSize, \
+                                                  (length))
 
-/* additional macros used by constructor - always allow allocation */
-#define GETINITIALDIRPROPSMEMORY(length) \
-                                         GetMemory((void **)&mDirPropsMemory, &mDirPropsSize, \
-                                         true, (length))
+#define GETRUNSMEMORY(length) nsBidi::GetMemory((void **)&mRunsMemory, \
+                                                &mRunsSize, \
+                                                (length)*sizeof(Run))
 
-#define GETINITIALLEVELSMEMORY(length) \
-                                       GetMemory((void **)&mLevelsMemory, &mLevelsSize, \
-                                       true, (length))
+#define GETISOLATESMEMORY(length) nsBidi::GetMemory((void **)&mIsolatesMemory, \
+                                                    &mIsolatesSize, \
+                                                    (length)*sizeof(Isolate))
 
-#define GETINITIALRUNSMEMORY(length) \
-                                     GetMemory((void **)&mRunsMemory, &mRunsSize, \
-                                     true, (length)*sizeof(Run))
-
-#define GETINITIALISOLATESMEMORY(length) \
-                                     GetMemory((void **)&mIsolatesMemory, &mIsolatesSize, \
-                                     true, (length)*sizeof(Isolate))
+#define GETOPENINGSMEMORY(length) nsBidi::GetMemory((void **)&mOpeningsMemory, \
+                                                    &mOpeningsSize, \
+                                                    (length)*sizeof(Opening))
 
 /*
  * Sometimes, bit values are more appropriate
  * to deal with directionality properties.
  * Abbreviations in these macro names refer to names
  * used in the Bidi algorithm.
  */
 typedef uint8_t DirProp;
@@ -229,33 +185,27 @@ typedef uint8_t DirProp;
 #define MASK_EMBEDDING (DIRPROP_FLAG(NSM)|MASK_POSSIBLE_N)
 
 /* the dirProp's L and R are defined to 0 and 1 values in nsCharType */
 #define GET_LR_FROM_LEVEL(level) ((DirProp)((level)&1))
 
 #define IS_DEFAULT_LEVEL(level) (((level)&0xfe)==0xfe)
 
 /*
- * The following bit is ORed to the property of directional control
- * characters which are ignored: unmatched PDF or PDI; LRx, RLx or FSI
- * which would exceed the maximum explicit bidi level.
- */
-#define IGNORE_CC 0x40
-
-#define PURE_DIRPROP(prop) ((prop)&~IGNORE_CC)
-
-/*
  * The following bit is used for the directional isolate status.
  * Stack entries corresponding to isolate sequences are greater than ISOLATE.
  */
 #define ISOLATE 0x0100
 
 /* number of isolate entries allocated initially without malloc */
 #define SIMPLE_ISOLATES_SIZE 5
 
+/* number of isolate run entries for paired brackets allocated initially without malloc */
+#define SIMPLE_OPENINGS_COUNT 8
+
 /* handle surrogate pairs --------------------------------------------------- */
 
 #define IS_FIRST_SURROGATE(uchar) (((uchar)&0xfc00)==0xd800)
 #define IS_SECOND_SURROGATE(uchar) (((uchar)&0xfc00)==0xdc00)
 
 /* get the UTF-32 value directly from the surrogate pseudo-characters */
 #define SURROGATE_OFFSET ((0xd800<<10UL)+0xdc00-0x10000)
 #define GET_UTF_32(first, second) (((first)<<10UL)+(second)-SURROGATE_OFFSET)
@@ -385,16 +335,42 @@ typedef uint8_t DirProp;
 #define UTF_APPEND_CHAR(s, i, length, c)             UTF_APPEND_CHAR_SAFE(s, i, length, c)
 
 struct Isolate {
   int32_t start1;
   int16_t stateImp;
   int16_t state;
 };
 
+// For bracket matching
+
+#define FOUND_L DIRPROP_FLAG(L)
+#define FOUND_R DIRPROP_FLAG(R)
+
+struct Opening {
+  int32_t position;                   /* position of opening bracket */
+  int32_t match;                      /* matching char or -position of closing bracket */
+  int32_t contextPos;                 /* position of last strong char found before opening */
+  uint16_t flags;                     /* bits for L or R/AL found within the pair */
+  DirProp contextDir;                 /* L or R according to last strong char before opening */
+  uint8_t filler;                     /* to complete a nice multiple of 4 chars */
+};
+
+struct IsoRun {
+  int32_t  contextPos;                /* position of char determining context */
+  uint16_t start;                     /* index of first opening entry for this run */
+  uint16_t limit;                     /* index after last opening entry for this run */
+  nsBidiLevel level;                  /* level of this run */
+  DirProp lastStrong;                 /* bidi class of last strong char found in this run */
+  DirProp lastBase;                   /* bidi class of last base char found in this run */
+  DirProp contextDir;                 /* L or R to use as context for following openings */
+};
+
+class nsBidi;
+
 /* Run structure for reordering --------------------------------------------- */
 
 typedef struct Run {
   int32_t logicalStart;  /* first character of the run; b31 indicates even/odd level */
   int32_t visualLimit;   /* last visual position of the run +1 */
 } Run;
 
 /* in a Run, logicalStart will get this bit set if the run level is odd */
@@ -500,38 +476,18 @@ public:
    *      If the function shall determine the paragraph level from the text,
    *      then <code>aParaLevel</code> can be set to
    *      either <code>NSBIDI_DEFAULT_LTR</code>
    *      or <code>NSBIDI_DEFAULT_RTL</code>;
    *      if there is no strongly typed character, then
    *      the desired default is used (0 for LTR or 1 for RTL).
    *      Any other value between 0 and <code>NSBIDI_MAX_EXPLICIT_LEVEL</code> is also valid,
    *      with odd levels indicating RTL.
-   *
-   * @param aEmbeddingLevels (in) may be used to preset the embedding and override levels,
-   *      ignoring characters like LRE and PDF in the text.
-   *      A level overrides the directional property of its corresponding
-   *      (same index) character if the level has the
-   *      <code>NSBIDI_LEVEL_OVERRIDE</code> bit set.<p>
-   *      Except for that bit, it must be
-   *      <code>aParaLevel<=aEmbeddingLevels[]<=NSBIDI_MAX_EXPLICIT_LEVEL</code>.<p>
-   *      <strong>Caution: </strong>A copy of this pointer, not of the levels,
-   *      will be stored in the <code>nsBidi</code> object;
-   *      the <code>aEmbeddingLevels</code> array must not be
-   *      deallocated before the <code>nsBidi</code> object is destroyed or reused,
-   *      and the <code>aEmbeddingLevels</code>
-   *      should not be modified to avoid unexpected results on subsequent Bidi operations.
-   *      However, the <code>SetPara</code> and
-   *      <code>SetLine</code> functions may modify some or all of the levels.<p>
-   *      After the <code>nsBidi</code> object is reused or destroyed, the caller
-   *      must take care of the deallocation of the <code>aEmbeddingLevels</code> array.<p>
-   *      <strong>The <code>aEmbeddingLevels</code> array must be
-   *      at least <code>aLength</code> long.</strong>
    */
-  nsresult SetPara(const char16_t *aText, int32_t aLength, nsBidiLevel aParaLevel, nsBidiLevel *aEmbeddingLevels);
+  nsresult SetPara(const char16_t *aText, int32_t aLength, nsBidiLevel aParaLevel);
 
   /**
    * Get the directionality of the text.
    *
    * @param aDirection receives a <code>NSBIDI_XXX</code> value that indicates if the entire text
    *       represented by this object is unidirectional,
    *       and which direction, or if it is mixed-directional.
    *
@@ -543,83 +499,16 @@ public:
    * Get the paragraph level of the text.
    *
    * @param aParaLevel receives a <code>NSBIDI_XXX</code> value indicating the paragraph level
    *
    * @see nsBidiLevel
    */
   nsresult GetParaLevel(nsBidiLevel* aParaLevel);
 
-#ifdef FULL_BIDI_ENGINE
-  /**
-   * <code>SetLine</code> sets an <code>nsBidi</code> to
-   * contain the reordering information, especially the resolved levels,
-   * for all the characters in a line of text. This line of text is
-   * specified by referring to an <code>nsBidi</code> object representing
-   * this information for a paragraph of text, and by specifying
-   * a range of indexes in this paragraph.<p>
-   * In the new line object, the indexes will range from 0 to <code>aLimit-aStart</code>.<p>
-   *
-   * This is used after calling <code>SetPara</code>
-   * for a paragraph, and after line-breaking on that paragraph.
-   * It is not necessary if the paragraph is treated as a single line.<p>
-   *
-   * After line-breaking, rules (L1) and (L2) for the treatment of
-   * trailing WS and for reordering are performed on
-   * an <code>nsBidi</code> object that represents a line.<p>
-   *
-   * <strong>Important:</strong> the line <code>nsBidi</code> object shares data with
-   * <code>aParaBidi</code>.
-   * You must destroy or reuse this object before <code>aParaBidi</code>.
-   * In other words, you must destroy or reuse the <code>nsBidi</code> object for a line
-   * before the object for its parent paragraph.
-   *
-   * @param aParaBidi is the parent paragraph object.
-   *
-   * @param aStart is the line's first index into the paragraph text.
-   *
-   * @param aLimit is just behind the line's last index into the paragraph text
-   *      (its last index +1).<br>
-   *      It must be <code>0<=aStart<=aLimit<=</code>paragraph length.
-   *
-   * @see SetPara
-   */
-  nsresult SetLine(const nsBidi* aParaBidi, int32_t aStart, int32_t aLimit);
-
-  /**
-   * Get the length of the text.
-   *
-   * @param aLength receives the length of the text that the nsBidi object was created for.
-   */
-  nsresult GetLength(int32_t* aLength);
-
-  /**
-   * Get the level for one character.
-   *
-   * @param aCharIndex the index of a character.
-   *
-   * @param aLevel receives the level for the character at aCharIndex.
-   *
-   * @see nsBidiLevel
-   */
-  nsresult GetLevelAt(int32_t aCharIndex,  nsBidiLevel* aLevel);
-
-  /**
-   * Get an array of levels for each character.<p>
-   *
-   * Note that this function may allocate memory under some
-   * circumstances, unlike <code>GetLevelAt</code>.
-   *
-   * @param aLevels receives a pointer to the levels array for the text,
-   *       or <code>nullptr</code> if an error occurs.
-   *
-   * @see nsBidiLevel
-   */
-  nsresult GetLevels(nsBidiLevel** aLevels);
-#endif // FULL_BIDI_ENGINE
   /**
    * Get the bidirectional type for one character.
    *
    * @param aCharIndex the index of a character.
    *
    * @param aType receives the bidirectional type of the character at aCharIndex.
    */
   nsresult GetCharTypeAt(int32_t aCharIndex,  nsCharType* aType);
@@ -702,102 +591,16 @@ public:
    * @endcode
    *
    * Note that in right-to-left runs, code like this places
    * modifier letters before base characters and second surrogates
    * before first ones.
    */
   nsresult GetVisualRun(int32_t aRunIndex, int32_t* aLogicalStart, int32_t* aLength, nsBidiDirection* aDirection);
 
-#ifdef FULL_BIDI_ENGINE
-  /**
-   * Get the visual position from a logical text position.
-   * If such a mapping is used many times on the same
-   * <code>nsBidi</code> object, then calling
-   * <code>GetLogicalMap</code> is more efficient.<p>
-   *
-   * Note that in right-to-left runs, this mapping places
-   * modifier letters before base characters and second surrogates
-   * before first ones.
-   *
-   * @param aLogicalIndex is the index of a character in the text.
-   *
-   * @param aVisualIndex will receive the visual position of this character.
-   *
-   * @see GetLogicalMap
-   * @see GetLogicalIndex
-   */
-  nsresult GetVisualIndex(int32_t aLogicalIndex, int32_t* aVisualIndex);
-
-  /**
-   * Get the logical text position from a visual position.
-   * If such a mapping is used many times on the same
-   * <code>nsBidi</code> object, then calling
-   * <code>GetVisualMap</code> is more efficient.<p>
-   *
-   * This is the inverse function to <code>GetVisualIndex</code>.
-   *
-   * @param aVisualIndex is the visual position of a character.
-   *
-   * @param aLogicalIndex will receive the index of this character in the text.
-   *
-   * @see GetVisualMap
-   * @see GetVisualIndex
-   */
-  nsresult GetLogicalIndex(int32_t aVisualIndex, int32_t* aLogicalIndex);
-
-  /**
-   * Get a logical-to-visual index map (array) for the characters in the nsBidi
-   * (paragraph or line) object.
-   *
-   * @param aIndexMap is a pointer to an array of <code>GetLength</code>
-   *      indexes which will reflect the reordering of the characters.
-   *      The array does not need to be initialized.<p>
-   *      The index map will result in <code>aIndexMap[aLogicalIndex]==aVisualIndex</code>.<p>
-   *
-   * @see GetVisualMap
-   * @see GetVisualIndex
-   */
-  nsresult GetLogicalMap(int32_t *aIndexMap);
-
-  /**
-   * Get a visual-to-logical index map (array) for the characters in the nsBidi
-   * (paragraph or line) object.
-   *
-   * @param aIndexMap is a pointer to an array of <code>GetLength</code>
-   *      indexes which will reflect the reordering of the characters.
-   *      The array does not need to be initialized.<p>
-   *      The index map will result in <code>aIndexMap[aVisualIndex]==aLogicalIndex</code>.<p>
-   *
-   * @see GetLogicalMap
-   * @see GetLogicalIndex
-   */
-  nsresult GetVisualMap(int32_t *aIndexMap);
-
-  /**
-   * This is a convenience function that does not use a nsBidi object.
-   * It is intended to be used for when an application has determined the levels
-   * of objects (character sequences) and just needs to have them reordered (L2).
-   * This is equivalent to using <code>GetLogicalMap</code> on a
-   * <code>nsBidi</code> object.
-   *
-   * @param aLevels is an array with <code>aLength</code> levels that have been determined by
-   *      the application.
-   *
-   * @param aLength is the number of levels in the array, or, semantically,
-   *      the number of objects to be reordered.
-   *      It must be <code>aLength>0</code>.
-   *
-   * @param aIndexMap is a pointer to an array of <code>aLength</code>
-   *      indexes which will reflect the reordering of the characters.
-   *      The array does not need to be initialized.<p>
-   *      The index map will result in <code>aIndexMap[aLogicalIndex]==aVisualIndex</code>.
-   */
-  static nsresult ReorderLogical(const nsBidiLevel *aLevels, int32_t aLength, int32_t *aIndexMap);
-#endif // FULL_BIDI_ENGINE
   /**
    * This is a convenience function that does not use a nsBidi object.
    * It is intended to be used for when an application has determined the levels
    * of objects (character sequences) and just needs to have them reordered (L2).
    * This is equivalent to using <code>GetVisualMap</code> on a
    * <code>nsBidi</code> object.
    *
    * @param aLevels is an array with <code>aLength</code> levels that have been determined by
@@ -809,32 +612,16 @@ public:
    *
    * @param aIndexMap is a pointer to an array of <code>aLength</code>
    *      indexes which will reflect the reordering of the characters.
    *      The array does not need to be initialized.<p>
    *      The index map will result in <code>aIndexMap[aVisualIndex]==aLogicalIndex</code>.
    */
   static nsresult ReorderVisual(const nsBidiLevel *aLevels, int32_t aLength, int32_t *aIndexMap);
 
-#ifdef FULL_BIDI_ENGINE
-  /**
-   * Invert an index map.
-   * The one-to-one index mapping of the first map is inverted and written to
-   * the second one.
-   *
-   * @param aSrcMap is an array with <code>aLength</code> indexes
-   *      which define the original mapping.
-   *
-   * @param aDestMap is an array with <code>aLength</code> indexes
-   *      which will be filled with the inverse mapping.
-   *
-   * @param aLength is the length of each array.
-   */
-  nsresult InvertMap(const int32_t *aSrcMap, int32_t *aDestMap, int32_t aLength);
-#endif // FULL_BIDI_ENGINE
   /**
    * Reverse a Right-To-Left run of Unicode text.
    *
    * This function preserves the integrity of characters with multiple
    * code units and (optionally) modifier letters.
    * Characters can be replaced by mirror-image characters
    * in the destination buffer. Note that "real" mirroring has
    * to be done in a rendering engine by glyph selection
@@ -865,32 +652,68 @@ public:
    *
    * @param aDestSize will receive the number of characters that were written to <code>aDest</code>.
    */
   nsresult WriteReverse(const char16_t *aSrc, int32_t aSrcLength, char16_t *aDest, uint16_t aOptions, int32_t *aDestSize);
 
 protected:
   friend class nsBidiPresUtils;
 
+  class BracketData {
+  public:
+    explicit BracketData(const nsBidi* aBidi);
+    ~BracketData();
+
+    void ProcessBoundary(int32_t aLastDirControlCharPos,
+                         nsBidiLevel aContextLevel,
+                         nsBidiLevel aEmbeddingLevel,
+                         const DirProp* aDirProps);
+    void ProcessLRI_RLI(nsBidiLevel aLevel);
+    void ProcessPDI();
+    bool AddOpening(char16_t aMatch, int32_t aPosition);
+    void FixN0c(int32_t aOpeningIndex, int32_t aNewPropPosition,
+                DirProp aNewProp, DirProp* aDirProps);
+    DirProp ProcessClosing(int32_t aOpenIdx, int32_t aPosition,
+                           DirProp* aDirProps);
+    bool ProcessChar(int32_t aPosition, char16_t aCh, DirProp* aDirProps,
+                     nsBidiLevel* aLevels);
+
+  private:
+    // array of opening entries which should be enough in most cases;
+    // no malloc() needed
+    Opening  mSimpleOpenings[SIMPLE_OPENINGS_COUNT];
+    Opening* mOpenings;      // pointer to current array of entries,
+                             // either mSimpleOpenings or malloced array
+
+    Opening* mOpeningsMemory;
+    size_t   mOpeningsSize;
+
+    // array of nested isolated sequence entries; can never exceed
+    // UBIDI_MAX_EXPLICIT_LEVEL
+    //   + 1 for index 0
+    //   + 1 for before the first isolated sequence
+    IsoRun  mIsoRuns[NSBIDI_MAX_EXPLICIT_LEVEL+2];
+    int32_t mIsoRunLast;     // index of last used entry in mIsoRuns
+
+    int32_t mOpeningsCount;  // number of allocated entries in mOpenings
+  };
+
   /** length of the current text */
   int32_t mLength;
 
   /** memory sizes in bytes */
   size_t mDirPropsSize, mLevelsSize, mRunsSize;
   size_t mIsolatesSize;
 
   /** allocated memory */
   DirProp* mDirPropsMemory;
   nsBidiLevel* mLevelsMemory;
   Run* mRunsMemory;
   Isolate* mIsolatesMemory;
 
-  /** indicators for whether memory may be allocated after construction */
-  bool mMayAllocateText, mMayAllocateRuns;
-
   DirProp* mDirProps;
   nsBidiLevel* mLevels;
 
   /** the paragraph level */
   nsBidiLevel mParaLevel;
 
   /** flags is a bit set for which directional properties are in the text */
   Flags mFlags;
@@ -919,25 +742,23 @@ protected:
 
   /** for simple text, have a small stack (no malloc()) */
   Isolate mSimpleIsolates[SIMPLE_ISOLATES_SIZE];
 
 private:
 
   void Init();
 
-  bool GetMemory(void **aMemory, size_t* aSize, bool aMayAllocate, size_t aSizeNeeded);
+  static bool GetMemory(void **aMemory, size_t* aSize, size_t aSizeNeeded);
 
   void Free();
 
   void GetDirProps(const char16_t *aText);
 
-  void ResolveExplicitLevels(nsBidiDirection *aDirection);
-
-  nsresult CheckExplicitLevels(nsBidiDirection *aDirection);
+  void ResolveExplicitLevels(nsBidiDirection *aDirection, const char16_t *aText);
 
   nsBidiDirection DirectionFromFlags(Flags aFlags);
 
   void ProcessPropertySeq(LevState *pLevState, uint8_t _prop, int32_t start, int32_t limit);
 
   void ResolveImplicitLevels(int32_t aStart, int32_t aLimit, DirProp aSOR, DirProp aEOR);
 
   void AdjustWSLevels();
@@ -946,15 +767,11 @@ private:
 
   bool GetRuns();
 
   void GetSingleRun(nsBidiLevel aLevel);
 
   void ReorderLine(nsBidiLevel aMinLevel, nsBidiLevel aMaxLevel);
 
   static bool PrepareReorder(const nsBidiLevel *aLevels, int32_t aLength, int32_t *aIndexMap, nsBidiLevel *aMinLevel, nsBidiLevel *aMaxLevel);
-
-  int32_t doWriteReverse(const char16_t *src, int32_t srcLength,
-                         char16_t *dest, uint16_t options);
-
 };
 
 #endif // _nsBidi_h_
--- a/layout/base/nsBidiPresUtils.cpp
+++ b/layout/base/nsBidiPresUtils.cpp
@@ -203,17 +203,17 @@ struct BidiParagraphData {
   void EmptyBuffer()
   {
     mBuffer.SetLength(0);
   }
 
   nsresult SetPara()
   {
     return mBidiEngine->SetPara(mBuffer.get(), BufferLength(),
-                                mParaLevel, nullptr);
+                                mParaLevel);
   }
 
   /**
    * mParaLevel can be NSBIDI_DEFAULT_LTR as well as NSBIDI_LTR or NSBIDI_RTL.
    * GetParaLevel() returns the actual (resolved) paragraph level which is
    * always either NSBIDI_LTR or NSBIDI_RTL
    */
   nsBidiLevel GetParaLevel()
@@ -2007,17 +2007,17 @@ nsresult nsBidiPresUtils::ProcessText(co
                                       nsBidi*                aBidiEngine)
 {
   NS_ASSERTION((aPosResolve == nullptr) != (aPosResolveCount > 0), "Incorrect aPosResolve / aPosResolveCount arguments");
 
   int32_t runCount;
 
   nsAutoString textBuffer(aText, aLength);
 
-  nsresult rv = aBidiEngine->SetPara(aText, aLength, aBaseLevel, nullptr);
+  nsresult rv = aBidiEngine->SetPara(aText, aLength, aBaseLevel);
   if (NS_FAILED(rv))
     return rv;
 
   rv = aBidiEngine->CountRuns(&runCount);
   if (NS_FAILED(rv))
     return rv;
 
   nscoord xOffset = 0;
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -693,17 +693,17 @@ bool nsDisplayListBuilder::NeedToForceTr
 AnimatedGeometryRoot*
 nsDisplayListBuilder::WrapAGRForFrame(nsIFrame* aAnimatedGeometryRoot,
                                       AnimatedGeometryRoot* aParent /* = nullptr */)
 {
   MOZ_ASSERT(IsAnimatedGeometryRoot(aAnimatedGeometryRoot));
 
   AnimatedGeometryRoot* result = nullptr;
   if (!mFrameToAnimatedGeometryRootMap.Get(aAnimatedGeometryRoot, &result)) {
-    MOZ_ASSERT(aAnimatedGeometryRoot != RootReferenceFrame());
+    MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(RootReferenceFrame(), aAnimatedGeometryRoot));
     AnimatedGeometryRoot* parent = aParent;
     if (!parent) {
       nsIFrame* parentFrame = nsLayoutUtils::GetCrossDocParentFrame(aAnimatedGeometryRoot);
       if (parentFrame) {
         nsIFrame* parentAGRFrame = FindAnimatedGeometryRootFrameFor(parentFrame);
         parent = WrapAGRForFrame(parentAGRFrame);
       }
     }
@@ -712,17 +712,17 @@ nsDisplayListBuilder::WrapAGRForFrame(ns
   }
   MOZ_ASSERT(!aParent || result->mParentAGR == aParent);
   return result;
 }
 
 AnimatedGeometryRoot*
 nsDisplayListBuilder::FindAnimatedGeometryRootFor(nsIFrame* aFrame)
 {
-  if (!IsForPainting()) {
+  if (!IsPaintingToWindow()) {
     return &mRootAGR;
   }
   if (aFrame == mCurrentFrame) {
     return mCurrentAGR;
   }
   AnimatedGeometryRoot* result = nullptr;
   if (mFrameToAnimatedGeometryRootMap.Get(aFrame, &result)) {
     return result;
@@ -1086,18 +1086,26 @@ IsStickyFrameActive(nsDisplayListBuilder
 
   nsIScrollableFrame* sf = do_QueryFrame(parent);
   return sf->IsScrollingActive(aBuilder) && sf->GetScrolledFrame() == cursor;
 }
 
 bool
 nsDisplayListBuilder::IsAnimatedGeometryRoot(nsIFrame* aFrame, nsIFrame** aParent)
 {
-  if (aFrame == mReferenceFrame)
+  if (aFrame == mReferenceFrame) {
     return true;
+  }
+  if (!IsPaintingToWindow()) {
+    if (aParent) {
+      *aParent = nsLayoutUtils::GetCrossDocParentFrame(aFrame);
+    }
+    return false;
+  }
+
   if (nsLayoutUtils::IsPopup(aFrame))
     return true;
   if (ActiveLayerTracker::IsOffsetOrMarginStyleAnimated(aFrame))
     return true;
   if (!aFrame->GetParent() &&
       nsLayoutUtils::ViewportHasDisplayPort(aFrame->PresContext())) {
     // Viewport frames in a display port need to be animated geometry roots
     // for background-attachment:fixed elements.
@@ -1140,16 +1148,17 @@ nsDisplayListBuilder::IsAnimatedGeometry
     *aParent = parent;
   }
   return false;
 }
 
 nsIFrame*
 nsDisplayListBuilder::FindAnimatedGeometryRootFrameFor(nsIFrame* aFrame)
 {
+  MOZ_ASSERT(nsLayoutUtils::IsAncestorFrameCrossDoc(RootReferenceFrame(), aFrame));
   nsIFrame* cursor = aFrame;
   while (cursor != RootReferenceFrame()) {
     nsIFrame* next;
     if (IsAnimatedGeometryRoot(cursor, &next))
       return cursor;
     cursor = next;
   }
   return cursor;
@@ -4552,16 +4561,31 @@ nsDisplayResolution::nsDisplayResolution
 }
 
 #ifdef NS_BUILD_REFCNT_LOGGING
 nsDisplayResolution::~nsDisplayResolution() {
   MOZ_COUNT_DTOR(nsDisplayResolution);
 }
 #endif
 
+void
+nsDisplayResolution::HitTest(nsDisplayListBuilder* aBuilder,
+                             const nsRect& aRect,
+                             HitTestState* aState,
+                             nsTArray<nsIFrame*> *aOutFrames)
+{
+#if defined(MOZ_SINGLE_PROCESS_APZ)
+  nsIPresShell* presShell = mFrame->PresContext()->PresShell();
+  nsRect rect = aRect.RemoveResolution(presShell->ScaleToResolution() ? presShell->GetResolution () : 1.0f);
+  mList.HitTest(aBuilder, rect, aState, aOutFrames);
+#else
+  mList.HitTest(aBuilder, aRect, aState, aOutFrames);
+#endif // MOZ_SINGLE_PROCESS_APZ
+}
+
 already_AddRefed<Layer>
 nsDisplayResolution::BuildLayer(nsDisplayListBuilder* aBuilder,
                                 LayerManager* aManager,
                                 const ContainerLayerParameters& aContainerParameters) {
   nsIPresShell* presShell = mFrame->PresContext()->PresShell();
   ContainerLayerParameters containerParameters(
     presShell->GetResolution(), presShell->GetResolution(), nsIntPoint(),
     aContainerParameters);
@@ -4878,32 +4902,32 @@ nsDisplayTransform::nsDisplayTransform(n
   MOZ_COUNT_CTOR(nsDisplayTransform);
   MOZ_ASSERT(aFrame, "Must have a frame!");
   Init(aBuilder);
 }
 
 void
 nsDisplayTransform::SetReferenceFrameToAncestor(nsDisplayListBuilder* aBuilder)
 {
+  mAnimatedGeometryRootForChildren = mAnimatedGeometryRoot;
   if (mFrame == aBuilder->RootReferenceFrame()) {
     return;
   }
   nsIFrame *outerFrame = nsLayoutUtils::GetCrossDocParentFrame(mFrame);
   mReferenceFrame =
     aBuilder->FindReferenceFrameFor(outerFrame);
   mToReferenceFrame = mFrame->GetOffsetToCrossDoc(mReferenceFrame);
-  mAnimatedGeometryRootForChildren = mAnimatedGeometryRoot;
   if (nsLayoutUtils::IsFixedPosFrameInDisplayPort(mFrame)) {
     // This is an odd special case. If we are both IsFixedPosFrameInDisplayPort
     // and transformed that we are our own AGR parent.
     // We want our frame to be our AGR because FrameLayerBuilder uses our AGR to
     // determine if we are inside a fixed pos subtree. If we use the outer AGR
     // from outside the fixed pos subtree FLB can't tell that we are fixed pos.
     mAnimatedGeometryRoot = mAnimatedGeometryRootForChildren;
-  } else {
+  } else if (mAnimatedGeometryRoot->mParentAGR) {
     mAnimatedGeometryRoot = mAnimatedGeometryRoot->mParentAGR;
   }
   mVisibleRect = aBuilder->GetDirtyRect() + mToReferenceFrame;
 }
 
 void
 nsDisplayTransform::Init(nsDisplayListBuilder* aBuilder)
 {
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -3503,17 +3503,20 @@ protected:
  */
 class nsDisplayResolution : public nsDisplaySubDocument {
 public:
   nsDisplayResolution(nsDisplayListBuilder* aBuilder, nsIFrame* aFrame,
                       nsDisplayList* aList, uint32_t aFlags);
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayResolution();
 #endif
-
+  virtual void HitTest(nsDisplayListBuilder* aBuilder,
+                       const nsRect& aRect,
+                       HitTestState* aState,
+                       nsTArray<nsIFrame*> *aOutFrames) override;
   virtual already_AddRefed<Layer> BuildLayer(nsDisplayListBuilder* aBuilder,
                                              LayerManager* aManager,
                                              const ContainerLayerParameters& aContainerParameters) override;
   NS_DISPLAY_DECL_NAME("Resolution", TYPE_RESOLUTION)
 };
 
 /**
  * A display item used to represent sticky position elements. The contents
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -135,20 +135,20 @@ typedef struct CapturingContentInfo {
   // capture should only be allowed during a mousedown event
   bool mAllowed;
   bool mPointerLock;
   bool mRetargetToElement;
   bool mPreventDrag;
   mozilla::StaticRefPtr<nsIContent> mContent;
 } CapturingContentInfo;
 
-// 327d78a0-0680-4709-b209-1cf9578720e6
+// 5023beaa-0e54-4fc7-b9dc-0344dc4fb8be
 #define NS_IPRESSHELL_IID \
-{ 0x327d78a0, 0x0680, 0x4709, \
-  { 0xb2, 0x09, 0x1c, 0xf9, 0x57, 0x87, 0x20, 0xe6 } }
+{ 0x5023beaa, 0x0e54, 0x4fc7, \
+  { 0xb9, 0xdc, 0x03, 0x44, 0xdc, 0x4f, 0xb8, 0xbe } }
 
 // debug VerifyReflow flags
 #define VERIFY_REFLOW_ON                    0x01
 #define VERIFY_REFLOW_NOISY                 0x02
 #define VERIFY_REFLOW_ALL                   0x04
 #define VERIFY_REFLOW_DUMP_COMMANDS         0x08
 #define VERIFY_REFLOW_NOISY_RC              0x10
 #define VERIFY_REFLOW_REALLY_NOISY_RC       0x20
@@ -1411,16 +1411,17 @@ public:
    * resolution bounds are sane, and the resolution of this was
    * actually updated.
    *
    * The resolution defaults to 1.0.
    */
   virtual nsresult SetResolution(float aResolution) = 0;
   float GetResolution() { return mResolution.valueOr(1.0); }
   virtual float GetCumulativeResolution() = 0;
+  virtual float GetCumulativeScaleResolution() = 0;
 
   /**
    * Was the current resolution set by the user or just default initialized?
    */
   bool IsResolutionSet() { return mResolution.isSome(); }
 
   /**
    * Similar to SetResolution() but also increases the scale of the content
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -2106,17 +2106,21 @@ nsLayoutUtils::GetEventCoordinatesRelati
     nsIWidget* frameWidget = view->GetWidget();
     if (frameWidget && frameWidget == aWidget) {
       // Special case this cause it happens a lot.
       // This also fixes bug 664707, events in the extra-special case of select
       // dropdown popups that are transformed.
       nsPresContext* presContext = aFrame->PresContext();
       nsPoint pt(presContext->DevPixelsToAppUnits(aPoint.x),
                  presContext->DevPixelsToAppUnits(aPoint.y));
-      return pt - view->ViewToWidgetOffset();
+      pt = pt - view->ViewToWidgetOffset();
+#if defined(MOZ_SINGLE_PROCESS_APZ)
+      pt = pt.RemoveResolution(presContext->PresShell()->GetCumulativeScaleResolution());
+#endif // MOZ_SINGLE_PROCESS_APZ
+      return pt;
     }
   }
 
   /* If we walk up the frame tree and discover that any of the frames are
    * transformed, we need to do extra work to convert from the global
    * space to the local space.
    */
   nsIFrame* rootFrame = aFrame;
@@ -2141,16 +2145,22 @@ nsLayoutUtils::GetEventCoordinatesRelati
     return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
   }
 
   // Convert from root document app units to app units of the document aFrame
   // is in.
   int32_t rootAPD = rootFrame->PresContext()->AppUnitsPerDevPixel();
   int32_t localAPD = aFrame->PresContext()->AppUnitsPerDevPixel();
   widgetToView = widgetToView.ScaleToOtherAppUnits(rootAPD, localAPD);
+#if defined(MOZ_SINGLE_PROCESS_APZ)
+  nsIPresShell* shell = aFrame->PresContext()->PresShell();
+
+  // XXX Bug 1224748 - Update nsLayoutUtils functions to correctly handle nsPresShell resolution
+  widgetToView = widgetToView.RemoveResolution(shell->GetCumulativeScaleResolution());
+#endif
 
   /* If we encountered a transform, we can't do simple arithmetic to figure
    * out how to convert back to aFrame's coordinates and must use the CTM.
    */
   if (transformFound || aFrame->IsSVGText()) {
     return TransformRootPointToFrame(aFrame, widgetToView);
   }
 
@@ -2888,18 +2898,22 @@ nsLayoutUtils::TranslateViewToWidget(nsP
                                      nsIWidget* aWidget)
 {
   nsPoint viewOffset;
   nsIWidget* viewWidget = aView->GetNearestWidget(&viewOffset);
   if (!viewWidget) {
     return LayoutDeviceIntPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
   }
 
-  LayoutDeviceIntPoint relativeToViewWidget(aPresContext->AppUnitsToDevPixels(aPt.x + viewOffset.x),
-                                            aPresContext->AppUnitsToDevPixels(aPt.y + viewOffset.y));
+  nsPoint pt = aPt + viewOffset;
+#if defined(MOZ_SINGLE_PROCESS_APZ)
+  pt = pt.ApplyResolution(aPresContext->PresShell()->GetCumulativeScaleResolution());
+#endif // MOZ_SINGLE_PROCESS_APZ
+  LayoutDeviceIntPoint relativeToViewWidget(aPresContext->AppUnitsToDevPixels(pt.x),
+                                            aPresContext->AppUnitsToDevPixels(pt.y));
   return relativeToViewWidget + WidgetToWidgetOffset(viewWidget, aWidget);
 }
 
 // Combine aNewBreakType with aOrigBreakType, but limit the break types
 // to NS_STYLE_CLEAR_LEFT, RIGHT, BOTH.
 uint8_t
 nsLayoutUtils::CombineBreakType(uint8_t aOrigBreakType,
                                 uint8_t aNewBreakType)
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -1079,16 +1079,30 @@ PresShell::Destroy()
   // dump out cumulative text perf metrics
   gfxTextPerfMetrics* tp;
   if (mPresContext && (tp = mPresContext->GetTextPerfMetrics())) {
     tp->Accumulate();
     if (tp->cumulative.numChars > 0) {
       LogTextPerfStats(tp, this, tp->cumulative, 0.0, eLog_totals, nullptr);
     }
   }
+  if (mPresContext) {
+    gfxUserFontSet* fs = mPresContext->GetUserFontSet();
+    if (fs) {
+      uint32_t fontCount;
+      uint64_t fontSize;
+      fs->GetLoadStatistics(fontCount, fontSize);
+      Telemetry::Accumulate(Telemetry::WEBFONT_PER_PAGE, fontCount);
+      Telemetry::Accumulate(Telemetry::WEBFONT_SIZE_PER_PAGE,
+                            uint32_t(fontSize/1024));
+    } else {
+      Telemetry::Accumulate(Telemetry::WEBFONT_PER_PAGE, 0);
+      Telemetry::Accumulate(Telemetry::WEBFONT_SIZE_PER_PAGE, 0);
+    }
+  }
 
 #ifdef MOZ_REFLOW_PERF
   DumpReflows();
   if (mReflowCountMgr) {
     delete mReflowCountMgr;
     mReflowCountMgr = nullptr;
   }
 #endif
@@ -5332,16 +5346,32 @@ float PresShell::GetCumulativeResolution
   float resolution = GetResolution();
   nsPresContext* parentCtx = GetPresContext()->GetParentPresContext();
   if (parentCtx) {
     resolution *= parentCtx->PresShell()->GetCumulativeResolution();
   }
   return resolution;
 }
 
+float PresShell::GetCumulativeScaleResolution()
+{
+  float resolution = 1.0;
+  nsIPresShell* currentShell = this;
+  while (currentShell) {
+    resolution *=  currentShell->ScaleToResolution() ? currentShell->GetResolution() : 1.0f;
+    nsPresContext* parentCtx = currentShell->GetPresContext()->GetParentPresContext();
+    if (parentCtx) {
+      currentShell = parentCtx->PresShell();
+    } else {
+      currentShell = nullptr;
+    }
+  }
+  return resolution;
+}
+
 void PresShell::SetRenderingState(const RenderingState& aState)
 {
   if (mRenderFlags != aState.mRenderFlags) {
     // Rendering state changed in a way that forces us to flush any
     // retained layers we might already have.
     LayerManager* manager = GetLayerManager();
     if (manager) {
       FrameLayerBuilder::InvalidateAllLayers(manager);
@@ -7078,18 +7108,19 @@ PresShell::HandleEvent(nsIFrame* aFrame,
       retargetEventDoc = window->GetExtantDoc();
       if (!retargetEventDoc)
         return NS_OK;
     } else if (capturingContent) {
       // if the mouse is being captured then retarget the mouse event at the
       // document that is being captured.
       retargetEventDoc = capturingContent->GetCrossShadowCurrentDoc();
 #ifdef ANDROID
-    } else if (aEvent->mClass == eTouchEventClass ||
-              (aEvent->AsMouseEvent() && aEvent->AsMouseEvent()->inputSource == nsIDOMMouseEvent::MOZ_SOURCE_TOUCH)) {
+    } else if ((aEvent->mClass == eTouchEventClass) ||
+               (aEvent->mClass == eMouseEventClass) ||
+               (aEvent->mClass == eWheelEventClass)) {
       retargetEventDoc = GetTouchEventTargetDocument();
 #endif
     }
 
     if (retargetEventDoc) {
       nsCOMPtr<nsIPresShell> presShell = retargetEventDoc->GetShell();
       if (!presShell)
         return NS_OK;
--- a/layout/base/nsPresShell.h
+++ b/layout/base/nsPresShell.h
@@ -213,16 +213,17 @@ public:
   virtual nsresult SetResolution(float aResolution) override {
     return SetResolutionImpl(aResolution, /* aScaleToResolution = */ false);
   }
   virtual nsresult SetResolutionAndScaleTo(float aResolution) override {
     return SetResolutionImpl(aResolution, /* aScaleToResolution = */ true);
   }
   virtual bool ScaleToResolution() const override;
   virtual float GetCumulativeResolution() override;
+  virtual float GetCumulativeScaleResolution() override;
 
   //nsIViewObserver interface
 
   virtual void Paint(nsView* aViewToPaint, const nsRegion& aDirtyRegion,
                      uint32_t aFlags) override;
   virtual nsresult HandleEvent(nsIFrame* aFrame,
                                mozilla::WidgetGUIEvent* aEvent,
                                bool aDontRetargetEvents,
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -5354,31 +5354,35 @@ ScrollFrameHelper::SaveState() const
   // that ScrollToRestoredPosition uses). This ensures if a reframe occurs
   // while we're in the process of loading content to scroll to a restored
   // position, we'll keep trying after the reframe.
   nsPoint pt = GetLogicalScrollPosition();
   if (mRestorePos.y != -1 && pt == mLastPos) {
     pt = mRestorePos;
   }
   state->SetScrollState(pt);
-  nsIPresShell* shell = mOuter->PresContext()->PresShell();
-  state->SetResolution(shell->GetResolution());
-  state->SetScaleToResolution(shell->ScaleToResolution());
+  if (mIsRoot) {
+    // Only save resolution properties for root scroll frames
+    nsIPresShell* shell = mOuter->PresContext()->PresShell();
+    state->SetResolution(shell->GetResolution());
+    state->SetScaleToResolution(shell->ScaleToResolution());
+  }
   return state;
 }
 
 void
 ScrollFrameHelper::RestoreState(nsPresState* aState)
 {
   mRestorePos = aState->GetScrollState();
   mDidHistoryRestore = true;
   mLastPos = mScrolledFrame ? GetLogicalScrollPosition() : nsPoint(0,0);
 
-  // Scaling-to-resolution should only be used on root scroll frames.
-  MOZ_ASSERT(mIsRoot || !aState->GetScaleToResolution());
+  // Resolution properties should only exist on root scroll frames.
+  MOZ_ASSERT(mIsRoot || (!aState->GetScaleToResolution() &&
+                         aState->GetResolution() == 1.0));
 
   if (mIsRoot) {
     nsIPresShell* presShell = mOuter->PresContext()->PresShell();
     if (aState->GetScaleToResolution()) {
       presShell->SetResolutionAndScaleTo(aState->GetResolution());
     } else {
       presShell->SetResolution(aState->GetResolution());
     }
--- a/layout/generic/nsSubDocumentFrame.cpp
+++ b/layout/generic/nsSubDocumentFrame.cpp
@@ -436,16 +436,23 @@ nsSubDocumentFrame::BuildDisplayList(nsD
           nsIScrollableFrame* rootScrollableFrame = do_QueryFrame(rootScrollFrame);
           dirty = rootScrollableFrame->ExpandRectToNearlyVisible(dirty);
         }
       }
     }
 
     aBuilder->EnterPresShell(subdocRootFrame,
                              pointerEventsNone && !passPointerEventsToChildren);
+
+#if defined(MOZ_SINGLE_PROCESS_APZ)
+    if (!haveDisplayPort) {
+      // Remove nsPresShell resolution
+      dirty = dirty.RemoveResolution(presShell->ScaleToResolution() ? presShell->GetResolution () : 1.0f);
+    }
+#endif
   } else {
     dirty = aDirtyRect;
   }
 
   DisplayListClipState::AutoSaveRestore clipState(aBuilder);
   if (ShouldClipSubdocument()) {
     clipState.ClipContainingBlockDescendantsToContentBox(aBuilder, this);
   }
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-1a-ltr-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; unicode-bidi: bidi-override; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 1:
+    AB ( CD [ & ef ] ! ) gh
+-->
+<div>&#x5d1;&#x5d0;(&#x5d3;&#x5d2;[&amp;ef]!)gh</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-1a-ltr.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 1:
+    AB ( CD [ & ef ] ! ) gh
+-->
+<!-- LTR -->
+<div>&#x5d0;&#x5d1;(&#x5d2;&#x5d3;[&amp;ef]!)gh</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-1a-rtl-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; unicode-bidi: bidi-override; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 1:
+    AB ( CD [ & ef ] ! ) gh
+-->
+<div>gh(![ef&amp;]&#x5d3;&#x5d2;)&#x5d1;&#x5d0;</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-1a-rtl.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 1:
+    AB ( CD [ & ef ] ! ) gh
+-->
+<!-- RTL -->
+<div dir=rtl>&#x5d0;&#x5d1;(&#x5d2;&#x5d3;[&amp;ef]!)gh</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-1b-ltr-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; unicode-bidi: bidi-override; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 1, AB and gh swapped:
+    gh ( CD [ & ef ] ! ) AB
+-->
+<div>gh(&#x5d3;&#x5d2;[&amp;ef]!)&#x5d1;&#x5d0;</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-1b-ltr.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 1, AB and gh swapped:
+    gh ( CD [ & ef ] ! ) AB
+-->
+<!-- LTR -->
+<div>gh(&#x5d2;&#x5d3;[&amp;ef]!)&#x5d0;&#x5d1;</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-1b-rtl-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; unicode-bidi: bidi-override; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 1, AB and gh swapped:
+    gh ( CD [ & ef ] ! ) AB
+-->
+<div>&#x5d1;&#x5d0;(![ef&amp;]&#x5d3;&#x5d2;)gh</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-1b-rtl.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 1, AB and gh swapped:
+    gh ( CD [ & ef ] ! ) AB
+-->
+<!-- RTL -->
+<div dir=rtl>gh(&#x5d2;&#x5d3;[&amp;ef]!)&#x5d0;&#x5d1;</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-1c-ltr-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; unicode-bidi: bidi-override; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 1:
+    AB ( ef [ & CD ] ! ) gh
+-->
+<div>&#x5d1;&#x5d0;(ef[&amp;&#x5d3;&#x5d2;]!)gh</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-1c-ltr.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 1, CD and ef swapped:
+    AB ( ef [ & CD ] ! ) gh
+-->
+<!-- LTR -->
+<div>&#x5d0;&#x5d1;(ef[&amp;&#x5d2;&#x5d3;]!)gh</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-1c-rtl-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; unicode-bidi: bidi-override; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 1:
+    AB ( ef [ & CD ] ! ) gh
+-->
+<div>gh(![&#x5d3;&#x5d2;&amp;]ef)&#x5d1;&#x5d0;</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-1c-rtl.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 1, CD and ef swapped:
+    AB ( ef [ & CD ] ! ) gh
+-->
+<!-- RTL -->
+<div dir=rtl>&#x5d0;&#x5d1;(ef[&amp;&#x5d2;&#x5d3;]!)gh</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-2a-ltr-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; unicode-bidi: bidi-override; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 2:
+    smith (fabrikam ARABIC) HEBREW
+-->
+<div>smith (fabrikam &#x64A;&#x628;&#x631;&#x639;&#x644;&#x627;) &#x5EA;&#x5D9;&#x5E8;&#x5D1;&#x5E2;</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-2a-ltr.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 2:
+    smith (fabrikam ARABIC) HEBREW
+-->
+<!-- LTR -->
+<div>smith (fabrikam &#x627;&#x644;&#x639;&#x631;&#x628;&#x64A;) &#x5E2;&#x5D1;&#x5E8;&#x5D9;&#x5EA;</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-2a-rtl-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; unicode-bidi: bidi-override; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 2:
+    smith (fabrikam ARABIC) HEBREW
+-->
+<div>&#x5EA;&#x5D9;&#x5E8;&#x5D1;&#x5E2; (&#x64A;&#x628;&#x631;&#x639;&#x644;&#x627; fabrikam) smith</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-2a-rtl.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 2:
+    smith (fabrikam ARABIC) HEBREW
+-->
+<!-- RTL -->
+<div dir=rtl>smith (fabrikam &#x627;&#x644;&#x639;&#x631;&#x628;&#x64A;) &#x5E2;&#x5D1;&#x5E8;&#x5D9;&#x5EA;</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-2b-ltr-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; unicode-bidi: bidi-override; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 2 with smith and HEBREW exchanged:
+    HEBREW (fabrikam ARABIC) smith
+-->
+<div>&#x5EA;&#x5D9;&#x5E8;&#x5D1;&#x5E2; (fabrikam &#x64A;&#x628;&#x631;&#x639;&#x644;&#x627;) smith</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-2b-ltr.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 2 with smith and HEBREW exchanged:
+    HEBREW (fabrikam ARABIC) smith
+-->
+<!-- LTR -->
+<div>&#x5E2;&#x5D1;&#x5E8;&#x5D9;&#x5EA; (fabrikam &#x627;&#x644;&#x639;&#x631;&#x628;&#x64A;) smith</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-2b-rtl-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; unicode-bidi: bidi-override; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 2 with smith and HEBREW exchanged:
+    HEBREW (fabrikam ARABIC) smith
+-->
+<div>smith (&#x64A;&#x628;&#x631;&#x639;&#x644;&#x627; fabrikam) &#x5EA;&#x5D9;&#x5E8;&#x5D1;&#x5E2;</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-2b-rtl.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 2 with smith and HEBREW exchanged:
+    HEBREW (fabrikam ARABIC) smith
+-->
+<!-- RTL -->
+<div dir=rtl>&#x5E2;&#x5D1;&#x5E8;&#x5D9;&#x5EA; (fabrikam &#x627;&#x644;&#x639;&#x631;&#x628;&#x64A;) smith</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-2c-ltr-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; unicode-bidi: bidi-override; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 2 with fabrikam and ARABIC exchanged:
+    smith (ARABIC fabrikam) HEBREW
+-->
+<div>smith (&#x64A;&#x628;&#x631;&#x639;&#x644;&#x627; fabrikam) &#x5EA;&#x5D9;&#x5E8;&#x5D1;&#x5E2;</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-2c-ltr.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 2 with fabrikam and ARABIC exchanged:
+    smith (ARABIC fabrikam) HEBREW
+-->
+<!-- LTR -->
+<div>smith (&#x627;&#x644;&#x639;&#x631;&#x628;&#x64A; fabrikam) &#x5E2;&#x5D1;&#x5E8;&#x5D9;&#x5EA;</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-2c-rtl-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; unicode-bidi: bidi-override; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 2 with fabrikam and ARABIC exchanged:
+    smith (ARABIC fabrikam) HEBREW
+-->
+<div>&#x5EA;&#x5D9;&#x5E8;&#x5D1;&#x5E2; (fabrikam &#x64A;&#x628;&#x631;&#x639;&#x644;&#x627;) smith</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-2c-rtl.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 2 with fabrikam and ARABIC exchanged:
+    smith (ARABIC fabrikam) HEBREW
+-->
+<!-- RTL -->
+<div dir=rtl>smith (&#x627;&#x644;&#x639;&#x631;&#x628;&#x64A; fabrikam) &#x5E2;&#x5D1;&#x5E8;&#x5D9;&#x5EA;</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-3a-ltr-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; unicode-bidi: bidi-override; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 3:
+    ARABIC book(s)
+-->
+<div>&#x64A;&#x628;&#x631;&#x639;&#x644;&#x627; book(s)</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-3a-ltr.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 3:
+    ARABIC book(s)
+-->
+<!-- LTR -->
+<div>&#x627;&#x644;&#x639;&#x631;&#x628;&#x64A; book(s)</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-3a-rtl-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; unicode-bidi: bidi-override; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 3:
+    ARABIC book(s)
+-->
+<div>book(s) &#x64A;&#x628;&#x631;&#x639;&#x644;&#x627;</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-3a-rtl.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 3:
+    ARABIC book(s)
+-->
+<!-- RTL -->
+<div dir=rtl>&#x627;&#x644;&#x639;&#x631;&#x628;&#x64A; book(s)</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-3b-ltr-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; }
+span { unicode-bidi: -moz-isolate; unicode-bidi: isolate; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 3, LTR and RTL exchanged:
+    arabic BOOK(S)
+-->
+<div>arabic (<span dir=rtl>&#x643;&#x64f;&#x62a;&#x64f;&#x628;</span>)<span dir=rtl>&#x643;&#x650;&#x62a;&#x64e;&#x627;&#x628;</span></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-3b-ltr.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 3, LTR and RTL exchanged:
+    arabic BOOK(S)
+-->
+<!-- LTR -->
+<div>arabic &#x643;&#x650;&#x62a;&#x64e;&#x627;&#x628;(&#x643;&#x64f;&#x62a;&#x64f;&#x628;)</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-3b-rtl-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; }
+span { unicode-bidi: -moz-isolate; unicode-bidi: isolate; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 3, LTR and RTL exchanged:
+    arabic BOOK(S)
+-->
+<div>(<span dir=rtl>&#x643;&#x64f;&#x62a;&#x64f;&#x628;</span>)<span dir=rtl>&#x643;&#x650;&#x62a;&#x64e;&#x627;&#x628;</span> arabic</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bidi/brackets-3b-rtl.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+
+<style>
+body { font: 32px monospace; }
+div { text-align: center; }
+</style>
+
+<body>
+
+<!--
+  http://unicode.org/reports/tr9/#N0, Example 3, LTR and RTL exchanged:
+    arabic BOOK(S)
+-->
+<!-- RTL -->
+<div dir=rtl>arabic &#x643;&#x650;&#x62a;&#x64e;&#x627;&#x628;(&#x643;&#x64f;&#x62a;&#x64f;&#x628;)</div>
--- a/layout/reftests/bidi/reftest.list
+++ b/layout/reftests/bidi/reftest.list
@@ -149,10 +149,26 @@ skip-if((B2G&&browserIsRemote)||Mulet) f
 == 922550-1.html 922550-1-ref.html
 == 1067268-1.html 1067268-1-ref.html
 == 1069941-inline-bidi-border-1.html 1069941-inline-bidi-border-1-ref.html
 == 1069941-inline-bidi-margin-1.html 1069941-inline-bidi-margin-1-ref.html
 skip-if(B2G||Mulet) != 1155359-1.xul 1155359-1-ref.xul
 == 1157726-1.html 1157726-1-ref.html
 == 1161752.html 1161752-ref.html
 == 1161752-5-embed.html 1161752-5-embed-ref.html
+== brackets-1a-ltr.html brackets-1a-ltr-ref.html
+== brackets-1a-rtl.html brackets-1a-rtl-ref.html
+== brackets-1b-ltr.html brackets-1b-ltr-ref.html
+== brackets-1b-rtl.html brackets-1b-rtl-ref.html
+== brackets-1c-ltr.html brackets-1c-ltr-ref.html
+== brackets-1c-rtl.html brackets-1c-rtl-ref.html
+== brackets-2a-ltr.html brackets-2a-ltr-ref.html
+fuzzy-if(Android,254,557) == brackets-2a-rtl.html brackets-2a-rtl-ref.html
+== brackets-2b-ltr.html brackets-2b-ltr-ref.html
+== brackets-2b-rtl.html brackets-2b-rtl-ref.html
+== brackets-2c-ltr.html brackets-2c-ltr-ref.html
+fuzzy-if(Android,254,231) == brackets-2c-rtl.html brackets-2c-rtl-ref.html
+== brackets-3a-ltr.html brackets-3a-ltr-ref.html
+== brackets-3a-rtl.html brackets-3a-rtl-ref.html
+== brackets-3b-ltr.html brackets-3b-ltr-ref.html
+== brackets-3b-rtl.html brackets-3b-rtl-ref.html
 == 1217833-1.html 1217833-1-ref.html
 == 1217833-2.html 1217833-2-ref.html
--- a/layout/style/FontFaceSet.cpp
+++ b/layout/style/FontFaceSet.cpp
@@ -13,16 +13,17 @@
 #include "mozilla/dom/FontFaceSetIterator.h"
 #include "mozilla/dom/FontFaceSetLoadEvent.h"
 #include "mozilla/dom/FontFaceSetLoadEventBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Snprintf.h"
+#include "mozilla/Telemetry.h"
 #include "nsCORSListenerProxy.h"
 #include "nsCSSParser.h"
 #include "nsDeviceContext.h"
 #include "nsFontFaceLoader.h"
 #include "nsIConsoleService.h"
 #include "nsIContentPolicy.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsIDocShell.h"
@@ -34,16 +35,17 @@
 #include "nsIWebNavigation.h"
 #include "nsNetUtil.h"
 #include "nsIProtocolHandler.h"
 #include "nsIInputStream.h"
 #include "nsPresContext.h"
 #include "nsPrintfCString.h"
 #include "nsStyleSet.h"
 #include "nsUTF8Utils.h"
+#include "nsDOMNavigationTiming.h"
 
 using namespace mozilla;
 using namespace mozilla::css;
 using namespace mozilla::dom;
 
 #define LOG(args) MOZ_LOG(gfxUserFontSet::GetUserFontsLog(), mozilla::LogLevel::Debug, args)
 #define LOG_ENABLED() MOZ_LOG_TEST(gfxUserFontSet::GetUserFontsLog(), \
                                   LogLevel::Debug)
@@ -300,16 +302,27 @@ FontFaceSet::FindMatchingFontFaces(const
       FontFace* f = record.mFontFace;
       if (matchingFaces.Contains(f)) {
         aFontFaces.AppendElement(f);
       }
     }
   }
 }
 
+TimeStamp
+FontFaceSet::GetNavigationStartTimeStamp()
+{
+  TimeStamp navStart;
+  RefPtr<nsDOMNavigationTiming> timing(mDocument->GetNavigationTiming());
+  if (timing) {
+    navStart = timing->GetNavigationStartTimeStamp();
+  }
+  return navStart;
+}
+
 already_AddRefed<Promise>
 FontFaceSet::Load(JSContext* aCx,
                   const nsAString& aFont,
                   const nsAString& aText,
                   ErrorResult& aRv)
 {
   FlushUserFontSet();
 
@@ -329,16 +342,17 @@ FontFaceSet::Load(JSContext* aCx,
     if (!promises.AppendElement(promise, fallible)) {
       aRv.Throw(NS_ERROR_FAILURE);
       return nullptr;
     }
   }
 
   nsIGlobalObject* globalObject = GetParentObject();
   if (!globalObject) {
+    aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   JS::Rooted<JSObject*> jsGlobal(aCx, globalObject->GetGlobalJSObject());
   GlobalObject global(aCx, jsGlobal);
 
   RefPtr<Promise> result = Promise::All(global, promises, aRv);
   return result.forget();
@@ -1717,16 +1731,36 @@ FontFaceSet::UserFontSet::StartLoad(gfxU
                                     const gfxFontFaceSrc* aFontFaceSrc)
 {
   if (!mFontFaceSet) {
     return NS_ERROR_FAILURE;
   }
   return mFontFaceSet->StartLoad(aUserFontEntry, aFontFaceSrc);
 }
 
+void
+FontFaceSet::UserFontSet::RecordFontLoadDone(uint32_t aFontSize,
+                                             TimeStamp aDoneTime)
+{
+  mDownloadCount++;
+  mDownloadSize += aFontSize;
+  Telemetry::Accumulate(Telemetry::WEBFONT_SIZE, aFontSize / 1024);
+
+  if (!mFontFaceSet) {
+    return;
+  }
+
+  TimeStamp navStart = mFontFaceSet->GetNavigationStartTimeStamp();
+  TimeStamp zero;
+  if (navStart != zero) {
+    Telemetry::AccumulateTimeDelta(Telemetry::WEBFONT_DOWNLOAD_TIME_AFTER_START,
+                                   navStart, aDoneTime);
+  }
+}
+
 /* virtual */ nsresult
 FontFaceSet::UserFontSet::LogMessage(gfxUserFontEntry* aUserFontEntry,
                                      const char* aMessage,
                                      uint32_t aFlags,
                                      nsresult aStatus)
 {
   if (!mFontFaceSet) {
     return NS_ERROR_FAILURE;
--- a/layout/style/FontFaceSet.h
+++ b/layout/style/FontFaceSet.h
@@ -63,16 +63,19 @@ public:
     FontFaceSet* GetFontFaceSet() { return mFontFaceSet; }
 
     virtual nsresult CheckFontLoad(const gfxFontFaceSrc* aFontFaceSrc,
                                    nsIPrincipal** aPrincipal,
                                    bool* aBypassCache) override;
     virtual nsresult StartLoad(gfxUserFontEntry* aUserFontEntry,
                                const gfxFontFaceSrc* aFontFaceSrc) override;
 
+    void RecordFontLoadDone(uint32_t aFontSize,
+                            mozilla::TimeStamp aDoneTime) override;
+
   protected:
     virtual bool GetPrivateBrowsing() override;
     virtual nsresult SyncLoadFontData(gfxUserFontEntry* aFontToLoad,
                                       const gfxFontFaceSrc* aFontFaceSrc,
                                       uint8_t*& aBuffer,
                                       uint32_t& aBufferLength) override;
     virtual nsresult LogMessage(gfxUserFontEntry* aUserFontEntry,
                                 const char* aMessage,
@@ -288,16 +291,18 @@ private:
               int32_t& aStretch,
               uint8_t& aStyle,
               ErrorResult& aRv);
   void FindMatchingFontFaces(const nsAString& aFont,
                              const nsAString& aText,
                              nsTArray<FontFace*>& aFontFaces,
                              mozilla::ErrorResult& aRv);
 
+  TimeStamp GetNavigationStartTimeStamp();
+
   RefPtr<UserFontSet> mUserFontSet;
 
   // The document this is a FontFaceSet for.
   nsCOMPtr<nsIDocument> mDocument;
 
   // A Promise that is fulfilled once all of the FontFace objects
   // in mRuleFaces and mNonRuleFaces that started or were loading at the
   // time the Promise was created have finished loading.  It is rejected if
new file mode 100644
--- /dev/null
+++ b/layout/style/crashtests/1226400-1.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>FontFaceSet::Load crasher</title>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+
+<style type="text/css">
+
+body {
+  margin: 50px;
+}
+
+p {
+  margin: 0;
+  font-size: 300%;
+}
+
+</style>
+
+</head>
+<body>
+
+<p>This may crash on load...</p>
+
+<script>
+var scriptText = `
+  var fontFaceSet = document.fonts;
+  var link = document.createElement("link");
+  link.onerror = link.onload = function() {
+    fontFaceSet.load("12px foo");
+  }
+  link.rel = "stylesheet";
+  link.href = "data:text/css,";
+  document.body.appendChild(link);
+`;
+
+var styleText = `
+  @font-face {
+    font-family: foo;
+    src: url("data:text/ttf,");
+  }
+`;
+
+var ifr = document.createElement("iframe");
+document.body.appendChild(ifr);
+var style = ifr.contentDocument.createElement("style");
+style.textContent = styleText;
+ifr.contentDocument.body.appendChild(style);
+var script = ifr.contentDocument.createElement("script");
+script.textContent = scriptText;
+ifr.contentDocument.body.appendChild(script);
+ifr.remove();
+</script>
+</body>
+</html>
--- a/layout/style/crashtests/crashtests.list
+++ b/layout/style/crashtests/crashtests.list
@@ -125,13 +125,14 @@ load 1161366-1.html
 load 1163446-1.html
 load 1164813-1.html
 load 1167782-1.html
 load 1186768-1.xhtml
 load 1200568-1.html
 load 1206105-1.html
 load 1223688-1.html
 load 1223694-1.html
+load 1226400-1.html
 load 1227501-1.html
 load border-image-visited-link.html
 load font-face-truncated-src.html 
 load large_border_image_width.html
 load long-url-list-stack-overflow.html
--- a/layout/style/nsFontFaceLoader.cpp
+++ b/layout/style/nsFontFaceLoader.cpp
@@ -8,16 +8,17 @@
 
 #include "mozilla/Logging.h"
 
 #include "nsFontFaceLoader.h"
 
 #include "nsError.h"
 #include "nsContentUtils.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
 #include "FontFaceSet.h"
 #include "nsPresContext.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIHttpChannel.h"
 #include "nsIContentPolicy.h"
 #include "nsContentPolicyUtils.h"
 
@@ -34,16 +35,17 @@ nsFontFaceLoader::nsFontFaceLoader(gfxUs
                                    nsIURI* aFontURI,
                                    FontFaceSet* aFontFaceSet,
                                    nsIChannel* aChannel)
   : mUserFontEntry(aUserFontEntry),
     mFontURI(aFontURI),
     mFontFaceSet(aFontFaceSet),
     mChannel(aChannel)
 {
+  mStartTime = TimeStamp::Now();
 }
 
 nsFontFaceLoader::~nsFontFaceLoader()
 {
   if (mUserFontEntry) {
     mUserFontEntry->mLoader = nullptr;
   }
   if (mLoadTimer) {
@@ -143,22 +145,27 @@ nsFontFaceLoader::OnStreamComplete(nsISt
 {
   if (!mFontFaceSet) {
     // We've been canceled
     return aStatus;
   }
 
   mFontFaceSet->RemoveLoader(this);
 
+  TimeStamp doneTime = TimeStamp::Now();
+  TimeDuration downloadTime = doneTime - mStartTime;
+  uint32_t downloadTimeMS = uint32_t(downloadTime.ToMilliseconds());
+  Telemetry::Accumulate(Telemetry::WEBFONT_DOWNLOAD_TIME, downloadTimeMS);
+
   if (LOG_ENABLED()) {
     nsAutoCString fontURI;
     mFontURI->GetSpec(fontURI);
     if (NS_SUCCEEDED(aStatus)) {
-      LOG(("userfonts (%p) download completed - font uri: (%s)\n",
-           this, fontURI.get()));
+      LOG(("userfonts (%p) download completed - font uri: (%s) time: %d ms\n",
+           this, fontURI.get(), downloadTimeMS));
     } else {
       LOG(("userfonts (%p) download failed - font uri: (%s) error: %8.8x\n",
            this, fontURI.get(), aStatus));
     }
   }
 
   if (NS_SUCCEEDED(aStatus)) {
     // for HTTP requests, check whether the request _actually_ succeeded;
@@ -183,16 +190,18 @@ nsFontFaceLoader::OnStreamComplete(nsISt
   // (aString) when finished with it; the pointer is no longer valid
   // after FontDataDownloadComplete returns.
   // This is called even in the case of a failed download (HTTP 404, etc),
   // as there may still be data to be freed (e.g. an error page),
   // and we need to load the next source.
   bool fontUpdate =
     mUserFontEntry->FontDataDownloadComplete(aString, aStringLen, aStatus);
 
+  mFontFaceSet->GetUserFontSet()->RecordFontLoadDone(aStringLen, doneTime);
+
   // when new font loaded, need to reflow
   if (fontUpdate) {
     nsTArray<gfxUserFontSet*> fontSets;
     mUserFontEntry->GetUserFontSets(fontSets);
     for (gfxUserFontSet* fontSet : fontSets) {
       nsPresContext* ctx = FontFaceSet::GetPresContextFor(fontSet);
       if (ctx) {
         // Update layout for the presence of the new font.  Since this is
--- a/layout/style/nsFontFaceLoader.h
+++ b/layout/style/nsFontFaceLoader.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* code for loading in @font-face defined font data */
 
 #ifndef nsFontFaceLoader_h_
 #define nsFontFaceLoader_h_
 
 #include "mozilla/Attributes.h"
+#include "mozilla/TimeStamp.h"
 #include "nsCOMPtr.h"
 #include "nsIStreamLoader.h"
 #include "nsIChannel.h"
 #include "gfxUserFontSet.h"
 #include "nsHashKeys.h"
 #include "nsTHashtable.h"
 #include "nsCSSRules.h"
 
@@ -50,13 +51,13 @@ protected:
   virtual ~nsFontFaceLoader();
 
 private:
   RefPtr<gfxUserFontEntry>  mUserFontEntry;
   nsCOMPtr<nsIURI>        mFontURI;
   RefPtr<mozilla::dom::FontFaceSet> mFontFaceSet;
   nsCOMPtr<nsIChannel>    mChannel;
   nsCOMPtr<nsITimer>      mLoadTimer;
-
+  TimeStamp               mStartTime;
   nsIStreamLoader*        mStreamLoader;
 };
 
 #endif /* !defined(nsFontFaceLoader_h_) */
--- a/layout/xul/crashtests/crashtests.list
+++ b/layout/xul/crashtests/crashtests.list
@@ -72,17 +72,17 @@ load 432058-1.xul
 load 432068-1.xul
 load 432068-2.xul
 load 433296-1.xul
 load 433429.xul
 load 434458-1.xul
 load 452185.html
 load 460900-1.xul
 load 464149-1.xul
-asserts-if(winWidget,1) asserts-if(Android&&asyncPan,1) load 464407-1.xhtml # Bug 450974 on win, Bug 1217984 on android
+asserts-if(winWidget,1) load 464407-1.xhtml # Bug 450974 on win
 load 467080.xul
 load 467481-1.xul
 load 470063-1.html
 load 470272.html
 load 472189.xul
 load 475133.html
 load 488210-1.xhtml
 load 495728-1.xul
--- a/media/gmp-clearkey/0.1/ClearKeyDecryptionManager.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeyDecryptionManager.cpp
@@ -117,17 +117,17 @@ ClearKeyDecryptionManager::ExpectKeyId(K
   }
   mDecryptors[aKeyId]->AddRef();
 }
 
 void
 ClearKeyDecryptionManager::ReleaseKeyId(KeyId aKeyId)
 {
   CK_LOGD("ClearKeyDecryptionManager::ReleaseKeyId");
-  assert(HasKeyForKeyId(aKeyId));
+  assert(HasSeenKeyId(aKeyId));
 
   ClearKeyDecryptor* decryptor = mDecryptors[aKeyId];
   if (!decryptor->Release()) {
     mDecryptors.erase(aKeyId);
   }
 }
 
 GMPErr
--- a/media/gmp-clearkey/0.1/ClearKeyPersistence.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeyPersistence.cpp
@@ -108,63 +108,68 @@ ClearKeyPersistence::GetNewSessionId(GMP
 }
 
 
 class CreateSessionTask : public GMPTask {
 public:
   CreateSessionTask(ClearKeySessionManager* aTarget,
                     uint32_t aCreateSessionToken,
                     uint32_t aPromiseId,
+                    const string& aInitDataType,
                     const uint8_t* aInitData,
                     uint32_t aInitDataSize,
                     GMPSessionType aSessionType)
     : mTarget(aTarget)
     , mCreateSessionToken(aCreateSessionToken)
     , mPromiseId(aPromiseId)
+    , mInitDataType(aInitDataType)
     , mSessionType(aSessionType)
   {
     mInitData.insert(mInitData.end(),
                      aInitData,
                      aInitData + aInitDataSize);
   }
   virtual void Run() override {
     mTarget->CreateSession(mCreateSessionToken,
                            mPromiseId,
-                           "cenc",
-                           strlen("cenc"),
+                           mInitDataType.c_str(),
+                           mInitDataType.size(),
                            &mInitData.front(),
                            mInitData.size(),
                            mSessionType);
   }
   virtual void Destroy() override {
     delete this;
   }
 private:
   RefPtr<ClearKeySessionManager> mTarget;
   uint32_t mCreateSessionToken;
   uint32_t mPromiseId;
+  const string mInitDataType;
   vector<uint8_t> mInitData;
   GMPSessionType mSessionType;
 };
 
 
 /* static */ bool
 ClearKeyPersistence::DeferCreateSessionIfNotReady(ClearKeySessionManager* aInstance,
                                                   uint32_t aCreateSessionToken,
                                                   uint32_t aPromiseId,
+                                                  const string& aInitDataType,
                                                   const uint8_t* aInitData,
                                                   uint32_t aInitDataSize,
                                                   GMPSessionType aSessionType)
 {
   if (sPersistentKeyState >= LOADED)  {
     return false;
   }
   GMPTask* t = new CreateSessionTask(aInstance,
                                      aCreateSessionToken,
                                      aPromiseId,
+                                     aInitDataType,
                                      aInitData,
                                      aInitDataSize,
                                      aSessionType);
   sTasksBlockedOnSessionIdLoad.push_back(t);
   return true;
 }
 
 class LoadSessionTask : public GMPTask {
--- a/media/gmp-clearkey/0.1/ClearKeyPersistence.h
+++ b/media/gmp-clearkey/0.1/ClearKeyPersistence.h
@@ -26,16 +26,17 @@ class ClearKeyPersistence {
 public:
   static void EnsureInitialized();
 
   static std::string GetNewSessionId(GMPSessionType aSessionType);
 
   static bool DeferCreateSessionIfNotReady(ClearKeySessionManager* aInstance,
                                            uint32_t aCreateSessionToken,
                                            uint32_t aPromiseId,
+                                           const std::string& aInitDataType,
                                            const uint8_t* aInitData,
                                            uint32_t aInitDataSize,
                                            GMPSessionType aSessionType);
 
   static bool DeferLoadSessionIfNotReady(ClearKeySessionManager* aInstance,
                                          uint32_t aPromiseId,
                                          const char* aSessionId,
                                          uint32_t aSessionIdLength);
--- a/media/gmp-clearkey/0.1/ClearKeySession.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeySession.cpp
@@ -37,35 +37,47 @@ ClearKeySession::ClearKeySession(const s
 }
 
 ClearKeySession::~ClearKeySession()
 {
   CK_LOGD("ClearKeySession dtor %p", this);
 
   auto& keyIds = GetKeyIds();
   for (auto it = keyIds.begin(); it != keyIds.end(); it++) {
-    assert(ClearKeyDecryptionManager::Get()->HasKeyForKeyId(*it));
+    assert(ClearKeyDecryptionManager::Get()->HasSeenKeyId(*it));
 
     ClearKeyDecryptionManager::Get()->ReleaseKeyId(*it);
     mCallback->KeyStatusChanged(&mSessionId[0], mSessionId.size(),
                                 &(*it)[0], it->size(),
                                 kGMPUnknown);
   }
 }
 
 void
 ClearKeySession::Init(uint32_t aCreateSessionToken,
                       uint32_t aPromiseId,
+                      const std::string& aInitDataType,
                       const uint8_t* aInitData, uint32_t aInitDataSize)
 {
   CK_LOGD("ClearKeySession::Init");
 
-  ClearKeyUtils::ParseInitData(aInitData, aInitDataSize, mKeyIds);
+  if (aInitDataType == "cenc") {
+    ClearKeyUtils::ParseCENCInitData(aInitData, aInitDataSize, mKeyIds);
+  } else if (aInitDataType == "keyids") {
+    std::string sessionType;
+    ClearKeyUtils::ParseKeyIdsInitData(aInitData, aInitDataSize, mKeyIds, sessionType);
+    if (sessionType != ClearKeyUtils::SessionTypeToString(mSessionType)) {
+      const char message[] = "Session type specified in keyids init data doesn't match session type.";
+      mCallback->RejectPromise(aPromiseId, kGMPAbortError, message, strlen(message));
+      return;
+    }
+  }
+
   if (!mKeyIds.size()) {
-    const char message[] = "Couldn't parse cenc key init data";
+    const char message[] = "Couldn't parse init data";
     mCallback->RejectPromise(aPromiseId, kGMPAbortError, message, strlen(message));
     return;
   }
 
   mCallback->SetSessionId(aCreateSessionToken, &mSessionId[0], mSessionId.length());
 
   mCallback->ResolvePromise(aPromiseId);
 }
--- a/media/gmp-clearkey/0.1/ClearKeySession.h
+++ b/media/gmp-clearkey/0.1/ClearKeySession.h
@@ -33,16 +33,17 @@ public:
                            GMPSessionType aSessionType);
 
   ~ClearKeySession();
 
   const std::vector<KeyId>& GetKeyIds() const { return mKeyIds; }
 
   void Init(uint32_t aCreateSessionToken,
             uint32_t aPromiseId,
+            const string& aInitDataType,
             const uint8_t* aInitData, uint32_t aInitDataSize);
 
   GMPSessionType Type() const;
 
   void AddKeyId(const KeyId& aKeyId);
 
   const std::string& Id() const { return mSessionId; }
 
--- a/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
@@ -96,37 +96,39 @@ ClearKeySessionManager::CreateSession(ui
                                       const char* aInitDataType,
                                       uint32_t aInitDataTypeSize,
                                       const uint8_t* aInitData,
                                       uint32_t aInitDataSize,
                                       GMPSessionType aSessionType)
 {
   CK_LOGD("ClearKeySessionManager::CreateSession type:%s", aInitDataType);
 
-  // initDataType must be "cenc".
-  if (strcmp("cenc", aInitDataType)) {
+  string initDataType(aInitDataType, aInitDataType + aInitDataTypeSize);
+  // initDataType must be "cenc" or "keyids".
+  if (initDataType != "cenc" && initDataType != "keyids") {
     mCallback->RejectPromise(aPromiseId, kGMPNotSupportedError,
                              nullptr /* message */, 0 /* messageLen */);
     return;
   }
 
   if (ClearKeyPersistence::DeferCreateSessionIfNotReady(this,
                                                         aCreateSessionToken,
                                                         aPromiseId,
+                                                        initDataType,
                                                         aInitData,
                                                         aInitDataSize,
                                                         aSessionType)) {
     return;
   }
 
   string sessionId = ClearKeyPersistence::GetNewSessionId(aSessionType);
   assert(mSessions.find(sessionId) == mSessions.end());
 
   ClearKeySession* session = new ClearKeySession(sessionId, mCallback, aSessionType);
-  session->Init(aCreateSessionToken, aPromiseId, aInitData, aInitDataSize);
+  session->Init(aCreateSessionToken, aPromiseId, initDataType, aInitData, aInitDataSize);
   mSessions[sessionId] = session;
 
   const vector<KeyId>& sessionKeys = session->GetKeyIds();
   vector<KeyId> neededKeys;
   for (auto it = sessionKeys.begin(); it != sessionKeys.end(); it++) {
     // Need to request this key ID from the client. We always send a key
     // request, whether or not another session has sent a request with the same
     // key ID. Otherwise a script can end up waiting for another script to
--- a/media/gmp-clearkey/0.1/ClearKeyUtils.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeyUtils.cpp
@@ -127,18 +127,19 @@ EncodeBase64Web(vector<uint8_t> aBinary,
     assert(idx < MOZ_ARRAY_LENGTH(sAlphabet)); // out of bounds index for 'sAlphabet'
     out[i] = sAlphabet[idx];
   }
 
   return true;
 }
 
 /* static */ void
-ClearKeyUtils::ParseInitData(const uint8_t* aInitData, uint32_t aInitDataSize,
-                             vector<KeyId>& aOutKeys)
+ClearKeyUtils::ParseCENCInitData(const uint8_t* aInitData,
+                                 uint32_t aInitDataSize,
+                                 vector<KeyId>& aOutKeyIds)
 {
   using mozilla::BigEndian;
 
   uint32_t size = 0;
   for (uint32_t offset = 0; offset + sizeof(uint32_t) < aInitDataSize; offset += size) {
     const uint8_t* data = aInitData + offset;
     size = BigEndian::readUint32(data); data += sizeof(uint32_t);
 
@@ -177,43 +178,43 @@ ClearKeyUtils::ParseInitData(const uint8
 
     uint32_t kidCount = BigEndian::readUint32(data); data += sizeof(uint32_t);
     if (data + kidCount * CLEARKEY_KEY_LEN > aInitData + aInitDataSize) {
       CK_LOGE("pssh key IDs overflow init data buffer");
       return;
     }
 
     for (uint32_t i = 0; i < kidCount; i++) {
-      aOutKeys.push_back(KeyId(data, data + CLEARKEY_KEY_LEN));
+      aOutKeyIds.push_back(KeyId(data, data + CLEARKEY_KEY_LEN));
       data += CLEARKEY_KEY_LEN;
     }
   }
 }
 
 /* static */ void
 ClearKeyUtils::MakeKeyRequest(const vector<KeyId>& aKeyIDs,
                               string& aOutRequest,
                               GMPSessionType aSessionType)
 {
   assert(aKeyIDs.size() && aOutRequest.empty());
 
-  aOutRequest.append("{ \"kids\":[");
+  aOutRequest.append("{\"kids\":[");
   for (size_t i = 0; i < aKeyIDs.size(); i++) {
     if (i) {
       aOutRequest.append(",");
     }
     aOutRequest.append("\"");
 
     string base64key;
     EncodeBase64Web(aKeyIDs[i], base64key);
     aOutRequest.append(base64key);
 
     aOutRequest.append("\"");
   }
-  aOutRequest.append("], \"type\":");
+  aOutRequest.append("],\"type\":");
 
   aOutRequest.append("\"");
   aOutRequest.append(SessionTypeToString(aSessionType));
   aOutRequest.append("\"}");
 }
 
 #define EXPECT_SYMBOL(CTX, X) do { \
   if (GetNextSymbol(CTX) != (X)) { \
@@ -503,16 +504,90 @@ ClearKeyUtils::ParseJWK(const uint8_t* a
   }
 
   // Consume '}' from end of object.
   EXPECT_SYMBOL(ctx, '}');
 
   return true;
 }
 
+static bool
+ParseKeyIds(ParserContext& aCtx, vector<KeyId>& aOutKeyIds)
+{
+  // Consume start of array.
+  EXPECT_SYMBOL(aCtx, '[');
+
+  while (true) {
+    string label;
+    vector<uint8_t> keyId;
+    if (!GetNextLabel(aCtx, label) ||
+        !DecodeBase64KeyOrId(label, keyId)) {
+      return false;
+    }
+    assert(!keyId.empty());
+    aOutKeyIds.push_back(keyId);
+
+    uint8_t sym = PeekSymbol(aCtx);
+    if (!sym || sym == ']') {
+      break;
+    }
+
+    EXPECT_SYMBOL(aCtx, ',');
+  }
+
+  return GetNextSymbol(aCtx) == ']';
+}
+
+
+/* static */ bool
+ClearKeyUtils::ParseKeyIdsInitData(const uint8_t* aInitData,
+                                   uint32_t aInitDataSize,
+                                   vector<KeyId>& aOutKeyIds,
+                                   string& aOutSessionType)
+{
+  aOutSessionType = "temporary";
+
+  ParserContext ctx;
+  ctx.mIter = aInitData;
+  ctx.mEnd = aInitData + aInitDataSize;
+
+  // Consume '{' from start of object.
+  EXPECT_SYMBOL(ctx, '{');
+
+  while (true) {
+    string label;
+    // Consume member kids.
+    if (!GetNextLabel(ctx, label)) return false;
+    EXPECT_SYMBOL(ctx, ':');
+
+    if (label == "kids") {
+      // Parse "kids" array.
+      if (!ParseKeyIds(ctx, aOutKeyIds)) return false;
+    } else if (label == "type") {
+      // Consume type string.
+      if (!GetNextLabel(ctx, aOutSessionType)) return false;
+    } else {
+      SkipToken(ctx);
+    }
+
+    // Check for end of object.
+    if (PeekSymbol(ctx) == '}') {
+      break;
+    }
+
+    // Consume ',' between object members.
+    EXPECT_SYMBOL(ctx, ',');
+  }
+
+  // Consume '}' from end of object.
+  EXPECT_SYMBOL(ctx, '}');
+
+  return true;
+}
+
 /* static */ const char*
 ClearKeyUtils::SessionTypeToString(GMPSessionType aSessionType)
 {
   switch (aSessionType) {
     case kGMPTemporySession: return "temporary";
     case kGMPPersistentSession: return "persistent";
     default: {
       assert(false); // Should not reach here.
--- a/media/gmp-clearkey/0.1/ClearKeyUtils.h
+++ b/media/gmp-clearkey/0.1/ClearKeyUtils.h
@@ -49,18 +49,24 @@ struct KeyIdPair
 };
 
 class ClearKeyUtils
 {
 public:
   static void DecryptAES(const std::vector<uint8_t>& aKey,
                          std::vector<uint8_t>& aData, std::vector<uint8_t>& aIV);
 
-  static void ParseInitData(const uint8_t* aInitData, uint32_t aInitDataSize,
-                            std::vector<Key>& aOutKeys);
+  static void ParseCENCInitData(const uint8_t* aInitData,
+                                uint32_t aInitDataSize,
+                                std::vector<Key>& aOutKeyIds);
+
+  static bool ParseKeyIdsInitData(const uint8_t* aInitData,
+                                  uint32_t aInitDataSize,
+                                  std::vector<KeyId>& aOutKeyIds,
+                                  std::string& aOutSessionType);
 
   static void MakeKeyRequest(const std::vector<KeyId>& aKeyIds,
                              std::string& aOutRequest,
                              GMPSessionType aSessionType);
 
   static bool ParseJWK(const uint8_t* aKeyData, uint32_t aKeyDataSize,
                        std::vector<KeyIdPair>& aOutKeys,
                        GMPSessionType aSessionType);
--- a/security/manager/ssl/CertBlocklist.cpp
+++ b/security/manager/ssl/CertBlocklist.cpp
@@ -25,22 +25,24 @@
 #include "prtime.h"
 
 NS_IMPL_ISUPPORTS(CertBlocklist, nsICertBlocklist)
 
 using namespace mozilla;
 using namespace mozilla::pkix;
 
 #define PREF_BACKGROUND_UPDATE_TIMER "app.update.lastUpdateTime.blocklist-background-update-timer"
+#define PREF_KINTO_ONECRL_CHECKED "services.kinto.onecrl.checked"
 #define PREF_MAX_STALENESS_IN_SECONDS "security.onecrl.maximum_staleness_in_seconds"
 #define PREF_ONECRL_VIA_AMO "security.onecrl.via.amo"
 
 static PRLogModuleInfo* gCertBlockPRLog;
 
 uint32_t CertBlocklist::sLastBlocklistUpdate = 0U;
+uint32_t CertBlocklist::sLastKintoUpdate = 0U;
 uint32_t CertBlocklist::sMaxStaleness = 0U;
 bool CertBlocklist::sUseAMO = true;
 
 CertBlocklistItem::CertBlocklistItem(const uint8_t* DNData,
                                      size_t DNLength,
                                      const uint8_t* otherData,
                                      size_t otherLength,
                                      CertBlocklistItemMechanism itemMechanism)
@@ -139,16 +141,19 @@ CertBlocklist::~CertBlocklist()
                                   PREF_BACKGROUND_UPDATE_TIMER,
                                   this);
   Preferences::UnregisterCallback(CertBlocklist::PreferenceChanged,
                                   PREF_MAX_STALENESS_IN_SECONDS,
                                   this);
   Preferences::UnregisterCallback(CertBlocklist::PreferenceChanged,
                                   PREF_ONECRL_VIA_AMO,
                                   this);
+  Preferences::UnregisterCallback(CertBlocklist::PreferenceChanged,
+                                  PREF_KINTO_ONECRL_CHECKED,
+                                  this);
 }
 
 nsresult
 CertBlocklist::Init()
 {
   MOZ_LOG(gCertBlockPRLog, LogLevel::Debug, ("CertBlocklist::Init"));
 
   // Init must be on main thread for getting the profile directory
@@ -173,16 +178,22 @@ CertBlocklist::Init()
     return rv;
   }
   rv = Preferences::RegisterCallbackAndCall(CertBlocklist::PreferenceChanged,
                                             PREF_ONECRL_VIA_AMO,
                                             this);
   if (NS_FAILED(rv)) {
     return rv;
   }
+  rv = Preferences::RegisterCallbackAndCall(CertBlocklist::PreferenceChanged,
+                                            PREF_KINTO_ONECRL_CHECKED,
+                                            this);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
 
   // Get the profile directory
   rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
                               getter_AddRefs(mBackingFile));
   if (NS_FAILED(rv) || !mBackingFile) {
     MOZ_LOG(gCertBlockPRLog, LogLevel::Debug,
            ("CertBlocklist::Init - couldn't get profile dir"));
     // Since we're returning NS_OK here, set mBackingFile to a safe value.
@@ -612,26 +623,25 @@ CertBlocklist::IsCertRevoked(const uint8
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CertBlocklist::IsBlocklistFresh(bool* _retval)
 {
   MutexAutoLock lock(mMutex);
   *_retval = false;
-  if (!sUseAMO) {
-    // for the time being, if we're not using AMO data, assume the blocklist is
-    // not fresh (in particular, prevent OneCRL OCSP bypass).
-    return NS_OK;
-  }
 
   uint32_t now = uint32_t(PR_Now() / PR_USEC_PER_SEC);
+  uint32_t lastUpdate = sUseAMO ? sLastBlocklistUpdate : sLastKintoUpdate;
+  MOZ_LOG(gCertBlockPRLog, LogLevel::Warning,
+          ("CertBlocklist::IsBlocklistFresh using AMO? %i lastUpdate is %i",
+           sUseAMO, lastUpdate));
 
-  if (now > sLastBlocklistUpdate) {
-    int64_t interval = now - sLastBlocklistUpdate;
+  if (now > lastUpdate) {
+    int64_t interval = now - lastUpdate;
     MOZ_LOG(gCertBlockPRLog, LogLevel::Warning,
            ("CertBlocklist::IsBlocklistFresh we're after the last BlocklistUpdate "
             "interval is %i, staleness %u", interval, sMaxStaleness));
     *_retval = sMaxStaleness > interval;
   }
   MOZ_LOG(gCertBlockPRLog, LogLevel::Warning,
          ("CertBlocklist::IsBlocklistFresh ? %s", *_retval ? "true" : "false"));
   return NS_OK;
@@ -646,15 +656,18 @@ CertBlocklist::PreferenceChanged(const c
   CertBlocklist* blocklist = reinterpret_cast<CertBlocklist*>(aClosure);
   MutexAutoLock lock(blocklist->mMutex);
 
   MOZ_LOG(gCertBlockPRLog, LogLevel::Warning,
          ("CertBlocklist::PreferenceChanged %s changed", aPref));
   if (strcmp(aPref, PREF_BACKGROUND_UPDATE_TIMER) == 0) {
     sLastBlocklistUpdate = Preferences::GetUint(PREF_BACKGROUND_UPDATE_TIMER,
                                                 uint32_t(0));
+  } else if (strcmp(aPref, PREF_KINTO_ONECRL_CHECKED) == 0) {
+    sLastKintoUpdate = Preferences::GetUint(PREF_KINTO_ONECRL_CHECKED,
+                                            uint32_t(0));
   } else if (strcmp(aPref, PREF_MAX_STALENESS_IN_SECONDS) == 0) {
     sMaxStaleness = Preferences::GetUint(PREF_MAX_STALENESS_IN_SECONDS,
                                          uint32_t(0));
   } else if (strcmp(aPref, PREF_ONECRL_VIA_AMO) == 0) {
     sUseAMO = Preferences::GetBool(PREF_ONECRL_VIA_AMO, true);
   }
 }
--- a/security/manager/ssl/CertBlocklist.h
+++ b/security/manager/ssl/CertBlocklist.h
@@ -75,14 +75,15 @@ private:
   // call EnsureBackingFileInitialized before operations that read or
   // modify CertBlocklist data
   nsresult EnsureBackingFileInitialized(mozilla::MutexAutoLock& lock);
   nsCOMPtr<nsIFile> mBackingFile;
 
 protected:
   static void PreferenceChanged(const char* aPref, void* aClosure);
   static uint32_t sLastBlocklistUpdate;
+  static uint32_t sLastKintoUpdate;
   static uint32_t sMaxStaleness;
   static bool sUseAMO;
   virtual ~CertBlocklist();
 };
 
 #endif // CertBlocklist_h
--- a/security/manager/ssl/tests/unit/test_ev_certs.js
+++ b/security/manager/ssl/tests/unit/test_ev_certs.js
@@ -183,31 +183,55 @@ function run_test() {
                           gEVExpected ? ["int-ev-valid", "ev-valid"]
                                       : ["ev-valid"]);
     check_ee_for_ev("ev-valid", gEVExpected);
     Services.prefs.clearUserPref("security.onecrl.maximum_staleness_in_seconds");
     ocspResponder.stop(run_next_test);
   });
 
   add_test(function () {
-    // test that setting "security.onecrl.via.amo" to false will prevent
-    // OCSP skipping
+    // test that setting "security.onecrl.via.amo" results in the correct
+    // OCSP behavior when services.kinto.onecrl.checked is in the distant past
+    // and blacklist-background-update-timer is recent
     Services.prefs.setBoolPref("security.onecrl.via.amo", false);
     // enable OneCRL OCSP skipping - allow staleness of up to 30 hours
     Services.prefs.setIntPref("security.onecrl.maximum_staleness_in_seconds", 108000);
     // set the blocklist-background-update-timer value to the recent past
+    // (services.kinto.onecrl.checked defaults to 0)
     Services.prefs.setIntPref("app.update.lastUpdateTime.blocklist-background-update-timer",
                               Math.floor(Date.now() / 1000) - 1);
     clearOCSPCache();
     // the intermediate should have an associated OCSP request
     let ocspResponder = start_ocsp_responder(
                           gEVExpected ? ["int-ev-valid", "ev-valid"]
                                       : ["ev-valid"]);
     check_ee_for_ev("ev-valid", gEVExpected);
-    Services.prefs.clearUserPref("security.onecrl.maximum_staleness_in_seconds");
+    ocspResponder.stop(run_next_test);
+  });
+
+  add_test(function () {
+    // test that setting "security.onecrl.via.amo" results in the correct
+    // OCSP behavior when services.kinto.onecrl.checked is recent
+    Services.prefs.setBoolPref("security.onecrl.via.amo", false);
+
+    // enable OneCRL OCSP skipping - allow staleness of up to 30 hours
+    Services.prefs.setIntPref("security.onecrl.maximum_staleness_in_seconds", 108000);
+
+    // now set services.kinto.onecrl.checked to a recent value
+    Services.prefs.setIntPref("services.kinto.onecrl.checked",
+                              Math.floor(Date.now() / 1000) - 1);
+
+    clearOCSPCache();
+    // the intermediate should not have an associated OCSP request
+    let ocspResponder = start_ocsp_responder(["ev-valid"]);
+    check_ee_for_ev("ev-valid", gEVExpected);
+    // The tests following this assume no OCSP bypass
+    Services.prefs.setIntPref("security.onecrl.maximum_staleness_in_seconds", 0);
+    Services.prefs.clearUserPref("security.onecrl.via.amo");
+    Services.prefs.clearUserPref("services.kinto.onecrl.checked");
     ocspResponder.stop(run_next_test);
   });
 
   // Test the EV continues to work with flags after successful EV verification
   add_test(function () {
     clearOCSPCache();
     let ocspResponder = start_ocsp_responder(
                           gEVExpected ? ["int-ev-valid", "ev-valid"]
--- a/testing/marionette/actions.js
+++ b/testing/marionette/actions.js
@@ -69,17 +69,17 @@ ActionChain.prototype.dispatchActions = 
     ctrlKey: false,
     altKey: false,
     metaKey: false
   };
 
   try {
     this.actions(commandArray, touchId, 0, keyModifiers);
   } catch (e) {
-    this.onError(e);
+    callbacks.onError(e);
     this.resetValues();
   }
 };
 
 /**
  * This function emit mouse event.
  *
  * @param {Document} doc
@@ -151,17 +151,17 @@ ActionChain.prototype.resetValues = func
  * Function to emit touch events for each finger. e.g.
  * finger=[['press', id], ['wait', 5], ['release']] touchId represents
  * the finger id, i keeps track of the current action of the chain
  * keyModifiers is an object keeping track keyDown/keyUp pairs through
  * an action chain.
  */
 ActionChain.prototype.actions = function(chain, touchId, i, keyModifiers) {
   if (i == chain.length) {
-    this.onSuccess({value: touchId || null});
+    this.onSuccess(touchId || null);
     this.resetValues();
     return;
   }
 
   let pack = chain[i];
   let command = pack[0];
   let el;
   let c;
--- a/testing/marionette/client/marionette/tests/unit/test_multi_finger.py
+++ b/testing/marionette/client/marionette/tests/unit/test_multi_finger.py
@@ -1,14 +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/.
 
 from marionette import MarionetteTestCase
-from marionette.marionette import MultiActions, Actions
+from marionette_driver.marionette import MultiActions, Actions
 
 class testMultiFinger(MarionetteTestCase):
     def test_move_element(self):
       testAction = self.marionette.absolute_url("testAction.html")
       self.marionette.navigate(testAction)
       start = self.marionette.find_element("id", "button1")
       drop = self.marionette.find_element("id", "button2")
       ele = self.marionette.find_element("id", "button3")
new file mode 100644
--- /dev/null
+++ b/testing/marionette/cookies.js
@@ -0,0 +1,131 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+Cu.import("resource://gre/modules/Log.jsm");
+Cu.import("chrome://marionette/content/error.js");
+
+const logger = Log.repository.getLogger("Marionette");
+
+this.EXPORTED_SYMBOLS = ["Cookies"];
+
+const IPV4_PORT_EXPR = /:\d+$/;
+
+/**
+ * Interface for manipulating cookies from content space.
+ */
+this.Cookies = class {
+
+  /**
+   * @param {function(): Document} documentFn
+   *     Closure that returns the current content document.
+   * @param {Proxy(SyncChromeSender)} chromeProxy
+   *     A synchronous proxy interface to chrome space.
+   */
+  constructor(documentFn, chromeProxy) {
+    this.documentFn_ = documentFn;
+    this.chrome = chromeProxy;
+  }
+
+  get document() {
+    return this.documentFn_();
+  }
+
+  [Symbol.iterator]() {
+    let path = this.document.location.pathname || "/";
+    let cs = this.chrome.getVisibleCookies(path, this.document.location.hostname)[0];
+    return cs[Symbol.iterator]();
+  }
+
+  /**
+   * Add a new cookie to a content document.
+   *
+   * @param {string} name
+   *     Cookie key.
+   * @param {string} value
+   *     Cookie value.
+   * @param {Object.<string, ?>} opts
+   *     An object with the optional fields {@code domain}, {@code path},
+   *     {@code secure}, {@code httpOnly}, and {@code expiry}.
+   *
+   * @return {Object.<string, ?>}
+   *     A serialisation of the cookie that was added.
+   *
+   * @throws UnableToSetCookieError
+   *     If the document's content type isn't HTML, the current document's
+   *     domain is a mismatch to the cookie's provided domain, or there
+   *     otherwise was issues with the input data.
+   */
+  add(name, value, opts={}) {
+    if (typeof this.document == "undefined" || !this.document.contentType.match(/html/i)) {
+      throw new UnableToSetCookieError(
+          "You may only set cookies on HTML documents: " + this.document.contentType);
+    }
+
+    if (!opts.expiry) {
+      // date twenty years into future, in seconds
+      let date = new Date();
+      let now = new Date(Date.now());
+      date.setYear(now.getFullYear() + 20);
+      opts.expiry = date.getTime() / 1000;
+    }
+
+    if (!opts.domain) {
+      opts.domain = this.document.location.host;
+    } else if (this.document.location.host.indexOf(opts.domain) < 0) {
+      throw new InvalidCookieDomainError(
+          "You may only set cookies for the current domain");
+    }
+
+    // remove port from domain, if present.
+    // unfortunately this catches IPv6 addresses by mistake
+    // TODO: Bug 814416
+    opts.domain = opts.domain.replace(IPV4_PORT_EXPR, "");
+
+    let cookie = {
+      domain: opts.domain,
+      path: opts.path,
+      name: name,
+      value: value,
+      secure: opts.secure,
+      httpOnly: opts.httpOnly,
+      session: false,
+      expiry: opts.expiry,
+    };
+    if (!this.chrome.addCookie(cookie)) {
+      throw new UnableToSetCookieError();
+    }
+
+    return cookie;
+  }
+
+  /**
+   * Delete cookie by reference or by name.
+   *
+   * @param {(string|Object.<string, ?>)} cookie
+   *     Name of cookie or cookie object.
+   *
+   * @throws {UnknownError}
+   *     If unable to delete the cookie.
+   */
+  delete(cookie) {
+    let name;
+    if (cookie.hasOwnProperty("name")) {
+      name = cookie.name;
+    } else {
+      name = cookie;
+    }
+
+    for (let candidate of this) {
+      if (candidate.name == name) {
+        if (!this.chrome.deleteCookie(candidate)) {
+          throw new UnknownError("Unable to delete cookie by name: " + name);
+        }
+      }
+    }
+  }
+};
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -1852,22 +1852,22 @@ GeckoDriver.prototype.actionChain = func
       }
 
       let cbs = {};
       cbs.onSuccess = val => resp.body.value = val;
       cbs.onError = err => { throw err; };
 
       let win = this.getCurrentWindow();
       let elm = this.curBrowser.elementManager;
-      this.actions.dispatchActions(chain, nextId, { frame: win }, elm, cbs);
+      this.actions.dispatchActions(chain, nextId, {frame: win}, elm, cbs);
       break;
 
     case Context.CONTENT:
       this.addFrameCloseListener("action chain");
-      resp.body.value = yield this.listener.actionChain({chain: chain, nextId: nextId});
+      resp.body.value = yield this.listener.actionChain(chain, nextId);
       break;
   }
 };
 
 /**
  * A multi-action chain.
  *
  * @param {Object} value
@@ -1877,18 +1877,17 @@ GeckoDriver.prototype.actionChain = func
  */
 GeckoDriver.prototype.multiAction = function(cmd, resp) {
   switch (this.context) {
     case Context.CHROME:
       throw new WebDriverError("Command 'multiAction' is not available in chrome context");
 
     case Context.CONTENT:
       this.addFrameCloseListener("multi action chain");
-      yield this.listener.multiAction(
-          {value: cmd.parameters.value, maxlen: cmd.parameters.max_len});
+      yield this.listener.multiAction( cmd.parameters.value, cmd.parameters.max_len);
       break;
   }
 };
 
 /**
  * Find an element using the indicated search strategy.
  *
  * @param {string} using
@@ -2286,37 +2285,74 @@ GeckoDriver.prototype.clearElement = fun
 GeckoDriver.prototype.switchToShadowRoot = function(cmd, resp) {
   let id;
   if (cmd.parameters) { id = cmd.parameters.id; }
   yield this.listener.switchToShadowRoot(id);
 };
 
 /** Add a cookie to the document. */
 GeckoDriver.prototype.addCookie = function(cmd, resp) {
-  yield this.listener.addCookie({cookie: cmd.parameters.cookie});
+  let cb = msg => {
+    this.mm.removeMessageListener("Marionette:addCookie", cb);
+    let cookie = msg.json;
+    Services.cookies.add(
+        cookie.domain,
+        cookie.path,
+        cookie.name,
+        cookie.value,
+        cookie.secure,
+        cookie.httpOnly,
+        cookie.session,
+        cookie.expiry);
+    return true;
+  };
+  this.mm.addMessageListener("Marionette:addCookie", cb);
+  yield this.listener.addCookie(cmd.parameters.cookie);
 };
 
 /**
  * Get all the cookies for the current domain.
  *
  * This is the equivalent of calling {@code document.cookie} and parsing
  * the result.
  */
 GeckoDriver.prototype.getCookies = function(cmd, resp) {
   resp.body = yield this.listener.getCookies();
 };
 
 /** Delete all cookies that are visible to a document. */
 GeckoDriver.prototype.deleteAllCookies = function(cmd, resp) {
+  let cb = msg => {
+    let cookie = msg.json;
+    cookieManager.remove(
+        cookie.host,
+        cookie.name,
+        cookie.path,
+        false);
+    return true;
+  };
+  this.mm.addMessageListener("Marionette:deleteCookie", cb);
   yield this.listener.deleteAllCookies();
+  this.mm.removeMessageListener("Marionette:deleteCookie", cb);
 };
 
 /** Delete a cookie by name. */
 GeckoDriver.prototype.deleteCookie = function(cmd, resp) {
-  yield this.listener.deleteCookie({name: cmd.parameters.name});
+  let cb = msg => {
+    this.mm.removeMessageListener("Marionette:deleteCookie", cb);
+    let cookie = msg.json;
+    cookieManager.remove(
+        cookie.host,
+        cookie.name,
+        cookie.path,
+        false);
+    return true;
+  };
+  this.mm.addMessageListener("Marionette:deleteCookie", cb);
+  yield this.listener.deleteCookie(cmd.parameters.name);
 };
 
 /**
  * Close the current window, ending the session if it's the last
  * window currently open.
  *
  * On B2G this method is a noop and will return immediately.
  */
@@ -2820,17 +2856,17 @@ GeckoDriver.prototype.receiveMessage = f
         if (message.json.storePrevious) {
           this.previousFrameElement = this.currentFrameElement;
         }
         this.currentFrameElement = message.json.frameValue;
       }
       break;
 
     case "Marionette:getVisibleCookies":
-      let [currentPath, host] = message.json.value;
+      let [currentPath, host] = message.json;
       let isForCurrentPath = path => currentPath.indexOf(path) != -1;
       let results = [];
 
       let en = cookieManager.getCookiesFromHost(host);
       while (en.hasMoreElements()) {
         let cookie = en.getNext().QueryInterface(Ci.nsICookie2);
         // take the hostname and progressively shorten
         let hostname = host;
@@ -2848,38 +2884,16 @@ GeckoDriver.prototype.receiveMessage = f
             });
             break;
           }
           hostname = hostname.replace(/^.*?\./, "");
         } while (hostname.indexOf(".") != -1);
       }
       return results;
 
-    case "Marionette:addCookie":
-      let cookieToAdd = message.json.value;
-      Services.cookies.add(
-          cookieToAdd.domain,
-          cookieToAdd.path,
-          cookieToAdd.name,
-          cookieToAdd.value,
-          cookieToAdd.secure,
-          cookieToAdd.httpOnly,
-          false,
-          cookieToAdd.expiry);
-      return true;
-
-    case "Marionette:deleteCookie":
-      let cookieToDelete = message.json.value;
-      cookieManager.remove(
-          cookieToDelete.host,
-          cookieToDelete.name,
-          cookieToDelete.path,
-          false);
-      return true;
-
     case "Marionette:getFiles":
       // Generates file objects to send back to the content script
       // for handling file uploads.
       let val = message.json.value;
       let command_id = message.json.command_id;
       Cu.importGlobalProperties(["File"]);
       try {
         let file = new File(val);
--- a/testing/marionette/error.js
+++ b/testing/marionette/error.js
@@ -49,21 +49,16 @@ const XPCOM_EXCEPTIONS = [];
   for (let prop in Cr) {
     XPCOM_EXCEPTIONS.push(Cr[prop]);
   }
 }
 
 this.error = {};
 
 /**
- * Determines if the given status is successful.
- */
-error.isSuccess = status => status === "success";
-
-/**
  * Checks if obj is an instance of the Error prototype in a safe manner.
  * Prefer using this over using instanceof since the Error prototype
  * isn't unique across browsers, and XPCOM exceptions are special
  * snowflakes.
  *
  * @param {*} val
  *     Any value that should be undergo the test for errorness.
  * @return {boolean}
--- a/testing/marionette/frame-manager.js
+++ b/testing/marionette/frame-manager.js
@@ -185,19 +185,17 @@ FrameManager.prototype = {
     mm.addWeakMessageListener("Marionette:error", this.server);
     mm.addWeakMessageListener("Marionette:emitTouchEvent", this.server);
     mm.addWeakMessageListener("Marionette:log", this.server);
     mm.addWeakMessageListener("Marionette:runEmulatorCmd", this.server);
     mm.addWeakMessageListener("Marionette:runEmulatorShell", this.server);
     mm.addWeakMessageListener("Marionette:shareData", this.server);
     mm.addWeakMessageListener("Marionette:switchToModalOrigin", this.server);
     mm.addWeakMessageListener("Marionette:switchedToFrame", this.server);
-    mm.addWeakMessageListener("Marionette:addCookie", this.server);
     mm.addWeakMessageListener("Marionette:getVisibleCookies", this.server);
-    mm.addWeakMessageListener("Marionette:deleteCookie", this.server);
     mm.addWeakMessageListener("Marionette:register", this.server);
     mm.addWeakMessageListener("Marionette:listenersAttached", this.server);
     mm.addWeakMessageListener("Marionette:getFiles", this.server);
     mm.addWeakMessageListener("MarionetteFrame:handleModal", this);
     mm.addWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
     mm.addWeakMessageListener("MarionetteFrame:getInterruptedState", this);
   },
 
@@ -217,18 +215,16 @@ FrameManager.prototype = {
     mm.removeWeakMessageListener("Marionette:ok", this.server);
     mm.removeWeakMessageListener("Marionette:done", this.server);
     mm.removeWeakMessageListener("Marionette:error", this.server);
     mm.removeWeakMessageListener("Marionette:log", this.server);
     mm.removeWeakMessageListener("Marionette:shareData", this.server);
     mm.removeWeakMessageListener("Marionette:runEmulatorCmd", this.server);
     mm.removeWeakMessageListener("Marionette:runEmulatorShell", this.server);
     mm.removeWeakMessageListener("Marionette:switchedToFrame", this.server);
-    mm.removeWeakMessageListener("Marionette:addCookie", this.server);
     mm.removeWeakMessageListener("Marionette:getVisibleCookies", this.server);
-    mm.removeWeakMessageListener("Marionette:deleteCookie", this.server);
     mm.removeWeakMessageListener("Marionette:listenersAttached", this.server);
     mm.removeWeakMessageListener("Marionette:register", this.server);
     mm.removeWeakMessageListener("Marionette:getFiles", this.server);
     mm.removeWeakMessageListener("MarionetteFrame:handleModal", this);
     mm.removeWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
   }
 };
--- a/testing/marionette/jar.mn
+++ b/testing/marionette/jar.mn
@@ -17,16 +17,17 @@ marionette.jar:
   content/ChromeUtils.js  (ChromeUtils.js)
   content/error.js (error.js)
   content/command.js (command.js)
   content/dispatcher.js (dispatcher.js)
   content/emulator.js (emulator.js)
   content/modal.js (modal.js)
   content/proxy.js (proxy.js)
   content/capture.js (capture.js)
+  content/cookies.js (cookies.js)
 #ifdef ENABLE_TESTS
   content/test.xul  (client/marionette/chrome/test.xul)
   content/test2.xul  (client/marionette/chrome/test2.xul)
   content/test_nested_iframe.xul  (client/marionette/chrome/test_nested_iframe.xul)
   content/test_anonymous_content.xul  (client/marionette/chrome/test_anonymous_content.xul)
 #endif
 
 % content specialpowers %content/
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -9,18 +9,21 @@ var uuidGen = Cc["@mozilla.org/uuid-gene
 
 var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
                .getService(Ci.mozIJSSubScriptLoader);
 
 loader.loadSubScript("chrome://marionette/content/simpletest.js");
 loader.loadSubScript("chrome://marionette/content/common.js");
 loader.loadSubScript("chrome://marionette/content/actions.js");
 Cu.import("chrome://marionette/content/capture.js");
+Cu.import("chrome://marionette/content/cookies.js");
 Cu.import("chrome://marionette/content/elements.js");
 Cu.import("chrome://marionette/content/error.js");
+Cu.import("chrome://marionette/content/proxy.js");
+
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 var utils = {};
 utils.window = content;
 // Load Event/ChromeUtils for use with JS scripts:
 loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils);
@@ -72,16 +75,19 @@ var readyStateTimer = Cc["@mozilla.org/t
 // timer for navigation commands.
 var navTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
 var onDOMContentLoaded;
 // Send move events about this often
 var EVENT_INTERVAL = 30; // milliseconds
 // last touch for each fingerId
 var multiLast = {};
 
+var chrome = proxy.toChrome(sendSyncMessage.bind(this));
+var cookies = new Cookies(() => curContainer.frame.document, chrome);
+
 Cu.import("resource://gre/modules/Log.jsm");
 var logger = Log.repository.getLogger("Marionette");
 logger.info("loaded listener.js");
 var modalHandler = function() {
   // This gets called on the system app only since it receives the mozbrowserprompt event
   sendSyncMessage("Marionette:switchedToFrame", { frameValue: null, storePrevious: true });
   let isLocal = sendSyncMessage("MarionetteFrame:handleModal", {})[0].value;
   if (isLocal) {
@@ -212,29 +218,34 @@ var findElementsContentFn = dispatch(fin
 var isElementSelectedFn = dispatch(isElementSelected);
 var clearElementFn = dispatch(clearElement);
 var isElementDisplayedFn = dispatch(isElementDisplayed);
 var getElementValueOfCssPropertyFn = dispatch(getElementValueOfCssProperty);
 var switchToShadowRootFn = dispatch(switchToShadowRoot);
 var getCookiesFn = dispatch(getCookies);
 var singleTapFn = dispatch(singleTap);
 var takeScreenshotFn = dispatch(takeScreenshot);
+var actionChainFn = dispatch(actionChain);
+var multiActionFn = dispatch(multiAction);
+var addCookieFn = dispatch(addCookie);
+var deleteCookieFn = dispatch(deleteCookie);
+var deleteAllCookiesFn = dispatch(deleteAllCookies);
 
 /**
  * Start all message listeners
  */
 function startListeners() {
   addMessageListenerId("Marionette:receiveFiles", receiveFiles);
   addMessageListenerId("Marionette:newSession", newSession);
   addMessageListenerId("Marionette:executeScript", executeScript);
   addMessageListenerId("Marionette:executeAsyncScript", executeAsyncScript);
   addMessageListenerId("Marionette:executeJSScript", executeJSScript);
   addMessageListenerId("Marionette:singleTap", singleTapFn);
-  addMessageListenerId("Marionette:actionChain", actionChain);
-  addMessageListenerId("Marionette:multiAction", multiAction);
+  addMessageListenerId("Marionette:actionChain", actionChainFn);
+  addMessageListenerId("Marionette:multiAction", multiActionFn);
   addMessageListenerId("Marionette:get", get);
   addMessageListenerId("Marionette:pollForReadyState", pollForReadyState);
   addMessageListenerId("Marionette:cancelRequest", cancelRequest);
   addMessageListenerId("Marionette:getCurrentUrl", getCurrentUrlFn);
   addMessageListenerId("Marionette:getTitle", getTitleFn);
   addMessageListenerId("Marionette:getPageSource", getPageSourceFn);
   addMessageListenerId("Marionette:goBack", goBackFn);
   addMessageListenerId("Marionette:goForward", goForward);
@@ -258,20 +269,20 @@ function startListeners() {
   addMessageListenerId("Marionette:switchToShadowRoot", switchToShadowRootFn);
   addMessageListenerId("Marionette:deleteSession", deleteSession);
   addMessageListenerId("Marionette:sleepSession", sleepSession);
   addMessageListenerId("Marionette:emulatorCmdResult", emulatorCmdResult);
   addMessageListenerId("Marionette:importScript", importScript);
   addMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus);
   addMessageListenerId("Marionette:setTestName", setTestName);
   addMessageListenerId("Marionette:takeScreenshot", takeScreenshotFn);
-  addMessageListenerId("Marionette:addCookie", addCookie);
+  addMessageListenerId("Marionette:addCookie", addCookieFn);
   addMessageListenerId("Marionette:getCookies", getCookiesFn);
-  addMessageListenerId("Marionette:deleteAllCookies", deleteAllCookies);
-  addMessageListenerId("Marionette:deleteCookie", deleteCookie);
+  addMessageListenerId("Marionette:deleteAllCookies", deleteAllCookiesFn);
+  addMessageListenerId("Marionette:deleteCookie", deleteCookieFn);
 }
 
 /**
  * Used during newSession and restart, called to set up the modal dialog listener in b2g
  */
 function waitForReady() {
   if (content.document.readyState == 'complete') {
     readyStateTimer.cancel();
@@ -328,18 +339,18 @@ function restart(msg) {
  */
 function deleteSession(msg) {
   removeMessageListenerId("Marionette:receiveFiles", receiveFiles);
   removeMessageListenerId("Marionette:newSession", newSession);
   removeMessageListenerId("Marionette:executeScript", executeScript);
   removeMessageListenerId("Marionette:executeAsyncScript", executeAsyncScript);
   removeMessageListenerId("Marionette:executeJSScript", executeJSScript);
   removeMessageListenerId("Marionette:singleTap", singleTapFn);
-  removeMessageListenerId("Marionette:actionChain", actionChain);
-  removeMessageListenerId("Marionette:multiAction", multiAction);
+  removeMessageListenerId("Marionette:actionChain", actionChainFn);
+  removeMessageListenerId("Marionette:multiAction", multiActionFn);
   removeMessageListenerId("Marionette:get", get);
   removeMessageListenerId("Marionette:pollForReadyState", pollForReadyState);
   removeMessageListenerId("Marionette:cancelRequest", cancelRequest);
   removeMessageListenerId("Marionette:getTitle", getTitleFn);
   removeMessageListenerId("Marionette:getPageSource", getPageSourceFn);
   removeMessageListenerId("Marionette:getCurrentUrl", getCurrentUrlFn);
   removeMessageListenerId("Marionette:goBack", goBackFn);
   removeMessageListenerId("Marionette:goForward", goForward);
@@ -363,20 +374,20 @@ function deleteSession(msg) {
   removeMessageListenerId("Marionette:switchToShadowRoot", switchToShadowRootFn);
   removeMessageListenerId("Marionette:deleteSession", deleteSession);
   removeMessageListenerId("Marionette:sleepSession", sleepSession);
   removeMessageListenerId("Marionette:emulatorCmdResult", emulatorCmdResult);
   removeMessageListenerId("Marionette:importScript", importScript);
   removeMessageListenerId("Marionette:getAppCacheStatus", getAppCacheStatus);
   removeMessageListenerId("Marionette:setTestName", setTestName);
   removeMessageListenerId("Marionette:takeScreenshot", takeScreenshotFn);
-  removeMessageListenerId("Marionette:addCookie", addCookie);
+  removeMessageListenerId("Marionette:addCookie", addCookieFn);
   removeMessageListenerId("Marionette:getCookies", getCookiesFn);
-  removeMessageListenerId("Marionette:deleteAllCookies", deleteAllCookies);
-  removeMessageListenerId("Marionette:deleteCookie", deleteCookie);
+  removeMessageListenerId("Marionette:deleteAllCookies", deleteAllCookiesFn);
+  removeMessageListenerId("Marionette:deleteCookie", deleteCookieFn);
   if (isB2G) {
     content.removeEventListener("mozbrowsershowmodalprompt", modalHandler, false);
   }
   elementManager.reset();
   // reset container frame to the top-most frame
   curContainer = { frame: content, shadowRoot: null };
   curContainer.frame.focus();
   actions.touchIds = {};
@@ -1055,42 +1066,32 @@ function createATouch(el, corx, cory, to
   let win = doc.defaultView;
   let [clientX, clientY, pageX, pageY, screenX, screenY] =
     actions.getCoordinateInfo(el, corx, cory);
   let atouch = doc.createTouch(win, el, touchId, pageX, pageY, screenX, screenY, clientX, clientY);
   return atouch;
 }
 
 /**
- * Function to start action chain on one finger
+ * Start action chain on one finger.
  */
-function actionChain(msg) {
-  let command_id = msg.json.command_id;
-  let args = msg.json.chain;
-  let touchId = msg.json.nextId;
-
-  let callbacks = {};
-  callbacks.onSuccess = value => sendResponse(value, command_id);
-  callbacks.onError = err => sendError(err, command_id);
-
+function actionChain(chain, touchId) {
   let touchProvider = {};
   touchProvider.createATouch = createATouch;
   touchProvider.emitTouchEvent = emitTouchEvent;
 
-  try {
+  return new Promise((resolve, reject) => {
     actions.dispatchActions(
-        args,
+        chain,
         touchId,
         curContainer,
         elementManager,
-        callbacks,
+        {onSuccess: resolve, onError: reject},
         touchProvider);
-  } catch (e) {
-    sendError(e, command_id);
-  }
+  });
 }
 
 /**
  * Function to emit touch events which allow multi touch on the screen
  * @param type represents the type of event, touch represents the current touch,touches are all pending touches
  */
 function emitMultiEvents(type, touch, touches) {
   let target = touch.target;
@@ -1119,26 +1120,23 @@ function emitMultiEvents(type, touch, to
                        changedTouches);
   target.dispatchEvent(event);
 }
 
 /**
  * Function to dispatch one set of actions
  * @param touches represents all pending touches, batchIndex represents the batch we are dispatching right now
  */
-function setDispatch(batches, touches, command_id, batchIndex) {
-  if (typeof batchIndex === "undefined") {
-    batchIndex = 0;
-  }
+function setDispatch(batches, touches, batchIndex=0) {
   // check if all the sets have been fired
   if (batchIndex >= batches.length) {
     multiLast = {};
-    sendOk(command_id);
     return;
   }
+
   // a set of actions need to be done
   let batch = batches[batchIndex];
   // each action for some finger
   let pack;
   // the touch id for the finger (pack)
   let touchId;
   // command for the finger
   let command;
@@ -1147,116 +1145,120 @@ function setDispatch(batches, touches, c
   let corx;
   let cory;
   let touch;
   let lastTouch;
   let touchIndex;
   let waitTime = 0;
   let maxTime = 0;
   let c;
+
+  // loop through the batch
   batchIndex++;
-  // loop through the batch
   for (let i = 0; i < batch.length; i++) {
     pack = batch[i];
     touchId = pack[0];
     command = pack[1];
+
     switch (command) {
-      case 'press':
+      case "press":
         el = elementManager.getKnownElement(pack[2], curContainer);
         c = coordinates(el, pack[3], pack[4]);
         touch = createATouch(el, c.x, c.y, touchId);
         multiLast[touchId] = touch;
         touches.push(touch);
-        emitMultiEvents('touchstart', touch, touches);
+        emitMultiEvents("touchstart", touch, touches);
         break;
-      case 'release':
+
+      case "release":
         touch = multiLast[touchId];
         // the index of the previous touch for the finger may change in the touches array
         touchIndex = touches.indexOf(touch);
         touches.splice(touchIndex, 1);
-        emitMultiEvents('touchend', touch, touches);
+        emitMultiEvents("touchend", touch, touches);
         break;
-      case 'move':
+
+      case "move":
         el = elementManager.getKnownElement(pack[2], curContainer);
         c = coordinates(el);
         touch = createATouch(multiLast[touchId].target, c.x, c.y, touchId);
         touchIndex = touches.indexOf(lastTouch);
         touches[touchIndex] = touch;
         multiLast[touchId] = touch;
-        emitMultiEvents('touchmove', touch, touches);
+        emitMultiEvents("touchmove", touch, touches);
         break;
-      case 'moveByOffset':
+
+      case "moveByOffset":
         el = multiLast[touchId].target;
         lastTouch = multiLast[touchId];
         touchIndex = touches.indexOf(lastTouch);
         let doc = el.ownerDocument;
         let win = doc.defaultView;
         // since x and y are relative to the last touch, therefore, it's relative to the position of the last touch
         let clientX = lastTouch.clientX + pack[2],
             clientY = lastTouch.clientY + pack[3];
         let pageX = clientX + win.pageXOffset,
             pageY = clientY + win.pageYOffset;
         let screenX = clientX + win.mozInnerScreenX,
             screenY = clientY + win.mozInnerScreenY;
         touch = doc.createTouch(win, el, touchId, pageX, pageY, screenX, screenY, clientX, clientY);
         touches[touchIndex] = touch;
         multiLast[touchId] = touch;
-        emitMultiEvents('touchmove', touch, touches);
+        emitMultiEvents("touchmove", touch, touches);
         break;
-      case 'wait':
-        if (pack[2] != undefined ) {
-          waitTime = pack[2]*1000;
+
+      case "wait":
+        if (typeof pack[2] != "undefined") {
+          waitTime = pack[2] * 1000;
           if (waitTime > maxTime) {
             maxTime = waitTime;
           }
         }
         break;
-    }//end of switch block
-  }//end of for loop
+    }
+  }
+
   if (maxTime != 0) {
-    checkTimer.initWithCallback(function(){setDispatch(batches, touches, command_id, batchIndex);}, maxTime, Ci.nsITimer.TYPE_ONE_SHOT);
-  }
-  else {
-    setDispatch(batches, touches, command_id, batchIndex);
+    checkTimer.initWithCallback(function() {
+      setDispatch(batches, touches, batchIndex);
+    }, maxTime, Ci.nsITimer.TYPE_ONE_SHOT);
+  } else {
+    setDispatch(batches, touches, batchIndex);
   }
 }
 
 /**
- * Function to start multi-action
+ * Start multi-action.
+ *
+ * @param {Number} maxLen
+ *     Longest action chain for one finger.
  */
-function multiAction(msg) {
-  let command_id = msg.json.command_id;
-  let args = msg.json.value;
-  // maxlen is the longest action chain for one finger
-  let maxlen = msg.json.maxlen;
-  try {
-    // unwrap the original nested array
-    let commandArray = elementManager.convertWrappedArguments(args, curContainer);
-    let concurrentEvent = [];
-    let temp;
-    for (let i = 0; i < maxlen; i++) {
-      let row = [];
-      for (let j = 0; j < commandArray.length; j++) {
-        if (commandArray[j][i] != undefined) {
-          // add finger id to the front of each action, i.e. [finger_id, action, element]
-          temp = commandArray[j][i];
-          temp.unshift(j);
-          row.push(temp);
-        }
+function multiAction(args, maxLen) {
+  // unwrap the original nested array
+  let commandArray = elementManager.convertWrappedArguments(args, curContainer);
+  let concurrentEvent = [];
+  let temp;
+  for (let i = 0; i < maxLen; i++) {
+    let row = [];
+    for (let j = 0; j < commandArray.length; j++) {
+      if (typeof commandArray[j][i] != "undefined") {
+        // add finger id to the front of each action, i.e. [finger_id, action, element]
+        temp = commandArray[j][i];
+        temp.unshift(j);
+        row.push(temp);
       }
-      concurrentEvent.push(row);
     }
-    // now concurrent event is made of sets where each set contain a list of actions that need to be fired.
-    // note: each action belongs to a different finger
-    // pendingTouches keeps track of current touches that's on the screen
-    let pendingTouches = [];
-    setDispatch(concurrentEvent, pendingTouches, command_id);
-  } catch (e) {
-    sendError(e, command_id);
+    concurrentEvent.push(row);
   }
+
+  // now concurrent event is made of sets where each set contain a list of actions that need to be fired.
+  // note: each action belongs to a different finger
+  // pendingTouches keeps track of current touches that's on the screen
+  let pendingTouches = [];
+  setDispatch(concurrentEvent, pendingTouches);
 }
 
 /*
  * This implements the latter part of a get request (for the case we need to resume one
  * when a remoteness update happens in the middle of a navigate request). This is most of
  * of the work of a navigate request, but doesn't assume DOMContentLoaded is yet to fire.
  */
 function pollForReadyState(msg, start, callback) {
@@ -1662,17 +1664,25 @@ function clearElement(id) {
  * @param {WebElement} id
  *     Reference to web element.
  */
 function switchToShadowRoot(id) {
   if (!id) {
     // If no host element is passed, attempt to find a parent shadow root or, if
     // none found, unset the current shadow root
     if (curContainer.shadowRoot) {
-      let parent = curContainer.shadowRoot.host;
+      let parent;
+      try {
+        parent = curContainer.shadowRoot.host;
+      } catch (e) {
+        // There is a chance that host element is dead and we are trying to
+        // access a dead object.
+        curContainer.shadowRoot = null;
+        return;
+      }
       while (parent && !(parent instanceof curContainer.frame.ShadowRoot)) {
         parent = parent.parentNode;
       }
       curContainer.shadowRoot = parent;
     }
     return;
   }
 
@@ -1832,65 +1842,26 @@ function switchToFrame(msg) {
     curContainer.frame = curContainer.frame.contentWindow;
     if (msg.json.focus)
       curContainer.frame.focus();
     checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
   }
 
   sendResponse({value: rv}, command_id);
 }
- /**
-  * Add a cookie to the document
-  */
-function addCookie(msg) {
-  let cookie = msg.json.cookie;
-  if (!cookie.expiry) {
-    var date = new Date();
-    var thePresent = new Date(Date.now());
-    date.setYear(thePresent.getFullYear() + 20);
-    cookie.expiry = date.getTime() / 1000;  // Stored in seconds.
-  }
 
-  if (!cookie.domain) {
-    var location = curContainer.frame.document.location;
-    cookie.domain = location.hostname;
-  } else {
-    var currLocation = curContainer.frame.location;
-    var currDomain = currLocation.host;
-    if (currDomain.indexOf(cookie.domain) == -1) {
-      sendError(new InvalidCookieDomainError("You may only set cookies for the current domain"), msg.json.command_id);
-    }
-  }
-
-  // The cookie's domain may include a port. Which is bad. Remove it
-  // We'll catch ip6 addresses by mistake. Since no-one uses those
-  // this will be okay for now. See Bug 814416
-  if (cookie.domain.match(/:\d+$/)) {
-    cookie.domain = cookie.domain.replace(/:\d+$/, '');
-  }
-
-  var document = curContainer.frame.document;
-  if (!document || !document.contentType.match(/html/i)) {
-    sendError(new UnableToSetCookieError("You may only set cookies on html documents"), msg.json.command_id);
-  }
-
-  let added = sendSyncMessage("Marionette:addCookie", {value: cookie});
-  if (added[0] !== true) {
-    sendError(new UnableToSetCookieError(), msg.json.command_id);
-    return;
-  }
-  sendOk(msg.json.command_id);
+function addCookie(cookie) {
+  cookies.add(cookie.name, cookie.value, cookie);
 }
 
 /**
  * Get all cookies for the current domain.
  */
 function getCookies() {
   let rv = [];
-  let cookies = getVisibleCookies(curContainer.frame.location);
 
   for (let cookie of cookies) {
     let expires = cookie.expires;
     // session cookie, don't return an expiry
     if (expires == 0) {
       expires = null;
     // date before epoch time, cap to epoch
     } else if (expires == 1) {
@@ -1906,57 +1877,29 @@ function getCookies() {
       'expiry': expires
     });
   }
 
   return rv;
 }
 
 /**
- * Delete a cookie by name
+ * Delete a cookie by name.
  */
-function deleteCookie(msg) {
-  let toDelete = msg.json.name;
-  let cookies = getVisibleCookies(curContainer.frame.location);
-  for (let cookie of cookies) {
-    if (cookie.name == toDelete) {
-      let deleted = sendSyncMessage("Marionette:deleteCookie", {value: cookie});
-      if (deleted[0] !== true) {
-        sendError(new UnknownError("Could not delete cookie: " + msg.json.name), msg.json.command_id);
-        return;
-      }
-    }
-  }
-
-  sendOk(msg.json.command_id);
+function deleteCookie(name) {
+  cookies.delete(name);
 }
 
 /**
- * Delete all the visibile cookies on a page
+ * Delete all the visibile cookies on a page.
  */
-function deleteAllCookies(msg) {
-  let cookies = getVisibleCookies(curContainer.frame.location);
+function deleteAllCookies() {
   for (let cookie of cookies) {
-    let deleted = sendSyncMessage("Marionette:deleteCookie", {value: cookie});
-    if (!deleted[0]) {
-      sendError(new UnknownError("Could not delete cookie: " + JSON.stringify(cookie)), msg.json.command_id);
-      return;
-    }
+    cookies.delete(cookie);
   }
-  sendOk(msg.json.command_id);
-}
-
-/**
- * Get all the visible cookies from a location
- */
-function getVisibleCookies(location) {
-  let currentPath = location.pathname || '/';
-  let result = sendSyncMessage("Marionette:getVisibleCookies",
-                               {value: [currentPath, location.hostname]});
-  return result[0];
 }
 
 function getAppCacheStatus(msg) {
   sendResponse({ value: curContainer.frame.applicationCache.status },
                msg.json.command_id);
 }
 
 // emulator callbacks
--- a/testing/marionette/proxy.js
+++ b/testing/marionette/proxy.js
@@ -1,164 +1,202 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("chrome://marionette/content/modal.js");
 
 this.EXPORTED_SYMBOLS = ["proxy"];
 
 const MARIONETTE_OK = "Marionette:ok";
 const MARIONETTE_DONE = "Marionette:done";
 const MARIONETTE_ERROR = "Marionette:error";
 
 const logger = Log.repository.getLogger("Marionette");
 const uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
 
+// Proxy handler that traps requests to get a property.  Will prioritise
+// properties that exist on the object's own prototype.
+var ownPriorityGetterTrap = {
+  get: (obj, prop) => {
+    if (obj.hasOwnProperty(prop)) {
+      return obj[prop];
+    }
+    return (...args) => obj.send(prop, args);
+  }
+};
+
 this.proxy = {};
 
 /**
  * Creates a transparent interface between the chrome- and content
- * processes.
+ * contexts.
  *
  * Calls to this object will be proxied via the message manager to the active
  * browsing context (content) and responses will be provided back as
  * promises.
  *
  * The argument sequence is serialised and passed as an array, unless it
  * consists of a single object type that isn't null, in which case it's
  * passed literally.  The latter specialisation is temporary to achieve
  * backwards compatibility with listener.js.
  *
  * @param {function(): (nsIMessageSender|nsIMessageBroadcaster)} mmFn
  *     Function returning the current message manager.
  * @param {function(string, Object, number)} sendAsyncFn
  *     Callback for sending async messages to the current listener.
  */
 proxy.toListener = function(mmFn, sendAsyncFn) {
-  let sender = new ContentSender(mmFn, sendAsyncFn);
-  let handler = {
-    get: (obj, prop) => {
-      if (obj.hasOwnProperty(prop)) {
-        return obj[prop];
-      }
-      return (...args) => obj.send(prop, args);
-    }
-  };
-  return new Proxy(sender, handler);
+  let sender = new AsyncContentSender(mmFn, sendAsyncFn);
+  return new Proxy(sender, ownPriorityGetterTrap);
 };
 
 /**
- * The ContentSender allows one to make synchronous calls to the
+ * The AsyncContentSender allows one to make synchronous calls to the
  * message listener of the content frame of the current browsing context.
  *
  * Presumptions about the responses from content space are made so we
  * can provide a nicer API on top of the message listener primitives that
  * make calls from chrome- to content space seem synchronous by leveraging
  * promises.
  *
  * The promise is guaranteed not to resolve until the execution of the
  * command in content space is complete.
- *
- * @param {function(): (nsIMessageSender|nsIMessageBroadcaster)} mmFn
- *     Function returning the current message manager.
- * @param {function(string, Object, number)} sendAsyncFn
- *     Callback for sending async messages to the current listener.
  */
-var ContentSender = function(mmFn, sendAsyncFn) {
-  this.curId = null;
-  this.sendAsync = sendAsyncFn;
-  this.mmFn_ = mmFn;
-  this._listeners = [];
-};
-
-Object.defineProperty(ContentSender.prototype, "mm", {
-  get: function() { return this.mmFn_(); }
-});
-
-ContentSender.prototype.removeListeners = function () {
-  this._listeners.map(l => this.mm.removeMessageListener(l[0], l[1]));
-  this._listeners = [];
-}
+this.AsyncContentSender = class {
+  constructor(mmFn, sendAsyncFn) {
+    this.curId = null;
+    this.sendAsync = sendAsyncFn;
+    this.mmFn_ = mmFn;
+    this._listeners = [];
+  }
 
-/**
- * Call registered function in the frame script environment of the
- * current browsing context's content frame.
- *
- * @param {string} name
- *     Function to call in the listener, e.g. for "Marionette:foo8",
- *     use "foo".
- * @param {Array} args
- *     Argument list to pass the function.  If args has a single entry
- *     that is an object, we assume it's an old style dispatch, and
- *     the object will passed literally.
- *
- * @return {Promise}
- *     A promise that resolves to the result of the command.
- */
-ContentSender.prototype.send = function(name, args) {
-  if (this._listeners[0]) {
-    // A prior (probably timed-out) request has left listeners behind.
-    // Remove them before proceeding.
-    logger.warn("A previous failed command left content listeners behind!");
-    this.removeListeners();
+  get mm() {
+    return this.mmFn_();
+  }
+
+  removeListeners() {
+    this._listeners.map(l => this.mm.removeMessageListener(l[0], l[1]));
+    this._listeners = [];
   }
 
-  this.curId = uuidgen.generateUUID().toString();
+  /**
+   * Call registered function in the frame script environment of the
+   * current browsing context's content frame.
+   *
+   * @param {string} name
+   *     Function to call in the listener, e.g. for "Marionette:foo8",
+   *     use "foo".
+   * @param {Array} args
+   *     Argument list to pass the function.  If args has a single entry
+   *     that is an object, we assume it's an old style dispatch, and
+   *     the object will passed literally.
+   *
+   * @return {Promise}
+   *     A promise that resolves to the result of the command.
+   */
+  send(name, args) {
+    if (this._listeners[0]) {
+      // A prior (probably timed-out) request has left listeners behind.
+      // Remove them before proceeding.
+      logger.warn("A previous failed command left content listeners behind!");
+      this.removeListeners();
+    }
+
+    this.curId = uuidgen.generateUUID().toString();
 
-  let proxy = new Promise((resolve, reject) => {
-    let removeListeners = (n, fn) => {
-      let rmFn = msg => {
-        if (this.curId !== msg.json.command_id) {
-          logger.warn("Skipping out-of-sync response from listener: " +
-              `Expected response to ${name} with ID ${this.curId}, ` +
-              "but got: " + msg.name + msg.json.toSource());
-          return;
-        }
+    let proxy = new Promise((resolve, reject) => {
+      let removeListeners = (n, fn) => {
+        let rmFn = msg => {
+          if (this.curId !== msg.json.command_id) {
+            logger.warn("Skipping out-of-sync response from listener: " +
+                `Expected response to ${name} with ID ${this.curId}, ` +
+                "but got: " + msg.name + msg.json.toSource());
+            return;
+          }
+
+          this.removeListeners();
+          modal.removeHandler(handleDialog);
 
-        this.removeListeners();
-        modal.removeHandler(handleDialog);
+          fn(msg);
+          this.curId = null;
+        };
+
+        this._listeners.push([n, rmFn]);
+        return rmFn;
+      };
 
-        fn(msg);
-        this.curId = null;
+      let okListener = () => resolve();
+      let valListener = msg => resolve(msg.json.value);
+      let errListener = msg => reject(msg.objects.error);
+
+      let handleDialog = (subject, topic) => {
+        this.removeListeners()
+        modal.removeHandler(handleDialog);
+        this.sendAsync("cancelRequest");
+        resolve();
       };
 
-      this._listeners.push([n, rmFn]);
-      return rmFn;
-    };
-
-    let okListener = () => resolve();
-    let valListener = msg => resolve(msg.json.value);
-    let errListener = msg => reject(msg.objects.error);
-
-    let handleDialog = (subject, topic) => {
-      this.removeListeners()
-      modal.removeHandler(handleDialog);
-      this.sendAsync("cancelRequest");
-      resolve();
-    };
+      // start content process listeners, and install observers for global-
+      // and tab modal dialogues
+      this.mm.addMessageListener(MARIONETTE_OK, removeListeners(MARIONETTE_OK, okListener));
+      this.mm.addMessageListener(MARIONETTE_DONE, removeListeners(MARIONETTE_DONE, valListener));
+      this.mm.addMessageListener(MARIONETTE_ERROR, removeListeners(MARIONETTE_ERROR, errListener));
+      modal.addHandler(handleDialog);
 
-    // start content process listeners, and install observers for global-
-    // and tab modal dialogues
-    this.mm.addMessageListener(MARIONETTE_OK, removeListeners(MARIONETTE_OK, okListener));
-    this.mm.addMessageListener(MARIONETTE_DONE, removeListeners(MARIONETTE_DONE, valListener));
-    this.mm.addMessageListener(MARIONETTE_ERROR, removeListeners(MARIONETTE_ERROR, errListener));
-    modal.addHandler(handleDialog);
+      this.sendAsync(name, marshal(args), this.curId);
+    });
 
-    // new style dispatches are arrays of arguments, old style dispatches
-    // are key-value objects
-    let msg = args;
-    if (args.length == 1 && typeof args[0] == "object") {
-      msg = args[0];
-    }
-
-    this.sendAsync(name, msg, this.curId);
-  });
-
-  return proxy;
+    return proxy;
+  }
 };
 
-proxy.ContentSender = ContentSender;
+/**
+ * Creates a transparent interface from the content- to the chrome context.
+ *
+ * Calls to this object will be proxied via the frame's sendSyncMessage
+ * (nsISyncMessageSender) function.  Since the message is synchronous,
+ * the return value is presented as a return value.
+ *
+ * Example on how to use from a frame content script:
+ *
+ *     let chrome = proxy.toChrome(sendSyncMessage.bind(this));
+ *     let cookie = chrome.getCookie("foo");
+ *
+ * @param {nsISyncMessageSender} sendSyncMessageFn
+ *     The frame message manager's sendSyncMessage function.
+ */
+proxy.toChrome = function(sendSyncMessageFn) {
+  let sender = new SyncChromeSender(sendSyncMessageFn);
+  return new Proxy(sender, ownPriorityGetterTrap);
+};
+
+/**
+ * The SyncChromeSender sends synchronous RPC messages to the chrome
+ * context, using a frame's sendSyncMessage (nsISyncMessageSender) function.
+ *
+ * Example on how to use from a frame content script:
+ *
+ *     let sender = new SyncChromeSender(sendSyncMessage.bind(this));
+ *     let res = sender.send("addCookie", cookie);
+ */
+this.SyncChromeSender = class {
+  constructor(sendSyncMessage) {
+    this.sendSyncMessage_ = sendSyncMessage;
+  }
+
+  send(func, args) {
+    let name = "Marionette:" + func;
+    return this.sendSyncMessage_(name, marshal(args));
+  }
+};
+
+var marshal = function(args) {
+  if (args.length == 1 && typeof args[0] == "object") {
+    return args[0];
+  }
+  return args;
+};
--- a/testing/mozharness/mozharness/mozilla/testing/firefox_ui_tests.py
+++ b/testing/mozharness/mozharness/mozilla/testing/firefox_ui_tests.py
@@ -18,16 +18,26 @@ from mozharness.base.log import FATAL
 from mozharness.base.python import PostScriptRun, PreScriptAction
 from mozharness.mozilla.structuredlog import StructuredOutputParser
 from mozharness.mozilla.testing.testbase import (
     TestingMixin,
     testing_config_options,
 )
 from mozharness.mozilla.vcstools import VCSToolsScript
 
+# Command line arguments for firefox ui tests
+firefox_ui_tests_harness_config_options = [
+    [["--e10s"], {
+        'dest': 'e10s',
+        'action': 'store_true',
+        'default': False,
+        'help': 'Enable multi-process (e10s) mode when running tests.',
+    }],
+]
+
 # General command line arguments for Firefox ui tests
 firefox_ui_tests_config_options = [
     [['--dry-run'], {
         'dest': 'dry_run',
         'default': False,
         'help': 'Only show what was going to be tested.',
     }],
     [['--firefox-ui-branch'], {
@@ -39,17 +49,18 @@ firefox_ui_tests_config_options = [
         'default': 'https://github.com/mozilla/firefox-ui-tests.git',
         'help': 'which firefox_ui_tests repo to use',
     }],
     [['--symbols-path=SYMBOLS_PATH'], {
         'dest': 'symbols_path',
         'help': 'absolute path to directory containing breakpad '
                 'symbols, or the url of a zip file containing symbols.',
     }],
-] + copy.deepcopy(testing_config_options)
+] + firefox_ui_tests_harness_config_options \
+    + copy.deepcopy(testing_config_options)
 
 # Command line arguments for update tests
 firefox_ui_update_harness_config_options = [
     [['--update-allow-mar-channel'], {
         'dest': 'update_allow_mar_channel',
         'help': 'Additional MAR channel to be allowed for updates, e.g. '
                 '"firefox-mozilla-beta" for updating a release build to '
                 'the latest beta build.',
@@ -200,22 +211,36 @@ class FirefoxUITests(TestingMixin, VCSTo
         abs_dirs.update({
             'abs_reports_dir': os.path.join(abs_dirs['base_work_dir'], 'reports'),
             'fx_ui_dir': os.path.join(abs_dirs['abs_work_dir'], 'firefox_ui_tests'),
         })
         self.abs_dirs = abs_dirs
 
         return self.abs_dirs
 
-    def query_extra_cmd_args(self):
+    def query_harness_args(self, extra_harness_config_options):
         """Collects specific update test related command line arguments.
 
         Sub classes should override this method for their own specific arguments.
         """
-        return []
+        extra_harness_config_options = extra_harness_config_options or []
+        config_options = firefox_ui_tests_harness_config_options + extra_harness_config_options
+
+        args = []
+        for option in config_options:
+            dest = option[1]['dest']
+            name = self.config.get(dest)
+
+            if name:
+                if type(name) is bool:
+                    args.append(option[0][0])
+                else:
+                    args.extend([option[0][0], self.config[dest]])
+
+        return args
 
     def query_minidump_stackwalk(self):
         """We don't have an extracted test package available to get the manifest file.
 
         So we have to explicitely download the latest version of the manifest from the
         mozilla-central repository and feed it into the query_minidump_stackwalk() method.
 
         We can remove this whole method once our tests are part of the tree.
@@ -260,17 +285,17 @@ class FirefoxUITests(TestingMixin, VCSTo
             '--log-raw=-',  # structured log for output parser redirected to stdout
 
             # additional reports helpful for Jenkins and inpection via Treeherder
             '--log-html', os.path.join(dirs["abs_reports_dir"], self.reports['html']),
             '--log-xunit', os.path.join(dirs["abs_reports_dir"], self.reports['xunit']),
         ]
 
         # Collect all pass-through harness options to the script
-        cmd.extend(self.query_extra_cmd_args())
+        cmd.extend(self.query_harness_args())
 
         # Set further environment settings
         env = env or self.query_env()
 
         if self.symbols_url:
             cmd.extend(['--symbols-path', self.symbols_url])
 
         if self.query_minidump_stackwalk():
@@ -310,23 +335,12 @@ class FirefoxUIUpdateTests(FirefoxUITest
     cli_script = 'cli_update.py'
 
     def __init__(self, config_options=None, *args, **kwargs):
         config_options = config_options or firefox_ui_update_config_options
 
         FirefoxUITests.__init__(self, config_options=config_options,
                                 *args, **kwargs)
 
-    def query_extra_cmd_args(self):
+    def query_harness_args(self):
         """Collects specific update test related command line arguments."""
-        args = []
-
-        for option in firefox_ui_update_harness_config_options:
-            dest = option[1]['dest']
-            name = self.config.get(dest)
-
-            if name:
-                if type(name) is bool:
-                    args.append(option[0][0])
-                else:
-                    args.extend([option[0][0], self.config[dest]])
-
-        return args
+        return FirefoxUITests.query_harness_args(self,
+                                                 firefox_ui_update_harness_config_options)
deleted file mode 100644
--- a/testing/web-platform/meta/dom/nodes/MutationObserver-document.html.ini
+++ /dev/null
@@ -1,11 +0,0 @@
-[MutationObserver-document.html]
-  type: testharness
-  [parser insertion mutations]
-    expected: FAIL
-
-  [parser script insertion mutation]
-    expected: FAIL
-
-  [removal of parent during parsing]
-    expected: FAIL
-
deleted file mode 100644
--- a/testing/web-platform/mozilla/meta/service-workers/service-worker/register-wait-forever-in-install-worker.https.html.ini
+++ /dev/null
@@ -1,6 +0,0 @@
-[register-wait-forever-in-install-worker.https.html]
-  type: testharness
-  expected: TIMEOUT
-  [register worker that calls waitUntil with a promise that never resolves in oninstall]
-    expected: TIMEOUT
-
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/getregistration.https.html
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/getregistration.https.html
@@ -19,36 +19,38 @@ async_test(function(t) {
     var registration;
     service_worker_unregister_and_register(t, 'resources/empty-worker.js',
                                            scope)
       .then(function(r) {
           registration = r;
           return navigator.serviceWorker.getRegistration(scope);
         })
       .then(function(value) {
-          assert_equals(value.scope, registration.scope,
-                        'getRegistration should resolve with registration');
+          assert_equals(
+              value, registration,
+              'getRegistration should resolve to the same registration object');
           service_worker_unregister_and_done(t, scope);
         })
       .catch(unreached_rejection(t));
   }, 'Register then getRegistration');
 
 async_test(function(t) {
     var scope = 'resources/scope/getregistration/url-with-fragment';
     var documentURL = scope + '#ref';
     var registration;
     service_worker_unregister_and_register(t, 'resources/empty-worker.js',
                                            scope)
       .then(function(r) {
           registration = r;
           return navigator.serviceWorker.getRegistration(documentURL);
         })
       .then(function(value) {
-          assert_equals(value.scope, registration.scope,
-                        'getRegistration should resolve with registration');
+          assert_equals(
+              value, registration,
+              'getRegistration should resolve to the same registration object');
           service_worker_unregister_and_done(t, scope);
         })
       .catch(unreached_rejection(t));
   }, 'Register then getRegistration with a URL having a fragment');
 
 async_test(function(t) {
     var documentURL = 'http://example.com/';
     navigator.serviceWorker.getRegistration(documentURL)
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/getregistrations.https.html
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/getregistrations.https.html
@@ -31,45 +31,45 @@ promise_test(function(t) {
   }, 'getRegistrations');
 
 promise_test(function(t) {
     var scope = 'resources/scope/getregistrations/normal';
     var script = 'resources/empty-worker.js';
     var registrations = [];
     return service_worker_unregister_and_register(t, script, scope)
       .then(function(r) {
-          registrations.push(r.scope);
+          registrations.push(r);
           return navigator.serviceWorker.getRegistrations();
         })
       .then(function(value) {
           assert_array_equals(
-            value.map((r) => r.scope),
+            value,
             registrations,
             'getRegistrations should resolve with array of registrations.');
           return service_worker_unregister(t, scope);
         });
   }, 'Register then getRegistrations');
 
 promise_test(function(t) {
     var scope1 = 'resources/scope/getregistrations/scope1';
     var scope2 = 'resources/scope/getregistrations/scope2';
     var script = 'resources/empty-worker.js';
     var registrations = [];
     return service_worker_unregister_and_register(t, script, scope1)
       .then(function(r) {
-          registrations.push(r.scope);
+          registrations.push(r);
           return service_worker_unregister_and_register(t, script, scope2);
         })
       .then(function(r) {
-          registrations.push(r.scope);
+          registrations.push(r);
           return navigator.serviceWorker.getRegistrations();
         })
       .then(function(value) {
           assert_array_equals(
-            value.map((r) => r.scope),
+            value,
             registrations,
             'getRegistrations should resolve with array of registrations.');
           return service_worker_unregister(t, scope1);
         })
       .then(function() {
           return service_worker_unregister(t, scope2);
         });
   }, 'Register multiple times then getRegistrations');
@@ -124,22 +124,22 @@ promise_test(function(t) {
     // test. So we have to wait until the cross origin register() is done, and not
     // just until the frame loads.
     return with_iframe_ready(frame_url)
       .then(function(f) {
           frame = f;
           return service_worker_unregister_and_register(t, script, scope);
         })
       .then(function(r) {
-          registrations.push(r.scope);
+          registrations.push(r);
           return navigator.serviceWorker.getRegistrations();
         })
       .then(function(value) {
           assert_array_equals(
-            value.map((r) => r.scope),
+            value,
             registrations,
             'getRegistrations should only return same origin registrations.');
 
           var channel = new MessageChannel();
           var resolve;
           var p = new Promise(function(r) { resolve = r; });
 
           channel.port1.onmessage = function(e) {
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/multiple-register.https.html
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/multiple-register.https.html
@@ -14,24 +14,20 @@ async_test(function(t) {
     .then(function(r) {
         registration = r;
         return wait_for_state(t, r.installing, 'activated');
       })
     .then(function() {
         return navigator.serviceWorker.register(worker_url, { scope: scope });
       })
     .then(function(new_registration) {
-        // FIXME: Bug 1201127 will fix scope vs object comparisons.
-        assert_not_equals(
-          registration, new_registration,
-          'register should resolve to the different registration');
-        assert_equals(new_registration.scope, registration.scope,
-                      'registrations should have the same scope');
-        assert_equals(new_registration.active.scriptURL, registration.active.scriptURL,
-                      'active workers should have same scriptURL');
+        assert_equals(new_registration, registration,
+                      'register should resolve to the same registration');
+        assert_equals(new_registration.active, registration.active,
+                      'register should resolve to the same worker');
         assert_equals(new_registration.active.state, 'activated',
                       'the worker should be in state "activated"');
         return registration.unregister();
       })
     .then(function() { t.done(); })
     .catch(unreached_rejection(t));
 }, 'Subsequent registrations resolve to the same registration object');
 
@@ -47,20 +43,19 @@ async_test(function(t) {
       })
     .then(function() { return with_iframe('resources/404.py'); })
     .then(function(f) {
         frame = f;
         return frame.contentWindow.navigator.serviceWorker.register(
             worker_url, { scope: scope });
       })
     .then(function(new_registration) {
-        // FIXME: Bug 1201127 will fix scope vs object comparisons.
         assert_not_equals(
           registration, new_registration,
-          'register should resolve to the different registration');
+          'register should resolve to a different registration');
         assert_equals(
           registration.scope, new_registration.scope,
           'registrations should have the same scope');
 
         assert_equals(
           registration.installing, null,
           'installing worker should be null');
         assert_equals(
@@ -103,19 +98,18 @@ async_test(function(t) {
         for (var i = 0; i < 10; ++i) {
           promises.push(navigator.serviceWorker.register(worker_url,
                                                          { scope: scope }));
         }
         return Promise.all(promises);
       })
     .then(function(registrations) {
         registrations.forEach(function(registration) {
-            // FIXME: Bug 1201127 will fix scope vs object comparisons.
-            assert_equals(registration.scope, registrations[0].scope,
-                          'register should resolve to registrations with the same scope');
+            assert_equals(registration, registrations[0],
+                          'register should resolve to the same registration');
           });
         return registrations[0].unregister();
       })
     .then(function() { t.done(); })
     .catch(unreached_rejection(t));
 }, 'Concurrent registrations resolve to the same registration object');
 
 </script>
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/skip-waiting-worker.js
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/resources/skip-waiting-worker.js
@@ -1,12 +1,16 @@
 importScripts('worker-testharness.js');
 
 promise_test(function() {
-    return self.skipWaiting()
+    // wait for the worker to reach "installing" state, otherwise skipWaiting()
+    // will fail. Bug 1228277
+    return new Promise(function(res, rej) {
+      oninstall = res;
+    }).then(() => skipWaiting())
       .then(function(result) {
           assert_equals(result, undefined,
                         'Promise should be resolved with undefined');
         })
       .then(function() {
           var promises = [];
           for (var i = 0; i < 8; ++i)
             promises.push(self.skipWaiting());
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/skip-waiting.https.html
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/skip-waiting.https.html
@@ -41,14 +41,13 @@ promise_test(function(t) {
         })
       .then(function() {
           assert_equals(activated_worker.state, 'redundant',
                         'Worker with url1 should be redundant');
           assert_equals(waiting_worker.state, 'redundant',
                         'Worker with url2 should be redundant');
           assert_equals(sw_registration.active.scriptURL, normalizeURL(url3),
                         'Worker with url3 should be activated');
-          frame.remove();
           return service_worker_unregister_and_done(t, scope);
         });
   }, 'Test skipWaiting with both active and waiting workers');
 
 </script>
--- a/testing/web-platform/mozilla/tests/service-workers/service-worker/unregister-then-register.https.html
+++ b/testing/web-platform/mozilla/tests/service-workers/service-worker/unregister-then-register.https.html
@@ -43,18 +43,18 @@ async_test(function(t) {
         })
       .then(function(frame) {
           return registration.unregister();
         })
       .then(function() {
           return navigator.serviceWorker.register(worker_url, { scope: scope });
         })
       .then(function(new_registration) {
-          assert_equals(registration.scope, new_registration.scope,
-                        'new registration should resolve to the same scope');
+          assert_equals(registration, new_registration,
+                        'new registration should resolve to the same registration');
           service_worker_unregister_and_done(t, scope);
         })
       .catch(unreached_rejection(t));
   }, 'Unregister then register resolves to the original value if the ' +
          'registration is in use.');
 
 async_test(function(t) {
     var scope = 'resources/scope/re-register-does-not-affect-existing-controllee';
--- a/testing/web-platform/tests/dom/nodes/MutationObserver-document.html
+++ b/testing/web-platform/tests/dom/nodes/MutationObserver-document.html
@@ -44,29 +44,29 @@
                          }}]);
         });
     } else if (testCounter == 2) {
       insertionTest2.step(
         function () {
           checkRecords(document, sequence,
                        [{type: "childList",
                          addedNodes: function () {
+                           return [ document.getElementById("inserted_script") ];
+                         },
+                         target: function () {
+                          return document.getElementById("n00");
+                         }},
+                        {type: "childList",
+                         addedNodes: function () {
                            return [ document.getElementById("inserted_element") ];
                          },
                          previousSibling: function () {
                            return document.getElementById("s002");
                          },
-                         target: document.body},
-                        {type: "childList",
-                         addedNodes: function () {
-                           return [ document.getElementById("inserted_script") ];
-                         },
-                         target: function () {
-                          return document.getElementById("n00");
-                         }}
+                         target: document.body}
                         ]);
         });
     }
   }
   var document_observer;
   var newElement;
   setupTest.step(function() {
     document_observer = new MutationObserver(masterMO);
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -10071,16 +10071,86 @@
     "description": "Counts number of times a certain plugin has been activated."
   },
   "YOUTUBE_EMBED_SEEN": {
     "alert_emails": ["cpeterson@mozilla.com"],
     "expires_in_version": "48",
     "kind": "flag",
     "description": "Flag activated whenever a youtube flash embed is seen during a session."
   },
+  "WEBFONT_DOWNLOAD_TIME": {
+    "alert_emails": ["jdaggett@mozilla.com"],
+    "expires_in_version": "never",
+    "kind": "exponential",
+    "high": "60000",
+    "n_buckets": 50,
+    "extended_statistics_ok": true,
+    "description": "Time to download a webfont (ms)"
+  },
+  "WEBFONT_DOWNLOAD_TIME_AFTER_START": {
+    "alert_emails": ["jdaggett@mozilla.com"],
+    "expires_in_version": "never",
+    "kind": "exponential",
+    "high": "60000",
+    "n_buckets": 50,
+    "extended_statistics_ok": true,
+    "description": "Time after navigationStart webfont download completed (ms)"
+  },
+  "WEBFONT_FONTTYPE": {
+    "alert_emails": ["jdaggett@mozilla.com"],
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values": 10,
+    "description": "Font format type (woff/woff2/ttf/...)"
+  },
+  "WEBFONT_SRCTYPE": {
+    "alert_emails": ["jdaggett@mozilla.com"],
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values": 5,
+    "description": "Font src type loaded (1 = local, 2 = url, 3 = data)"
+  },
+  "WEBFONT_PER_PAGE": {
+    "alert_emails": ["jdaggett@mozilla.com"],
+    "expires_in_version": "never",
+    "kind": "count",
+    "description": "Number of fonts loaded at page load"
+  },
+  "WEBFONT_SIZE_PER_PAGE": {
+    "alert_emails": ["jdaggett@mozilla.com"],
+    "expires_in_version": "never",
+    "kind": "exponential",
+    "high": "5000",
+    "n_buckets": 50,
+    "extended_statistics_ok": true,
+    "description": "Size of all fonts loaded at page load (kb)"
+  },
+  "WEBFONT_SIZE": {
+    "alert_emails": ["jdaggett@mozilla.com"],
+    "expires_in_version": "never",
+    "kind": "exponential",
+    "high": "5000",
+    "n_buckets": 50,
+    "extended_statistics_ok": true,
+    "description": "Size of font loaded (kb)"
+  },
+  "WEBFONT_COMPRESSION_WOFF": {
+    "alert_emails": ["jdaggett@mozilla.com"],
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values": 50,
+    "description": "Compression ratio of WOFF data (%)"
+  },
+  "WEBFONT_COMPRESSION_WOFF2": {
+    "alert_emails": ["jdaggett@mozilla.com"],
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values": 50,
+    "description": "Compression ratio of WOFF2 data (%)"
+  },
   "WEBRTC_ICE_CHECKING_RATE": {
     "alert_emails": ["webrtc-ice-telemetry-alerts@mozilla.com"],
     "expires_in_version": "53",
     "kind": "boolean",
     "bug_numbers": [1188391],
     "description": "The number of ICE connections which immediately failed (0) vs. reached at least checking state (1)."
   }
 }
--- a/tools/profiler/core/GeckoSampler.cpp
+++ b/tools/profiler/core/GeckoSampler.cpp
@@ -215,16 +215,22 @@ GeckoSampler::GeckoSampler(double aInter
 #endif
 
   // Deep copy aThreadNameFilters
   MOZ_ALWAYS_TRUE(mThreadNameFilters.resize(aFilterCount));
   for (uint32_t i = 0; i < aFilterCount; ++i) {
     mThreadNameFilters[i] = aThreadNameFilters[i];
   }
 
+  // Deep copy aFeatures
+  MOZ_ALWAYS_TRUE(mFeatures.resize(aFeatureCount));
+  for (uint32_t i = 0; i < aFeatureCount; ++i) {
+    mFeatures[i] = aFeatures[i];
+  }
+
   bool ignore;
   sStartTime = mozilla::TimeStamp::ProcessCreation(ignore);
 
   {
     ::MutexAutoLock lock(*sRegisteredThreadsMutex);
 
     // Create ThreadProfile for each registered thread
     for (uint32_t i = 0; i < sRegisteredThreads->size(); i++) {
--- a/tools/profiler/core/GeckoSampler.h
+++ b/tools/profiler/core/GeckoSampler.h
@@ -18,16 +18,17 @@
 #include "GeckoTaskTracer.h"
 #endif
 
 namespace mozilla {
 class ProfileGatherer;
 } // namespace mozilla
 
 typedef mozilla::Vector<std::string> ThreadNameFilterList;
+typedef mozilla::Vector<std::string> FeatureList;
 
 static bool
 threadSelected(ThreadInfo* aInfo, const ThreadNameFilterList &aThreadNameFilters) {
   if (aThreadNameFilters.empty()) {
     return true;
   }
 
   for (uint32_t i = 0; i < aThreadNameFilters.length(); ++i) {
@@ -116,16 +117,18 @@ class GeckoSampler: public Sampler {
   bool ProfileThreads() const override { return mProfileThreads; }
   bool InPrivacyMode() const { return mPrivacyMode; }
   bool AddMainThreadIO() const { return mAddMainThreadIO; }
   bool ProfileMemory() const { return mProfileMemory; }
   bool TaskTracer() const { return mTaskTracer; }
   bool LayersDump() const { return mLayersDump; }
   bool DisplayListDump() const { return mDisplayListDump; }
   bool ProfileRestyle() const { return mProfileRestyle; }
+  const ThreadNameFilterList& ThreadNameFilters() { return mThreadNameFilters; }
+  const FeatureList& Features() { return mFeatures; }
 
   void GetBufferInfo(uint32_t *aCurrentPosition, uint32_t *aTotalSize, uint32_t *aGeneration);
 
   void ProfileGathered();
 
 protected:
   // Called within a signal. This function must be reentrant
   virtual void InplaceTick(TickSample* sample);
@@ -148,16 +151,17 @@ protected:
   bool mProfilePower;
   bool mLayersDump;
   bool mDisplayListDump;
   bool mProfileRestyle;
 
   // Keep the thread filter to check against new thread that
   // are started while profiling
   ThreadNameFilterList mThreadNameFilters;
+  FeatureList mFeatures;
   bool mPrivacyMode;
   bool mAddMainThreadIO;
   bool mProfileMemory;
   bool mTaskTracer;
 #if defined(XP_WIN)
   IntelPowerGadget* mIntelPowerGadget;
 #endif
 
--- a/tools/profiler/core/platform.cpp
+++ b/tools/profiler/core/platform.cpp
@@ -605,16 +605,48 @@ void mozilla_sampler_get_profile_data_as
 {
   GeckoSampler *t = tlsTicker.get();
   if (NS_WARN_IF(!t)) {
     return;
   }
 
   t->ToJSObjectAsync(aSinceTime, aPromise);
 }
+
+void mozilla_sampler_get_profiler_start_params(int* aEntrySize,
+                                               double* aInterval,
+                                               mozilla::Vector<const char*>* aFilters,
+                                               mozilla::Vector<const char*>* aFeatures)
+{
+  if (NS_WARN_IF(!aEntrySize) || NS_WARN_IF(!aInterval) ||
+      NS_WARN_IF(!aFilters) || NS_WARN_IF(!aFeatures)) {
+    return;
+  }
+
+  GeckoSampler *t = tlsTicker.get();
+  if (NS_WARN_IF(!t)) {
+    return;
+  }
+
+  *aEntrySize = t->EntrySize();
+  *aInterval = t->interval();
+
+  const ThreadNameFilterList& threadNameFilterList = t->ThreadNameFilters();
+  MOZ_ALWAYS_TRUE(aFilters->resize(threadNameFilterList.length()));
+  for (uint32_t i = 0; i < threadNameFilterList.length(); ++i) {
+    (*aFilters)[i] = threadNameFilterList[i].c_str();
+  }
+
+  const FeatureList& featureList = t->Features();
+  MOZ_ALWAYS_TRUE(aFeatures->resize(featureList.length()));
+  for (size_t i = 0; i < featureList.length(); ++i) {
+    (*aFeatures)[i] = featureList[i].c_str();
+  }
+}
+
 #endif
 
 void mozilla_sampler_save_profile_to_file(const char* aFilename)
 {
   GeckoSampler *t = tlsTicker.get();
   if (!t) {
     return;
   }
new file mode 100644
--- /dev/null
+++ b/tools/profiler/gecko/ProfilerTypes.ipdlh
@@ -0,0 +1,16 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* 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/. */
+
+namespace mozilla {
+
+struct ProfilerInitParams {
+  bool enabled;
+  uint32_t entries;
+  double interval;
+  nsCString[] threadFilters;
+  nsCString[] features;
+};
+
+} // namespace mozilla
\ No newline at end of file
--- a/tools/profiler/gecko/nsIProfiler.idl
+++ b/tools/profiler/gecko/nsIProfiler.idl
@@ -7,17 +7,32 @@
 
 %{C++
 template<class T> class nsTArray;
 class nsCString;
 %}
 
 [ref] native StringArrayRef(const nsTArray<nsCString>);
 
-[scriptable, uuid(ff398a14-df1c-4966-9ab2-772ea6a6da6c)]
+/**
+ * Start-up parameters for subprocesses are passed through nsIObserverService,
+ * which, unfortunately, means we need to implement nsISupports in order to
+ * go through it.
+ */
+[uuid(0a175ba7-8fcf-4ce9-9c4b-ccc6272f4425)]
+interface nsIProfilerStartParams : nsISupports
+{
+  attribute uint32_t entries;
+  attribute double interval;
+
+  [noscript, notxpcom, nostdcall] StringArrayRef getFeatures();
+  [noscript, notxpcom, nostdcall] StringArrayRef getThreadFilterNames();
+};
+
+[scriptable, uuid(b373b360-c997-448a-b60d-4985b70dc810)]
 interface nsIProfiler : nsISupports
 {
   boolean CanProfile();
   void StartProfiler(in uint32_t aEntries, in double aInterval,
                       [array, size_is(aFeatureCount)] in string aFeatures,
                       in uint32_t aFeatureCount,
                       [array, size_is(aFilterCount), optional] in string aThreadNameFilters,
                       [optional] in uint32_t aFilterCount);
@@ -40,16 +55,22 @@ interface nsIProfiler : nsISupports
   jsval getProfileData([optional] in double aSinceTime);
 
   [implicit_jscontext]
   nsISupports getProfileDataAsync([optional] in double aSinceTime);
 
   boolean IsActive();
   void GetFeatures(out uint32_t aCount, [retval, array, size_is(aCount)] out string aFeatures);
 
+  /**
+   * The starting parameters that were sent to the profiler for sampling.
+   * If the profiler is not currently sampling, this will return null.
+   */
+  readonly attribute nsIProfilerStartParams startParams;
+
   void GetBufferInfo(out uint32_t aCurrentPosition, out uint32_t aTotalSize,
                      out uint32_t aGeneration);
 
   /**
    * Returns the elapsed time, in milliseconds, since the profiler's epoch.
    * The epoch is guaranteed to be constant for the duration of the
    * process, but is otherwise arbitrary.
    */
@@ -67,23 +88,8 @@ interface nsIProfiler : nsISupports
    */
   AString getSharedLibraryInformation();
 
   /**
    * Dump the collected profile to a file.
    */
   void dumpProfileToFile(in string aFilename);
 };
-
-/**
- * Start-up parameters for subprocesses are passed through nsIObserverService,
- * which, unfortunately, means we need to implement nsISupports in order to
- * go through it.
- */
-[uuid(0a175ba7-8fcf-4ce9-9c4b-ccc6272f4425)]
-interface nsIProfilerStartParams : nsISupports
-{
-  attribute uint32_t entries;
-  attribute double interval;
-
-  [noscript, notxpcom, nostdcall] StringArrayRef getFeatures();
-  [noscript, notxpcom, nostdcall] StringArrayRef getThreadFilterNames();
-};
--- a/tools/profiler/gecko/nsProfiler.cpp
+++ b/tools/profiler/gecko/nsProfiler.cpp
@@ -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/. */
 
 #include <string>
 #include <sstream>
 #include "GeckoProfiler.h"
 #include "nsProfiler.h"
+#include "nsProfilerStartParams.h"
 #include "nsMemory.h"
 #include "nsString.h"
 #include "mozilla/Services.h"
 #include "nsIObserverService.h"
 #include "nsIInterfaceRequestor.h"
 #include "nsILoadContext.h"
 #include "nsIWebNavigation.h"
 #include "nsIInterfaceRequestorUtils.h"
@@ -243,16 +244,47 @@ nsProfiler::GetFeatures(uint32_t *aCount
   }
 
   *aFeatures = featureList;
   *aCount = len;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsProfiler::GetStartParams(nsIProfilerStartParams** aRetVal)
+{
+  if (!profiler_is_active()) {
+    *aRetVal = nullptr;
+  } else {
+    int entrySize = 0;
+    double interval = 0;
+    mozilla::Vector<const char*> filters;
+    mozilla::Vector<const char*> features;
+    profiler_get_start_params(&entrySize, &interval, &filters, &features);
+
+    nsTArray<nsCString> filtersArray;
+    for (uint32_t i = 0; i < filters.length(); ++i) {
+      filtersArray.AppendElement(filters[i]);
+    }
+
+    nsTArray<nsCString> featuresArray;
+    for (size_t i = 0; i < features.length(); ++i) {
+      featuresArray.AppendElement(features[i]);
+    }
+
+    nsCOMPtr<nsIProfilerStartParams> startParams =
+      new nsProfilerStartParams(entrySize, interval, featuresArray,
+                                filtersArray);
+
+    startParams.forget(aRetVal);
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsProfiler::GetBufferInfo(uint32_t *aCurrentPosition, uint32_t *aTotalSize, uint32_t *aGeneration)
 {
   MOZ_ASSERT(aCurrentPosition);
   MOZ_ASSERT(aTotalSize);
   MOZ_ASSERT(aGeneration);
   profiler_get_buffer_info(aCurrentPosition, aTotalSize, aGeneration);
   return NS_OK;
 }
--- a/tools/profiler/moz.build
+++ b/tools/profiler/moz.build
@@ -104,16 +104,22 @@ if CONFIG['MOZ_ENABLE_PROFILER_SPS']:
     if CONFIG['ENABLE_TESTS']:
         DIRS += ['tests/gtest']
 
     if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and (CONFIG['ANDROID_VERSION'] <= '17' or CONFIG['ANDROID_VERSION'] >= '21'):
         DEFINES['ELFSIZE'] = 32
 
     FINAL_LIBRARY = 'xul'
 
+IPDL_SOURCES += [
+    'gecko/ProfilerTypes.ipdlh',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
 EXPORTS += [
     'public/GeckoProfiler.h',
 ]
 
 if CONFIG['MOZ_TASK_TRACER']:
     EXPORTS += [
         'public/GeckoTaskTracer.h',
         'public/GeckoTaskTracerImpl.h',
--- a/tools/profiler/public/GeckoProfiler.h
+++ b/tools/profiler/public/GeckoProfiler.h
@@ -48,26 +48,31 @@
 
 #ifndef SAMPLER_H
 #define SAMPLER_H
 
 #ifndef SPS_STANDALONE
 #include "js/TypeDecls.h"
 #endif
 #include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
 
 namespace mozilla {
 class TimeStamp;
 
 namespace dom {
 class Promise;
 } // namespace dom
 
 } // namespace mozilla
 
+#ifndef SPS_STANDALONE
+class nsIProfilerStartParams;
+#endif
+
 enum TracingMetadata {
   TRACING_DEFAULT,
   TRACING_INTERVAL_START,
   TRACING_INTERVAL_END,
   TRACING_EVENT,
   TRACING_EVENT_BACKTRACE,
   TRACING_TIMESTAMP
 };
@@ -177,16 +182,20 @@ static inline JSObject* profiler_get_pro
                                                       double aSinceTime = 0) {
   return nullptr;
 }
 
 #ifndef SPS_STANDALONE
 // Get the profile encoded as a JSON object.
 static inline void profiler_get_profile_jsobject_async(double aSinceTime = 0,
                                                        mozilla::dom::Promise* = 0) {}
+static inline void profiler_get_start_params(int* aEntrySize,
+                                             double* aInterval,
+                                             mozilla::Vector<const char*>* aFilters,
+                                             mozilla::Vector<const char*>* aFeatures) {}
 #endif
 
 // Get the profile and write it into a file
 static inline void profiler_save_profile_to_file(char* aFilename) { }
 
 // Get the features supported by the profiler that are accepted by profiler_init.
 // Returns a null terminated char* array.
 static inline char** profiler_get_features() { return nullptr; }
--- a/tools/profiler/public/GeckoProfilerFunc.h
+++ b/tools/profiler/public/GeckoProfilerFunc.h
@@ -6,16 +6,17 @@
 #ifndef PROFILER_FUNCS_H
 #define PROFILER_FUNCS_H
 
 #ifndef SPS_STANDALONE
 #include "js/TypeDecls.h"
 #endif
 #include "js/ProfilingStack.h"
 #include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
 #include <stdint.h>
 
 namespace mozilla {
 class TimeStamp;
 
 namespace dom {
 class Promise;
 } // namespace dom
@@ -63,16 +64,20 @@ const double* mozilla_sampler_get_respon
 void mozilla_sampler_save();
 
 mozilla::UniquePtr<char[]> mozilla_sampler_get_profile(double aSinceTime);
 
 #ifndef SPS_STANDALONE
 JSObject *mozilla_sampler_get_profile_data(JSContext* aCx, double aSinceTime);
 void mozilla_sampler_get_profile_data_async(double aSinceTime,
                                             mozilla::dom::Promise* aPromise);
+void mozilla_sampler_get_profiler_start_params(int* aEntrySize,
+                                               double* aInterval,
+                                               mozilla::Vector<const char*>* aFilters,
+                                               mozilla::Vector<const char*>* aFeatures);
 #endif
 
 // Make this function easily callable from a debugger in a build without
 // debugging information (work around http://llvm.org/bugs/show_bug.cgi?id=22211)
 extern "C" {
   void mozilla_sampler_save_profile_to_file(const char* aFilename);
 }
 
--- a/tools/profiler/public/GeckoProfilerImpl.h
+++ b/tools/profiler/public/GeckoProfilerImpl.h
@@ -165,16 +165,25 @@ JSObject* profiler_get_profile_jsobject(
 }
 
 static inline
 void profiler_get_profile_jsobject_async(double aSinceTime = 0,
                                          mozilla::dom::Promise* aPromise = 0)
 {
   mozilla_sampler_get_profile_data_async(aSinceTime, aPromise);
 }
+
+static inline
+void profiler_get_start_params(int* aEntrySize,
+                               double* aInterval,
+                               mozilla::Vector<const char*>* aFilters,
+                               mozilla::Vector<const char*>* aFeatures)
+{
+  mozilla_sampler_get_profiler_start_params(aEntrySize, aInterval, aFilters, aFeatures);
+}
 #endif
 
 static inline
 void profiler_save_profile_to_file(const char* aFilename)
 {
   return mozilla_sampler_save_profile_to_file(aFilename);
 }