merge mozilla-inbound to mozilla-central. r=merge a=merge
authorSebastian Hengst <archaeopteryx@coole-files.de>
Mon, 30 Oct 2017 23:52:23 +0100
changeset 389179 083a9c84fbd09a6ff9bfecabbf773650842fe1c0
parent 389178 dd0f265a130098cda83a0c25c7617d2365b28f2d (current diff)
parent 389071 7cc3c1afc8ce8eee7879e2b194eae2643b35a7b6 (diff)
child 389180 10868f1aa9293cbb44c5d0ebe9e20f4f7f5787b4
child 389246 4853ca9ebe913afb11105427849b2d9f5ea99794
push id96799
push userarchaeopteryx@coole-files.de
push dateMon, 30 Oct 2017 23:02:37 +0000
treeherdermozilla-inbound@10868f1aa929 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central. r=merge a=merge MozReview-Commit-ID: 4PW6ESqLL73
gfx/gl/AndroidSurfaceTexture.cpp
gfx/layers/SourceSurfaceSharedData.cpp
gfx/layers/SourceSurfaceSharedData.h
gfx/layers/client/ContentClient.cpp
gfx/layers/ipc/CompositorManagerChild.h
gfx/layers/ipc/CompositorManagerParent.cpp
gfx/layers/ipc/CompositorManagerParent.h
gfx/layers/ipc/CompositorThread.cpp
gfx/layers/ipc/ImageBridgeParent.cpp
gfx/layers/ipc/ImageBridgeParent.h
gfx/layers/ipc/LayersSurfaces.ipdlh
gfx/layers/opengl/TextureHostOGL.cpp
gfx/layers/opengl/TexturePoolOGL.cpp
gfx/layers/wr/AsyncImagePipelineManager.cpp
gfx/layers/wr/WebRenderBridgeParent.cpp
gfx/layers/wr/WebRenderUserData.cpp
gfx/layers/wr/WebRenderUserData.h
gfx/thebes/gfxPrefs.h
gfx/webrender_bindings/RenderBufferTextureHost.cpp
gfx/webrender_bindings/RenderBufferTextureHost.h
gfx/webrender_bindings/RenderD3D11TextureHostOGL.cpp
gfx/webrender_bindings/RenderD3D11TextureHostOGL.h
gfx/webrender_bindings/RenderMacIOSurfaceTextureHostOGL.cpp
gfx/webrender_bindings/RenderMacIOSurfaceTextureHostOGL.h
gfx/webrender_bindings/RenderTextureHost.h
gfx/webrender_bindings/RenderTextureHostOGL.h
gfx/webrender_bindings/RendererOGL.cpp
mobile/android/chrome/content/browser.js
mobile/android/geckoview/src/main/java/org/mozilla/gecko/gfx/GeckoSurfaceTexture.java
mobile/android/tests/browser/robocop/roboextender/Makefile.in
testing/web-platform/meta/content-security-policy/worker-src/dedicated-fallback.sub.html.ini
testing/web-platform/meta/content-security-policy/worker-src/service-fallback.https.sub.html.ini
testing/web-platform/meta/content-security-policy/worker-src/shared-fallback.sub.html.ini
--- a/accessible/windows/msaa/Compatibility.cpp
+++ b/accessible/windows/msaa/Compatibility.cpp
@@ -224,17 +224,18 @@ Compatibility::Init()
     // Check to see if the pref for disallowing CtrlTab is already set. If so,
     // bail out (respect the user settings). If not, set it.
     if (!Preferences::HasUserValue("browser.ctrlTab.disallowForScreenReaders"))
       Preferences::SetBool("browser.ctrlTab.disallowForScreenReaders", true);
   }
 
   // If we have a consumer who is not NVDA, we enable detection for the
   // InSendMessageEx compatibility hack. NVDA does not require this.
-  if ((sConsumers & (~NVDA)) &&
+  // We also skip UIA, as we see crashes there.
+  if ((sConsumers & (~(UIAUTOMATION | NVDA))) &&
       BrowserTabsRemoteAutostart()) {
     sUser32Interceptor.Init("user32.dll");
     if (!sInSendMessageExStub) {
       sUser32Interceptor.AddHook("InSendMessageEx",
                                  reinterpret_cast<intptr_t>(&InSendMessageExHook),
                                  (void**)&sInSendMessageExStub);
     }
     // The vectored exception handler allows us to catch exceptions ahead of any
--- a/browser/base/content/test/tabs/browser_tabCloseProbes.js
+++ b/browser/base/content/test/tabs/browser_tabCloseProbes.js
@@ -22,20 +22,16 @@ function assertCount(snapshot, expectedC
   // snapshot.count entries
   Assert.equal(snapshot.counts.reduce((a, b) => a + b), expectedCount,
                `Should only be ${expectedCount} collected value.`);
 }
 
 add_task(async function setup() {
   // These probes are opt-in, meaning we only capture them if extended
   // Telemetry recording is enabled.
-  await SpecialPowers.pushPrefEnv({
-    set: [["toolkit.telemetry.enabled", true]]
-  });
-
   let oldCanRecord = Services.telemetry.canRecordExtended;
   Services.telemetry.canRecordExtended = true;
   registerCleanupFunction(() => {
     Services.telemetry.canRecordExtended = oldCanRecord;
   });
 });
 
 /**
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -34,16 +34,17 @@ support-files =
   searchSuggestionEngine.sjs
   ../../../../../toolkit/components/extensions/test/mochitest/head_webrequest.js
   ../../../../../toolkit/components/extensions/test/mochitest/redirection.sjs
   ../../../../../toolkit/components/reader/test/readerModeNonArticle.html
   ../../../../../toolkit/components/reader/test/readerModeArticle.html
 
 [browser_ext_browserAction_area.js]
 [browser_ext_browserAction_context.js]
+skip-if = os == 'win' || os == 'mac' # Bug 1405453
 [browser_ext_browserAction_contextMenu.js]
 # bug 1369197
 skip-if = os == 'linux'
 [browser_ext_browserAction_disabled.js]
 [browser_ext_browserAction_pageAction_icon.js]
 [browser_ext_browserAction_pageAction_icon_permissions.js]
 [browser_ext_browserAction_popup.js]
 skip-if = debug && (os == 'linux' && bits == 32) # Bug 1313372
--- a/browser/components/search/test/browser_contextSearchTabPosition.js
+++ b/browser/components/search/test/browser_contextSearchTabPosition.js
@@ -1,14 +1,13 @@
 /* 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/. */
 
 add_task(async function test() {
-  await SpecialPowers.pushPrefEnv({set: [["toolkit.telemetry.enabled", true]]});
   let engine = await promiseNewEngine("testEngine.xml");
   let histogramKey = "other-" + engine.name + ".contextmenu";
   let numSearchesBefore = 0;
 
   try {
     let hs = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS").snapshot();
     if (histogramKey in hs) {
       numSearchesBefore = hs[histogramKey].sum;
--- a/browser/components/search/test/browser_healthreport.js
+++ b/browser/components/search/test/browser_healthreport.js
@@ -71,17 +71,16 @@ function test() {
         Services.obs.removeObserver(observer, "browser-search-engine-modified");
         finish();
         break;
     }
   }
 
   Services.obs.addObserver(observer, "browser-search-engine-modified");
   SpecialPowers.pushPrefEnv({set: [
-    ["toolkit.telemetry.enabled", true],
     ["browser.search.widget.inNavBar", true],
   ]}).then(function() {
     Services.search.addEngine("http://mochi.test:8888/browser/browser/components/search/test/testEngine.xml",
                               null, "data:image/x-icon,%00", false);
   });
 }
 
 function resetPreferences() {
--- a/browser/components/translation/test/browser_translation_telemetry.js
+++ b/browser/components/translation/test/browser_translation_telemetry.js
@@ -148,17 +148,16 @@ add_task(async function setup() {
 
   const restorePrefs = (prefs, backup) => {
     for (let p of prefs) {
       Services.prefs.setBoolPref(p, backup[p]);
     }
   };
 
   const prefs = [
-    "toolkit.telemetry.enabled",
     "browser.translation.detectLanguage",
     "browser.translation.ui.show"
   ];
 
   let prefsBackup = setupPrefs(prefs);
 
   let oldCanRecord = Telemetry.canRecordExtended;
   Telemetry.canRecordExtended = true;
--- a/browser/experiments/Experiments.jsm
+++ b/browser/experiments/Experiments.jsm
@@ -21,18 +21,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
                                   "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryEnvironment",
                                   "resource://gre/modules/TelemetryEnvironment.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryLog",
                                   "resource://gre/modules/TelemetryLog.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "TelemetryUtils",
-                                  "resource://gre/modules/TelemetryUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CommonUtils",
                                   "resource://services-common/utils.js");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gCrashReporter",
                                    "@mozilla.org/xre/app-info;1",
                                    "nsICrashReporter");
 
 const FILE_CACHE                = "experiments.json";
@@ -47,18 +45,16 @@ const PREF_BRANCH               = "exper
 const PREF_ENABLED              = "enabled"; // experiments.enabled
 const PREF_ACTIVE_EXPERIMENT    = "activeExperiment"; // whether we have an active experiment
 const PREF_LOGGING              = "logging";
 const PREF_LOGGING_LEVEL        = PREF_LOGGING + ".level"; // experiments.logging.level
 const PREF_LOGGING_DUMP         = PREF_LOGGING + ".dump"; // experiments.logging.dump
 const PREF_MANIFEST_URI         = "manifest.uri"; // experiments.logging.manifest.uri
 const PREF_FORCE_SAMPLE         = "force-sample-value"; // experiments.force-sample-value
 
-const PREF_TELEMETRY_ENABLED      = "toolkit.telemetry.enabled";
-
 const URI_EXTENSION_STRINGS     = "chrome://mozapps/locale/extensions/extensions.properties";
 
 const CACHE_WRITE_RETRY_DELAY_SEC = 60 * 3;
 const MANIFEST_FETCH_TIMEOUT_MSEC = 60 * 3 * 1000; // 3 minutes
 
 const TELEMETRY_LOG = {
   // log(key, [kind, experimentId, details])
   ACTIVATION_KEY: "EXPERIMENT_ACTIVATION",
@@ -381,36 +377,32 @@ Experiments.Experiments.prototype = {
 
   observe(subject, topic, data) {
     switch (topic) {
       case PREF_CHANGED_TOPIC:
         if (data == PREF_BRANCH + PREF_MANIFEST_URI) {
           this.updateManifest();
         } else if (data == PREF_BRANCH + PREF_ENABLED) {
           this._toggleExperimentsEnabled(gPrefs.getBoolPref(PREF_ENABLED, false));
-        } else if (data == PREF_TELEMETRY_ENABLED) {
-          this._telemetryStatusChanged();
         }
         break;
     }
   },
 
   init() {
     this._shutdown = false;
     configureLogging();
 
-    gExperimentsEnabled = gPrefs.getBoolPref(PREF_ENABLED, false) && TelemetryUtils.isTelemetryEnabled;
+    gExperimentsEnabled = gPrefs.getBoolPref(PREF_ENABLED, false) && Services.telemetry.canRecordExtended;
     this._log.trace("enabled=" + gExperimentsEnabled + ", " + this.enabled);
 
     Services.prefs.addObserver(PREF_BRANCH + PREF_LOGGING, configureLogging);
     Services.prefs.addObserver(PREF_BRANCH + PREF_MANIFEST_URI, this, true);
     Services.prefs.addObserver(PREF_BRANCH + PREF_ENABLED, this, true);
 
-    Services.prefs.addObserver(PREF_TELEMETRY_ENABLED, this, true);
-
     AddonManager.shutdown.addBlocker("Experiments.jsm shutdown",
       this.uninit.bind(this),
       this._getState.bind(this)
     );
 
     this._registerWithAddonManager();
 
     this._loadTask = this._loadFromCache();
@@ -448,18 +440,16 @@ Experiments.Experiments.prototype = {
     if (!this._shutdown) {
       this._log.trace("uninit: no previous shutdown");
       this._unregisterWithAddonManager();
 
       Services.prefs.removeObserver(PREF_BRANCH + PREF_LOGGING, configureLogging);
       Services.prefs.removeObserver(PREF_BRANCH + PREF_MANIFEST_URI, this);
       Services.prefs.removeObserver(PREF_BRANCH + PREF_ENABLED, this);
 
-      Services.prefs.removeObserver(PREF_TELEMETRY_ENABLED, this);
-
       if (this._timer) {
         this._timer.clear();
       }
     }
 
     this._shutdown = true;
     if (this._mainTask) {
       if (this._networkRequest) {
@@ -593,36 +583,32 @@ Experiments.Experiments.prototype = {
   set enabled(enabled) {
     this._log.trace("set enabled(" + enabled + ")");
     gPrefs.setBoolPref(PREF_ENABLED, enabled);
   },
 
   async _toggleExperimentsEnabled(enabled) {
     this._log.trace("_toggleExperimentsEnabled(" + enabled + ")");
     let wasEnabled = gExperimentsEnabled;
-    gExperimentsEnabled = enabled && TelemetryUtils.isTelemetryEnabled;
+    gExperimentsEnabled = enabled && Services.telemetry.canRecordExtended;
 
     if (wasEnabled == gExperimentsEnabled) {
       return;
     }
 
     if (gExperimentsEnabled) {
       await this.updateManifest();
     } else {
       await this.disableExperiment(TELEMETRY_LOG.TERMINATION.SERVICE_DISABLED);
       if (this._timer) {
         this._timer.clear();
       }
     }
   },
 
-  _telemetryStatusChanged() {
-    this._toggleExperimentsEnabled(gPrefs.getBoolPref(PREF_ENABLED, false));
-  },
-
   /**
    * Returns a promise that is resolved with an array of `ExperimentInfo` objects,
    * which provide info on the currently and recently active experiments.
    * The array is in chronological order.
    *
    * The experiment info is of the form:
    * {
    *   id: <string>,
--- a/browser/experiments/test/xpcshell/head.js
+++ b/browser/experiments/test/xpcshell/head.js
@@ -21,17 +21,16 @@ Cu.import("resource://testing-common/Add
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
 
 const PREF_EXPERIMENTS_ENABLED  = "experiments.enabled";
 const PREF_LOGGING_LEVEL        = "experiments.logging.level";
 const PREF_LOGGING_DUMP         = "experiments.logging.dump";
 const PREF_MANIFEST_URI         = "experiments.manifest.uri";
 const PREF_FETCHINTERVAL        = "experiments.manifest.fetchIntervalSeconds";
-const PREF_TELEMETRY_ENABLED    = "toolkit.telemetry.enabled";
 
 function getExperimentPath(base) {
   let p = do_get_cwd();
   p.append(base);
   return p.path;
 }
 
 function sha1File(path) {
@@ -188,11 +187,9 @@ function replaceExperiments(experiment, 
   Object.defineProperty(experiment, "getExperiments", {
     writable: true,
     value: () => {
       return Promise.resolve(list);
     },
   });
 }
 
-// Experiments require Telemetry to be enabled, and that's not true for debug
-// builds. Let's just enable it here instead of going through each test.
-Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, true);
+Services.telemetry.canRecordExtended = true;
--- a/browser/experiments/test/xpcshell/test_telemetry_disabled.js
+++ b/browser/experiments/test/xpcshell/test_telemetry_disabled.js
@@ -5,24 +5,16 @@
 
 Cu.import("resource:///modules/experiments/Experiments.jsm");
 
 add_test(function test_experiments_activation() {
   do_get_profile();
   loadAddonManager();
 
   Services.prefs.setBoolPref(PREF_EXPERIMENTS_ENABLED, true);
-  Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, false);
+  Services.telemetry.canRecordExtended = false;
 
   let experiments = Experiments.instance();
 
   Assert.ok(!experiments.enabled, "Experiments must be disabled if Telemetry is disabled.");
 
-  // Patch updateManifest to not do anything when the pref is switched back to true,
-  // otherwise it attempts to connect to the server.
-  experiments.updateManifest = () => Promise.resolve();
-
-  Services.prefs.setBoolPref(PREF_TELEMETRY_ENABLED, true);
-
-  Assert.ok(experiments.enabled, "Experiments must be re-enabled if Telemetry is re-enabled");
-
   run_next_test();
 });
--- a/browser/modules/test/browser/browser_UsageTelemetry_content.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_content.js
@@ -20,17 +20,16 @@ add_task(async function setup() {
   Services.search.currentEngine = engineDefault;
 
   // Move the second engine at the beginning of the one-off list.
   let engineOneOff = Services.search.getEngineByName("MozSearch2");
   Services.search.moveEngine(engineOneOff, 0);
 
   await SpecialPowers.pushPrefEnv({"set": [
     ["dom.select_events.enabled", true], // We want select events to be fired.
-    ["toolkit.telemetry.enabled", true]  // And Extended Telemetry to be enabled.
   ]});
 
   // Enable event recording for the events tested here.
   Services.telemetry.setEventRecordingEnabled("navigation", true);
 
   // Make sure to restore the engine once we're done.
   registerCleanupFunction(async function() {
     Services.search.currentEngine = originalEngine;
--- a/browser/modules/test/browser/browser_UsageTelemetry_content_aboutHome.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_content_aboutHome.js
@@ -22,19 +22,16 @@ add_task(async function setup() {
   let engineDefault = Services.search.getEngineByName("MozSearch");
   let originalEngine = Services.search.currentEngine;
   Services.search.currentEngine = engineDefault;
 
   // Move the second engine at the beginning of the one-off list.
   let engineOneOff = Services.search.getEngineByName("MozSearch2");
   Services.search.moveEngine(engineOneOff, 0);
 
-  // Enable Extended Telemetry.
-  await SpecialPowers.pushPrefEnv({"set": [["toolkit.telemetry.enabled", true]]});
-
   // Enable event recording for the events tested here.
   Services.telemetry.setEventRecordingEnabled("navigation", true);
 
   // Make sure to restore the engine once we're done.
   registerCleanupFunction(async function() {
     Services.search.currentEngine = originalEngine;
     Services.search.removeEngine(engineDefault);
     Services.search.removeEngine(engineOneOff);
--- a/browser/modules/test/browser/browser_UsageTelemetry_searchbar.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_searchbar.js
@@ -85,19 +85,16 @@ add_task(async function setup() {
   // Move the second engine at the beginning of the one-off list.
   let engineOneOff = Services.search.getEngineByName("MozSearch2");
   Services.search.moveEngine(engineOneOff, 0);
 
   // Enable local telemetry recording for the duration of the tests.
   let oldCanRecord = Services.telemetry.canRecordExtended;
   Services.telemetry.canRecordExtended = true;
 
-  // Enable Extended Telemetry.
-  await SpecialPowers.pushPrefEnv({"set": [["toolkit.telemetry.enabled", true]]});
-
   // Enable event recording for the events tested here.
   Services.telemetry.setEventRecordingEnabled("navigation", true);
 
   // Make sure to restore the engine once we're done.
   registerCleanupFunction(function() {
     Services.telemetry.canRecordExtended = oldCanRecord;
     Services.search.currentEngine = originalEngine;
     Services.search.removeEngine(engineDefault);
--- a/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
+++ b/browser/modules/test/browser/browser_UsageTelemetry_urlbar.js
@@ -84,19 +84,16 @@ add_task(async function setup() {
 
   // Enable search suggestions in the urlbar.
   let suggestionsEnabled = Services.prefs.getBoolPref(SUGGEST_URLBAR_PREF);
   Services.prefs.setBoolPref(SUGGEST_URLBAR_PREF, true);
 
   // Enable the urlbar one-off buttons.
   Services.prefs.setBoolPref(ONEOFF_URLBAR_PREF, true);
 
-  // Enable Extended Telemetry.
-  await SpecialPowers.pushPrefEnv({"set": [["toolkit.telemetry.enabled", true]]});
-
   // Enable local telemetry recording for the duration of the tests.
   let oldCanRecord = Services.telemetry.canRecordExtended;
   Services.telemetry.canRecordExtended = true;
 
   // Enable event recording for the events tested here.
   Services.telemetry.setEventRecordingEnabled("navigation", true);
 
   // Clear history so that history added by previous tests doesn't mess up this
--- a/devtools/client/sourceeditor/codemirror/README
+++ b/devtools/client/sourceeditor/codemirror/README
@@ -1,16 +1,16 @@
 This is the CodeMirror editor packaged for the Mozilla Project. CodeMirror
 is a JavaScript component that provides a code editor in the browser. When
 a mode is available for the language you are coding in, it will color your
 code, and optionally help with indentation.
 
 # Upgrade
 
-Currently used version is 5.30.0. To upgrade: download a new version of
+Currently used version is 5.31.0. To upgrade: download a new version of
 CodeMirror from the project's page [1] and replace all JavaScript and
 CSS files inside the codemirror directory [2].
 
 Then to recreate codemirror.bundle.js:
  > cd devtools/client/sourceeditor
  > npm install
  > webpack
 
--- a/devtools/client/sourceeditor/codemirror/addon/comment/continuecomment.js
+++ b/devtools/client/sourceeditor/codemirror/addon/comment/continuecomment.js
@@ -4,38 +4,33 @@
 (function(mod) {
   if (typeof exports == "object" && typeof module == "object") // CommonJS
     mod(require("../../lib/codemirror"));
   else if (typeof define == "function" && define.amd) // AMD
     define(["../../lib/codemirror"], mod);
   else // Plain browser env
     mod(CodeMirror);
 })(function(CodeMirror) {
-  var modes = ["clike", "css", "javascript"];
-
-  for (var i = 0; i < modes.length; ++i)
-    CodeMirror.extendMode(modes[i], {blockCommentContinue: " * "});
-
   function continueComment(cm) {
     if (cm.getOption("disableInput")) return CodeMirror.Pass;
     var ranges = cm.listSelections(), mode, inserts = [];
     for (var i = 0; i < ranges.length; i++) {
       var pos = ranges[i].head
       if (!/\bcomment\b/.test(cm.getTokenTypeAt(pos))) return CodeMirror.Pass;
       var modeHere = cm.getModeAt(pos)
       if (!mode) mode = modeHere;
       else if (mode != modeHere) return CodeMirror.Pass;
 
       var insert = null;
       if (mode.blockCommentStart && mode.blockCommentContinue) {
         var line = cm.getLine(pos.line).slice(0, pos.ch)
-        var end = line.indexOf(mode.blockCommentEnd), found
+        var end = line.lastIndexOf(mode.blockCommentEnd), found
         if (end != -1 && end == pos.ch - mode.blockCommentEnd.length) {
           // Comment ended, don't continue it
-        } else if ((found = line.indexOf(mode.blockCommentStart)) > -1) {
+        } else if ((found = line.lastIndexOf(mode.blockCommentStart)) > -1 && found > end) {
           insert = line.slice(0, found)
           if (/\S/.test(insert)) {
             insert = ""
             for (var j = 0; j < found; ++j) insert += " "
           }
         } else if ((found = line.indexOf(mode.blockCommentContinue)) > -1 && !/\S/.test(line.slice(0, found))) {
           insert = line.slice(0, found)
         }
--- a/devtools/client/sourceeditor/codemirror/addon/edit/closebrackets.js
+++ b/devtools/client/sourceeditor/codemirror/addon/edit/closebrackets.js
@@ -79,17 +79,18 @@
 
     var ranges = cm.listSelections();
     for (var i = 0; i < ranges.length; i++) {
       if (!ranges[i].empty()) return CodeMirror.Pass;
       var around = charsAround(cm, ranges[i].head);
       if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
     }
     cm.operation(function() {
-      cm.replaceSelection("\n\n", null);
+      var linesep = cm.lineSeparator() || "\n";
+      cm.replaceSelection(linesep + linesep, null);
       cm.execCommand("goCharLeft");
       ranges = cm.listSelections();
       for (var i = 0; i < ranges.length; i++) {
         var line = ranges[i].head.line;
         cm.indentLine(line, null, true);
         cm.indentLine(line + 1, null, true);
       }
     });
--- a/devtools/client/sourceeditor/codemirror/addon/edit/continuelist.js
+++ b/devtools/client/sourceeditor/codemirror/addon/edit/continuelist.js
@@ -20,17 +20,18 @@
     var ranges = cm.listSelections(), replacements = [];
     for (var i = 0; i < ranges.length; i++) {
       var pos = ranges[i].head;
       var eolState = cm.getStateAfter(pos.line);
       var inList = eolState.list !== false;
       var inQuote = eolState.quote !== 0;
 
       var line = cm.getLine(pos.line), match = listRE.exec(line);
-      if (!ranges[i].empty() || (!inList && !inQuote) || !match) {
+      var cursorBeforeBullet = /^\s*$/.test(line.slice(0, pos.ch));
+      if (!ranges[i].empty() || (!inList && !inQuote) || !match || cursorBeforeBullet) {
         cm.execCommand("newlineAndIndent");
         return;
       }
       if (emptyListRE.test(line)) {
         if (!/>\s*$/.test(line)) cm.replaceRange("", {
           line: pos.line, ch: 0
         }, {
           line: pos.line, ch: pos.ch + 1
--- a/devtools/client/sourceeditor/codemirror/codemirror.bundle.js
+++ b/devtools/client/sourceeditor/codemirror/codemirror.bundle.js
@@ -1325,23 +1325,25 @@ var CodeMirror =
 	            for (++j$2; j$2 < i$7 && countsAsNum.test(types[j$2]); ++j$2) {}
 	            order.splice(at, 0, new BidiSpan(2, nstart, j$2))
 	            pos = j$2
 	          } else { ++j$2 }
 	        }
 	        if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)) }
 	      }
 	    }
-	    if (order[0].level == 1 && (m = str.match(/^\s+/))) {
-	      order[0].from = m[0].length
-	      order.unshift(new BidiSpan(0, 0, m[0].length))
-	    }
-	    if (lst(order).level == 1 && (m = str.match(/\s+$/))) {
-	      lst(order).to -= m[0].length
-	      order.push(new BidiSpan(0, len - m[0].length, len))
+	    if (direction == "ltr") {
+	      if (order[0].level == 1 && (m = str.match(/^\s+/))) {
+	        order[0].from = m[0].length
+	        order.unshift(new BidiSpan(0, 0, m[0].length))
+	      }
+	      if (lst(order).level == 1 && (m = str.match(/\s+$/))) {
+	        lst(order).to -= m[0].length
+	        order.push(new BidiSpan(0, len - m[0].length, len))
+	      }
 	    }
 
 	    return direction == "rtl" ? order.reverse() : order
 	  }
 	})()
 
 	// Get the bidi ordering for the given line (and cache it). Returns
 	// false for lines that are fully left-to-right, and an array of
@@ -1706,35 +1708,52 @@ var CodeMirror =
 	  this.lineStart += n
 	  try { return inner() }
 	  finally { this.lineStart -= n }
 	};
 	StringStream.prototype.lookAhead = function (n) {
 	  var oracle = this.lineOracle
 	  return oracle && oracle.lookAhead(n)
 	};
+	StringStream.prototype.baseToken = function () {
+	  var oracle = this.lineOracle
+	  return oracle && oracle.baseToken(this.pos)
+	};
 
 	var SavedContext = function(state, lookAhead) {
 	  this.state = state
 	  this.lookAhead = lookAhead
 	};
 
 	var Context = function(doc, state, line, lookAhead) {
 	  this.state = state
 	  this.doc = doc
 	  this.line = line
 	  this.maxLookAhead = lookAhead || 0
+	  this.baseTokens = null
+	  this.baseTokenPos = 1
 	};
 
 	Context.prototype.lookAhead = function (n) {
 	  var line = this.doc.getLine(this.line + n)
 	  if (line != null && n > this.maxLookAhead) { this.maxLookAhead = n }
 	  return line
 	};
 
+	Context.prototype.baseToken = function (n) {
+	    var this$1 = this;
+
+	  if (!this.baseTokens) { return null }
+	  while (this.baseTokens[this.baseTokenPos] <= n)
+	    { this$1.baseTokenPos += 2 }
+	  var type = this.baseTokens[this.baseTokenPos + 1]
+	  return {type: type && type.replace(/( |^)overlay .*/, ""),
+	          size: this.baseTokens[this.baseTokenPos] - n}
+	};
+
 	Context.prototype.nextLine = function () {
 	  this.line++
 	  if (this.maxLookAhead > 0) { this.maxLookAhead-- }
 	};
 
 	Context.fromSaved = function (doc, saved, line) {
 	  if (saved instanceof SavedContext)
 	    { return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead) }
@@ -1758,16 +1777,17 @@ var CodeMirror =
 	  var st = [cm.state.modeGen], lineClasses = {}
 	  // Compute the base array of styles
 	  runMode(cm, line.text, cm.doc.mode, context, function (end, style) { return st.push(end, style); },
 	          lineClasses, forceToEnd)
 	  var state = context.state
 
 	  // Run overlays, adjust style array.
 	  var loop = function ( o ) {
+	    context.baseTokens = st
 	    var overlay = cm.state.overlays[o], i = 1, at = 0
 	    context.state = true
 	    runMode(cm, line.text, overlay.mode, context, function (end, style) {
 	      var start = i
 	      // Ensure there's a token end at the current position, and that i points at it
 	      while (at < end) {
 	        var i_end = st[i]
 	        if (i_end > end)
@@ -1781,20 +1801,22 @@ var CodeMirror =
 	        i = start + 2
 	      } else {
 	        for (; start < i; start += 2) {
 	          var cur = st[start+1]
 	          st[start+1] = (cur ? cur + " " : "") + "overlay " + style
 	        }
 	      }
 	    }, lineClasses)
+	    context.state = state
+	    context.baseTokens = null
+	    context.baseTokenPos = 1
 	  };
 
 	  for (var o = 0; o < cm.state.overlays.length; ++o) loop( o );
-	  context.state = state
 
 	  return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null}
 	}
 
 	function getLineStyles(cm, line, updateFrontier) {
 	  if (!line.styles || line.styles[0] != cm.state.modeGen) {
 	    var context = getContextBefore(cm, lineNo(line))
 	    var resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state)
@@ -3124,16 +3146,17 @@ var CodeMirror =
 	  // all, so a binary search doesn't work, and we want to return a
 	  // part that only spans one line so that the binary search in
 	  // coordsCharInner is safe. As such, we first find the extent of the
 	  // wrapped line, and then do a flat search in which we discard any
 	  // spans that aren't on the line.
 	  var ref = wrappedLineExtent(cm, lineObj, preparedMeasure, y);
 	  var begin = ref.begin;
 	  var end = ref.end;
+	  if (/\s/.test(lineObj.text.charAt(end - 1))) { end-- }
 	  var part = null, closestDist = null
 	  for (var i = 0; i < order.length; i++) {
 	    var p = order[i]
 	    if (p.from >= end || p.to <= begin) { continue }
 	    var ltr = p.level != 1
 	    var endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right
 	    // Weigh against spans ending before this, so that they are only
 	    // picked if nothing ends after
@@ -3314,68 +3337,70 @@ var CodeMirror =
 	function cmpCoords(a, b) { return a.top - b.top || a.left - b.left }
 
 	// Draws the given range as a highlighted selection
 	function drawSelectionRange(cm, range, output) {
 	  var display = cm.display, doc = cm.doc
 	  var fragment = document.createDocumentFragment()
 	  var padding = paddingH(cm.display), leftSide = padding.left
 	  var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right
+	  var docLTR = doc.direction == "ltr"
 
 	  function add(left, top, width, bottom) {
 	    if (top < 0) { top = 0 }
 	    top = Math.round(top)
 	    bottom = Math.round(bottom)
 	    fragment.appendChild(elt("div", null, "CodeMirror-selected", ("position: absolute; left: " + left + "px;\n                             top: " + top + "px; width: " + (width == null ? rightSide - left : width) + "px;\n                             height: " + (bottom - top) + "px")))
 	  }
 
 	  function drawForLine(line, fromArg, toArg) {
 	    var lineObj = getLine(doc, line)
 	    var lineLen = lineObj.text.length
 	    var start, end
 	    function coords(ch, bias) {
 	      return charCoords(cm, Pos(line, ch), "div", lineObj, bias)
 	    }
 
+	    function wrapX(pos, dir, side) {
+	      var extent = wrappedLineExtentChar(cm, lineObj, null, pos)
+	      var prop = (dir == "ltr") == (side == "after") ? "left" : "right"
+	      var ch = side == "after" ? extent.begin : extent.end - (/\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1)
+	      return coords(ch, prop)[prop]
+	    }
+
 	    var order = getOrder(lineObj, doc.direction)
 	    iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir, i) {
-	      var fromPos = coords(from, dir == "ltr" ? "left" : "right")
-	      var toPos = coords(to - 1, dir == "ltr" ? "right" : "left")
-	      if (dir == "ltr") {
-	        var fromLeft = fromArg == null && from == 0 ? leftSide : fromPos.left
-	        var toRight = toArg == null && to == lineLen ? rightSide : toPos.right
-	        if (toPos.top - fromPos.top <= 3) { // Single line
-	          add(fromLeft, toPos.top, toRight - fromLeft, toPos.bottom)
-	        } else { // Multiple lines
-	          add(fromLeft, fromPos.top, null, fromPos.bottom)
-	          if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top) }
-	          add(leftSide, toPos.top, toPos.right, toPos.bottom)
-	        }
-	      } else if (from < to) { // RTL
-	        var fromRight = fromArg == null && from == 0 ? rightSide : fromPos.right
-	        var toLeft = toArg == null && to == lineLen ? leftSide : toPos.left
-	        if (toPos.top - fromPos.top <= 3) { // Single line
-	          add(toLeft, toPos.top, fromRight - toLeft, toPos.bottom)
-	        } else { // Multiple lines
-	          var topLeft = leftSide
-	          if (i) {
-	            var topEnd = wrappedLineExtentChar(cm, lineObj, null, from).end
-	            // The coordinates returned for an RTL wrapped space tend to
-	            // be complete bogus, so try to skip that here.
-	            topLeft = coords(topEnd - (/\s/.test(lineObj.text.charAt(topEnd - 1)) ? 2 : 1), "left").left
-	          }
-	          add(topLeft, fromPos.top, fromRight - topLeft, fromPos.bottom)
-	          if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top) }
-	          var botWidth = null
-	          if (i < order.length  - 1 || true) {
-	            var botStart = wrappedLineExtentChar(cm, lineObj, null, to).begin
-	            botWidth = coords(botStart, "right").right - toLeft
-	          }
-	          add(toLeft, toPos.top, botWidth, toPos.bottom)
-	        }
+	      var ltr = dir == "ltr"
+	      var fromPos = coords(from, ltr ? "left" : "right")
+	      var toPos = coords(to - 1, ltr ? "right" : "left")
+
+	      var openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen
+	      var first = i == 0, last = !order || i == order.length - 1
+	      if (toPos.top - fromPos.top <= 3) { // Single line
+	        var openLeft = (docLTR ? openStart : openEnd) && first
+	        var openRight = (docLTR ? openEnd : openStart) && last
+	        var left = openLeft ? leftSide : (ltr ? fromPos : toPos).left
+	        var right = openRight ? rightSide : (ltr ? toPos : fromPos).right
+	        add(left, fromPos.top, right - left, fromPos.bottom)
+	      } else { // Multiple lines
+	        var topLeft, topRight, botLeft, botRight
+	        if (ltr) {
+	          topLeft = docLTR && openStart && first ? leftSide : fromPos.left
+	          topRight = docLTR ? rightSide : wrapX(from, dir, "before")
+	          botLeft = docLTR ? leftSide : wrapX(to, dir, "after")
+	          botRight = docLTR && openEnd && last ? rightSide : toPos.right
+	        } else {
+	          topLeft = !docLTR ? leftSide : wrapX(from, dir, "before")
+	          topRight = !docLTR && openStart && first ? rightSide : fromPos.right
+	          botLeft = !docLTR && openEnd && last ? leftSide : toPos.left
+	          botRight = !docLTR ? rightSide : wrapX(to, dir, "after")
+	        }
+	        add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom)
+	        if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top) }
+	        add(botLeft, toPos.top, botRight - botLeft, toPos.bottom)
 	      }
 
 	      if (!start || cmpCoords(fromPos, start) < 0) { start = fromPos }
 	      if (cmpCoords(toPos, start) < 0) { start = toPos }
 	      if (!end || cmpCoords(fromPos, end) < 0) { end = fromPos }
 	      if (cmpCoords(toPos, end) < 0) { end = toPos }
 	    })
 	    return {start: start, end: end}
@@ -6990,17 +7015,17 @@ var CodeMirror =
 	      var sticky = moveInStorageOrder ? "after" : "before"
 	      var ch
 	      // With a wrapped rtl chunk (possibly spanning multiple bidi parts),
 	      // it could be that the last bidi part is not on the last visual line,
 	      // since visual lines contain content order-consecutive chunks.
 	      // Thus, in rtl, we are looking for the first (content-order) character
 	      // in the rtl chunk that is on the last line (that is, the same line
 	      // as the last (content-order) character).
-	      if (part.level > 0) {
+	      if (part.level > 0 || cm.doc.direction == "rtl") {
 	        var prep = prepareMeasureForLine(cm, lineObj)
 	        ch = dir < 0 ? lineObj.text.length - 1 : 0
 	        var targetTop = measureCharPrepared(cm, prep, ch).top
 	        ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch).top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch)
 	        if (sticky == "before") { ch = moveCharLogically(lineObj, ch, 1) }
 	      } else { ch = dir < 0 ? part.to : part.from }
 	      return new Pos(lineNo, ch, sticky)
 	    }
@@ -9852,17 +9877,17 @@ var CodeMirror =
 	CodeMirror.defineDocExtension = function (name, func) {
 	  Doc.prototype[name] = func
 	}
 
 	CodeMirror.fromTextArea = fromTextArea
 
 	addLegacyProps(CodeMirror)
 
-	CodeMirror.version = "5.30.0"
+	CodeMirror.version = "5.31.0"
 
 	return CodeMirror;
 
 	})));
 
 /***/ }),
 /* 3 */
 /***/ (function(module, exports, __webpack_require__) {
@@ -10662,17 +10687,18 @@ var CodeMirror =
 
 	    var ranges = cm.listSelections();
 	    for (var i = 0; i < ranges.length; i++) {
 	      if (!ranges[i].empty()) return CodeMirror.Pass;
 	      var around = charsAround(cm, ranges[i].head);
 	      if (!around || explode.indexOf(around) % 2 != 0) return CodeMirror.Pass;
 	    }
 	    cm.operation(function() {
-	      cm.replaceSelection("\n\n", null);
+	      var linesep = cm.lineSeparator() || "\n";
+	      cm.replaceSelection(linesep + linesep, null);
 	      cm.execCommand("goCharLeft");
 	      ranges = cm.listSelections();
 	      for (var i = 0; i < ranges.length; i++) {
 	        var line = ranges[i].head.line;
 	        cm.indentLine(line, null, true);
 	        cm.indentLine(line + 1, null, true);
 	      }
 	    });
@@ -11033,23 +11059,23 @@ var CodeMirror =
 	  var jsonMode = parserConfig.json || jsonldMode;
 	  var isTS = parserConfig.typescript;
 	  var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
 
 	  // Tokenizer
 
 	  var keywords = function(){
 	    function kw(type) {return {type: type, style: "keyword"};}
-	    var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
+	    var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"), D = kw("keyword d");
 	    var operator = kw("operator"), atom = {type: "atom", style: "atom"};
 
 	    var jsKeywords = {
 	      "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
-	      "return": C, "break": C, "continue": C, "new": kw("new"), "delete": C, "void": C, "throw": C, "debugger": C,
-	      "var": kw("var"), "const": kw("var"), "let": kw("var"),
+	      "return": D, "break": D, "continue": D, "new": kw("new"), "delete": C, "void": C, "throw": C,
+	      "debugger": kw("debugger"), "var": kw("var"), "const": kw("var"), "let": kw("var"),
 	      "function": kw("function"), "catch": kw("catch"),
 	      "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
 	      "in": operator, "typeof": operator, "instanceof": operator,
 	      "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
 	      "this": kw("this"), "class": kw("class"), "super": kw("atom"),
 	      "yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
 	      "await": C
 	    };
@@ -11138,28 +11164,34 @@ var CodeMirror =
 	      } else if (stream.eat("/")) {
 	        stream.skipToEnd();
 	        return ret("comment", "comment");
 	      } else if (expressionAllowed(stream, state, 1)) {
 	        readRegexp(stream);
 	        stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/);
 	        return ret("regexp", "string-2");
 	      } else {
-	        stream.eatWhile(isOperatorChar);
+	        stream.eat("=");
 	        return ret("operator", "operator", stream.current());
 	      }
 	    } else if (ch == "`") {
 	      state.tokenize = tokenQuasi;
 	      return tokenQuasi(stream, state);
 	    } else if (ch == "#") {
 	      stream.skipToEnd();
 	      return ret("error", "error");
 	    } else if (isOperatorChar.test(ch)) {
-	      if (ch != ">" || !state.lexical || state.lexical.type != ">")
-	        stream.eatWhile(isOperatorChar);
+	      if (ch != ">" || !state.lexical || state.lexical.type != ">") {
+	        if (stream.eat("=")) {
+	          if (ch == "!" || ch == "=") stream.eat("=")
+	        } else if (/[<>*+\-]/.test(ch)) {
+	          stream.eat(ch)
+	          if (ch == ">") stream.eat(ch)
+	        }
+	      }
 	      return ret("operator", "operator", stream.current());
 	    } else if (wordRE.test(ch)) {
 	      stream.eatWhile(wordRE);
 	      var word = stream.current()
 	      if (state.lastType != ".") {
 	        if (keywords.propertyIsEnumerable(word)) {
 	          var kw = keywords[word]
 	          return ret(kw.type, kw.style, word)
@@ -11361,16 +11393,18 @@ var CodeMirror =
 	    };
 	    return exp;
 	  }
 
 	  function statement(type, value) {
 	    if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex);
 	    if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
 	    if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
+	    if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex);
+	    if (type == "debugger") return cont(expect(";"));
 	    if (type == "{") return cont(pushlex("}"), block, poplex);
 	    if (type == ";") return cont();
 	    if (type == "if") {
 	      if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
 	        cx.state.cc.pop()();
 	      return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
 	    }
 	    if (type == "function") return cont(functiondef);
@@ -11416,33 +11450,29 @@ var CodeMirror =
 	      if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext);
 	      else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
 	    }
 
 	    var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
 	    if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
 	    if (type == "function") return cont(functiondef, maybeop);
 	    if (type == "class") return cont(pushlex("form"), classExpression, poplex);
-	    if (type == "keyword c" || type == "async") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
+	    if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression);
 	    if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
 	    if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
 	    if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
 	    if (type == "{") return contCommasep(objprop, "}", null, maybeop);
 	    if (type == "quasi") return pass(quasi, maybeop);
 	    if (type == "new") return cont(maybeTarget(noComma));
 	    return cont();
 	  }
 	  function maybeexpression(type) {
 	    if (type.match(/[;\}\)\],]/)) return pass();
 	    return pass(expression);
 	  }
-	  function maybeexpressionNoComma(type) {
-	    if (type.match(/[;\}\)\],]/)) return pass();
-	    return pass(expressionNoComma);
-	  }
 
 	  function maybeoperatorComma(type, value) {
 	    if (type == ",") return cont(expression);
 	    return maybeoperatorNoComma(type, value, false);
 	  }
 	  function maybeoperatorNoComma(type, value, noComma) {
 	    var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
 	    var expr = noComma == false ? expression : expressionNoComma;
@@ -11520,17 +11550,20 @@ var CodeMirror =
 	      return cont(afterprop);
 	    } else if (type == "jsonld-keyword") {
 	      return cont(afterprop);
 	    } else if (type == "modifier") {
 	      return cont(objprop)
 	    } else if (type == "[") {
 	      return cont(expression, expect("]"), afterprop);
 	    } else if (type == "spread") {
-	      return cont(expression, afterprop);
+	      return cont(expressionNoComma, afterprop);
+	    } else if (value == "*") {
+	      cx.marked = "keyword";
+	      return cont(objprop);
 	    } else if (type == ":") {
 	      return pass(afterprop)
 	    }
 	  }
 	  function getterSetter(type) {
 	    if (type != "variable") return pass(afterprop);
 	    cx.marked = "property";
 	    return cont(functiondef);
@@ -11568,17 +11601,17 @@ var CodeMirror =
 	  }
 	  function maybetype(type, value) {
 	    if (isTS) {
 	      if (type == ":") return cont(typeexpr);
 	      if (value == "?") return cont(maybetype);
 	    }
 	  }
 	  function typeexpr(type, value) {
-	    if (type == "variable") {
+	    if (type == "variable" || value == "void") {
 	      if (value == "keyof") {
 	        cx.marked = "keyword"
 	        return cont(typeexpr)
 	      } else {
 	        cx.marked = "type"
 	        return cont(afterType)
 	      }
 	    }
@@ -11755,17 +11788,17 @@ var CodeMirror =
 	  function isContinuedStatement(state, textAfter) {
 	    return state.lastType == "operator" || state.lastType == "," ||
 	      isOperatorChar.test(textAfter.charAt(0)) ||
 	      /[,.]/.test(textAfter.charAt(0));
 	  }
 
 	  function expressionAllowed(stream, state, backUp) {
 	    return state.tokenize == tokenBase &&
-	      /^(?:operator|sof|keyword [bc]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
+	      /^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
 	      (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
 	  }
 
 	  // Interface
 
 	  return {
 	    startState: function(basecolumn) {
 	      var state = {
@@ -11824,16 +11857,17 @@ var CodeMirror =
 	        return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
 	      else if (lexical.align) return lexical.column + (closing ? 0 : 1);
 	      else return lexical.indented + (closing ? 0 : indentUnit);
 	    },
 
 	    electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
 	    blockCommentStart: jsonMode ? null : "/*",
 	    blockCommentEnd: jsonMode ? null : "*/",
+	    blockCommentContinue: jsonMode ? null : " * ",
 	    lineComment: jsonMode ? null : "//",
 	    fold: "brace",
 	    closeBrackets: "()[]{}''\"\"``",
 
 	    helperType: jsonMode ? "json" : "javascript",
 	    jsonldMode: jsonldMode,
 	    jsonMode: jsonMode,
 
@@ -12673,16 +12707,17 @@ var CodeMirror =
 	        }
 	      }
 	      return indent;
 	    },
 
 	    electricChars: "}",
 	    blockCommentStart: "/*",
 	    blockCommentEnd: "*/",
+	    blockCommentContinue: " * ",
 	    lineComment: lineComment,
 	    fold: "brace"
 	  };
 	});
 
 	  function keySet(array) {
 	    var keys = {};
 	    for (var i = 0; i < array.length; ++i) {
@@ -14232,16 +14267,17 @@ var CodeMirror =
 
 	      return ctx.indented + (closing ? 0 : indentUnit) +
 	        (!closing && switchBlock && !/^(?:case|default)\b/.test(textAfter) ? indentUnit : 0);
 	    },
 
 	    electricInput: indentSwitch ? /^\s*(?:case .*?:|default:|\{\}?|\})$/ : /^\s*[{}]$/,
 	    blockCommentStart: "/*",
 	    blockCommentEnd: "*/",
+	    blockCommentContinue: " * ",
 	    lineComment: "//",
 	    fold: "brace"
 	  };
 	});
 
 	  function words(str) {
 	    var obj = {}, words = str.split(" ");
 	    for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
@@ -14579,17 +14615,17 @@ var CodeMirror =
 	      "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " +
 	      "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void"
 	    ),
 	    intendSwitch: false,
 	    indentStatements: false,
 	    multiLineStrings: true,
 	    number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+\.?\d*|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i,
 	    blockKeywords: words("catch class do else finally for if where try while enum"),
-	    defKeywords: words("class val var object package interface fun"),
+	    defKeywords: words("class val var object interface fun"),
 	    atoms: words("true false null this"),
 	    hooks: {
 	      '"': function(stream, state) {
 	        state.tokenize = tokenKotlinString(stream.match('""'));
 	        return state.tokenize(stream, state);
 	      }
 	    },
 	    modeProps: {closeBrackets: {triples: '"'}}
@@ -15780,30 +15816,77 @@ var CodeMirror =
 	    function leaveVimMode(cm) {
 	      cm.setOption('disableInput', false);
 	      cm.off('cursorActivity', onCursorActivity);
 	      CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm));
 	      cm.state.vim = null;
 	    }
 
 	    function detachVimMap(cm, next) {
-	      if (this == CodeMirror.keyMap.vim)
+	      if (this == CodeMirror.keyMap.vim) {
 	        CodeMirror.rmClass(cm.getWrapperElement(), "cm-fat-cursor");
+	        if (cm.getOption("inputStyle") == "contenteditable" && document.body.style.caretColor != null) {
+	          disableFatCursorMark(cm);
+	          cm.getInputField().style.caretColor = "";
+	        }
+	      }
 
 	      if (!next || next.attach != attachVimMap)
 	        leaveVimMode(cm);
 	    }
 	    function attachVimMap(cm, prev) {
-	      if (this == CodeMirror.keyMap.vim)
+	      if (this == CodeMirror.keyMap.vim) {
 	        CodeMirror.addClass(cm.getWrapperElement(), "cm-fat-cursor");
+	        if (cm.getOption("inputStyle") == "contenteditable" && document.body.style.caretColor != null) {
+	          enableFatCursorMark(cm);
+	          cm.getInputField().style.caretColor = "transparent";
+	        }
+	      }
 
 	      if (!prev || prev.attach != attachVimMap)
 	        enterVimMode(cm);
 	    }
 
+	    function fatCursorMarks(cm) {
+	      var ranges = cm.listSelections(), result = []
+	      for (var i = 0; i < ranges.length; i++) {
+	        var range = ranges[i]
+	        if (range.empty()) {
+	          if (range.anchor.ch < cm.getLine(range.anchor.line).length) {
+	            result.push(cm.markText(range.anchor, Pos(range.anchor.line, range.anchor.ch + 1),
+	                                    {className: "cm-fat-cursor-mark"}))
+	          } else {
+	            var widget = document.createElement("span")
+	            widget.textContent = "\u00a0"
+	            widget.className = "cm-fat-cursor-mark"
+	            result.push(cm.setBookmark(range.anchor, {widget: widget}))
+	          }
+	        }
+	      }
+	      return result
+	    }
+
+	    function updateFatCursorMark(cm) {
+	      var marks = cm.state.fatCursorMarks
+	      if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear()
+	      cm.state.fatCursorMarks = fatCursorMarks(cm)
+	    }
+
+	    function enableFatCursorMark(cm) {
+	      cm.state.fatCursorMarks = fatCursorMarks(cm)
+	      cm.on("cursorActivity", updateFatCursorMark)
+	    }
+
+	    function disableFatCursorMark(cm) {
+	      var marks = cm.state.fatCursorMarks
+	      if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear()
+	      cm.state.fatCursorMarks = null
+	      cm.off("cursorActivity", updateFatCursorMark)
+	    }
+
 	    // Deprecated, simply setting the keymap works again.
 	    CodeMirror.defineOption('vimMode', false, function(cm, val, prev) {
 	      if (val && cm.getOption("keyMap") != "vim")
 	        cm.setOption("keyMap", "vim");
 	      else if (!val && prev != CodeMirror.Init && /^vim/.test(cm.getOption("keyMap")))
 	        cm.setOption("keyMap", "default");
 	    });
 
@@ -17559,17 +17642,18 @@ var CodeMirror =
 	          text = cm.getSelection();
 	          var replacement = fillArray('', ranges.length);
 	          cm.replaceSelections(replacement);
 	          finalHead = ranges[0].anchor;
 	        }
 	        vimGlobalState.registerController.pushText(
 	            args.registerName, 'delete', text,
 	            args.linewise, vim.visualBlock);
-	        return clipCursorToContent(cm, finalHead);
+	        var includeLineBreak = vim.insertMode
+	        return clipCursorToContent(cm, finalHead, includeLineBreak);
 	      },
 	      indent: function(cm, args, ranges) {
 	        var vim = cm.state.vim;
 	        var startLine = ranges[0].anchor.line;
 	        var endLine = vim.visualBlock ?
 	          ranges[ranges.length - 1].anchor.line :
 	          ranges[0].head.line;
 	        // In visual mode, n> shifts the selection right n times, instead of
@@ -18769,17 +18853,17 @@ var CodeMirror =
 	        }
 	      }
 	      if (state.nextCh || state.curMoveThrough) {
 	        return Pos(line, state.index);
 	      }
 	      return cur;
 	    }
 
-	    /**
+	    /*
 	     * Returns the boundaries of the next word. If the cursor in the middle of
 	     * the word, then returns the boundaries of the current word, starting at
 	     * the cursor. If the cursor is at the start/end of a word, and we are going
 	     * forward/backward, respectively, find the boundaries of the next word.
 	     *
 	     * @param {CodeMirror} cm CodeMirror object.
 	     * @param {Cursor} cur The cursor position.
 	     * @param {boolean} forward True to search forward. False to search
@@ -19503,16 +19587,25 @@ var CodeMirror =
 	      return {top: from.line, bottom: to.line};
 	    }
 
 	    function getMarkPos(cm, vim, markName) {
 	      if (markName == '\'') {
 	        var history = cm.doc.history.done;
 	        var event = history[history.length - 2];
 	        return event && event.ranges && event.ranges[0].head;
+	      } else if (markName == '.') {
+	        if (cm.doc.history.lastModTime == 0) {
+	          return  // If no changes, bail out; don't bother to copy or reverse history array.
+	        } else {
+	          var changeHistory = cm.doc.history.done.filter(function(el){ if (el.changes !== undefined) { return el } });
+	          changeHistory.reverse();
+	          var lastEditPos = changeHistory[0].changes[0].to;
+	        }
+	        return lastEditPos;
 	      }
 
 	      var mark = vim.marks[markName];
 	      return mark && mark.find();
 	    }
 
 	    var ExCommandDispatcher = function() {
 	      this.buildCommandMap_();
@@ -20313,17 +20406,18 @@ var CodeMirror =
 	      var insertModeChangeRegister = vimGlobalState.registerController.getRegister('.');
 	      var isPlaying = macroModeState.isPlaying;
 	      var lastChange = macroModeState.lastInsertModeChanges;
 	      // In case of visual block, the insertModeChanges are not saved as a
 	      // single word, so we convert them to a single word
 	      // so as to update the ". register as expected in real vim.
 	      var text = [];
 	      if (!isPlaying) {
-	        var selLength = lastChange.inVisualBlock ? vim.lastSelection.visualBlock.height : 1;
+	        var selLength = lastChange.inVisualBlock && vim.lastSelection ?
+	            vim.lastSelection.visualBlock.height : 1;
 	        var changes = lastChange.changes;
 	        var text = [];
 	        var i = 0;
 	        // In case of multiple selections in blockwise visual,
 	        // the inserted text, for example: 'f<Backspace>oo', is stored as
 	        // 'f', 'f', InsertModeKey 'o', 'o', 'o', 'o'. (if you have a block with 2 lines).
 	        // We push the contents of the changes array as per the following:
 	        // 1. In case of InsertModeKey, just increment by 1.
@@ -20699,21 +20793,18 @@ var CodeMirror =
 	    mod(__webpack_require__(2), __webpack_require__(3), __webpack_require__(5));
 	  else if (typeof define == "function" && define.amd) // AMD
 	    define(["../lib/codemirror", "../addon/search/searchcursor", "../addon/edit/matchbrackets"], mod);
 	  else // Plain browser env
 	    mod(CodeMirror);
 	})(function(CodeMirror) {
 	  "use strict";
 
-	  var map = CodeMirror.keyMap.sublime = {fallthrough: "default"};
 	  var cmds = CodeMirror.commands;
 	  var Pos = CodeMirror.Pos;
-	  var mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault;
-	  var ctrl = mac ? "Cmd-" : "Ctrl-";
 
 	  // This is not exactly Sublime's algorithm. I couldn't make heads or tails of that.
 	  function findPosSubword(doc, start, dir) {
 	    if (dir < 0 && start.ch == 0) return doc.clipPos(Pos(start.line - 1));
 	    var line = doc.getLine(start.line);
 	    if (dir > 0 && start.ch >= line.length) return doc.clipPos(Pos(start.line + 1, 0));
 	    var state = "start", type;
 	    for (var pos = start.ch, e = dir < 0 ? 0 : line.length, i = 0; pos != e; pos += dir, i++) {
@@ -20737,75 +20828,65 @@ var CodeMirror =
 	    cm.extendSelectionsBy(function(range) {
 	      if (cm.display.shift || cm.doc.extend || range.empty())
 	        return findPosSubword(cm.doc, range.head, dir);
 	      else
 	        return dir < 0 ? range.from() : range.to();
 	    });
 	  }
 
-	  var goSubwordCombo = mac ? "Ctrl-" : "Alt-";
-
-	  cmds[map[goSubwordCombo + "Left"] = "goSubwordLeft"] = function(cm) { moveSubword(cm, -1); };
-	  cmds[map[goSubwordCombo + "Right"] = "goSubwordRight"] = function(cm) { moveSubword(cm, 1); };
-
-	  if (mac) map["Cmd-Left"] = "goLineStartSmart";
-
-	  var scrollLineCombo = mac ? "Ctrl-Alt-" : "Ctrl-";
-
-	  cmds[map[scrollLineCombo + "Up"] = "scrollLineUp"] = function(cm) {
+	  cmds.goSubwordLeft = function(cm) { moveSubword(cm, -1); };
+	  cmds.goSubwordRight = function(cm) { moveSubword(cm, 1); };
+
+	  cmds.scrollLineUp = function(cm) {
 	    var info = cm.getScrollInfo();
 	    if (!cm.somethingSelected()) {
 	      var visibleBottomLine = cm.lineAtHeight(info.top + info.clientHeight, "local");
 	      if (cm.getCursor().line >= visibleBottomLine)
 	        cm.execCommand("goLineUp");
 	    }
 	    cm.scrollTo(null, info.top - cm.defaultTextHeight());
 	  };
-	  cmds[map[scrollLineCombo + "Down"] = "scrollLineDown"] = function(cm) {
+	  cmds.scrollLineDown = function(cm) {
 	    var info = cm.getScrollInfo();
 	    if (!cm.somethingSelected()) {
 	      var visibleTopLine = cm.lineAtHeight(info.top, "local")+1;
 	      if (cm.getCursor().line <= visibleTopLine)
 	        cm.execCommand("goLineDown");
 	    }
 	    cm.scrollTo(null, info.top + cm.defaultTextHeight());
 	  };
 
-	  cmds[map["Shift-" + ctrl + "L"] = "splitSelectionByLine"] = function(cm) {
+	  cmds.splitSelectionByLine = function(cm) {
 	    var ranges = cm.listSelections(), lineRanges = [];
 	    for (var i = 0; i < ranges.length; i++) {
 	      var from = ranges[i].from(), to = ranges[i].to();
 	      for (var line = from.line; line <= to.line; ++line)
 	        if (!(to.line > from.line && line == to.line && to.ch == 0))
 	          lineRanges.push({anchor: line == from.line ? from : Pos(line, 0),
 	                           head: line == to.line ? to : Pos(line)});
 	    }
 	    cm.setSelections(lineRanges, 0);
 	  };
 
-	  map["Shift-Tab"] = "indentLess";
-
-	  cmds[map["Esc"] = "singleSelectionTop"] = function(cm) {
+	  cmds.singleSelectionTop = function(cm) {
 	    var range = cm.listSelections()[0];
 	    cm.setSelection(range.anchor, range.head, {scroll: false});
 	  };
 
-	  cmds[map[ctrl + "L"] = "selectLine"] = function(cm) {
+	  cmds.selectLine = function(cm) {
 	    var ranges = cm.listSelections(), extended = [];
 	    for (var i = 0; i < ranges.length; i++) {
 	      var range = ranges[i];
 	      extended.push({anchor: Pos(range.from().line, 0),
 	                     head: Pos(range.to().line + 1, 0)});
 	    }
 	    cm.setSelections(extended);
 	  };
 
-	  map["Shift-Ctrl-K"] = "deleteLine";
-
 	  function insertLine(cm, above) {
 	    if (cm.isReadOnly()) return CodeMirror.Pass
 	    cm.operation(function() {
 	      var len = cm.listSelections().length, newSelection = [], last = -1;
 	      for (var i = 0; i < len; i++) {
 	        var head = cm.listSelections()[i].head;
 	        if (head.line <= last) continue;
 	        var at = Pos(head.line + (above ? 0 : 1), 0);
@@ -20814,28 +20895,28 @@ var CodeMirror =
 	        newSelection.push({head: at, anchor: at});
 	        last = head.line + 1;
 	      }
 	      cm.setSelections(newSelection);
 	    });
 	    cm.execCommand("indentAuto");
 	  }
 
-	  cmds[map[ctrl + "Enter"] = "insertLineAfter"] = function(cm) { return insertLine(cm, false); };
-
-	  cmds[map["Shift-" + ctrl + "Enter"] = "insertLineBefore"] = function(cm) { return insertLine(cm, true); };
+	  cmds.insertLineAfter = function(cm) { return insertLine(cm, false); };
+
+	  cmds.insertLineBefore = function(cm) { return insertLine(cm, true); };
 
 	  function wordAt(cm, pos) {
 	    var start = pos.ch, end = start, line = cm.getLine(pos.line);
 	    while (start && CodeMirror.isWordChar(line.charAt(start - 1))) --start;
 	    while (end < line.length && CodeMirror.isWordChar(line.charAt(end))) ++end;
 	    return {from: Pos(pos.line, start), to: Pos(pos.line, end), word: line.slice(start, end)};
 	  }
 
-	  cmds[map[ctrl + "D"] = "selectNextOccurrence"] = function(cm) {
+	  cmds.selectNextOccurrence = function(cm) {
 	    var from = cm.getCursor("from"), to = cm.getCursor("to");
 	    var fullWord = cm.state.sublimeFindFullWord == cm.doc.sel;
 	    if (CodeMirror.cmpPos(from, to) == 0) {
 	      var word = wordAt(cm, from);
 	      if (!word.word) return;
 	      cm.setSelection(word.from, word.to);
 	      fullWord = true;
 	    } else {
@@ -20862,20 +20943,18 @@ var CodeMirror =
 	      var newAnchor = cm.findPosV(range.anchor, dir, "line");
 	      var newHead = cm.findPosV(range.head, dir, "line");
 	      var newRange = {anchor: newAnchor, head: newHead};
 	      newRanges.push(range);
 	      newRanges.push(newRange);
 	    }
 	    cm.setSelections(newRanges);
 	  }
-
-	  var addCursorToLineCombo = mac ? "Shift-Cmd" : 'Alt-Ctrl';
-	  cmds[map[addCursorToLineCombo + "Up"] = "addCursorToPrevLine"] = function(cm) { addCursorToSelection(cm, -1); };
-	  cmds[map[addCursorToLineCombo + "Down"] = "addCursorToNextLine"] = function(cm) { addCursorToSelection(cm, 1); };
+	  cmds.addCursorToPrevLine = function(cm) { addCursorToSelection(cm, -1); };
+	  cmds.addCursorToNextLine = function(cm) { addCursorToSelection(cm, 1); };
 
 	  function isSelectedRange(ranges, from, to) {
 	    for (var i = 0; i < ranges.length; i++)
 	      if (ranges[i].from() == from && ranges[i].to() == to) return true
 	    return false
 	  }
 
 	  var mirror = "(){}[]";
@@ -20894,35 +20973,33 @@ var CodeMirror =
 	        }
 	        pos = Pos(closing.pos.line, closing.pos.ch + 1);
 	      }
 	    }
 	    cm.setSelections(newRanges);
 	    return true;
 	  }
 
-	  cmds[map["Shift-" + ctrl + "Space"] = "selectScope"] = function(cm) {
+	  cmds.selectScope = function(cm) {
 	    selectBetweenBrackets(cm) || cm.execCommand("selectAll");
 	  };
-	  cmds[map["Shift-" + ctrl + "M"] = "selectBetweenBrackets"] = function(cm) {
+	  cmds.selectBetweenBrackets = function(cm) {
 	    if (!selectBetweenBrackets(cm)) return CodeMirror.Pass;
 	  };
 
-	  cmds[map[ctrl + "M"] = "goToBracket"] = function(cm) {
+	  cmds.goToBracket = function(cm) {
 	    cm.extendSelectionsBy(function(range) {
 	      var next = cm.scanForBracket(range.head, 1);
 	      if (next && CodeMirror.cmpPos(next.pos, range.head) != 0) return next.pos;
 	      var prev = cm.scanForBracket(range.head, -1);
 	      return prev && Pos(prev.pos.line, prev.pos.ch + 1) || range.head;
 	    });
 	  };
 
-	  var swapLineCombo = mac ? "Cmd-Ctrl-" : "Shift-Ctrl-";
-
-	  cmds[map[swapLineCombo + "Up"] = "swapLineUp"] = function(cm) {
+	  cmds.swapLineUp = function(cm) {
 	    if (cm.isReadOnly()) return CodeMirror.Pass
 	    var ranges = cm.listSelections(), linesToMove = [], at = cm.firstLine() - 1, newSels = [];
 	    for (var i = 0; i < ranges.length; i++) {
 	      var range = ranges[i], from = range.from().line - 1, to = range.to().line;
 	      newSels.push({anchor: Pos(range.anchor.line - 1, range.anchor.ch),
 	                    head: Pos(range.head.line - 1, range.head.ch)});
 	      if (range.to().ch == 0 && !range.empty()) --to;
 	      if (from > at) linesToMove.push(from, to);
@@ -20939,17 +21016,17 @@ var CodeMirror =
 	        else
 	          cm.replaceRange(line + "\n", Pos(to, 0), null, "+swapLine");
 	      }
 	      cm.setSelections(newSels);
 	      cm.scrollIntoView();
 	    });
 	  };
 
-	  cmds[map[swapLineCombo + "Down"] = "swapLineDown"] = function(cm) {
+	  cmds.swapLineDown = function(cm) {
 	    if (cm.isReadOnly()) return CodeMirror.Pass
 	    var ranges = cm.listSelections(), linesToMove = [], at = cm.lastLine() + 1;
 	    for (var i = ranges.length - 1; i >= 0; i--) {
 	      var range = ranges[i], from = range.to().line + 1, to = range.from().line;
 	      if (range.to().ch == 0 && !range.empty()) from--;
 	      if (from < at) linesToMove.push(from, to);
 	      else if (linesToMove.length) linesToMove[linesToMove.length - 1] = to;
 	      at = to;
@@ -20963,21 +21040,21 @@ var CodeMirror =
 	        else
 	          cm.replaceRange("", Pos(from, 0), Pos(from + 1, 0), "+swapLine");
 	        cm.replaceRange(line + "\n", Pos(to, 0), null, "+swapLine");
 	      }
 	      cm.scrollIntoView();
 	    });
 	  };
 
-	  cmds[map[ctrl + "/"] = "toggleCommentIndented"] = function(cm) {
+	  cmds.toggleCommentIndented = function(cm) {
 	    cm.toggleComment({ indent: true });
 	  }
 
-	  cmds[map[ctrl + "J"] = "joinLines"] = function(cm) {
+	  cmds.joinLines = function(cm) {
 	    var ranges = cm.listSelections(), joined = [];
 	    for (var i = 0; i < ranges.length; i++) {
 	      var range = ranges[i], from = range.from();
 	      var start = from.line, end = range.to().line;
 	      while (i < ranges.length - 1 && ranges[i + 1].from().line == end)
 	        end = ranges[++i].to().line;
 	      joined.push({start: start, end: end, anchor: !range.empty() && from});
 	    }
@@ -20995,31 +21072,30 @@ var CodeMirror =
 	          }
 	        }
 	        ranges.push({anchor: anchor || head, head: head});
 	      }
 	      cm.setSelections(ranges, 0);
 	    });
 	  };
 
-	  cmds[map["Shift-" + ctrl + "D"] = "duplicateLine"] = function(cm) {
+	  cmds.duplicateLine = function(cm) {
 	    cm.operation(function() {
 	      var rangeCount = cm.listSelections().length;
 	      for (var i = 0; i < rangeCount; i++) {
 	        var range = cm.listSelections()[i];
 	        if (range.empty())
 	          cm.replaceRange(cm.getLine(range.head.line) + "\n", Pos(range.head.line, 0));
 	        else
 	          cm.replaceRange(cm.getRange(range.from(), range.to()), range.from());
 	      }
 	      cm.scrollIntoView();
 	    });
 	  };
 
-	  if (!mac) map[ctrl + "T"] = "transposeChars";
 
 	  function sortLines(cm, caseSensitive) {
 	    if (cm.isReadOnly()) return CodeMirror.Pass
 	    var ranges = cm.listSelections(), toSort = [], selected;
 	    for (var i = 0; i < ranges.length; i++) {
 	      var range = ranges[i];
 	      if (range.empty()) continue;
 	      var from = range.from().line, to = range.to().line;
@@ -21047,44 +21123,44 @@ var CodeMirror =
 	          });
 	        cm.replaceRange(lines, start, end);
 	        if (selected) ranges.push({anchor: start, head: Pos(to + 1, 0)});
 	      }
 	      if (selected) cm.setSelections(ranges, 0);
 	    });
 	  }
 
-	  cmds[map["F9"] = "sortLines"] = function(cm) { sortLines(cm, true); };
-	  cmds[map[ctrl + "F9"] = "sortLinesInsensitive"] = function(cm) { sortLines(cm, false); };
-
-	  cmds[map["F2"] = "nextBookmark"] = function(cm) {
+	  cmds.sortLines = function(cm) { sortLines(cm, true); };
+	  cmds.sortLinesInsensitive = function(cm) { sortLines(cm, false); };
+
+	  cmds.nextBookmark = function(cm) {
 	    var marks = cm.state.sublimeBookmarks;
 	    if (marks) while (marks.length) {
 	      var current = marks.shift();
 	      var found = current.find();
 	      if (found) {
 	        marks.push(current);
 	        return cm.setSelection(found.from, found.to);
 	      }
 	    }
 	  };
 
-	  cmds[map["Shift-F2"] = "prevBookmark"] = function(cm) {
+	  cmds.prevBookmark = function(cm) {
 	    var marks = cm.state.sublimeBookmarks;
 	    if (marks) while (marks.length) {
 	      marks.unshift(marks.pop());
 	      var found = marks[marks.length - 1].find();
 	      if (!found)
 	        marks.pop();
 	      else
 	        return cm.setSelection(found.from, found.to);
 	    }
 	  };
 
-	  cmds[map[ctrl + "F2"] = "toggleBookmark"] = function(cm) {
+	  cmds.toggleBookmark = function(cm) {
 	    var ranges = cm.listSelections();
 	    var marks = cm.state.sublimeBookmarks || (cm.state.sublimeBookmarks = []);
 	    for (var i = 0; i < ranges.length; i++) {
 	      var from = ranges[i].from(), to = ranges[i].to();
 	      var found = cm.findMarks(from, to);
 	      for (var j = 0; j < found.length; j++) {
 	        if (found[j].sublimeBookmark) {
 	          found[j].clear();
@@ -21094,39 +21170,35 @@ var CodeMirror =
 	          break;
 	        }
 	      }
 	      if (j == found.length)
 	        marks.push(cm.markText(from, to, {sublimeBookmark: true, clearWhenEmpty: false}));
 	    }
 	  };
 
-	  cmds[map["Shift-" + ctrl + "F2"] = "clearBookmarks"] = function(cm) {
+	  cmds.clearBookmarks = function(cm) {
 	    var marks = cm.state.sublimeBookmarks;
 	    if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear();
 	    marks.length = 0;
 	  };
 
-	  cmds[map["Alt-F2"] = "selectBookmarks"] = function(cm) {
+	  cmds.selectBookmarks = function(cm) {
 	    var marks = cm.state.sublimeBookmarks, ranges = [];
 	    if (marks) for (var i = 0; i < marks.length; i++) {
 	      var found = marks[i].find();
 	      if (!found)
 	        marks.splice(i--, 0);
 	      else
 	        ranges.push({anchor: found.from, head: found.to});
 	    }
 	    if (ranges.length)
 	      cm.setSelections(ranges, 0);
 	  };
 
-	  map["Alt-Q"] = "wrapLines";
-
-	  var cK = ctrl + "K ";
-
 	  function modifyWordOrSelection(cm, mod) {
 	    cm.operation(function() {
 	      var ranges = cm.listSelections(), indices = [], replacements = [];
 	      for (var i = 0; i < ranges.length; i++) {
 	        var range = ranges[i];
 	        if (range.empty()) { indices.push(i); replacements.push(""); }
 	        else replacements.push(mod(cm.getRange(range.from(), range.to())));
 	      }
@@ -21136,19 +21208,17 @@ var CodeMirror =
 	        if (at && CodeMirror.cmpPos(range.head, at) > 0) continue;
 	        var word = wordAt(cm, range.head);
 	        at = word.from;
 	        cm.replaceRange(mod(word.word), word.from, word.to);
 	      }
 	    });
 	  }
 
-	  map[cK + ctrl + "Backspace"] = "delLineLeft";
-
-	  cmds[map["Backspace"] = "smartBackspace"] = function(cm) {
+	  cmds.smartBackspace = function(cm) {
 	    if (cm.somethingSelected()) return CodeMirror.Pass;
 
 	    cm.operation(function() {
 	      var cursors = cm.listSelections();
 	      var indentUnit = cm.getOption("indentUnit");
 
 	      for (var i = cursors.length - 1; i >= 0; i--) {
 	        var cursor = cursors[i].head;
@@ -21166,80 +21236,78 @@ var CodeMirror =
 	          if (prevIndent.ch != cursor.ch) deletePos = prevIndent;
 	        }
 
 	        cm.replaceRange("", deletePos, cursor, "+delete");
 	      }
 	    });
 	  };
 
-	  cmds[map[cK + ctrl + "K"] = "delLineRight"] = function(cm) {
+	  cmds.delLineRight = function(cm) {
 	    cm.operation(function() {
 	      var ranges = cm.listSelections();
 	      for (var i = ranges.length - 1; i >= 0; i--)
 	        cm.replaceRange("", ranges[i].anchor, Pos(ranges[i].to().line), "+delete");
 	      cm.scrollIntoView();
 	    });
 	  };
 
-	  cmds[map[cK + ctrl + "U"] = "upcaseAtCursor"] = function(cm) {
+	  cmds.upcaseAtCursor = function(cm) {
 	    modifyWordOrSelection(cm, function(str) { return str.toUpperCase(); });
 	  };
-	  cmds[map[cK + ctrl + "L"] = "downcaseAtCursor"] = function(cm) {
+	  cmds.downcaseAtCursor = function(cm) {
 	    modifyWordOrSelection(cm, function(str) { return str.toLowerCase(); });
 	  };
 
-	  cmds[map[cK + ctrl + "Space"] = "setSublimeMark"] = function(cm) {
+	  cmds.setSublimeMark = function(cm) {
 	    if (cm.state.sublimeMark) cm.state.sublimeMark.clear();
 	    cm.state.sublimeMark = cm.setBookmark(cm.getCursor());
 	  };
-	  cmds[map[cK + ctrl + "A"] = "selectToSublimeMark"] = function(cm) {
+	  cmds.selectToSublimeMark = function(cm) {
 	    var found = cm.state.sublimeMark && cm.state.sublimeMark.find();
 	    if (found) cm.setSelection(cm.getCursor(), found);
 	  };
-	  cmds[map[cK + ctrl + "W"] = "deleteToSublimeMark"] = function(cm) {
+	  cmds.deleteToSublimeMark = function(cm) {
 	    var found = cm.state.sublimeMark && cm.state.sublimeMark.find();
 	    if (found) {
 	      var from = cm.getCursor(), to = found;
 	      if (CodeMirror.cmpPos(from, to) > 0) { var tmp = to; to = from; from = tmp; }
 	      cm.state.sublimeKilled = cm.getRange(from, to);
 	      cm.replaceRange("", from, to);
 	    }
 	  };
-	  cmds[map[cK + ctrl + "X"] = "swapWithSublimeMark"] = function(cm) {
+	  cmds.swapWithSublimeMark = function(cm) {
 	    var found = cm.state.sublimeMark && cm.state.sublimeMark.find();
 	    if (found) {
 	      cm.state.sublimeMark.clear();
 	      cm.state.sublimeMark = cm.setBookmark(cm.getCursor());
 	      cm.setCursor(found);
 	    }
 	  };
-	  cmds[map[cK + ctrl + "Y"] = "sublimeYank"] = function(cm) {
+	  cmds.sublimeYank = function(cm) {
 	    if (cm.state.sublimeKilled != null)
 	      cm.replaceSelection(cm.state.sublimeKilled, null, "paste");
 	  };
 
-	  map[cK + ctrl + "G"] = "clearBookmarks";
-	  cmds[map[cK + ctrl + "C"] = "showInCenter"] = function(cm) {
+	  cmds.showInCenter = function(cm) {
 	    var pos = cm.cursorCoords(null, "local");
 	    cm.scrollTo(null, (pos.top + pos.bottom) / 2 - cm.getScrollInfo().clientHeight / 2);
 	  };
 
-	  var selectLinesCombo = mac ? "Ctrl-Shift-" : "Ctrl-Alt-";
-	  cmds[map[selectLinesCombo + "Up"] = "selectLinesUpward"] = function(cm) {
+	  cmds.selectLinesUpward = function(cm) {
 	    cm.operation(function() {
 	      var ranges = cm.listSelections();
 	      for (var i = 0; i < ranges.length; i++) {
 	        var range = ranges[i];
 	        if (range.head.line > cm.firstLine())
 	          cm.addSelection(Pos(range.head.line - 1, range.head.ch));
 	      }
 	    });
 	  };
-	  cmds[map[selectLinesCombo + "Down"] = "selectLinesDownward"] = function(cm) {
+	  cmds.selectLinesDownward = function(cm) {
 	    cm.operation(function() {
 	      var ranges = cm.listSelections();
 	      for (var i = 0; i < ranges.length; i++) {
 	        var range = ranges[i];
 	        if (range.head.line < cm.lastLine())
 	          cm.addSelection(Pos(range.head.line + 1, range.head.ch));
 	      }
 	    });
@@ -21268,43 +21336,160 @@ var CodeMirror =
 	      cur = cm.getSearchCursor(query, forward ? Pos(cm.firstLine(), 0)
 	                                              : cm.clipPos(Pos(cm.lastLine())));
 	      if (forward ? cur.findNext() : cur.findPrevious())
 	        cm.setSelection(cur.from(), cur.to());
 	      else if (target.word)
 	        cm.setSelection(target.from, target.to);
 	    }
 	  };
-	  cmds[map[ctrl + "F3"] = "findUnder"] = function(cm) { findAndGoTo(cm, true); };
-	  cmds[map["Shift-" + ctrl + "F3"] = "findUnderPrevious"] = function(cm) { findAndGoTo(cm,false); };
-	  cmds[map["Alt-F3"] = "findAllUnder"] = function(cm) {
+	  cmds.findUnder = function(cm) { findAndGoTo(cm, true); };
+	  cmds.findUnderPrevious = function(cm) { findAndGoTo(cm,false); };
+	  cmds.findAllUnder = function(cm) {
 	    var target = getTarget(cm);
 	    if (!target) return;
 	    var cur = cm.getSearchCursor(target.query);
 	    var matches = [];
 	    var primaryIndex = -1;
 	    while (cur.findNext()) {
 	      matches.push({anchor: cur.from(), head: cur.to()});
 	      if (cur.from().line <= target.from.line && cur.from().ch <= target.from.ch)
 	        primaryIndex++;
 	    }
 	    cm.setSelections(matches, primaryIndex);
 	  };
 
-	  map["Shift-" + ctrl + "["] = "fold";
-	  map["Shift-" + ctrl + "]"] = "unfold";
-	  map[cK + ctrl + "0"] = map[cK + ctrl + "J"] = "unfoldAll";
-
-	  map[ctrl + "I"] = "findIncremental";
-	  map["Shift-" + ctrl + "I"] = "findIncrementalReverse";
-	  map[ctrl + "H"] = "replace";
-	  map["F3"] = "findNext";
-	  map["Shift-F3"] = "findPrev";
-
-	  CodeMirror.normalizeKeyMap(map);
+
+	  var keyMap = CodeMirror.keyMap;
+	  keyMap.macSublime = {
+	    "Cmd-Left": "goLineStartSmart",
+	    "Shift-Tab": "indentLess",
+	    "Shift-Ctrl-K": "deleteLine",
+	    "Alt-Q": "wrapLines",
+	    "Ctrl-Left": "goSubwordLeft",
+	    "Ctrl-Right": "goSubwordRight",
+	    "Ctrl-Alt-Up": "scrollLineUp",
+	    "Ctrl-Alt-Down": "scrollLineDown",
+	    "Cmd-L": "selectLine",
+	    "Shift-Cmd-L": "splitSelectionByLine",
+	    "Esc": "singleSelectionTop",
+	    "Cmd-Enter": "insertLineAfter",
+	    "Shift-Cmd-Enter": "insertLineBefore",
+	    "Cmd-D": "selectNextOccurrence",
+	    "Shift-Cmd-Up": "addCursorToPrevLine",
+	    "Shift-Cmd-Down": "addCursorToNextLine",
+	    "Shift-Cmd-Space": "selectScope",
+	    "Shift-Cmd-M": "selectBetweenBrackets",
+	    "Cmd-M": "goToBracket",
+	    "Cmd-Ctrl-Up": "swapLineUp",
+	    "Cmd-Ctrl-Down": "swapLineDown",
+	    "Cmd-/": "toggleCommentIndented",
+	    "Cmd-J": "joinLines",
+	    "Shift-Cmd-D": "duplicateLine",
+	    "F9": "sortLines",
+	    "Cmd-F9": "sortLinesInsensitive",
+	    "F2": "nextBookmark",
+	    "Shift-F2": "prevBookmark",
+	    "Cmd-F2": "toggleBookmark",
+	    "Shift-Cmd-F2": "clearBookmarks",
+	    "Alt-F2": "selectBookmarks",
+	    "Backspace": "smartBackspace",
+	    "Cmd-K Cmd-K": "delLineRight",
+	    "Cmd-K Cmd-U": "upcaseAtCursor",
+	    "Cmd-K Cmd-L": "downcaseAtCursor",
+	    "Cmd-K Cmd-Space": "setSublimeMark",
+	    "Cmd-K Cmd-A": "selectToSublimeMark",
+	    "Cmd-K Cmd-W": "deleteToSublimeMark",
+	    "Cmd-K Cmd-X": "swapWithSublimeMark",
+	    "Cmd-K Cmd-Y": "sublimeYank",
+	    "Cmd-K Cmd-C": "showInCenter",
+	    "Cmd-K Cmd-G": "clearBookmarks",
+	    "Cmd-K Cmd-Backspace": "delLineLeft",
+	    "Cmd-K Cmd-0": "unfoldAll",
+	    "Cmd-K Cmd-J": "unfoldAll",
+	    "Ctrl-Shift-Up": "selectLinesUpward",
+	    "Ctrl-Shift-Down": "selectLinesDownward",
+	    "Cmd-F3": "findUnder",
+	    "Shift-Cmd-F3": "findUnderPrevious",
+	    "Alt-F3": "findAllUnder",
+	    "Shift-Cmd-[": "fold",
+	    "Shift-Cmd-]": "unfold",
+	    "Cmd-I": "findIncremental",
+	    "Shift-Cmd-I": "findIncrementalReverse",
+	    "Cmd-H": "replace",
+	    "F3": "findNext",
+	    "Shift-F3": "findPrev",
+	    "fallthrough": "macDefault"
+	  };
+	  CodeMirror.normalizeKeyMap(keyMap.macSublime);
+
+	  keyMap.pcSublime = {
+	    "Shift-Tab": "indentLess",
+	    "Shift-Ctrl-K": "deleteLine",
+	    "Alt-Q": "wrapLines",
+	    "Ctrl-T": "transposeChars",
+	    "Alt-Left": "goSubwordLeft",
+	    "Alt-Right": "goSubwordRight",
+	    "Ctrl-Up": "scrollLineUp",
+	    "Ctrl-Down": "scrollLineDown",
+	    "Ctrl-L": "selectLine",
+	    "Shift-Ctrl-L": "splitSelectionByLine",
+	    "Esc": "singleSelectionTop",
+	    "Ctrl-Enter": "insertLineAfter",
+	    "Shift-Ctrl-Enter": "insertLineBefore",
+	    "Ctrl-D": "selectNextOccurrence",
+	    "Alt-CtrlUp": "addCursorToPrevLine",
+	    "Alt-CtrlDown": "addCursorToNextLine",
+	    "Shift-Ctrl-Space": "selectScope",
+	    "Shift-Ctrl-M": "selectBetweenBrackets",
+	    "Ctrl-M": "goToBracket",
+	    "Shift-Ctrl-Up": "swapLineUp",
+	    "Shift-Ctrl-Down": "swapLineDown",
+	    "Ctrl-/": "toggleCommentIndented",
+	    "Ctrl-J": "joinLines",
+	    "Shift-Ctrl-D": "duplicateLine",
+	    "F9": "sortLines",
+	    "Ctrl-F9": "sortLinesInsensitive",
+	    "F2": "nextBookmark",
+	    "Shift-F2": "prevBookmark",
+	    "Ctrl-F2": "toggleBookmark",
+	    "Shift-Ctrl-F2": "clearBookmarks",
+	    "Alt-F2": "selectBookmarks",
+	    "Backspace": "smartBackspace",
+	    "Ctrl-K Ctrl-K": "delLineRight",
+	    "Ctrl-K Ctrl-U": "upcaseAtCursor",
+	    "Ctrl-K Ctrl-L": "downcaseAtCursor",
+	    "Ctrl-K Ctrl-Space": "setSublimeMark",
+	    "Ctrl-K Ctrl-A": "selectToSublimeMark",
+	    "Ctrl-K Ctrl-W": "deleteToSublimeMark",
+	    "Ctrl-K Ctrl-X": "swapWithSublimeMark",
+	    "Ctrl-K Ctrl-Y": "sublimeYank",
+	    "Ctrl-K Ctrl-C": "showInCenter",
+	    "Ctrl-K Ctrl-G": "clearBookmarks",
+	    "Ctrl-K Ctrl-Backspace": "delLineLeft",
+	    "Ctrl-K Ctrl-0": "unfoldAll",
+	    "Ctrl-K Ctrl-J": "unfoldAll",
+	    "Ctrl-Alt-Up": "selectLinesUpward",
+	    "Ctrl-Alt-Down": "selectLinesDownward",
+	    "Ctrl-F3": "findUnder",
+	    "Shift-Ctrl-F3": "findUnderPrevious",
+	    "Alt-F3": "findAllUnder",
+	    "Shift-Ctrl-[": "fold",
+	    "Shift-Ctrl-]": "unfold",
+	    "Ctrl-I": "findIncremental",
+	    "Shift-Ctrl-I": "findIncrementalReverse",
+	    "Ctrl-H": "replace",
+	    "F3": "findNext",
+	    "Shift-F3": "findPrev",
+	    "fallthrough": "pcDefault"
+	  };
+	  CodeMirror.normalizeKeyMap(keyMap.pcSublime);
+
+	  var mac = keyMap.default == keyMap.macDefault;
+	  keyMap.sublime = mac ? keyMap.macSublime : keyMap.pcSublime;
 	});
 
 
 /***/ }),
 /* 22 */
 /***/ (function(module, exports, __webpack_require__) {
 
 	// CodeMirror, copyright (c) by Marijn Haverbeke and others
--- a/devtools/client/sourceeditor/codemirror/keymap/sublime.js
+++ b/devtools/client/sourceeditor/codemirror/keymap/sublime.js
@@ -9,21 +9,18 @@
     mod(require("../lib/codemirror"), require("../addon/search/searchcursor"), require("../addon/edit/matchbrackets"));
   else if (typeof define == "function" && define.amd) // AMD
     define(["../lib/codemirror", "../addon/search/searchcursor", "../addon/edit/matchbrackets"], mod);
   else // Plain browser env
     mod(CodeMirror);
 })(function(CodeMirror) {
   "use strict";
 
-  var map = CodeMirror.keyMap.sublime = {fallthrough: "default"};
   var cmds = CodeMirror.commands;
   var Pos = CodeMirror.Pos;
-  var mac = CodeMirror.keyMap["default"] == CodeMirror.keyMap.macDefault;
-  var ctrl = mac ? "Cmd-" : "Ctrl-";
 
   // This is not exactly Sublime's algorithm. I couldn't make heads or tails of that.
   function findPosSubword(doc, start, dir) {
     if (dir < 0 && start.ch == 0) return doc.clipPos(Pos(start.line - 1));
     var line = doc.getLine(start.line);
     if (dir > 0 && start.ch >= line.length) return doc.clipPos(Pos(start.line + 1, 0));
     var state = "start", type;
     for (var pos = start.ch, e = dir < 0 ? 0 : line.length, i = 0; pos != e; pos += dir, i++) {
@@ -47,75 +44,65 @@
     cm.extendSelectionsBy(function(range) {
       if (cm.display.shift || cm.doc.extend || range.empty())
         return findPosSubword(cm.doc, range.head, dir);
       else
         return dir < 0 ? range.from() : range.to();
     });
   }
 
-  var goSubwordCombo = mac ? "Ctrl-" : "Alt-";
-
-  cmds[map[goSubwordCombo + "Left"] = "goSubwordLeft"] = function(cm) { moveSubword(cm, -1); };
-  cmds[map[goSubwordCombo + "Right"] = "goSubwordRight"] = function(cm) { moveSubword(cm, 1); };
+  cmds.goSubwordLeft = function(cm) { moveSubword(cm, -1); };
+  cmds.goSubwordRight = function(cm) { moveSubword(cm, 1); };
 
-  if (mac) map["Cmd-Left"] = "goLineStartSmart";
-
-  var scrollLineCombo = mac ? "Ctrl-Alt-" : "Ctrl-";
-
-  cmds[map[scrollLineCombo + "Up"] = "scrollLineUp"] = function(cm) {
+  cmds.scrollLineUp = function(cm) {
     var info = cm.getScrollInfo();
     if (!cm.somethingSelected()) {
       var visibleBottomLine = cm.lineAtHeight(info.top + info.clientHeight, "local");
       if (cm.getCursor().line >= visibleBottomLine)
         cm.execCommand("goLineUp");
     }
     cm.scrollTo(null, info.top - cm.defaultTextHeight());
   };
-  cmds[map[scrollLineCombo + "Down"] = "scrollLineDown"] = function(cm) {
+  cmds.scrollLineDown = function(cm) {
     var info = cm.getScrollInfo();
     if (!cm.somethingSelected()) {
       var visibleTopLine = cm.lineAtHeight(info.top, "local")+1;
       if (cm.getCursor().line <= visibleTopLine)
         cm.execCommand("goLineDown");
     }
     cm.scrollTo(null, info.top + cm.defaultTextHeight());
   };
 
-  cmds[map["Shift-" + ctrl + "L"] = "splitSelectionByLine"] = function(cm) {
+  cmds.splitSelectionByLine = function(cm) {
     var ranges = cm.listSelections(), lineRanges = [];
     for (var i = 0; i < ranges.length; i++) {
       var from = ranges[i].from(), to = ranges[i].to();
       for (var line = from.line; line <= to.line; ++line)
         if (!(to.line > from.line && line == to.line && to.ch == 0))
           lineRanges.push({anchor: line == from.line ? from : Pos(line, 0),
                            head: line == to.line ? to : Pos(line)});
     }
     cm.setSelections(lineRanges, 0);
   };
 
-  map["Shift-Tab"] = "indentLess";
-
-  cmds[map["Esc"] = "singleSelectionTop"] = function(cm) {
+  cmds.singleSelectionTop = function(cm) {
     var range = cm.listSelections()[0];
     cm.setSelection(range.anchor, range.head, {scroll: false});
   };
 
-  cmds[map[ctrl + "L"] = "selectLine"] = function(cm) {
+  cmds.selectLine = function(cm) {
     var ranges = cm.listSelections(), extended = [];
     for (var i = 0; i < ranges.length; i++) {
       var range = ranges[i];
       extended.push({anchor: Pos(range.from().line, 0),
                      head: Pos(range.to().line + 1, 0)});
     }
     cm.setSelections(extended);
   };
 
-  map["Shift-Ctrl-K"] = "deleteLine";
-
   function insertLine(cm, above) {
     if (cm.isReadOnly()) return CodeMirror.Pass
     cm.operation(function() {
       var len = cm.listSelections().length, newSelection = [], last = -1;
       for (var i = 0; i < len; i++) {
         var head = cm.listSelections()[i].head;
         if (head.line <= last) continue;
         var at = Pos(head.line + (above ? 0 : 1), 0);
@@ -124,28 +111,28 @@
         newSelection.push({head: at, anchor: at});
         last = head.line + 1;
       }
       cm.setSelections(newSelection);
     });
     cm.execCommand("indentAuto");
   }
 
-  cmds[map[ctrl + "Enter"] = "insertLineAfter"] = function(cm) { return insertLine(cm, false); };
+  cmds.insertLineAfter = function(cm) { return insertLine(cm, false); };
 
-  cmds[map["Shift-" + ctrl + "Enter"] = "insertLineBefore"] = function(cm) { return insertLine(cm, true); };
+  cmds.insertLineBefore = function(cm) { return insertLine(cm, true); };
 
   function wordAt(cm, pos) {
     var start = pos.ch, end = start, line = cm.getLine(pos.line);
     while (start && CodeMirror.isWordChar(line.charAt(start - 1))) --start;
     while (end < line.length && CodeMirror.isWordChar(line.charAt(end))) ++end;
     return {from: Pos(pos.line, start), to: Pos(pos.line, end), word: line.slice(start, end)};
   }
 
-  cmds[map[ctrl + "D"] = "selectNextOccurrence"] = function(cm) {
+  cmds.selectNextOccurrence = function(cm) {
     var from = cm.getCursor("from"), to = cm.getCursor("to");
     var fullWord = cm.state.sublimeFindFullWord == cm.doc.sel;
     if (CodeMirror.cmpPos(from, to) == 0) {
       var word = wordAt(cm, from);
       if (!word.word) return;
       cm.setSelection(word.from, word.to);
       fullWord = true;
     } else {
@@ -172,20 +159,18 @@
       var newAnchor = cm.findPosV(range.anchor, dir, "line");
       var newHead = cm.findPosV(range.head, dir, "line");
       var newRange = {anchor: newAnchor, head: newHead};
       newRanges.push(range);
       newRanges.push(newRange);
     }
     cm.setSelections(newRanges);
   }
-
-  var addCursorToLineCombo = mac ? "Shift-Cmd" : 'Alt-Ctrl';
-  cmds[map[addCursorToLineCombo + "Up"] = "addCursorToPrevLine"] = function(cm) { addCursorToSelection(cm, -1); };
-  cmds[map[addCursorToLineCombo + "Down"] = "addCursorToNextLine"] = function(cm) { addCursorToSelection(cm, 1); };
+  cmds.addCursorToPrevLine = function(cm) { addCursorToSelection(cm, -1); };
+  cmds.addCursorToNextLine = function(cm) { addCursorToSelection(cm, 1); };
 
   function isSelectedRange(ranges, from, to) {
     for (var i = 0; i < ranges.length; i++)
       if (ranges[i].from() == from && ranges[i].to() == to) return true
     return false
   }
 
   var mirror = "(){}[]";
@@ -204,35 +189,33 @@
         }
         pos = Pos(closing.pos.line, closing.pos.ch + 1);
       }
     }
     cm.setSelections(newRanges);
     return true;
   }
 
-  cmds[map["Shift-" + ctrl + "Space"] = "selectScope"] = function(cm) {
+  cmds.selectScope = function(cm) {
     selectBetweenBrackets(cm) || cm.execCommand("selectAll");
   };
-  cmds[map["Shift-" + ctrl + "M"] = "selectBetweenBrackets"] = function(cm) {
+  cmds.selectBetweenBrackets = function(cm) {
     if (!selectBetweenBrackets(cm)) return CodeMirror.Pass;
   };
 
-  cmds[map[ctrl + "M"] = "goToBracket"] = function(cm) {
+  cmds.goToBracket = function(cm) {
     cm.extendSelectionsBy(function(range) {
       var next = cm.scanForBracket(range.head, 1);
       if (next && CodeMirror.cmpPos(next.pos, range.head) != 0) return next.pos;
       var prev = cm.scanForBracket(range.head, -1);
       return prev && Pos(prev.pos.line, prev.pos.ch + 1) || range.head;
     });
   };
 
-  var swapLineCombo = mac ? "Cmd-Ctrl-" : "Shift-Ctrl-";
-
-  cmds[map[swapLineCombo + "Up"] = "swapLineUp"] = function(cm) {
+  cmds.swapLineUp = function(cm) {
     if (cm.isReadOnly()) return CodeMirror.Pass
     var ranges = cm.listSelections(), linesToMove = [], at = cm.firstLine() - 1, newSels = [];
     for (var i = 0; i < ranges.length; i++) {
       var range = ranges[i], from = range.from().line - 1, to = range.to().line;
       newSels.push({anchor: Pos(range.anchor.line - 1, range.anchor.ch),
                     head: Pos(range.head.line - 1, range.head.ch)});
       if (range.to().ch == 0 && !range.empty()) --to;
       if (from > at) linesToMove.push(from, to);
@@ -249,17 +232,17 @@
         else
           cm.replaceRange(line + "\n", Pos(to, 0), null, "+swapLine");
       }
       cm.setSelections(newSels);
       cm.scrollIntoView();
     });
   };
 
-  cmds[map[swapLineCombo + "Down"] = "swapLineDown"] = function(cm) {
+  cmds.swapLineDown = function(cm) {
     if (cm.isReadOnly()) return CodeMirror.Pass
     var ranges = cm.listSelections(), linesToMove = [], at = cm.lastLine() + 1;
     for (var i = ranges.length - 1; i >= 0; i--) {
       var range = ranges[i], from = range.to().line + 1, to = range.from().line;
       if (range.to().ch == 0 && !range.empty()) from--;
       if (from < at) linesToMove.push(from, to);
       else if (linesToMove.length) linesToMove[linesToMove.length - 1] = to;
       at = to;
@@ -273,21 +256,21 @@
         else
           cm.replaceRange("", Pos(from, 0), Pos(from + 1, 0), "+swapLine");
         cm.replaceRange(line + "\n", Pos(to, 0), null, "+swapLine");
       }
       cm.scrollIntoView();
     });
   };
 
-  cmds[map[ctrl + "/"] = "toggleCommentIndented"] = function(cm) {
+  cmds.toggleCommentIndented = function(cm) {
     cm.toggleComment({ indent: true });
   }
 
-  cmds[map[ctrl + "J"] = "joinLines"] = function(cm) {
+  cmds.joinLines = function(cm) {
     var ranges = cm.listSelections(), joined = [];
     for (var i = 0; i < ranges.length; i++) {
       var range = ranges[i], from = range.from();
       var start = from.line, end = range.to().line;
       while (i < ranges.length - 1 && ranges[i + 1].from().line == end)
         end = ranges[++i].to().line;
       joined.push({start: start, end: end, anchor: !range.empty() && from});
     }
@@ -305,31 +288,30 @@
           }
         }
         ranges.push({anchor: anchor || head, head: head});
       }
       cm.setSelections(ranges, 0);
     });
   };
 
-  cmds[map["Shift-" + ctrl + "D"] = "duplicateLine"] = function(cm) {
+  cmds.duplicateLine = function(cm) {
     cm.operation(function() {
       var rangeCount = cm.listSelections().length;
       for (var i = 0; i < rangeCount; i++) {
         var range = cm.listSelections()[i];
         if (range.empty())
           cm.replaceRange(cm.getLine(range.head.line) + "\n", Pos(range.head.line, 0));
         else
           cm.replaceRange(cm.getRange(range.from(), range.to()), range.from());
       }
       cm.scrollIntoView();
     });
   };
 
-  if (!mac) map[ctrl + "T"] = "transposeChars";
 
   function sortLines(cm, caseSensitive) {
     if (cm.isReadOnly()) return CodeMirror.Pass
     var ranges = cm.listSelections(), toSort = [], selected;
     for (var i = 0; i < ranges.length; i++) {
       var range = ranges[i];
       if (range.empty()) continue;
       var from = range.from().line, to = range.to().line;
@@ -357,44 +339,44 @@
           });
         cm.replaceRange(lines, start, end);
         if (selected) ranges.push({anchor: start, head: Pos(to + 1, 0)});
       }
       if (selected) cm.setSelections(ranges, 0);
     });
   }
 
-  cmds[map["F9"] = "sortLines"] = function(cm) { sortLines(cm, true); };
-  cmds[map[ctrl + "F9"] = "sortLinesInsensitive"] = function(cm) { sortLines(cm, false); };
+  cmds.sortLines = function(cm) { sortLines(cm, true); };
+  cmds.sortLinesInsensitive = function(cm) { sortLines(cm, false); };
 
-  cmds[map["F2"] = "nextBookmark"] = function(cm) {
+  cmds.nextBookmark = function(cm) {
     var marks = cm.state.sublimeBookmarks;
     if (marks) while (marks.length) {
       var current = marks.shift();
       var found = current.find();
       if (found) {
         marks.push(current);
         return cm.setSelection(found.from, found.to);
       }
     }
   };
 
-  cmds[map["Shift-F2"] = "prevBookmark"] = function(cm) {
+  cmds.prevBookmark = function(cm) {
     var marks = cm.state.sublimeBookmarks;
     if (marks) while (marks.length) {
       marks.unshift(marks.pop());
       var found = marks[marks.length - 1].find();
       if (!found)
         marks.pop();
       else
         return cm.setSelection(found.from, found.to);
     }
   };
 
-  cmds[map[ctrl + "F2"] = "toggleBookmark"] = function(cm) {
+  cmds.toggleBookmark = function(cm) {
     var ranges = cm.listSelections();
     var marks = cm.state.sublimeBookmarks || (cm.state.sublimeBookmarks = []);
     for (var i = 0; i < ranges.length; i++) {
       var from = ranges[i].from(), to = ranges[i].to();
       var found = cm.findMarks(from, to);
       for (var j = 0; j < found.length; j++) {
         if (found[j].sublimeBookmark) {
           found[j].clear();
@@ -404,39 +386,35 @@
           break;
         }
       }
       if (j == found.length)
         marks.push(cm.markText(from, to, {sublimeBookmark: true, clearWhenEmpty: false}));
     }
   };
 
-  cmds[map["Shift-" + ctrl + "F2"] = "clearBookmarks"] = function(cm) {
+  cmds.clearBookmarks = function(cm) {
     var marks = cm.state.sublimeBookmarks;
     if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear();
     marks.length = 0;
   };
 
-  cmds[map["Alt-F2"] = "selectBookmarks"] = function(cm) {
+  cmds.selectBookmarks = function(cm) {
     var marks = cm.state.sublimeBookmarks, ranges = [];
     if (marks) for (var i = 0; i < marks.length; i++) {
       var found = marks[i].find();
       if (!found)
         marks.splice(i--, 0);
       else
         ranges.push({anchor: found.from, head: found.to});
     }
     if (ranges.length)
       cm.setSelections(ranges, 0);
   };
 
-  map["Alt-Q"] = "wrapLines";
-
-  var cK = ctrl + "K ";
-
   function modifyWordOrSelection(cm, mod) {
     cm.operation(function() {
       var ranges = cm.listSelections(), indices = [], replacements = [];
       for (var i = 0; i < ranges.length; i++) {
         var range = ranges[i];
         if (range.empty()) { indices.push(i); replacements.push(""); }
         else replacements.push(mod(cm.getRange(range.from(), range.to())));
       }
@@ -446,19 +424,17 @@
         if (at && CodeMirror.cmpPos(range.head, at) > 0) continue;
         var word = wordAt(cm, range.head);
         at = word.from;
         cm.replaceRange(mod(word.word), word.from, word.to);
       }
     });
   }
 
-  map[cK + ctrl + "Backspace"] = "delLineLeft";
-
-  cmds[map["Backspace"] = "smartBackspace"] = function(cm) {
+  cmds.smartBackspace = function(cm) {
     if (cm.somethingSelected()) return CodeMirror.Pass;
 
     cm.operation(function() {
       var cursors = cm.listSelections();
       var indentUnit = cm.getOption("indentUnit");
 
       for (var i = cursors.length - 1; i >= 0; i--) {
         var cursor = cursors[i].head;
@@ -476,80 +452,78 @@
           if (prevIndent.ch != cursor.ch) deletePos = prevIndent;
         }
 
         cm.replaceRange("", deletePos, cursor, "+delete");
       }
     });
   };
 
-  cmds[map[cK + ctrl + "K"] = "delLineRight"] = function(cm) {
+  cmds.delLineRight = function(cm) {
     cm.operation(function() {
       var ranges = cm.listSelections();
       for (var i = ranges.length - 1; i >= 0; i--)
         cm.replaceRange("", ranges[i].anchor, Pos(ranges[i].to().line), "+delete");
       cm.scrollIntoView();
     });
   };
 
-  cmds[map[cK + ctrl + "U"] = "upcaseAtCursor"] = function(cm) {
+  cmds.upcaseAtCursor = function(cm) {
     modifyWordOrSelection(cm, function(str) { return str.toUpperCase(); });
   };
-  cmds[map[cK + ctrl + "L"] = "downcaseAtCursor"] = function(cm) {
+  cmds.downcaseAtCursor = function(cm) {
     modifyWordOrSelection(cm, function(str) { return str.toLowerCase(); });
   };
 
-  cmds[map[cK + ctrl + "Space"] = "setSublimeMark"] = function(cm) {
+  cmds.setSublimeMark = function(cm) {
     if (cm.state.sublimeMark) cm.state.sublimeMark.clear();
     cm.state.sublimeMark = cm.setBookmark(cm.getCursor());
   };
-  cmds[map[cK + ctrl + "A"] = "selectToSublimeMark"] = function(cm) {
+  cmds.selectToSublimeMark = function(cm) {
     var found = cm.state.sublimeMark && cm.state.sublimeMark.find();
     if (found) cm.setSelection(cm.getCursor(), found);
   };
-  cmds[map[cK + ctrl + "W"] = "deleteToSublimeMark"] = function(cm) {
+  cmds.deleteToSublimeMark = function(cm) {
     var found = cm.state.sublimeMark && cm.state.sublimeMark.find();
     if (found) {
       var from = cm.getCursor(), to = found;
       if (CodeMirror.cmpPos(from, to) > 0) { var tmp = to; to = from; from = tmp; }
       cm.state.sublimeKilled = cm.getRange(from, to);
       cm.replaceRange("", from, to);
     }
   };
-  cmds[map[cK + ctrl + "X"] = "swapWithSublimeMark"] = function(cm) {
+  cmds.swapWithSublimeMark = function(cm) {
     var found = cm.state.sublimeMark && cm.state.sublimeMark.find();
     if (found) {
       cm.state.sublimeMark.clear();
       cm.state.sublimeMark = cm.setBookmark(cm.getCursor());
       cm.setCursor(found);
     }
   };
-  cmds[map[cK + ctrl + "Y"] = "sublimeYank"] = function(cm) {
+  cmds.sublimeYank = function(cm) {
     if (cm.state.sublimeKilled != null)
       cm.replaceSelection(cm.state.sublimeKilled, null, "paste");
   };
 
-  map[cK + ctrl + "G"] = "clearBookmarks";
-  cmds[map[cK + ctrl + "C"] = "showInCenter"] = function(cm) {
+  cmds.showInCenter = function(cm) {
     var pos = cm.cursorCoords(null, "local");
     cm.scrollTo(null, (pos.top + pos.bottom) / 2 - cm.getScrollInfo().clientHeight / 2);
   };
 
-  var selectLinesCombo = mac ? "Ctrl-Shift-" : "Ctrl-Alt-";
-  cmds[map[selectLinesCombo + "Up"] = "selectLinesUpward"] = function(cm) {
+  cmds.selectLinesUpward = function(cm) {
     cm.operation(function() {
       var ranges = cm.listSelections();
       for (var i = 0; i < ranges.length; i++) {
         var range = ranges[i];
         if (range.head.line > cm.firstLine())
           cm.addSelection(Pos(range.head.line - 1, range.head.ch));
       }
     });
   };
-  cmds[map[selectLinesCombo + "Down"] = "selectLinesDownward"] = function(cm) {
+  cmds.selectLinesDownward = function(cm) {
     cm.operation(function() {
       var ranges = cm.listSelections();
       for (var i = 0; i < ranges.length; i++) {
         var range = ranges[i];
         if (range.head.line < cm.lastLine())
           cm.addSelection(Pos(range.head.line + 1, range.head.ch));
       }
     });
@@ -578,36 +552,153 @@
       cur = cm.getSearchCursor(query, forward ? Pos(cm.firstLine(), 0)
                                               : cm.clipPos(Pos(cm.lastLine())));
       if (forward ? cur.findNext() : cur.findPrevious())
         cm.setSelection(cur.from(), cur.to());
       else if (target.word)
         cm.setSelection(target.from, target.to);
     }
   };
-  cmds[map[ctrl + "F3"] = "findUnder"] = function(cm) { findAndGoTo(cm, true); };
-  cmds[map["Shift-" + ctrl + "F3"] = "findUnderPrevious"] = function(cm) { findAndGoTo(cm,false); };
-  cmds[map["Alt-F3"] = "findAllUnder"] = function(cm) {
+  cmds.findUnder = function(cm) { findAndGoTo(cm, true); };
+  cmds.findUnderPrevious = function(cm) { findAndGoTo(cm,false); };
+  cmds.findAllUnder = function(cm) {
     var target = getTarget(cm);
     if (!target) return;
     var cur = cm.getSearchCursor(target.query);
     var matches = [];
     var primaryIndex = -1;
     while (cur.findNext()) {
       matches.push({anchor: cur.from(), head: cur.to()});
       if (cur.from().line <= target.from.line && cur.from().ch <= target.from.ch)
         primaryIndex++;
     }
     cm.setSelections(matches, primaryIndex);
   };
 
-  map["Shift-" + ctrl + "["] = "fold";
-  map["Shift-" + ctrl + "]"] = "unfold";
-  map[cK + ctrl + "0"] = map[cK + ctrl + "J"] = "unfoldAll";
+
+  var keyMap = CodeMirror.keyMap;
+  keyMap.macSublime = {
+    "Cmd-Left": "goLineStartSmart",
+    "Shift-Tab": "indentLess",
+    "Shift-Ctrl-K": "deleteLine",
+    "Alt-Q": "wrapLines",
+    "Ctrl-Left": "goSubwordLeft",
+    "Ctrl-Right": "goSubwordRight",
+    "Ctrl-Alt-Up": "scrollLineUp",
+    "Ctrl-Alt-Down": "scrollLineDown",
+    "Cmd-L": "selectLine",
+    "Shift-Cmd-L": "splitSelectionByLine",
+    "Esc": "singleSelectionTop",
+    "Cmd-Enter": "insertLineAfter",
+    "Shift-Cmd-Enter": "insertLineBefore",
+    "Cmd-D": "selectNextOccurrence",
+    "Shift-Cmd-Up": "addCursorToPrevLine",
+    "Shift-Cmd-Down": "addCursorToNextLine",
+    "Shift-Cmd-Space": "selectScope",
+    "Shift-Cmd-M": "selectBetweenBrackets",
+    "Cmd-M": "goToBracket",
+    "Cmd-Ctrl-Up": "swapLineUp",
+    "Cmd-Ctrl-Down": "swapLineDown",
+    "Cmd-/": "toggleCommentIndented",
+    "Cmd-J": "joinLines",
+    "Shift-Cmd-D": "duplicateLine",
+    "F9": "sortLines",
+    "Cmd-F9": "sortLinesInsensitive",
+    "F2": "nextBookmark",
+    "Shift-F2": "prevBookmark",
+    "Cmd-F2": "toggleBookmark",
+    "Shift-Cmd-F2": "clearBookmarks",
+    "Alt-F2": "selectBookmarks",
+    "Backspace": "smartBackspace",
+    "Cmd-K Cmd-K": "delLineRight",
+    "Cmd-K Cmd-U": "upcaseAtCursor",
+    "Cmd-K Cmd-L": "downcaseAtCursor",
+    "Cmd-K Cmd-Space": "setSublimeMark",
+    "Cmd-K Cmd-A": "selectToSublimeMark",
+    "Cmd-K Cmd-W": "deleteToSublimeMark",
+    "Cmd-K Cmd-X": "swapWithSublimeMark",
+    "Cmd-K Cmd-Y": "sublimeYank",
+    "Cmd-K Cmd-C": "showInCenter",
+    "Cmd-K Cmd-G": "clearBookmarks",
+    "Cmd-K Cmd-Backspace": "delLineLeft",
+    "Cmd-K Cmd-0": "unfoldAll",
+    "Cmd-K Cmd-J": "unfoldAll",
+    "Ctrl-Shift-Up": "selectLinesUpward",
+    "Ctrl-Shift-Down": "selectLinesDownward",
+    "Cmd-F3": "findUnder",
+    "Shift-Cmd-F3": "findUnderPrevious",
+    "Alt-F3": "findAllUnder",
+    "Shift-Cmd-[": "fold",
+    "Shift-Cmd-]": "unfold",
+    "Cmd-I": "findIncremental",
+    "Shift-Cmd-I": "findIncrementalReverse",
+    "Cmd-H": "replace",
+    "F3": "findNext",
+    "Shift-F3": "findPrev",
+    "fallthrough": "macDefault"
+  };
+  CodeMirror.normalizeKeyMap(keyMap.macSublime);
 
-  map[ctrl + "I"] = "findIncremental";
-  map["Shift-" + ctrl + "I"] = "findIncrementalReverse";
-  map[ctrl + "H"] = "replace";
-  map["F3"] = "findNext";
-  map["Shift-F3"] = "findPrev";
+  keyMap.pcSublime = {
+    "Shift-Tab": "indentLess",
+    "Shift-Ctrl-K": "deleteLine",
+    "Alt-Q": "wrapLines",
+    "Ctrl-T": "transposeChars",
+    "Alt-Left": "goSubwordLeft",
+    "Alt-Right": "goSubwordRight",
+    "Ctrl-Up": "scrollLineUp",
+    "Ctrl-Down": "scrollLineDown",
+    "Ctrl-L": "selectLine",
+    "Shift-Ctrl-L": "splitSelectionByLine",
+    "Esc": "singleSelectionTop",
+    "Ctrl-Enter": "insertLineAfter",
+    "Shift-Ctrl-Enter": "insertLineBefore",
+    "Ctrl-D": "selectNextOccurrence",
+    "Alt-CtrlUp": "addCursorToPrevLine",
+    "Alt-CtrlDown": "addCursorToNextLine",
+    "Shift-Ctrl-Space": "selectScope",
+    "Shift-Ctrl-M": "selectBetweenBrackets",
+    "Ctrl-M": "goToBracket",
+    "Shift-Ctrl-Up": "swapLineUp",
+    "Shift-Ctrl-Down": "swapLineDown",
+    "Ctrl-/": "toggleCommentIndented",
+    "Ctrl-J": "joinLines",
+    "Shift-Ctrl-D": "duplicateLine",
+    "F9": "sortLines",
+    "Ctrl-F9": "sortLinesInsensitive",
+    "F2": "nextBookmark",
+    "Shift-F2": "prevBookmark",
+    "Ctrl-F2": "toggleBookmark",
+    "Shift-Ctrl-F2": "clearBookmarks",
+    "Alt-F2": "selectBookmarks",
+    "Backspace": "smartBackspace",
+    "Ctrl-K Ctrl-K": "delLineRight",
+    "Ctrl-K Ctrl-U": "upcaseAtCursor",
+    "Ctrl-K Ctrl-L": "downcaseAtCursor",
+    "Ctrl-K Ctrl-Space": "setSublimeMark",
+    "Ctrl-K Ctrl-A": "selectToSublimeMark",
+    "Ctrl-K Ctrl-W": "deleteToSublimeMark",
+    "Ctrl-K Ctrl-X": "swapWithSublimeMark",
+    "Ctrl-K Ctrl-Y": "sublimeYank",
+    "Ctrl-K Ctrl-C": "showInCenter",
+    "Ctrl-K Ctrl-G": "clearBookmarks",
+    "Ctrl-K Ctrl-Backspace": "delLineLeft",
+    "Ctrl-K Ctrl-0": "unfoldAll",
+    "Ctrl-K Ctrl-J": "unfoldAll",
+    "Ctrl-Alt-Up": "selectLinesUpward",
+    "Ctrl-Alt-Down": "selectLinesDownward",
+    "Ctrl-F3": "findUnder",
+    "Shift-Ctrl-F3": "findUnderPrevious",
+    "Alt-F3": "findAllUnder",
+    "Shift-Ctrl-[": "fold",
+    "Shift-Ctrl-]": "unfold",
+    "Ctrl-I": "findIncremental",
+    "Shift-Ctrl-I": "findIncrementalReverse",
+    "Ctrl-H": "replace",
+    "F3": "findNext",
+    "Shift-F3": "findPrev",
+    "fallthrough": "pcDefault"
+  };
+  CodeMirror.normalizeKeyMap(keyMap.pcSublime);
 
-  CodeMirror.normalizeKeyMap(map);
+  var mac = keyMap.default == keyMap.macDefault;
+  keyMap.sublime = mac ? keyMap.macSublime : keyMap.pcSublime;
 });
--- a/devtools/client/sourceeditor/codemirror/keymap/vim.js
+++ b/devtools/client/sourceeditor/codemirror/keymap/vim.js
@@ -250,30 +250,77 @@
     function leaveVimMode(cm) {
       cm.setOption('disableInput', false);
       cm.off('cursorActivity', onCursorActivity);
       CodeMirror.off(cm.getInputField(), 'paste', getOnPasteFn(cm));
       cm.state.vim = null;
     }
 
     function detachVimMap(cm, next) {
-      if (this == CodeMirror.keyMap.vim)
+      if (this == CodeMirror.keyMap.vim) {
         CodeMirror.rmClass(cm.getWrapperElement(), "cm-fat-cursor");
+        if (cm.getOption("inputStyle") == "contenteditable" && document.body.style.caretColor != null) {
+          disableFatCursorMark(cm);
+          cm.getInputField().style.caretColor = "";
+        }
+      }
 
       if (!next || next.attach != attachVimMap)
         leaveVimMode(cm);
     }
     function attachVimMap(cm, prev) {
-      if (this == CodeMirror.keyMap.vim)
+      if (this == CodeMirror.keyMap.vim) {
         CodeMirror.addClass(cm.getWrapperElement(), "cm-fat-cursor");
+        if (cm.getOption("inputStyle") == "contenteditable" && document.body.style.caretColor != null) {
+          enableFatCursorMark(cm);
+          cm.getInputField().style.caretColor = "transparent";
+        }
+      }
 
       if (!prev || prev.attach != attachVimMap)
         enterVimMode(cm);
     }
 
+    function fatCursorMarks(cm) {
+      var ranges = cm.listSelections(), result = []
+      for (var i = 0; i < ranges.length; i++) {
+        var range = ranges[i]
+        if (range.empty()) {
+          if (range.anchor.ch < cm.getLine(range.anchor.line).length) {
+            result.push(cm.markText(range.anchor, Pos(range.anchor.line, range.anchor.ch + 1),
+                                    {className: "cm-fat-cursor-mark"}))
+          } else {
+            var widget = document.createElement("span")
+            widget.textContent = "\u00a0"
+            widget.className = "cm-fat-cursor-mark"
+            result.push(cm.setBookmark(range.anchor, {widget: widget}))
+          }
+        }
+      }
+      return result
+    }
+
+    function updateFatCursorMark(cm) {
+      var marks = cm.state.fatCursorMarks
+      if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear()
+      cm.state.fatCursorMarks = fatCursorMarks(cm)
+    }
+
+    function enableFatCursorMark(cm) {
+      cm.state.fatCursorMarks = fatCursorMarks(cm)
+      cm.on("cursorActivity", updateFatCursorMark)
+    }
+
+    function disableFatCursorMark(cm) {
+      var marks = cm.state.fatCursorMarks
+      if (marks) for (var i = 0; i < marks.length; i++) marks[i].clear()
+      cm.state.fatCursorMarks = null
+      cm.off("cursorActivity", updateFatCursorMark)
+    }
+
     // Deprecated, simply setting the keymap works again.
     CodeMirror.defineOption('vimMode', false, function(cm, val, prev) {
       if (val && cm.getOption("keyMap") != "vim")
         cm.setOption("keyMap", "vim");
       else if (!val && prev != CodeMirror.Init && /^vim/.test(cm.getOption("keyMap")))
         cm.setOption("keyMap", "default");
     });
 
@@ -2029,17 +2076,18 @@
           text = cm.getSelection();
           var replacement = fillArray('', ranges.length);
           cm.replaceSelections(replacement);
           finalHead = ranges[0].anchor;
         }
         vimGlobalState.registerController.pushText(
             args.registerName, 'delete', text,
             args.linewise, vim.visualBlock);
-        return clipCursorToContent(cm, finalHead);
+        var includeLineBreak = vim.insertMode
+        return clipCursorToContent(cm, finalHead, includeLineBreak);
       },
       indent: function(cm, args, ranges) {
         var vim = cm.state.vim;
         var startLine = ranges[0].anchor.line;
         var endLine = vim.visualBlock ?
           ranges[ranges.length - 1].anchor.line :
           ranges[0].head.line;
         // In visual mode, n> shifts the selection right n times, instead of
@@ -3239,17 +3287,17 @@
         }
       }
       if (state.nextCh || state.curMoveThrough) {
         return Pos(line, state.index);
       }
       return cur;
     }
 
-    /**
+    /*
      * Returns the boundaries of the next word. If the cursor in the middle of
      * the word, then returns the boundaries of the current word, starting at
      * the cursor. If the cursor is at the start/end of a word, and we are going
      * forward/backward, respectively, find the boundaries of the next word.
      *
      * @param {CodeMirror} cm CodeMirror object.
      * @param {Cursor} cur The cursor position.
      * @param {boolean} forward True to search forward. False to search
@@ -3973,16 +4021,25 @@
       return {top: from.line, bottom: to.line};
     }
 
     function getMarkPos(cm, vim, markName) {
       if (markName == '\'') {
         var history = cm.doc.history.done;
         var event = history[history.length - 2];
         return event && event.ranges && event.ranges[0].head;
+      } else if (markName == '.') {
+        if (cm.doc.history.lastModTime == 0) {
+          return  // If no changes, bail out; don't bother to copy or reverse history array.
+        } else {
+          var changeHistory = cm.doc.history.done.filter(function(el){ if (el.changes !== undefined) { return el } });
+          changeHistory.reverse();
+          var lastEditPos = changeHistory[0].changes[0].to;
+        }
+        return lastEditPos;
       }
 
       var mark = vim.marks[markName];
       return mark && mark.find();
     }
 
     var ExCommandDispatcher = function() {
       this.buildCommandMap_();
@@ -4783,17 +4840,18 @@
       var insertModeChangeRegister = vimGlobalState.registerController.getRegister('.');
       var isPlaying = macroModeState.isPlaying;
       var lastChange = macroModeState.lastInsertModeChanges;
       // In case of visual block, the insertModeChanges are not saved as a
       // single word, so we convert them to a single word
       // so as to update the ". register as expected in real vim.
       var text = [];
       if (!isPlaying) {
-        var selLength = lastChange.inVisualBlock ? vim.lastSelection.visualBlock.height : 1;
+        var selLength = lastChange.inVisualBlock && vim.lastSelection ?
+            vim.lastSelection.visualBlock.height : 1;
         var changes = lastChange.changes;
         var text = [];
         var i = 0;
         // In case of multiple selections in blockwise visual,
         // the inserted text, for example: 'f<Backspace>oo', is stored as
         // 'f', 'f', InsertModeKey 'o', 'o', 'o', 'o'. (if you have a block with 2 lines).
         // We push the contents of the changes array as per the following:
         // 1. In case of InsertModeKey, just increment by 1.
--- a/devtools/client/sourceeditor/codemirror/lib/codemirror.css
+++ b/devtools/client/sourceeditor/codemirror/lib/codemirror.css
@@ -54,17 +54,22 @@
 .cm-fat-cursor .CodeMirror-cursor {
   width: auto;
   border: 0 !important;
   background: #7e7;
 }
 .cm-fat-cursor div.CodeMirror-cursors {
   z-index: 1;
 }
-
+.cm-fat-cursor-mark {
+  background-color: rgba(20, 255, 20, 0.5);
+  -webkit-animation: blink 1.06s steps(1) infinite;
+  -moz-animation: blink 1.06s steps(1) infinite;
+  animation: blink 1.06s steps(1) infinite;
+}
 .cm-animate-fat-cursor {
   width: auto;
   border: 0;
   -webkit-animation: blink 1.06s steps(1) infinite;
   -moz-animation: blink 1.06s steps(1) infinite;
   animation: blink 1.06s steps(1) infinite;
   background-color: #7e7;
 }
--- a/devtools/client/sourceeditor/codemirror/lib/codemirror.js
+++ b/devtools/client/sourceeditor/codemirror/lib/codemirror.js
@@ -1083,23 +1083,25 @@ var bidiOrdering = (function() {
             for (++j$2; j$2 < i$7 && countsAsNum.test(types[j$2]); ++j$2) {}
             order.splice(at, 0, new BidiSpan(2, nstart, j$2))
             pos = j$2
           } else { ++j$2 }
         }
         if (pos < i$7) { order.splice(at, 0, new BidiSpan(1, pos, i$7)) }
       }
     }
-    if (order[0].level == 1 && (m = str.match(/^\s+/))) {
-      order[0].from = m[0].length
-      order.unshift(new BidiSpan(0, 0, m[0].length))
-    }
-    if (lst(order).level == 1 && (m = str.match(/\s+$/))) {
-      lst(order).to -= m[0].length
-      order.push(new BidiSpan(0, len - m[0].length, len))
+    if (direction == "ltr") {
+      if (order[0].level == 1 && (m = str.match(/^\s+/))) {
+        order[0].from = m[0].length
+        order.unshift(new BidiSpan(0, 0, m[0].length))
+      }
+      if (lst(order).level == 1 && (m = str.match(/\s+$/))) {
+        lst(order).to -= m[0].length
+        order.push(new BidiSpan(0, len - m[0].length, len))
+      }
     }
 
     return direction == "rtl" ? order.reverse() : order
   }
 })()
 
 // Get the bidi ordering for the given line (and cache it). Returns
 // false for lines that are fully left-to-right, and an array of
@@ -1464,35 +1466,52 @@ StringStream.prototype.hideFirstChars = 
   this.lineStart += n
   try { return inner() }
   finally { this.lineStart -= n }
 };
 StringStream.prototype.lookAhead = function (n) {
   var oracle = this.lineOracle
   return oracle && oracle.lookAhead(n)
 };
+StringStream.prototype.baseToken = function () {
+  var oracle = this.lineOracle
+  return oracle && oracle.baseToken(this.pos)
+};
 
 var SavedContext = function(state, lookAhead) {
   this.state = state
   this.lookAhead = lookAhead
 };
 
 var Context = function(doc, state, line, lookAhead) {
   this.state = state
   this.doc = doc
   this.line = line
   this.maxLookAhead = lookAhead || 0
+  this.baseTokens = null
+  this.baseTokenPos = 1
 };
 
 Context.prototype.lookAhead = function (n) {
   var line = this.doc.getLine(this.line + n)
   if (line != null && n > this.maxLookAhead) { this.maxLookAhead = n }
   return line
 };
 
+Context.prototype.baseToken = function (n) {
+    var this$1 = this;
+
+  if (!this.baseTokens) { return null }
+  while (this.baseTokens[this.baseTokenPos] <= n)
+    { this$1.baseTokenPos += 2 }
+  var type = this.baseTokens[this.baseTokenPos + 1]
+  return {type: type && type.replace(/( |^)overlay .*/, ""),
+          size: this.baseTokens[this.baseTokenPos] - n}
+};
+
 Context.prototype.nextLine = function () {
   this.line++
   if (this.maxLookAhead > 0) { this.maxLookAhead-- }
 };
 
 Context.fromSaved = function (doc, saved, line) {
   if (saved instanceof SavedContext)
     { return new Context(doc, copyState(doc.mode, saved.state), line, saved.lookAhead) }
@@ -1516,16 +1535,17 @@ function highlightLine(cm, line, context
   var st = [cm.state.modeGen], lineClasses = {}
   // Compute the base array of styles
   runMode(cm, line.text, cm.doc.mode, context, function (end, style) { return st.push(end, style); },
           lineClasses, forceToEnd)
   var state = context.state
 
   // Run overlays, adjust style array.
   var loop = function ( o ) {
+    context.baseTokens = st
     var overlay = cm.state.overlays[o], i = 1, at = 0
     context.state = true
     runMode(cm, line.text, overlay.mode, context, function (end, style) {
       var start = i
       // Ensure there's a token end at the current position, and that i points at it
       while (at < end) {
         var i_end = st[i]
         if (i_end > end)
@@ -1539,20 +1559,22 @@ function highlightLine(cm, line, context
         i = start + 2
       } else {
         for (; start < i; start += 2) {
           var cur = st[start+1]
           st[start+1] = (cur ? cur + " " : "") + "overlay " + style
         }
       }
     }, lineClasses)
+    context.state = state
+    context.baseTokens = null
+    context.baseTokenPos = 1
   };
 
   for (var o = 0; o < cm.state.overlays.length; ++o) loop( o );
-  context.state = state
 
   return {styles: st, classes: lineClasses.bgClass || lineClasses.textClass ? lineClasses : null}
 }
 
 function getLineStyles(cm, line, updateFrontier) {
   if (!line.styles || line.styles[0] != cm.state.modeGen) {
     var context = getContextBefore(cm, lineNo(line))
     var resetState = line.text.length > cm.options.maxHighlightLength && copyState(cm.doc.mode, context.state)
@@ -2882,16 +2904,17 @@ function coordsBidiPartWrapped(cm, lineO
   // all, so a binary search doesn't work, and we want to return a
   // part that only spans one line so that the binary search in
   // coordsCharInner is safe. As such, we first find the extent of the
   // wrapped line, and then do a flat search in which we discard any
   // spans that aren't on the line.
   var ref = wrappedLineExtent(cm, lineObj, preparedMeasure, y);
   var begin = ref.begin;
   var end = ref.end;
+  if (/\s/.test(lineObj.text.charAt(end - 1))) { end-- }
   var part = null, closestDist = null
   for (var i = 0; i < order.length; i++) {
     var p = order[i]
     if (p.from >= end || p.to <= begin) { continue }
     var ltr = p.level != 1
     var endX = measureCharPrepared(cm, preparedMeasure, ltr ? Math.min(end, p.to) - 1 : Math.max(begin, p.from)).right
     // Weigh against spans ending before this, so that they are only
     // picked if nothing ends after
@@ -3072,68 +3095,70 @@ function drawSelectionCursor(cm, head, o
 function cmpCoords(a, b) { return a.top - b.top || a.left - b.left }
 
 // Draws the given range as a highlighted selection
 function drawSelectionRange(cm, range, output) {
   var display = cm.display, doc = cm.doc
   var fragment = document.createDocumentFragment()
   var padding = paddingH(cm.display), leftSide = padding.left
   var rightSide = Math.max(display.sizerWidth, displayWidth(cm) - display.sizer.offsetLeft) - padding.right
+  var docLTR = doc.direction == "ltr"
 
   function add(left, top, width, bottom) {
     if (top < 0) { top = 0 }
     top = Math.round(top)
     bottom = Math.round(bottom)
     fragment.appendChild(elt("div", null, "CodeMirror-selected", ("position: absolute; left: " + left + "px;\n                             top: " + top + "px; width: " + (width == null ? rightSide - left : width) + "px;\n                             height: " + (bottom - top) + "px")))
   }
 
   function drawForLine(line, fromArg, toArg) {
     var lineObj = getLine(doc, line)
     var lineLen = lineObj.text.length
     var start, end
     function coords(ch, bias) {
       return charCoords(cm, Pos(line, ch), "div", lineObj, bias)
     }
 
+    function wrapX(pos, dir, side) {
+      var extent = wrappedLineExtentChar(cm, lineObj, null, pos)
+      var prop = (dir == "ltr") == (side == "after") ? "left" : "right"
+      var ch = side == "after" ? extent.begin : extent.end - (/\s/.test(lineObj.text.charAt(extent.end - 1)) ? 2 : 1)
+      return coords(ch, prop)[prop]
+    }
+
     var order = getOrder(lineObj, doc.direction)
     iterateBidiSections(order, fromArg || 0, toArg == null ? lineLen : toArg, function (from, to, dir, i) {
-      var fromPos = coords(from, dir == "ltr" ? "left" : "right")
-      var toPos = coords(to - 1, dir == "ltr" ? "right" : "left")
-      if (dir == "ltr") {
-        var fromLeft = fromArg == null && from == 0 ? leftSide : fromPos.left
-        var toRight = toArg == null && to == lineLen ? rightSide : toPos.right
-        if (toPos.top - fromPos.top <= 3) { // Single line
-          add(fromLeft, toPos.top, toRight - fromLeft, toPos.bottom)
-        } else { // Multiple lines
-          add(fromLeft, fromPos.top, null, fromPos.bottom)
-          if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top) }
-          add(leftSide, toPos.top, toPos.right, toPos.bottom)
+      var ltr = dir == "ltr"
+      var fromPos = coords(from, ltr ? "left" : "right")
+      var toPos = coords(to - 1, ltr ? "right" : "left")
+
+      var openStart = fromArg == null && from == 0, openEnd = toArg == null && to == lineLen
+      var first = i == 0, last = !order || i == order.length - 1
+      if (toPos.top - fromPos.top <= 3) { // Single line
+        var openLeft = (docLTR ? openStart : openEnd) && first
+        var openRight = (docLTR ? openEnd : openStart) && last
+        var left = openLeft ? leftSide : (ltr ? fromPos : toPos).left
+        var right = openRight ? rightSide : (ltr ? toPos : fromPos).right
+        add(left, fromPos.top, right - left, fromPos.bottom)
+      } else { // Multiple lines
+        var topLeft, topRight, botLeft, botRight
+        if (ltr) {
+          topLeft = docLTR && openStart && first ? leftSide : fromPos.left
+          topRight = docLTR ? rightSide : wrapX(from, dir, "before")
+          botLeft = docLTR ? leftSide : wrapX(to, dir, "after")
+          botRight = docLTR && openEnd && last ? rightSide : toPos.right
+        } else {
+          topLeft = !docLTR ? leftSide : wrapX(from, dir, "before")
+          topRight = !docLTR && openStart && first ? rightSide : fromPos.right
+          botLeft = !docLTR && openEnd && last ? leftSide : toPos.left
+          botRight = !docLTR ? rightSide : wrapX(to, dir, "after")
         }
-      } else if (from < to) { // RTL
-        var fromRight = fromArg == null && from == 0 ? rightSide : fromPos.right
-        var toLeft = toArg == null && to == lineLen ? leftSide : toPos.left
-        if (toPos.top - fromPos.top <= 3) { // Single line
-          add(toLeft, toPos.top, fromRight - toLeft, toPos.bottom)
-        } else { // Multiple lines
-          var topLeft = leftSide
-          if (i) {
-            var topEnd = wrappedLineExtentChar(cm, lineObj, null, from).end
-            // The coordinates returned for an RTL wrapped space tend to
-            // be complete bogus, so try to skip that here.
-            topLeft = coords(topEnd - (/\s/.test(lineObj.text.charAt(topEnd - 1)) ? 2 : 1), "left").left
-          }
-          add(topLeft, fromPos.top, fromRight - topLeft, fromPos.bottom)
-          if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top) }
-          var botWidth = null
-          if (i < order.length  - 1 || true) {
-            var botStart = wrappedLineExtentChar(cm, lineObj, null, to).begin
-            botWidth = coords(botStart, "right").right - toLeft
-          }
-          add(toLeft, toPos.top, botWidth, toPos.bottom)
-        }
+        add(topLeft, fromPos.top, topRight - topLeft, fromPos.bottom)
+        if (fromPos.bottom < toPos.top) { add(leftSide, fromPos.bottom, null, toPos.top) }
+        add(botLeft, toPos.top, botRight - botLeft, toPos.bottom)
       }
 
       if (!start || cmpCoords(fromPos, start) < 0) { start = fromPos }
       if (cmpCoords(toPos, start) < 0) { start = toPos }
       if (!end || cmpCoords(fromPos, end) < 0) { end = fromPos }
       if (cmpCoords(toPos, end) < 0) { end = toPos }
     })
     return {start: start, end: end}
@@ -6748,17 +6773,17 @@ function endOfLine(visually, cm, lineObj
       var sticky = moveInStorageOrder ? "after" : "before"
       var ch
       // With a wrapped rtl chunk (possibly spanning multiple bidi parts),
       // it could be that the last bidi part is not on the last visual line,
       // since visual lines contain content order-consecutive chunks.
       // Thus, in rtl, we are looking for the first (content-order) character
       // in the rtl chunk that is on the last line (that is, the same line
       // as the last (content-order) character).
-      if (part.level > 0) {
+      if (part.level > 0 || cm.doc.direction == "rtl") {
         var prep = prepareMeasureForLine(cm, lineObj)
         ch = dir < 0 ? lineObj.text.length - 1 : 0
         var targetTop = measureCharPrepared(cm, prep, ch).top
         ch = findFirst(function (ch) { return measureCharPrepared(cm, prep, ch).top == targetTop; }, (dir < 0) == (part.level == 1) ? part.from : part.to - 1, ch)
         if (sticky == "before") { ch = moveCharLogically(lineObj, ch, 1) }
       } else { ch = dir < 0 ? part.to : part.from }
       return new Pos(lineNo, ch, sticky)
     }
@@ -9610,13 +9635,13 @@ CodeMirror.defineExtension = function (n
 CodeMirror.defineDocExtension = function (name, func) {
   Doc.prototype[name] = func
 }
 
 CodeMirror.fromTextArea = fromTextArea
 
 addLegacyProps(CodeMirror)
 
-CodeMirror.version = "5.30.0"
+CodeMirror.version = "5.31.0"
 
 return CodeMirror;
 
 })));
\ No newline at end of file
--- a/devtools/client/sourceeditor/codemirror/mode/clike/clike.js
+++ b/devtools/client/sourceeditor/codemirror/mode/clike/clike.js
@@ -239,16 +239,17 @@ CodeMirror.defineMode("clike", function(
 
       return ctx.indented + (closing ? 0 : indentUnit) +
         (!closing && switchBlock && !/^(?:case|default)\b/.test(textAfter) ? indentUnit : 0);
     },
 
     electricInput: indentSwitch ? /^\s*(?:case .*?:|default:|\{\}?|\})$/ : /^\s*[{}]$/,
     blockCommentStart: "/*",
     blockCommentEnd: "*/",
+    blockCommentContinue: " * ",
     lineComment: "//",
     fold: "brace"
   };
 });
 
   function words(str) {
     var obj = {}, words = str.split(" ");
     for (var i = 0; i < words.length; ++i) obj[words[i]] = true;
@@ -586,17 +587,17 @@ CodeMirror.defineMode("clike", function(
       "Runtime Runnable SecurityManager Short StackTraceElement StrictMath String " +
       "StringBuffer System Thread ThreadGroup ThreadLocal Throwable Triple Void"
     ),
     intendSwitch: false,
     indentStatements: false,
     multiLineStrings: true,
     number: /^(?:0x[a-f\d_]+|0b[01_]+|(?:[\d_]+\.?\d*|\.\d+)(?:e[-+]?[\d_]+)?)(u|ll?|l|f)?/i,
     blockKeywords: words("catch class do else finally for if where try while enum"),
-    defKeywords: words("class val var object package interface fun"),
+    defKeywords: words("class val var object interface fun"),
     atoms: words("true false null this"),
     hooks: {
       '"': function(stream, state) {
         state.tokenize = tokenKotlinString(stream.match('""'));
         return state.tokenize(stream, state);
       }
     },
     modeProps: {closeBrackets: {triples: '"'}}
--- a/devtools/client/sourceeditor/codemirror/mode/css/css.js
+++ b/devtools/client/sourceeditor/codemirror/mode/css/css.js
@@ -405,16 +405,17 @@ CodeMirror.defineMode("css", function(co
         }
       }
       return indent;
     },
 
     electricChars: "}",
     blockCommentStart: "/*",
     blockCommentEnd: "*/",
+    blockCommentContinue: " * ",
     lineComment: lineComment,
     fold: "brace"
   };
 });
 
   function keySet(array) {
     var keys = {};
     for (var i = 0; i < array.length; ++i) {
--- a/devtools/client/sourceeditor/codemirror/mode/javascript/javascript.js
+++ b/devtools/client/sourceeditor/codemirror/mode/javascript/javascript.js
@@ -18,23 +18,23 @@ CodeMirror.defineMode("javascript", func
   var jsonMode = parserConfig.json || jsonldMode;
   var isTS = parserConfig.typescript;
   var wordRE = parserConfig.wordCharacters || /[\w$\xa1-\uffff]/;
 
   // Tokenizer
 
   var keywords = function(){
     function kw(type) {return {type: type, style: "keyword"};}
-    var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
+    var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c"), D = kw("keyword d");
     var operator = kw("operator"), atom = {type: "atom", style: "atom"};
 
     var jsKeywords = {
       "if": kw("if"), "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
-      "return": C, "break": C, "continue": C, "new": kw("new"), "delete": C, "void": C, "throw": C, "debugger": C,
-      "var": kw("var"), "const": kw("var"), "let": kw("var"),
+      "return": D, "break": D, "continue": D, "new": kw("new"), "delete": C, "void": C, "throw": C,
+      "debugger": kw("debugger"), "var": kw("var"), "const": kw("var"), "let": kw("var"),
       "function": kw("function"), "catch": kw("catch"),
       "for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
       "in": operator, "typeof": operator, "instanceof": operator,
       "true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom,
       "this": kw("this"), "class": kw("class"), "super": kw("atom"),
       "yield": C, "export": kw("export"), "import": kw("import"), "extends": C,
       "await": C
     };
@@ -123,28 +123,34 @@ CodeMirror.defineMode("javascript", func
       } else if (stream.eat("/")) {
         stream.skipToEnd();
         return ret("comment", "comment");
       } else if (expressionAllowed(stream, state, 1)) {
         readRegexp(stream);
         stream.match(/^\b(([gimyu])(?![gimyu]*\2))+\b/);
         return ret("regexp", "string-2");
       } else {
-        stream.eatWhile(isOperatorChar);
+        stream.eat("=");
         return ret("operator", "operator", stream.current());
       }
     } else if (ch == "`") {
       state.tokenize = tokenQuasi;
       return tokenQuasi(stream, state);
     } else if (ch == "#") {
       stream.skipToEnd();
       return ret("error", "error");
     } else if (isOperatorChar.test(ch)) {
-      if (ch != ">" || !state.lexical || state.lexical.type != ">")
-        stream.eatWhile(isOperatorChar);
+      if (ch != ">" || !state.lexical || state.lexical.type != ">") {
+        if (stream.eat("=")) {
+          if (ch == "!" || ch == "=") stream.eat("=")
+        } else if (/[<>*+\-]/.test(ch)) {
+          stream.eat(ch)
+          if (ch == ">") stream.eat(ch)
+        }
+      }
       return ret("operator", "operator", stream.current());
     } else if (wordRE.test(ch)) {
       stream.eatWhile(wordRE);
       var word = stream.current()
       if (state.lastType != ".") {
         if (keywords.propertyIsEnumerable(word)) {
           var kw = keywords[word]
           return ret(kw.type, kw.style, word)
@@ -346,16 +352,18 @@ CodeMirror.defineMode("javascript", func
     };
     return exp;
   }
 
   function statement(type, value) {
     if (type == "var") return cont(pushlex("vardef", value.length), vardef, expect(";"), poplex);
     if (type == "keyword a") return cont(pushlex("form"), parenExpr, statement, poplex);
     if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
+    if (type == "keyword d") return cx.stream.match(/^\s*$/, false) ? cont() : cont(pushlex("stat"), maybeexpression, expect(";"), poplex);
+    if (type == "debugger") return cont(expect(";"));
     if (type == "{") return cont(pushlex("}"), block, poplex);
     if (type == ";") return cont();
     if (type == "if") {
       if (cx.state.lexical.info == "else" && cx.state.cc[cx.state.cc.length - 1] == poplex)
         cx.state.cc.pop()();
       return cont(pushlex("form"), parenExpr, statement, poplex, maybeelse);
     }
     if (type == "function") return cont(functiondef);
@@ -401,33 +409,29 @@ CodeMirror.defineMode("javascript", func
       if (type == "(") return cont(pushcontext, pushlex(")"), commasep(funarg, ")"), poplex, expect("=>"), body, popcontext);
       else if (type == "variable") return pass(pushcontext, pattern, expect("=>"), body, popcontext);
     }
 
     var maybeop = noComma ? maybeoperatorNoComma : maybeoperatorComma;
     if (atomicTypes.hasOwnProperty(type)) return cont(maybeop);
     if (type == "function") return cont(functiondef, maybeop);
     if (type == "class") return cont(pushlex("form"), classExpression, poplex);
-    if (type == "keyword c" || type == "async") return cont(noComma ? maybeexpressionNoComma : maybeexpression);
+    if (type == "keyword c" || type == "async") return cont(noComma ? expressionNoComma : expression);
     if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeop);
     if (type == "operator" || type == "spread") return cont(noComma ? expressionNoComma : expression);
     if (type == "[") return cont(pushlex("]"), arrayLiteral, poplex, maybeop);
     if (type == "{") return contCommasep(objprop, "}", null, maybeop);
     if (type == "quasi") return pass(quasi, maybeop);
     if (type == "new") return cont(maybeTarget(noComma));
     return cont();
   }
   function maybeexpression(type) {
     if (type.match(/[;\}\)\],]/)) return pass();
     return pass(expression);
   }
-  function maybeexpressionNoComma(type) {
-    if (type.match(/[;\}\)\],]/)) return pass();
-    return pass(expressionNoComma);
-  }
 
   function maybeoperatorComma(type, value) {
     if (type == ",") return cont(expression);
     return maybeoperatorNoComma(type, value, false);
   }
   function maybeoperatorNoComma(type, value, noComma) {
     var me = noComma == false ? maybeoperatorComma : maybeoperatorNoComma;
     var expr = noComma == false ? expression : expressionNoComma;
@@ -505,17 +509,20 @@ CodeMirror.defineMode("javascript", func
       return cont(afterprop);
     } else if (type == "jsonld-keyword") {
       return cont(afterprop);
     } else if (type == "modifier") {
       return cont(objprop)
     } else if (type == "[") {
       return cont(expression, expect("]"), afterprop);
     } else if (type == "spread") {
-      return cont(expression, afterprop);
+      return cont(expressionNoComma, afterprop);
+    } else if (value == "*") {
+      cx.marked = "keyword";
+      return cont(objprop);
     } else if (type == ":") {
       return pass(afterprop)
     }
   }
   function getterSetter(type) {
     if (type != "variable") return pass(afterprop);
     cx.marked = "property";
     return cont(functiondef);
@@ -553,17 +560,17 @@ CodeMirror.defineMode("javascript", func
   }
   function maybetype(type, value) {
     if (isTS) {
       if (type == ":") return cont(typeexpr);
       if (value == "?") return cont(maybetype);
     }
   }
   function typeexpr(type, value) {
-    if (type == "variable") {
+    if (type == "variable" || value == "void") {
       if (value == "keyof") {
         cx.marked = "keyword"
         return cont(typeexpr)
       } else {
         cx.marked = "type"
         return cont(afterType)
       }
     }
@@ -740,17 +747,17 @@ CodeMirror.defineMode("javascript", func
   function isContinuedStatement(state, textAfter) {
     return state.lastType == "operator" || state.lastType == "," ||
       isOperatorChar.test(textAfter.charAt(0)) ||
       /[,.]/.test(textAfter.charAt(0));
   }
 
   function expressionAllowed(stream, state, backUp) {
     return state.tokenize == tokenBase &&
-      /^(?:operator|sof|keyword [bc]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
+      /^(?:operator|sof|keyword [bcd]|case|new|export|default|spread|[\[{}\(,;:]|=>)$/.test(state.lastType) ||
       (state.lastType == "quasi" && /\{\s*$/.test(stream.string.slice(0, stream.pos - (backUp || 0))))
   }
 
   // Interface
 
   return {
     startState: function(basecolumn) {
       var state = {
@@ -809,16 +816,17 @@ CodeMirror.defineMode("javascript", func
         return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
       else if (lexical.align) return lexical.column + (closing ? 0 : 1);
       else return lexical.indented + (closing ? 0 : indentUnit);
     },
 
     electricInput: /^\s*(?:case .*?:|default:|\{|\})$/,
     blockCommentStart: jsonMode ? null : "/*",
     blockCommentEnd: jsonMode ? null : "*/",
+    blockCommentContinue: jsonMode ? null : " * ",
     lineComment: jsonMode ? null : "//",
     fold: "brace",
     closeBrackets: "()[]{}''\"\"``",
 
     helperType: jsonMode ? "json" : "javascript",
     jsonldMode: jsonldMode,
     jsonMode: jsonMode,
 
--- a/devtools/client/sourceeditor/test/codemirror/mode/javascript/test.js
+++ b/devtools/client/sourceeditor/test/codemirror/mode/javascript/test.js
@@ -231,16 +231,29 @@
      "[keyword const] [def foo] [operator =] [string-2 `bar ${][variable async].[property a][string-2 }`];")
 
   MT("indent_switch",
      "[keyword switch] ([variable x]) {",
      "  [keyword default]:",
      "    [keyword return] [number 2]",
      "}")
 
+  MT("regexp_corner_case",
+     "[operator +]{} [operator /] [atom undefined];",
+     "[[[meta ...][string-2 /\\//] ]];",
+     "[keyword void] [string-2 /\\//];",
+     "[keyword do] [string-2 /\\//]; [keyword while] ([number 0]);",
+     "[keyword if] ([number 0]) {} [keyword else] [string-2 /\\//];",
+     "[string-2 `${][variable async][operator ++][string-2 }//`];",
+     "[string-2 `${]{} [operator /] [string-2 /\\//}`];")
+
+  MT("return_eol",
+     "[keyword return]",
+     "{} [string-2 /5/]")
+
   var ts_mode = CodeMirror.getMode({indentUnit: 2}, "application/typescript")
   function TS(name) {
     test.mode(name, ts_mode, Array.prototype.slice.call(arguments, 1))
   }
 
   TS("typescript_extend_type",
      "[keyword class] [def Foo] [keyword extends] [type Some][operator <][type Type][operator >] {}")
 
@@ -347,16 +360,19 @@
      "[keyword let] [def x] [operator =] [keyword new] [variable Map][operator <][type string], [type Date][operator >]([string-2 `foo${][variable bar][string-2 }`])")
 
   TS("modifiers",
      "[keyword class] [def Foo] {",
      "  [keyword public] [keyword abstract] [property bar]() {}",
      "  [property constructor]([keyword readonly] [keyword private] [def x]) {}",
      "}")
 
+  TS("arrow prop",
+     "({[property a]: [def p] [operator =>] [variable-2 p]})")
+
   var jsonld_mode = CodeMirror.getMode(
     {indentUnit: 2},
     {name: "javascript", jsonld: true}
   );
   function LD(name) {
     test.mode(name, jsonld_mode, Array.prototype.slice.call(arguments, 1));
   }
 
--- a/devtools/client/sourceeditor/test/codemirror/test.js
+++ b/devtools/client/sourceeditor/test/codemirror/test.js
@@ -2213,16 +2213,31 @@ testCM("getTokenTypeAt", function(cm) {
       if (stream.match("foo")) return "foo";
       else stream.next();
     }
   });
   eq(byClassName(cm.getWrapperElement(), "cm-foo").length, 1);
   eq(cm.getTokenTypeAt(Pos(0, 6)), "string");
 }, {value: "1 + 'foo'", mode: "javascript"});
 
+testCM("addOverlay", function(cm) {
+  cm.addOverlay({
+    token: function(stream) {
+      var base = stream.baseToken()
+      if (!/comment/.test(base.type) && stream.match(/\d+/)) return "x"
+      stream.next()
+    }
+  })
+  var x = byClassName(cm.getWrapperElement(), "cm-x")
+  is(x.length, 1)
+  is(x[0].textContent, "233")
+  cm.replaceRange("", Pos(0, 4), Pos(0, 6))
+  is(byClassName(cm.getWrapperElement(), "cm-x").length, 2)
+}, {value: "foo /* 100 */\nbar + 233;\nbaz", mode: "javascript"})
+
 testCM("resizeLineWidget", function(cm) {
   addDoc(cm, 200, 3);
   var widget = document.createElement("pre");
   widget.innerHTML = "imwidget";
   widget.style.background = "yellow";
   cm.addLineWidget(1, widget, {noHScroll: true});
   cm.setSize(40);
   is(widget.parentNode.offsetWidth < 42);
@@ -2502,16 +2517,24 @@ testCM("bidi_wrapped_selection", functio
 
 testCM("delete_wrapped", function(cm) {
   makeItWrapAfter(cm, Pos(0, 2));
   cm.doc.setCursor(Pos(0, 3, "after"));
   cm.deleteH(-1, "char");
   eq(cm.getLine(0), "1245");
 }, {value: "12345", lineWrapping: true})
 
+testCM("issue_4878", function(cm) {
+  if (phantom) return
+  cm.setCursor(Pos(1, 12, "after"));
+  cm.moveH(-1, "char");
+  eqCursorPos(cm.getCursor(), Pos(0, 113, "before"));
+}, {value: "  في تطبيق السمات مرة واحدة https://github.com/codemirror/CodeMirror/issues/4878#issuecomment-330550964على سبيل المثال <code>\"foo bar\"</code>\n" +
+"  سيتم تعيين", direction: "rtl", lineWrapping: true});
+
 CodeMirror.defineMode("lookahead_mode", function() {
   // Colors text as atom if the line two lines down has an x in it
   return {
     token: function(stream) {
       stream.skipToEnd()
       return /x/.test(stream.lookAhead(2)) ? "atom" : null
     }
   }
--- a/devtools/client/sourceeditor/test/codemirror/vim_test.js
+++ b/devtools/client/sourceeditor/test/codemirror/vim_test.js
@@ -1381,16 +1381,29 @@ testVim('<C-x>/<C-a> search forward', fu
     helpers.doKeys('l');
     helpers.doKeys(key);
     helpers.assertCursorAt(0, 10);
     cm.setCursor(0, 11);
     helpers.doKeys(key);
     helpers.assertCursorAt(0, 11);
   });
 }, {value: '__jmp1 jmp2 jmp'});
+testVim('insert_ctrl_w', function(cm, vim, helpers) {
+  var curStart = makeCursor(0, 10);
+  cm.setCursor(curStart);
+  helpers.doKeys('a');
+  helpers.doKeys('<C-w>');
+  eq('word1/', cm.getValue());
+  var register = helpers.getRegisterController().getRegister();
+  eq('word2', register.toString());
+  is(!register.linewise);
+  var curEnd = makeCursor(0, 6);
+  eqCursorPos(curEnd, cm.getCursor());
+  eq('vim-insert', cm.getOption('keyMap'));
+}, { value: 'word1/word2' });
 testVim('a', function(cm, vim, helpers) {
   cm.setCursor(0, 1);
   helpers.doKeys('a');
   helpers.assertCursorAt(0, 2);
   eq('vim-insert', cm.getOption('keyMap'));
 });
 testVim('a_eol', function(cm, vim, helpers) {
   cm.setCursor(0, lines[0].length - 1);
@@ -1657,16 +1670,26 @@ testVim('mark\'', function(cm, vim, help
   helpers.doKeys('`', '\'');
   helpers.assertCursorAt(2, 2);
   cm.setCursor(2, 0);
   cm.replaceRange('   h', cm.getCursor());
   cm.setCursor(0, 0);
   helpers.doKeys('\'', '\'');
   helpers.assertCursorAt(2, 3);
 });
+testVim('mark.', function(cm, vim, helpers) {
+  cm.setCursor(0, 0);
+  helpers.doKeys('O', 'testing', '<Esc>');
+  cm.setCursor(3, 3);
+  helpers.doKeys('\'', '.');
+  helpers.assertCursorAt(0, 0);
+  cm.setCursor(4, 4);
+  helpers.doKeys('`', '.');
+  helpers.assertCursorAt(0, 6);
+});
 testVim('jumpToMark_next', function(cm, vim, helpers) {
   cm.setCursor(2, 2);
   helpers.doKeys('m', 't');
   cm.setCursor(0, 0);
   helpers.doKeys(']', '`');
   helpers.assertCursorAt(2, 2);
   cm.setCursor(0, 0);
   helpers.doKeys(']', '\'');
new file mode 100644
--- /dev/null
+++ b/dom/clients/manager/ClientIPCTypes.ipdlh
@@ -0,0 +1,44 @@
+/* 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 DOMTypes;
+include PBackgroundSharedTypes;
+include ProtocolTypes;
+using class mozilla::TimeStamp from "mozilla/TimeStamp.h";
+using ClientType from "mozilla/dom/ClientIPCUtils.h";
+using FrameType from "mozilla/dom/ClientIPCUtils.h";
+using VisibilityState from "mozilla/dom/ClientIPCUtils.h";
+
+namespace mozilla {
+namespace dom {
+
+struct IPCClientInfo
+{
+  nsID id;
+  ClientType type;
+  PrincipalInfo principalInfo;
+  TimeStamp creationTime;
+  nsCString url;
+  FrameType frameType;
+};
+
+struct IPCClientWindowState
+{
+  VisibilityState visibilityState;
+  TimeStamp lastFocusTime;
+  bool focused;
+};
+
+struct IPCClientWorkerState
+{
+};
+
+union IPCClientState
+{
+  IPCClientWindowState;
+  IPCClientWorkerState;
+};
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/clients/manager/ClientIPCUtils.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _mozilla_dom_ClientIPCUtils_h
+#define _mozilla_dom_ClientIPCUtils_h
+
+#include "ipc/IPCMessageUtils.h"
+
+// Fix X11 header brain damage that conflicts with FrameType::None
+#undef None
+
+#include "mozilla/dom/ClientBinding.h"
+#include "mozilla/dom/ClientsBinding.h"
+#include "mozilla/dom/DocumentBinding.h"
+
+namespace IPC {
+  template<>
+  struct ParamTraits<mozilla::dom::ClientType> :
+    public ContiguousEnumSerializer<mozilla::dom::ClientType,
+                                    mozilla::dom::ClientType::Window,
+                                    mozilla::dom::ClientType::EndGuard_>
+  {};
+
+  template<>
+  struct ParamTraits<mozilla::dom::FrameType> :
+    public ContiguousEnumSerializer<mozilla::dom::FrameType,
+                                    mozilla::dom::FrameType::Auxiliary,
+                                    mozilla::dom::FrameType::EndGuard_>
+  {};
+
+  template<>
+  struct ParamTraits<mozilla::dom::VisibilityState> :
+    public ContiguousEnumSerializer<mozilla::dom::VisibilityState,
+                                    mozilla::dom::VisibilityState::Hidden,
+                                    mozilla::dom::VisibilityState::EndGuard_>
+  {};
+} // namespace IPC
+
+#endif // _mozilla_dom_ClientIPCUtils_h
new file mode 100644
--- /dev/null
+++ b/dom/clients/manager/ClientInfo.cpp
@@ -0,0 +1,114 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "ClientInfo.h"
+
+#include "mozilla/dom/ClientIPCTypes.h"
+
+namespace mozilla {
+namespace dom {
+
+ClientInfo::ClientInfo(const nsID& aId,
+                       ClientType aType,
+                       const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+                       const TimeStamp& aCreationTime)
+  : mData(MakeUnique<IPCClientInfo>(aId, aType, aPrincipalInfo, aCreationTime,
+                                    EmptyCString(),
+                                    mozilla::dom::FrameType::None))
+{
+}
+
+ClientInfo::ClientInfo(const IPCClientInfo& aData)
+  : mData(MakeUnique<IPCClientInfo>(aData))
+{
+}
+
+ClientInfo::ClientInfo(const ClientInfo& aRight)
+{
+  operator=(aRight);
+}
+
+ClientInfo&
+ClientInfo::operator=(const ClientInfo& aRight)
+{
+  mData.reset();
+  mData = MakeUnique<IPCClientInfo>(*aRight.mData);
+  return *this;
+}
+
+ClientInfo::ClientInfo(ClientInfo&& aRight)
+  : mData(Move(aRight.mData))
+{
+}
+
+ClientInfo&
+ClientInfo::operator=(ClientInfo&& aRight)
+{
+  mData.reset();
+  mData = Move(aRight.mData);
+  return *this;
+}
+
+ClientInfo::~ClientInfo()
+{
+}
+
+const nsID&
+ClientInfo::Id() const
+{
+  return mData->id();
+}
+
+ClientType
+ClientInfo::Type() const
+{
+  return mData->type();
+}
+
+const mozilla::ipc::PrincipalInfo&
+ClientInfo::PrincipalInfo() const
+{
+  return mData->principalInfo();
+}
+
+const TimeStamp&
+ClientInfo::CreationTime() const
+{
+  return mData->creationTime();
+}
+
+const nsCString&
+ClientInfo::URL() const
+{
+  return mData->url();
+}
+
+void
+ClientInfo::SetURL(const nsACString& aURL)
+{
+  mData->url() = aURL;
+}
+
+FrameType
+ClientInfo::FrameType() const
+{
+  return mData->frameType();
+}
+
+void
+ClientInfo::SetFrameType(mozilla::dom::FrameType aFrameType)
+{
+  mData->frameType() = aFrameType;
+}
+
+const IPCClientInfo&
+ClientInfo::ToIPC() const
+{
+  return *mData;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/clients/manager/ClientInfo.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _mozilla_dom_ClientInfo_h
+#define _mozilla_dom_ClientInfo_h
+
+#include "mozilla/dom/ClientBinding.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+namespace ipc {
+class PrincipalInfo;
+} // namespace ipc
+
+namespace dom {
+
+class IPCClientInfo;
+
+// This class provides a simple structure that represents a global living
+// in the system.  Its thread safe and can be transferred across process
+// boundaries.  A ClientInfo object can represent either a window or a worker.
+class ClientInfo final
+{
+  UniquePtr<IPCClientInfo> mData;
+
+public:
+  ClientInfo(const nsID& aId,
+             ClientType aType,
+             const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+             const TimeStamp& aCreationTime);
+
+  ClientInfo(const ClientInfo& aRight);
+
+  ClientInfo&
+  operator=(const ClientInfo& aRight);
+
+  ClientInfo(ClientInfo&& aRight);
+
+  ClientInfo&
+  operator=(ClientInfo&& aRight);
+
+  explicit ClientInfo(const IPCClientInfo& aData);
+
+  ~ClientInfo();
+
+  // Get the unique identifier chosen at the time of the global's creation.
+  const nsID&
+  Id() const;
+
+  // Determine what kind of global this is; e.g. Window, Worker, SharedWorker,
+  // etc.
+  ClientType
+  Type() const;
+
+  // Every global must have a principal that cannot change.
+  const mozilla::ipc::PrincipalInfo&
+  PrincipalInfo() const;
+
+  // The time at which the global was created.
+  const TimeStamp&
+  CreationTime() const;
+
+  // Each global has the concept of a creation URL.  For the most part this
+  // does not change.  The one exception is for about:blank replacement
+  // iframes.  In this case the URL starts as "about:blank", but is later
+  // overriden with the final URL.
+  const nsCString&
+  URL() const;
+
+  // Override the creation URL.  This should only be used for about:blank
+  // replacement iframes.
+  void
+  SetURL(const nsACString& aURL);
+
+  // The frame type is largely a window concept, but we track it as part
+  // of the global here because of the way the Clients WebAPI was designed.
+  // This is set at the time the global becomes execution ready.  Workers
+  // will always return None.
+  mozilla::dom::FrameType
+  FrameType() const;
+
+  // Set the frame type for the global.  This should only happen once the
+  // global has become execution ready.
+  void
+  SetFrameType(mozilla::dom::FrameType aFrameType);
+
+  // Convert to the ipdl generated type.
+  const IPCClientInfo&
+  ToIPC() const;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _mozilla_dom_ClientInfo_h
new file mode 100644
--- /dev/null
+++ b/dom/clients/manager/ClientState.cpp
@@ -0,0 +1,214 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "ClientState.h"
+
+namespace mozilla {
+namespace dom {
+
+ClientWindowState::ClientWindowState(mozilla::dom::VisibilityState aVisibilityState,
+                                     const TimeStamp& aLastFocusTime,
+                                     bool aFocused)
+  : mData(MakeUnique<IPCClientWindowState>(aVisibilityState, aLastFocusTime,
+                                           aFocused))
+{
+}
+
+ClientWindowState::ClientWindowState(const IPCClientWindowState& aData)
+  : mData(MakeUnique<IPCClientWindowState>(aData))
+{
+}
+
+ClientWindowState::ClientWindowState(const ClientWindowState& aRight)
+{
+  operator=(aRight);
+}
+
+ClientWindowState::ClientWindowState(ClientWindowState&& aRight)
+  : mData(Move(aRight.mData))
+{
+}
+
+ClientWindowState&
+ClientWindowState::operator=(const ClientWindowState& aRight)
+{
+  mData.reset();
+  mData = MakeUnique<IPCClientWindowState>(*aRight.mData);
+  return *this;
+}
+
+ClientWindowState&
+ClientWindowState::operator=(ClientWindowState&& aRight)
+{
+  mData.reset();
+  mData = Move(aRight.mData);
+  return *this;
+}
+
+ClientWindowState::~ClientWindowState()
+{
+}
+
+mozilla::dom::VisibilityState
+ClientWindowState::VisibilityState() const
+{
+  return mData->visibilityState();
+}
+
+const TimeStamp&
+ClientWindowState::LastFocusTime() const
+{
+  return mData->lastFocusTime();
+}
+
+bool
+ClientWindowState::Focused() const
+{
+  return mData->focused();
+}
+
+const IPCClientWindowState&
+ClientWindowState::ToIPC() const
+{
+  return *mData;
+}
+
+ClientWorkerState::ClientWorkerState()
+  : mData(MakeUnique<IPCClientWorkerState>())
+{
+}
+
+ClientWorkerState::ClientWorkerState(const IPCClientWorkerState& aData)
+  : mData(MakeUnique<IPCClientWorkerState>(aData))
+{
+}
+
+ClientWorkerState::ClientWorkerState(ClientWorkerState&& aRight)
+  : mData(Move(aRight.mData))
+{
+}
+
+ClientWorkerState::ClientWorkerState(const ClientWorkerState& aRight)
+{
+  operator=(aRight);
+}
+
+ClientWorkerState&
+ClientWorkerState::operator=(const ClientWorkerState& aRight)
+{
+  mData.reset();
+  mData = MakeUnique<IPCClientWorkerState>(*aRight.mData);
+  return *this;
+}
+
+ClientWorkerState&
+ClientWorkerState::operator=(ClientWorkerState&& aRight)
+{
+  mData.reset();
+  mData = Move(aRight.mData);
+  return *this;
+}
+
+ClientWorkerState::~ClientWorkerState()
+{
+}
+
+const IPCClientWorkerState&
+ClientWorkerState::ToIPC() const
+{
+  return *mData;
+}
+
+ClientState::ClientState()
+{
+}
+
+ClientState::ClientState(const ClientWindowState& aWindowState)
+{
+  mData.emplace(AsVariant(aWindowState));
+}
+
+ClientState::ClientState(const ClientWorkerState& aWorkerState)
+{
+  mData.emplace(AsVariant(aWorkerState));
+}
+
+ClientState::ClientState(const IPCClientWindowState& aData)
+{
+  mData.emplace(AsVariant(ClientWindowState(aData)));
+}
+
+ClientState::ClientState(const IPCClientWorkerState& aData)
+{
+  mData.emplace(AsVariant(ClientWorkerState(aData)));
+}
+
+ClientState::ClientState(ClientState&& aRight)
+  : mData(Move(aRight.mData))
+{
+}
+
+ClientState&
+ClientState::operator=(ClientState&& aRight)
+{
+  mData = Move(aRight.mData);
+  return *this;
+}
+
+ClientState::~ClientState()
+{
+}
+
+// static
+ClientState
+ClientState::FromIPC(const IPCClientState& aData)
+{
+  switch(aData.type()) {
+    case IPCClientState::TIPCClientWindowState:
+      return ClientState(aData.get_IPCClientWindowState());
+    case IPCClientState::TIPCClientWorkerState:
+      return ClientState(aData.get_IPCClientWorkerState());
+    default:
+      MOZ_CRASH("unexpected IPCClientState type");
+  }
+}
+
+bool
+ClientState::IsWindowState() const
+{
+  return mData.isSome() && mData.ref().is<ClientWindowState>();
+}
+
+const ClientWindowState&
+ClientState::AsWindowState() const
+{
+  return mData.ref().as<ClientWindowState>();
+}
+
+bool
+ClientState::IsWorkerState() const
+{
+  return mData.isSome() && mData.ref().is<ClientWorkerState>();
+}
+
+const ClientWorkerState&
+ClientState::AsWorkerState() const
+{
+  return mData.ref().as<ClientWorkerState>();
+}
+
+const IPCClientState
+ClientState::ToIPC() const
+{
+  if (IsWindowState()) {
+    return IPCClientState(AsWindowState().ToIPC());
+  }
+
+  return IPCClientState(AsWorkerState().ToIPC());
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/clients/manager/ClientState.h
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _mozilla_dom_ClientState_h
+#define _mozilla_dom_ClientState_h
+
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace dom {
+
+class IPCClientState;
+class IPCClientWindowState;
+class IPCClientWorkerState;
+
+// This class defines the mutable nsGlobalWindow state we support querying
+// through the ClientManagerService.  It is a snapshot of the state and
+// is not live updated.
+class ClientWindowState final
+{
+  UniquePtr<IPCClientWindowState> mData;
+
+public:
+  ClientWindowState(mozilla::dom::VisibilityState aVisibilityState,
+                    const TimeStamp& aLastFocusTime,
+                    bool aFocused);
+
+  explicit ClientWindowState(const IPCClientWindowState& aData);
+
+  ClientWindowState(const ClientWindowState& aRight);
+  ClientWindowState(ClientWindowState&& aRight);
+
+  ClientWindowState&
+  operator=(const ClientWindowState& aRight);
+
+  ClientWindowState&
+  operator=(ClientWindowState&& aRight);
+
+  ~ClientWindowState();
+
+  mozilla::dom::VisibilityState
+  VisibilityState() const;
+
+  const TimeStamp&
+  LastFocusTime() const;
+
+  bool
+  Focused() const;
+
+  const IPCClientWindowState&
+  ToIPC() const;
+};
+
+// This class defines the mutable worker state we support querying
+// through the ClientManagerService.  It is a snapshot of the state and
+// is not live updated.  Right now, we don't actually providate any
+// worker specific state values, but we may in the future.  This
+// class also services as a placeholder that the state is referring
+// to a worker in ClientState.
+class ClientWorkerState final
+{
+  UniquePtr<IPCClientWorkerState> mData;
+
+public:
+  ClientWorkerState();
+
+  explicit ClientWorkerState(const IPCClientWorkerState& aData);
+
+  ClientWorkerState(const ClientWorkerState& aRight);
+  ClientWorkerState(ClientWorkerState&& aRight);
+
+  ClientWorkerState&
+  operator=(const ClientWorkerState& aRight);
+
+  ClientWorkerState&
+  operator=(ClientWorkerState&& aRight);
+
+  ~ClientWorkerState();
+
+  const IPCClientWorkerState&
+  ToIPC() const;
+};
+
+// This is a union of the various types of mutable state we support
+// querying in ClientManagerService.  Right now it can contain either
+// window or worker states.
+class ClientState final
+{
+  Maybe<Variant<ClientWindowState, ClientWorkerState>> mData;
+
+public:
+  ClientState();
+
+  explicit ClientState(const ClientWindowState& aWindowState);
+  explicit ClientState(const ClientWorkerState& aWorkerState);
+  explicit ClientState(const IPCClientWindowState& aData);
+  explicit ClientState(const IPCClientWorkerState& aData);
+
+  ClientState(const ClientState& aRight) = default;
+  ClientState(ClientState&& aRight);
+
+  ClientState&
+  operator=(const ClientState& aRight) = default;
+
+  ClientState&
+  operator=(ClientState&& aRight);
+
+  ~ClientState();
+
+  static ClientState
+  FromIPC(const IPCClientState& aData);
+
+  bool
+  IsWindowState() const;
+
+  const ClientWindowState&
+  AsWindowState() const;
+
+  bool
+  IsWorkerState() const;
+
+  const ClientWorkerState&
+  AsWorkerState() const;
+
+  const IPCClientState
+  ToIPC() const;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _mozilla_dom_ClientState_h
new file mode 100644
--- /dev/null
+++ b/dom/clients/manager/moz.build
@@ -0,0 +1,36 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS.mozilla.dom += [
+  'ClientInfo.h',
+  'ClientIPCUtils.h',
+  'ClientState.h',
+]
+
+UNIFIED_SOURCES += [
+  'ClientInfo.cpp',
+  'ClientState.cpp',
+]
+
+IPDL_SOURCES += [
+  'ClientIPCTypes.ipdlh',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+LOCAL_INCLUDES += [
+]
+
+FINAL_LIBRARY = 'xul'
+
+MOCHITEST_MANIFESTS += [
+]
+
+BROWSER_CHROME_MANIFESTS += [
+]
+
+XPCSHELL_TESTS_MANIFESTS += [
+]
new file mode 100644
--- /dev/null
+++ b/dom/clients/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+DIRS += [
+  'manager',
+]
--- a/dom/file/MutableBlobStorage.cpp
+++ b/dom/file/MutableBlobStorage.cpp
@@ -95,91 +95,89 @@ public:
   }
 
 private:
   RefPtr<MutableBlobStorage> mBlobStorage;
   nsresult mRv;
 };
 
 // This runnable moves a buffer to the IO thread and there, it writes it into
-// the temporary file.
+// the temporary file, if its File Descriptor has not been already closed.
 class WriteRunnable final : public Runnable
 {
 public:
   static WriteRunnable*
-  CopyBuffer(MutableBlobStorage* aBlobStorage, PRFileDesc* aFD,
+  CopyBuffer(MutableBlobStorage* aBlobStorage,
              const void* aData, uint32_t aLength)
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(aBlobStorage);
-    MOZ_ASSERT(aFD);
     MOZ_ASSERT(aData);
 
     // We have to take a copy of this buffer.
     void* data = malloc(aLength);
     if (!data) {
       return nullptr;
     }
 
     memcpy((char*)data, aData, aLength);
-    return new WriteRunnable(aBlobStorage, aFD, data, aLength);
+    return new WriteRunnable(aBlobStorage, data, aLength);
   }
 
   static WriteRunnable*
-  AdoptBuffer(MutableBlobStorage* aBlobStorage, PRFileDesc* aFD,
-              void* aData, uint32_t aLength)
+  AdoptBuffer(MutableBlobStorage* aBlobStorage, void* aData, uint32_t aLength)
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(aBlobStorage);
-    MOZ_ASSERT(aFD);
     MOZ_ASSERT(aData);
 
-    return new WriteRunnable(aBlobStorage, aFD, aData, aLength);
+    return new WriteRunnable(aBlobStorage, aData, aLength);
   }
 
   NS_IMETHOD
   Run() override
   {
     MOZ_ASSERT(!NS_IsMainThread());
     MOZ_ASSERT(mBlobStorage);
 
-    int32_t written = PR_Write(mFD, mData, mLength);
+    PRFileDesc* fd = mBlobStorage->GetFD();
+    if (!fd) {
+      // The file descriptor has been closed in the meantime.
+      return NS_OK;
+    }
+
+    int32_t written = PR_Write(fd, mData, mLength);
     if (NS_WARN_IF(written < 0 || uint32_t(written) != mLength)) {
+      mBlobStorage->CloseFD();
       return mBlobStorage->EventTarget()->Dispatch(
         new ErrorPropagationRunnable(mBlobStorage, NS_ERROR_FAILURE),
         NS_DISPATCH_NORMAL);
     }
 
     return NS_OK;
   }
 
 private:
-  WriteRunnable(MutableBlobStorage* aBlobStorage,
-                PRFileDesc* aFD,
-                void* aData,
-                uint32_t aLength)
+  WriteRunnable(MutableBlobStorage* aBlobStorage, void* aData, uint32_t aLength)
     : Runnable("dom::WriteRunnable")
     , mBlobStorage(aBlobStorage)
-    , mFD(aFD)
     , mData(aData)
     , mLength(aLength)
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(mBlobStorage);
-    MOZ_ASSERT(aFD);
     MOZ_ASSERT(aData);
   }
 
   ~WriteRunnable()
   {
     free(mData);
   }
 
   RefPtr<MutableBlobStorage> mBlobStorage;
-  PRFileDesc* mFD;
   void* mData;
   uint32_t mLength;
 };
 
 // This runnable closes the FD in case something goes wrong or the temporary
 // file is not needed anymore.
 class CloseFileRunnable final : public Runnable
 {
@@ -280,40 +278,36 @@ private:
 NS_IMPL_ISUPPORTS_INHERITED0(CreateBlobRunnable, Runnable)
 
 // This task is used to know when the writing is completed. From the IO thread
 // it dispatches a CreateBlobRunnable to the main-thread.
 class LastRunnable final : public Runnable
 {
 public:
   LastRunnable(MutableBlobStorage* aBlobStorage,
-               PRFileDesc* aFD,
                nsISupports* aParent,
                const nsACString& aContentType,
                MutableBlobStorageCallback* aCallback)
     : Runnable("dom::LastRunnable")
     , mBlobStorage(aBlobStorage)
-    , mFD(aFD)
     , mParent(aParent)
     , mContentType(aContentType)
     , mCallback(aCallback)
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(mBlobStorage);
     MOZ_ASSERT(aCallback);
-    MOZ_ASSERT(aFD);
   }
 
   NS_IMETHOD
   Run() override
   {
     MOZ_ASSERT(!NS_IsMainThread());
 
-    PR_Close(mFD);
-    mFD = nullptr;
+    mBlobStorage->CloseFD();
 
     RefPtr<Runnable> runnable =
       new CreateBlobRunnable(mBlobStorage, mParent.forget(),
                              mContentType, mCallback.forget());
     return mBlobStorage->EventTarget()->Dispatch(runnable, NS_DISPATCH_NORMAL);
   }
 
 private:
@@ -326,17 +320,16 @@ private:
       "LastRunnable::mParent",
       mBlobStorage->EventTarget(), mParent.forget());
     NS_ProxyRelease(
       "LastRunnable::mCallback",
       mBlobStorage->EventTarget(), mCallback.forget());
   }
 
   RefPtr<MutableBlobStorage> mBlobStorage;
-  PRFileDesc* mFD;
   nsCOMPtr<nsISupports> mParent;
   nsCString mContentType;
   RefPtr<MutableBlobStorageCallback> mCallback;
 };
 
 } // anonymous namespace
 
 MutableBlobStorage::MutableBlobStorage(MutableBlobStorageType aType,
@@ -393,37 +386,34 @@ MutableBlobStorage::GetBlobWhenReady(nsI
   MOZ_ASSERT(aCallback);
 
   // GetBlob can be called just once.
   MOZ_ASSERT(mStorageState != eClosed);
   StorageState previousState = mStorageState;
   mStorageState = eClosed;
 
   if (previousState == eInTemporaryFile) {
-    MOZ_ASSERT(mFD);
-
     if (NS_FAILED(mErrorResult)) {
       MOZ_ASSERT(!mActor);
 
       RefPtr<Runnable> runnable =
         new BlobCreationDoneRunnable(this, aCallback, nullptr, mErrorResult);
       EventTarget()->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
       return;
     }
 
     MOZ_ASSERT(mActor);
 
     // We want to wait until all the WriteRunnable are completed. The way we do
     // this is to go to the I/O thread and then we come back: the runnables are
     // executed in order and this LastRunnable will be... the last one.
     // This Runnable will also close the FD on the I/O thread.
     RefPtr<Runnable> runnable =
-      new LastRunnable(this, mFD, aParent, aContentType, aCallback);
+      new LastRunnable(this, aParent, aContentType, aCallback);
     DispatchToIOThread(runnable.forget());
-    mFD = nullptr;
     return;
   }
 
   // If we are waiting for the temporary file, it's better to wait...
   if (previousState == eWaitingForTemporaryFile) {
     mPendingParent = aParent;
     mPendingContentType = aContentType;
     mPendingCallback = aCallback;
@@ -469,20 +459,18 @@ MutableBlobStorage::Append(const void* a
   if (mStorageState == eInMemory && ShouldBeTemporaryStorage(aLength) &&
       !MaybeCreateTemporaryFile()) {
     return NS_ERROR_FAILURE;
   }
 
   // If we are already in the temporaryFile mode, we have to dispatch a
   // runnable.
   if (mStorageState == eInTemporaryFile) {
-    MOZ_ASSERT(mFD);
-
     RefPtr<WriteRunnable> runnable =
-      WriteRunnable::CopyBuffer(this, mFD, aData, aLength);
+      WriteRunnable::CopyBuffer(this, aData, aLength);
     if (NS_WARN_IF(!runnable)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
 
     DispatchToIOThread(runnable.forget());
 
     mDataLen += aLength;
     return NS_OK;
@@ -601,36 +589,35 @@ MutableBlobStorage::TemporaryFileCreated
     mStorageState = eInTemporaryFile;
   }
 
   mFD = aFD;
 
   // This runnable takes the ownership of mData and it will write this buffer
   // into the temporary file.
   RefPtr<WriteRunnable> runnable =
-    WriteRunnable::AdoptBuffer(this, mFD, mData, mDataLen);
+    WriteRunnable::AdoptBuffer(this, mData, mDataLen);
   MOZ_ASSERT(runnable);
 
   mData = nullptr;
 
   DispatchToIOThread(runnable.forget());
 
   // If we are closed, it means that GetBlobWhenReady() has been called when we
   // were already waiting for a temporary file-descriptor. Finally we are here,
   // AdoptBuffer runnable is going to write the current buffer into this file.
   // After that, there is nothing else to write, and we dispatch LastRunnable
   // which ends up calling mPendingCallback via CreateBlobRunnable.
   if (mStorageState == eClosed) {
     MOZ_ASSERT(mPendingCallback);
 
     RefPtr<Runnable> runnable =
-      new LastRunnable(this, mFD, mPendingParent, mPendingContentType,
+      new LastRunnable(this, mPendingParent, mPendingContentType,
                        mPendingCallback);
     DispatchToIOThread(runnable.forget());
-    mFD = nullptr;
 
     mPendingParent = nullptr;
     mPendingCallback = nullptr;
   }
 }
 
 void
 MutableBlobStorage::AskForBlob(TemporaryIPCBlobChildCallback* aCallback,
@@ -675,10 +662,27 @@ MutableBlobStorage::DispatchToIOThread(a
 
 size_t
 MutableBlobStorage::SizeOfCurrentMemoryBuffer() const
 {
   MOZ_ASSERT(NS_IsMainThread());
   return mStorageState < eInTemporaryFile ? mDataLen : 0;
 }
 
+PRFileDesc*
+MutableBlobStorage::GetFD() const
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  return mFD;
+}
+
+void
+MutableBlobStorage::CloseFD()
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(mFD);
+
+  PR_Close(mFD);
+  mFD = nullptr;
+}
+
 } // dom namespace
 } // mozilla namespace
--- a/dom/file/MutableBlobStorage.h
+++ b/dom/file/MutableBlobStorage.h
@@ -71,16 +71,20 @@ public:
     MOZ_ASSERT(mEventTarget);
     return mEventTarget;
   }
 
   // Returns the heap size in bytes of our internal buffers.
   // Note that this intentionally ignores the data in the temp file.
   size_t SizeOfCurrentMemoryBuffer() const;
 
+  PRFileDesc* GetFD() const;
+
+  void CloseFD();
+
 private:
   ~MutableBlobStorage();
 
   bool ExpandBufferSize(uint64_t aSize);
 
   bool ShouldBeTemporaryStorage(uint64_t aSize) const;
 
   bool MaybeCreateTemporaryFile();
--- a/dom/interfaces/base/nsIServiceWorkerManager.idl
+++ b/dom/interfaces/base/nsIServiceWorkerManager.idl
@@ -26,22 +26,23 @@ interface nsIServiceWorkerUnregisterCall
 };
 
 interface nsIWorkerDebugger;
 
 [scriptable, builtinclass, uuid(76e357ed-208d-4e4c-9165-1c4059707879)]
 interface nsIServiceWorkerInfo : nsISupports
 {
   // State values below should match the ServiceWorkerState enumeration.
-  const unsigned short STATE_INSTALLING = 0;
-  const unsigned short STATE_INSTALLED = 1;
-  const unsigned short STATE_ACTIVATING = 2;
-  const unsigned short STATE_ACTIVATED = 3;
-  const unsigned short STATE_REDUNDANT = 4;
-  const unsigned short STATE_UNKNOWN = 5;
+  const unsigned short STATE_PARSED = 0;
+  const unsigned short STATE_INSTALLING = 1;
+  const unsigned short STATE_INSTALLED = 2;
+  const unsigned short STATE_ACTIVATING = 3;
+  const unsigned short STATE_ACTIVATED = 4;
+  const unsigned short STATE_REDUNDANT = 5;
+  const unsigned short STATE_UNKNOWN = 6;
 
   readonly attribute DOMString scriptSpec;
   readonly attribute DOMString cacheName;
 
   readonly attribute unsigned short state;
 
   readonly attribute nsIWorkerDebugger debugger;
 
--- a/dom/interfaces/security/nsIContentSecurityPolicy.idl
+++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl
@@ -58,16 +58,17 @@ interface nsIContentSecurityPolicy : nsI
   const unsigned short FORM_ACTION_DIRECTIVE          = 14;
   const unsigned short REFERRER_DIRECTIVE             = 15;
   const unsigned short WEB_MANIFEST_SRC_DIRECTIVE     = 16;
   const unsigned short UPGRADE_IF_INSECURE_DIRECTIVE  = 17;
   const unsigned short CHILD_SRC_DIRECTIVE            = 18;
   const unsigned short BLOCK_ALL_MIXED_CONTENT        = 19;
   const unsigned short REQUIRE_SRI_FOR                = 20;
   const unsigned short SANDBOX_DIRECTIVE              = 21;
+  const unsigned short WORKER_SRC_DIRECTIVE           = 22;
 
   /**
    * Accessor method for a read-only string version of the policy at a given
    * index.
    */
   [binaryname(GetPolicyString)] AString getPolicy(in unsigned long index);
 
   /**
--- a/dom/ipc/tests/browser_memory_distribution_telemetry.js
+++ b/dom/ipc/tests/browser_memory_distribution_telemetry.js
@@ -21,17 +21,16 @@ add_task(async function test_memory_dist
   waitForExplicitFinish();
 
   if (SpecialPowers.getIntPref("dom.ipc.processCount", 1) < 2) {
     ok(true, "Skip this test if e10s-multi is disabled.");
     finish();
     return;
   }
 
-  await SpecialPowers.pushPrefEnv({set: [["toolkit.telemetry.enabled", true]]});
   Services.telemetry.canRecordExtended = true;
 
   let histogram = Services.telemetry.getKeyedHistogramById("MEMORY_DISTRIBUTION_AMONG_CONTENT");
   histogram.clear();
 
   let tab1 = await BrowserTestUtils.openNewForegroundTab(gBrowser, DUMMY_PAGE_DATA_URI);
   let tab2 = await BrowserTestUtils.openNewForegroundTab(gBrowser, DUMMY_PAGE_DATA_URI);
   let tab3 = await BrowserTestUtils.openNewForegroundTab(gBrowser, DUMMY_PAGE_DATA_URI);
--- a/dom/ipc/tests/browser_remote_navigation_delay_telemetry.js
+++ b/dom/ipc/tests/browser_remote_navigation_delay_telemetry.js
@@ -3,17 +3,16 @@
 var session = Cu.import("resource://gre/modules/TelemetrySession.jsm", {});
 
 add_task(async function test_memory_distribution() {
   if (Services.prefs.getIntPref("dom.ipc.processCount", 1) < 2) {
     ok(true, "Skip this test if e10s-multi is disabled.");
     return;
   }
 
-  await SpecialPowers.pushPrefEnv({set: [["toolkit.telemetry.enabled", true]]});
   let canRecordExtended = Services.telemetry.canRecordExtended;
   Services.telemetry.canRecordExtended = true;
   registerCleanupFunction(() => Services.telemetry.canRecordExtended = canRecordExtended);
 
   Services.telemetry.snapshotKeyedHistograms(Ci.nsITelemetry.DATASET_RELEASE_CHANNEL_OPTIN,
                                              true /* subsession */,
                                              true /* clear */);
 
--- a/dom/locales/en-US/chrome/security/csp.properties
+++ b/dom/locales/en-US/chrome/security/csp.properties
@@ -107,14 +107,15 @@ couldntParseInvalidHost = Couldn’t parse invalid host %1$S
 # %1$S is the string source
 couldntParseScheme = Couldn’t parse scheme in %1$S
 # LOCALIZATION NOTE (couldntParsePort):
 # %1$S is the string source
 couldntParsePort = Couldn’t parse port in %1$S
 # LOCALIZATION NOTE (duplicateDirective):
 # %1$S is the name of the duplicate directive
 duplicateDirective = Duplicate %1$S directives detected.  All but the first instance will be ignored.
-# LOCALIZATION NOTE (deprecatedDirective):
-# %1$S is the name of the deprecated directive, %2$S is the name of the replacement.
-deprecatedDirective = Directive ‘%1$S’ has been deprecated. Please use directive ‘%2$S’ instead.
+# LOCALIZATION NOTE (deprecatedChildSrcDirective):
+# %1$S is the value of the deprecated directive.
+# Do not localize: worker-src, frame-src
+deprecatedChildSrcDirective = Directive ‘%1$S’ has been deprecated. Please use directive ‘worker-src’ to control workers, or directive ‘frame-src’ to control frames respectively.
 # LOCALIZATION NOTE (couldntParseInvalidSandboxFlag):
 # %1$S is the option that could not be understood
 couldntParseInvalidSandboxFlag = Couldn’t parse invalid sandbox flag ‘%1$S’
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -45,16 +45,17 @@ DIRS += [
     'abort',
     'animation',
     'base',
     'bindings',
     'battery',
     'browser-element',
     'cache',
     'canvas',
+    'clients',
     'commandhandler',
     'credentialmanagement',
     'crypto',
     'encoding',
     'events',
     'fetch',
     'file',
     'filehandle',
--- a/dom/security/nsCSPParser.cpp
+++ b/dom/security/nsCSPParser.cpp
@@ -129,16 +129,18 @@ nsCSPParser::nsCSPParser(cspTokens& aTok
                          bool aDeliveredViaMetaTag)
  : mCurChar(nullptr)
  , mEndChar(nullptr)
  , mHasHashOrNonce(false)
  , mStrictDynamic(false)
  , mUnsafeInlineKeywordSrc(nullptr)
  , mChildSrc(nullptr)
  , mFrameSrc(nullptr)
+ , mWorkerSrc(nullptr)
+ , mScriptSrc(nullptr)
  , mParsingFrameAncestorsDir(false)
  , mTokens(aTokens)
  , mSelfURI(aSelfURI)
  , mPolicy(nullptr)
  , mCSPContext(aCSPContext)
  , mDeliveredViaMetaTag(aDeliveredViaMetaTag)
 {
   static bool initialized = false;
@@ -1105,31 +1107,47 @@ nsCSPParser::directiveName()
     return new nsBlockAllMixedContentDirective(CSP_StringToCSPDirective(mCurToken));
   }
 
   // special case handling for upgrade-insecure-requests
   if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::UPGRADE_IF_INSECURE_DIRECTIVE)) {
     return new nsUpgradeInsecureDirective(CSP_StringToCSPDirective(mCurToken));
   }
 
-  // child-src has it's own class to handle frame-src if necessary
+  // child-src by itself is deprecatd but will be enforced
+  //   * for workers (if worker-src is not explicitly specified)
+  //   * for frames  (if frame-src is not explicitly specified)
   if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE)) {
+    const char16_t* params[] = { mCurToken.get() };
+    logWarningErrorToConsole(nsIScriptError::warningFlag,
+                             "deprecatedChildSrcDirective",
+                             params, ArrayLength(params));
     mChildSrc = new nsCSPChildSrcDirective(CSP_StringToCSPDirective(mCurToken));
     return mChildSrc;
   }
 
-  // if we have a frame-src, cache it so we can decide whether to use child-src
+  // if we have a frame-src, cache it so we can discard child-src for frames
   if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE)) {
-    const char16_t* params[] = { mCurToken.get(), u"child-src" };
-    logWarningErrorToConsole(nsIScriptError::warningFlag, "deprecatedDirective",
-                             params, ArrayLength(params));
     mFrameSrc = new nsCSPDirective(CSP_StringToCSPDirective(mCurToken));
     return mFrameSrc;
   }
 
+  // if we have a worker-src, cache it so we can discard child-src for workers
+  if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE)) {
+    mWorkerSrc = new nsCSPDirective(CSP_StringToCSPDirective(mCurToken));
+    return mWorkerSrc;
+  }
+
+  // if we have a script-src, cache it as a fallback for worker-src
+  // in case child-src is not present
+  if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE)) {
+    mScriptSrc = new nsCSPScriptSrcDirective(CSP_StringToCSPDirective(mCurToken));
+    return mScriptSrc;
+  }
+
   if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::REQUIRE_SRI_FOR)) {
     return new nsRequireSRIForDirective(CSP_StringToCSPDirective(mCurToken));
   }
 
   return new nsCSPDirective(CSP_StringToCSPDirective(mCurToken));
 }
 
 // directive = *WSP [ directive-name [ WSP directive-value ] ]
@@ -1296,19 +1314,32 @@ nsCSPParser::policy()
   for (uint32_t i = 0; i < mTokens.Length(); i++) {
     // All input is already tokenized; set one tokenized array in the form of
     // [ name, src, src, ... ]
     // to mCurDir and call directive which processes the current directive.
     mCurDir = mTokens[i];
     directive();
   }
 
-  if (mChildSrc && !mFrameSrc) {
-    // if we have a child-src, it handles frame-src too, unless frame-src is set
-    mChildSrc->setHandleFrameSrc();
+  if (mChildSrc) {
+    if (!mFrameSrc) {
+      // if frame-src is specified explicitly for that policy than child-src should
+      // not restrict frames; if not, than child-src needs to restrict frames.
+      mChildSrc->setRestrictFrames();
+    }
+    if (!mWorkerSrc) {
+      // if worker-src is specified explicitly for that policy than child-src should
+      // not restrict workers; if not, than child-src needs to restrict workers.
+      mChildSrc->setRestrictWorkers();
+    }
+  }
+  // if script-src is specified, but not worker-src and also no child-src, then
+  // script-src has to govern workers.
+  if (mScriptSrc && !mWorkerSrc && !mChildSrc) {
+    mScriptSrc->setRestrictWorkers();
   }
 
   return mPolicy;
 }
 
 nsCSPPolicy*
 nsCSPParser::parseContentSecurityPolicy(const nsAString& aPolicyString,
                                         nsIURI *aSelfURI,
--- a/dom/security/nsCSPParser.h
+++ b/dom/security/nsCSPParser.h
@@ -237,24 +237,27 @@ class nsCSPParser {
     nsTArray<nsString> mCurDir;
 
     // helpers to allow invalidation of srcs within script-src and style-src
     // if either 'strict-dynamic' or at least a hash or nonce is present.
     bool               mHasHashOrNonce; // false, if no hash or nonce is defined
     bool               mStrictDynamic;  // false, if 'strict-dynamic' is not defined
     nsCSPKeywordSrc*   mUnsafeInlineKeywordSrc; // null, otherwise invlidate()
 
-    // cache variables for child-src and frame-src directive handling.
-    // frame-src is deprecated in favor of child-src, however if we
-    // see a frame-src directive, it takes precedence for frames and iframes.
-    // At the end of parsing, if we have a child-src directive, we need to
-    // decide whether it will handle frames, or if there is a frame-src we
-    // should honor instead.
-    nsCSPChildSrcDirective* mChildSrc;
-    nsCSPDirective*         mFrameSrc;
+    // cache variables for child-src, frame-src and worker-src handling;
+    // in CSP 3 child-src is deprecated. For backwards compatibility
+    // child-src needs to restrict:
+    //   (*) frames, in case frame-src is not expicitly specified
+    //   (*) workers, in case worker-src is not expicitly specified
+    // If neither worker-src, nor child-src is present, then script-src
+    // needs to govern workers.
+    nsCSPChildSrcDirective*  mChildSrc;
+    nsCSPDirective*          mFrameSrc;
+    nsCSPDirective*          mWorkerSrc;
+    nsCSPScriptSrcDirective* mScriptSrc;
 
     // cache variable to let nsCSPHostSrc know that it's within
     // the frame-ancestors directive.
     bool                    mParsingFrameAncestorsDir;
 
     cspTokens          mTokens;
     nsIURI*            mSelfURI;
     nsCSPPolicy*       mPolicy;
--- a/dom/security/nsCSPUtils.cpp
+++ b/dom/security/nsCSPUtils.cpp
@@ -227,17 +227,17 @@ CSP_ContentTypeToDirective(nsContentPoli
       return nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE;
 
     case nsIContentPolicy::TYPE_WEB_MANIFEST:
       return nsIContentSecurityPolicy::WEB_MANIFEST_SRC_DIRECTIVE;
 
     case nsIContentPolicy::TYPE_INTERNAL_WORKER:
     case nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER:
     case nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER:
-      return nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE;
+      return nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE;
 
     case nsIContentPolicy::TYPE_SUBDOCUMENT:
       return nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE;
 
     case nsIContentPolicy::TYPE_WEBSOCKET:
     case nsIContentPolicy::TYPE_XMLHTTPREQUEST:
     case nsIContentPolicy::TYPE_BEACON:
     case nsIContentPolicy::TYPE_PING:
@@ -1185,16 +1185,21 @@ nsCSPDirective::toDomCSPStruct(mozilla::
       outCSP.mChild_src.Value() = mozilla::Move(srcs);
       return;
 
     case nsIContentSecurityPolicy::SANDBOX_DIRECTIVE:
       outCSP.mSandbox.Construct();
       outCSP.mSandbox.Value() = mozilla::Move(srcs);
       return;
 
+    case nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE:
+      outCSP.mWorker_src.Construct();
+      outCSP.mWorker_src.Value() = mozilla::Move(srcs);
+      return;
+
     // REFERRER_DIRECTIVE and REQUIRE_SRI_FOR are handled in nsCSPPolicy::toDomCSPStruct()
 
     default:
       NS_ASSERTION(false, "cannot find directive to convert CSP to JSON");
   }
 }
 
 
@@ -1237,48 +1242,77 @@ bool nsCSPDirective::equals(CSPDirective
 {
   return (mDirective == aDirective);
 }
 
 /* =============== nsCSPChildSrcDirective ============= */
 
 nsCSPChildSrcDirective::nsCSPChildSrcDirective(CSPDirective aDirective)
   : nsCSPDirective(aDirective)
-  , mHandleFrameSrc(false)
+  , mRestrictFrames(false)
+  , mRestrictWorkers(false)
 {
 }
 
 nsCSPChildSrcDirective::~nsCSPChildSrcDirective()
 {
 }
 
-void nsCSPChildSrcDirective::setHandleFrameSrc()
-{
-  mHandleFrameSrc = true;
-}
-
 bool nsCSPChildSrcDirective::restrictsContentType(nsContentPolicyType aContentType) const
 {
   if (aContentType == nsIContentPolicy::TYPE_SUBDOCUMENT) {
-    return mHandleFrameSrc;
+    return mRestrictFrames;
   }
-
-  return (aContentType == nsIContentPolicy::TYPE_INTERNAL_WORKER
-      || aContentType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER
-      || aContentType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER
-      );
+  if (aContentType == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
+      aContentType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER ||
+      aContentType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER) {
+    return mRestrictWorkers;
+  }
+  return false;
 }
 
 bool nsCSPChildSrcDirective::equals(CSPDirective aDirective) const
 {
   if (aDirective == nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE) {
-    return mHandleFrameSrc;
+    return mRestrictFrames;
+  }
+  if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) {
+    return mRestrictWorkers;
   }
+  return (mDirective == aDirective);
+}
 
-  return (aDirective == nsIContentSecurityPolicy::CHILD_SRC_DIRECTIVE);
+/* =============== nsCSPScriptSrcDirective ============= */
+
+nsCSPScriptSrcDirective::nsCSPScriptSrcDirective(CSPDirective aDirective)
+  : nsCSPDirective(aDirective)
+  , mRestrictWorkers(false)
+{
+}
+
+nsCSPScriptSrcDirective::~nsCSPScriptSrcDirective()
+{
+}
+
+bool nsCSPScriptSrcDirective::restrictsContentType(nsContentPolicyType aContentType) const
+{
+  if (aContentType == nsIContentPolicy::TYPE_INTERNAL_WORKER ||
+      aContentType == nsIContentPolicy::TYPE_INTERNAL_SHARED_WORKER ||
+      aContentType == nsIContentPolicy::TYPE_INTERNAL_SERVICE_WORKER) {
+    return mRestrictWorkers;
+  }
+  return mDirective == CSP_ContentTypeToDirective(aContentType);
+}
+
+bool nsCSPScriptSrcDirective::equals(CSPDirective aDirective) const
+{
+  if (aDirective == nsIContentSecurityPolicy::WORKER_SRC_DIRECTIVE) {
+    return mRestrictWorkers;
+  }
+  return (mDirective == aDirective);
 }
 
 /* =============== nsBlockAllMixedContentDirective ============= */
 
 nsBlockAllMixedContentDirective::nsBlockAllMixedContentDirective(CSPDirective aDirective)
 : nsCSPDirective(aDirective)
 {
 }
--- a/dom/security/nsCSPUtils.h
+++ b/dom/security/nsCSPUtils.h
@@ -89,17 +89,18 @@ static const char* CSPStrDirectives[] = 
   "base-uri",                  // BASE_URI_DIRECTIVE
   "form-action",               // FORM_ACTION_DIRECTIVE
   "referrer",                  // REFERRER_DIRECTIVE
   "manifest-src",              // MANIFEST_SRC_DIRECTIVE
   "upgrade-insecure-requests", // UPGRADE_IF_INSECURE_DIRECTIVE
   "child-src",                 // CHILD_SRC_DIRECTIVE
   "block-all-mixed-content",   // BLOCK_ALL_MIXED_CONTENT
   "require-sri-for",           // REQUIRE_SRI_FOR
-  "sandbox"                    // SANDBOX_DIRECTIVE
+  "sandbox",                   // SANDBOX_DIRECTIVE
+  "worker-src"                 // WORKER_SRC_DIRECTIVE
 };
 
 inline const char* CSP_CSPDirectiveToString(CSPDirective aDir)
 {
   return CSPStrDirectives[static_cast<uint32_t>(aDir)];
 }
 
 inline CSPDirective CSP_StringToCSPDirective(const nsAString& aDir)
@@ -465,44 +466,70 @@ class nsCSPDirective {
      { return mDirective == nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE; }
 
     virtual bool equals(CSPDirective aDirective) const;
 
     void getReportURIs(nsTArray<nsString> &outReportURIs) const;
 
     bool visitSrcs(nsCSPSrcVisitor* aVisitor) const;
 
-  private:
+  protected:
     CSPDirective            mDirective;
     nsTArray<nsCSPBaseSrc*> mSrcs;
 };
 
 /* =============== nsCSPChildSrcDirective ============= */
 
 /*
- * In CSP 2, the child-src directive covers both workers and
- * subdocuments (i.e., frames and iframes). Workers were removed
- * from script-src, but frames can be controlled by either child-src
- * or frame-src directives, so child-src needs to know whether it should
- * also restrict frames. When both are present the frame-src directive
- * takes precedent.
+ * In CSP 3 child-src is deprecated. For backwards compatibility
+ * child-src needs to restrict:
+ *   (*) frames, in case frame-src is not expicitly specified
+ *   (*) workers, in case worker-src is not expicitly specified
  */
 class nsCSPChildSrcDirective : public nsCSPDirective {
   public:
     explicit nsCSPChildSrcDirective(CSPDirective aDirective);
     virtual ~nsCSPChildSrcDirective();
 
-    void setHandleFrameSrc();
+    void setRestrictFrames()
+      { mRestrictFrames = true; }
+
+    void setRestrictWorkers()
+      { mRestrictWorkers = true; }
 
     virtual bool restrictsContentType(nsContentPolicyType aContentType) const;
 
     virtual bool equals(CSPDirective aDirective) const;
 
   private:
-    bool mHandleFrameSrc;
+    bool mRestrictFrames;
+    bool mRestrictWorkers;
+};
+
+/* =============== nsCSPScriptSrcDirective ============= */
+
+/*
+ * In CSP 3 worker-src restricts workers, for backwards compatibily
+ * script-src has to restrict workers as the ultimate fallback if
+ * neither worker-src nor child-src is present in a CSP.
+ */
+class nsCSPScriptSrcDirective : public nsCSPDirective {
+  public:
+    explicit nsCSPScriptSrcDirective(CSPDirective aDirective);
+    virtual ~nsCSPScriptSrcDirective();
+
+    void setRestrictWorkers()
+      { mRestrictWorkers = true; }
+
+    virtual bool restrictsContentType(nsContentPolicyType aContentType) const;
+
+    virtual bool equals(CSPDirective aDirective) const;
+
+  private:
+    bool mRestrictWorkers;
 };
 
 /* =============== nsBlockAllMixedContentDirective === */
 
 class nsBlockAllMixedContentDirective : public nsCSPDirective {
   public:
     explicit nsBlockAllMixedContentDirective(CSPDirective aDirective);
     ~nsBlockAllMixedContentDirective();
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_frame_src.js
@@ -0,0 +1,14 @@
+let testframe = document.getElementById("testframe");
+testframe.onload = function() {
+  parent.postMessage({
+    result: "frame-allowed",
+    href: document.location.href,
+  }, "*");
+}
+testframe.onerror = function() {
+  parent.postMessage({
+    result: "frame-blocked",
+    href: document.location.href,
+  }, "*");
+}
+testframe.src = "file_frame_src_inner.html"
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_frame_src_child_governs.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+  <meta charset="utf-8">
+  <meta http-equiv="Content-Security-Policy" content="child-src https://example.com">";
+</head>
+<body>
+<iframe id="testframe"></iframe>
+<script type="text/javascript" src="file_frame_src.js"></script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_frame_src_frame_governs.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+  <meta charset="utf-8">
+  <meta http-equiv="Content-Security-Policy" content="frame-src https://example.com; child-src 'none'">";
+</head>
+<body>
+<iframe id="testframe"></iframe>
+<script type="text/javascript" src="file_frame_src.js"></script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_frame_src_inner.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+dummy iframe
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_spawn_service_worker.js
@@ -0,0 +1,1 @@
+// dummy file
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_spawn_shared_worker.js
@@ -0,0 +1,7 @@
+onconnect = function(e) {
+  var port = e.ports[0];
+  port.addEventListener("message", function(e) {
+  	port.postMessage("shared worker is executing");
+  });
+  port.start();
+}
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_spawn_worker.js
@@ -0,0 +1,1 @@
+postMessage("worker is executing");
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_worker_src.js
@@ -0,0 +1,53 @@
+var mySharedWorker = new SharedWorker('file_spawn_shared_worker.js');
+mySharedWorker.port.onmessage = function(ev) {
+  parent.postMessage({
+    result: "shared-worker-allowed",
+    href: document.location.href,
+  }, "*");
+  mySharedWorker.port.close();
+}
+mySharedWorker.onerror = function(evt) {
+  evt.preventDefault();
+  parent.postMessage({
+    result: "shared-worker-blocked",
+    href: document.location.href,
+  }, "*");
+  mySharedWorker.port.close();
+}
+mySharedWorker.port.start();
+mySharedWorker.port.postMessage('foo');
+
+// --------------------------------------------
+
+let myWorker = new Worker("file_spawn_worker.js");
+myWorker.onmessage = function(event) {
+  parent.postMessage({
+    result: "worker-allowed",
+    href: document.location.href,
+  }, "*");
+}
+myWorker.onerror = function(event) {
+  parent.postMessage({
+    result: "worker-blocked",
+    href: document.location.href,
+  }, "*");
+}
+
+// --------------------------------------------
+
+navigator.serviceWorker.register('file_spawn_service_worker.js')
+.then(function(reg) {
+  // registration worked
+    reg.unregister().then(function() {
+      parent.postMessage({
+        result: "service-worker-allowed",
+        href: document.location.href,
+      }, "*");
+    });
+}).catch(function(error) {
+  // registration failed
+  parent.postMessage({
+    result: "service-worker-blocked",
+    href: document.location.href,
+  }, "*");
+});
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_worker_src_child_governs.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+  <meta charset="utf-8">
+  <meta http-equiv="Content-Security-Policy" content="child-src https://example.com; script-src 'nonce-foo'">";
+</head>
+<body>
+<script type="text/javascript" src="file_worker_src.js" nonce="foo"></script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_worker_src_script_governs.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+  <meta charset="utf-8">
+  <meta http-equiv="Content-Security-Policy" content="script-src 'nonce-foo' https://example.com">";
+</head>
+<body>
+<script type="text/javascript" src="file_worker_src.js" nonce="foo"></script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/file_worker_src_worker_governs.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+  <meta charset="utf-8">
+  <meta http-equiv="Content-Security-Policy" content="worker-src https://example.com; child-src 'none'; script-src 'nonce-foo'">";
+</head>
+<body>
+<script type="text/javascript" src="file_worker_src.js" nonce="foo"></script>
+</body>
+</html>
--- a/dom/security/test/csp/mochitest.ini
+++ b/dom/security/test/csp/mochitest.ini
@@ -326,8 +326,23 @@ skip-if = toolkit == 'android'
 [test_uir_top_nav.html]
 support-files =
   file_uir_top_nav.html
   file_uir_top_nav_dummy.html
 [test_sandbox_allow_scripts.html]
 support-files =
   file_sandbox_allow_scripts.html
   file_sandbox_allow_scripts.html^headers^
+[test_worker_src.html]
+support-files =
+  file_worker_src_worker_governs.html
+  file_worker_src_child_governs.html
+  file_worker_src_script_governs.html
+  file_worker_src.js
+  file_spawn_worker.js
+  file_spawn_shared_worker.js
+  file_spawn_service_worker.js
+[test_frame_src.html]
+support-files =
+  file_frame_src_frame_governs.html
+  file_frame_src_child_governs.html
+  file_frame_src.js
+  file_frame_src_inner.html
--- a/dom/security/test/csp/test_child-src_worker.html
+++ b/dom/security/test/csp/test_child-src_worker.html
@@ -78,29 +78,29 @@
           file: SHARED_WORKER_TEST_FILE,
           result : "blocked",
           policy : "default-src 'none'; script-src 'unsafe-inline'; child-src https://www.example.org"
         },
         'script-src-worker': {
           id: "script-src-worker",
           file: WORKER_TEST_FILE,
           result : "blocked",
-          policy : "default-src 'none'; script-src 'self' 'unsafe-inline'"
+          policy : "default-src 'none'; script-src https://www.example.org 'unsafe-inline'"
         },
         'script-src-service_worker': {
           id: "script-src-service_worker",
           file: SERVICE_WORKER_TEST_FILE,
           result : "blocked",
-          policy : "default-src 'none'; script-src 'self' 'unsafe-inline'"
+          policy : "default-src 'none'; script-src https://www.example.org 'unsafe-inline'"
         },
         'script-src-self-shared_worker': {
           id: "script-src-self-shared_worker",
           file: SHARED_WORKER_TEST_FILE,
           result : "blocked",
-          policy : "default-src 'none'; script-src 'self' 'unsafe-inline'"
+          policy : "default-src 'none'; script-src https://www.example.org 'unsafe-inline'"
         },
       };
 
       finished = {};
 
       function recvMessage(ev) {
         is(ev.data.message, tests[ev.data.id].result, "CSP child-src worker test " + ev.data.id);
         finished[ev.data.id] = ev.data.message;
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/test_frame_src.html
@@ -0,0 +1,84 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Bug 1302667 - Test frame-src</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+/* Description of the test:
+ * We load a page inlcuding a frame a CSP of:
+ *   >> frame-src https://example.com; child-src 'none'
+ * and make sure that frame-src governs frames correctly. In addition,
+ * we make sure that child-src is discarded in case frame-src is specified.
+ */
+
+const ORIGIN_1 = "https://example.com/tests/dom/security/test/csp/";
+const ORIGIN_2 = "https://test1.example.com/tests/dom/security/test/csp/";
+
+let TESTS = [
+  // frame-src tests
+  ORIGIN_1 + "file_frame_src_frame_governs.html",
+  ORIGIN_2 + "file_frame_src_frame_governs.html",
+  // child-src tests
+  ORIGIN_1 + "file_frame_src_child_governs.html",
+  ORIGIN_2 + "file_frame_src_child_governs.html",
+];
+
+let testIndex = 0;
+
+function checkFinish() {
+  if (testIndex >= TESTS.length) {
+    window.removeEventListener("message", receiveMessage);
+    SimpleTest.finish();
+    return;
+  }
+  runNextTest();
+}
+
+window.addEventListener("message", receiveMessage);
+function receiveMessage(event) {
+  let href = event.data.href;
+  let result = event.data.result;
+
+  if (href.startsWith("https://example.com")) {
+  	if (result == "frame-allowed") {
+  	  ok(true, "allowing frame from https://example.com (" + result + ")");
+  	}
+  	else {
+  	  ok(false, "blocking frame from https://example.com (" + result + ")");
+  	}
+  }
+  else if (href.startsWith("https://test1.example.com")) {
+  	if (result == "frame-blocked") {
+  	  ok(true, "blocking frame from https://test1.example.com (" + result + ")");
+  	}
+  	else {
+  	  ok(false, "allowing frame from https://test1.example.com (" + result + ")");
+  	}
+  }
+  else {
+    // sanity check, we should never enter that branch, bust just in case...
+  	ok(false, "unexpected result: " + result);
+  }
+  checkFinish();
+}
+
+function runNextTest() {
+  document.getElementById("testframe").src = TESTS[testIndex];
+  testIndex++;
+}
+
+// fire up the tests
+runNextTest();
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/security/test/csp/test_worker_src.html
@@ -0,0 +1,105 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Bug 1302667 - Test worker-src</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<iframe style="width:100%;" id="testframe"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(3);
+
+/* Description of the test:
+ * We load a page inlcuding a worker, a shared worker as well as a
+ * service worker with a CSP of:
+ *   >> worker-src https://example.com; child-src 'none'; script-src 'nonce-foo'
+ * and make sure that worker-src governs these three kinds of workers correctly.
+ * In addition, we make sure that child-src as well as script-src is discarded
+ * in case worker-src is specified. Ideally we would use "script-src 'none'" but
+ * we have to whitelist the actual script that spawns the workers, hence the nonce.
+ */
+
+let ALLOWED_HOST = "https://example.com/tests/dom/security/test/csp/";
+let BLOCKED_HOST = "https://test1.example.com/tests/dom/security/test/csp/";
+
+let TESTS = [
+  // allowed
+  ALLOWED_HOST + "file_worker_src_worker_governs.html",
+  ALLOWED_HOST + "file_worker_src_child_governs.html",
+  ALLOWED_HOST + "file_worker_src_script_governs.html",
+  // blocked
+  BLOCKED_HOST + "file_worker_src_worker_governs.html",
+  BLOCKED_HOST + "file_worker_src_child_governs.html",
+  BLOCKED_HOST + "file_worker_src_script_governs.html",
+];
+
+let numberSubTests = 3; // 1 web worker, 1 shared worker, 1 service worker
+let subTestCounter = 0; // keeps track of how many 
+let testIndex = 0;
+
+function checkFinish() {
+  subTestCounter = 0;
+  testIndex++;
+  if (testIndex < TESTS.length) {
+    runNextTest();
+    return;
+  }
+  window.removeEventListener("message", receiveMessage);
+  SimpleTest.finish();
+}
+
+window.addEventListener("message", receiveMessage);
+function receiveMessage(event) {
+  let href = event.data.href;
+  let result = event.data.result;
+
+  if (href.startsWith("https://example.com")) {
+    if (result == "worker-allowed" ||
+        result == "shared-worker-allowed" ||
+        result == "service-worker-allowed") {
+     ok(true, "allowing worker from https://example.com (" + result + ")");
+    }
+    else {
+     ok(false, "blocking worker from https://example.com (" + result + ")");
+    }
+  }
+  else if (href.startsWith("https://test1.example.com")) {
+    if (result == "worker-blocked" ||
+        result == "shared-worker-blocked" ||
+        result == "service-worker-blocked") {
+      ok(true, "blocking worker from https://test1.example.com (" + result + ")");
+    }
+    else {
+      ok(false, "allowing worker from https://test1.example.com (" + result + ")");
+    }
+  }
+  else {
+    // sanity check, we should never enter that branch, bust just in case...
+    ok(false, "unexpected result: " + result);
+  }
+  subTestCounter++;
+  if (subTestCounter < numberSubTests) {
+    return;
+  }
+  checkFinish();
+}
+
+function runNextTest() {
+  document.getElementById("testframe").src = TESTS[testIndex];
+}
+
+SpecialPowers.pushPrefEnv({"set": [
+  ["dom.serviceWorkers.enabled", true],
+  ["dom.serviceWorkers.testing.enabled", true],
+]}, function() {
+  runNextTest();
+});
+
+</script>
+</body>
+</html>
--- a/dom/security/test/gtest/TestCSPParser.cpp
+++ b/dom/security/test/gtest/TestCSPParser.cpp
@@ -214,16 +214,20 @@ TEST(CSPParser, Directives)
     { "require-sri-for script style",
       "require-sri-for script style"},
     { "script-src 'nonce-foo' 'unsafe-inline' ",
       "script-src 'nonce-foo' 'unsafe-inline'" },
     { "script-src 'nonce-foo' 'strict-dynamic' 'unsafe-inline' https:  ",
       "script-src 'nonce-foo' 'strict-dynamic' 'unsafe-inline' https:" },
     { "default-src 'sha256-siVR8' 'strict-dynamic' 'unsafe-inline' https:  ",
       "default-src 'sha256-siVR8' 'unsafe-inline' https:" },
+    { "worker-src https://example.com",
+      "worker-src https://example.com" },
+    { "worker-src http://worker.com; frame-src http://frame.com; child-src http://child.com",
+      "worker-src http://worker.com; frame-src http://frame.com; child-src http://child.com" },
   };
 
   uint32_t policyCount = sizeof(policies) / sizeof(PolicyTest);
   ASSERT_TRUE(NS_SUCCEEDED(runTestSuite(policies, policyCount, 1)));
 }
 
 // ============================= TestKeywords ========================
 
--- a/dom/security/test/hsts/head.js
+++ b/dom/security/test/hsts/head.js
@@ -403,17 +403,16 @@ function SetupPrefTestEnvironment(which,
   var prefs = [["security.mixed_content.block_active_content",
                 settings.block_active],
                ["security.mixed_content.block_display_content",
                 settings.block_display],
                ["security.mixed_content.use_hsts",
                 settings.use_hsts],
                ["security.mixed_content.send_hsts_priming",
                 settings.send_hsts_priming],
-               ["toolkit.telemetry.enabled", true],
   ];
 
   if (additional_prefs) {
     for (let idx in additional_prefs) {
       prefs.push(additional_prefs[idx]);
     }
   }
 
--- a/dom/tests/mochitest/pointerlock/mochitest.ini
+++ b/dom/tests/mochitest/pointerlock/mochitest.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 skip-if = toolkit == 'android'
 
 [test_closewindow-with-pointerlock.html]
 
 [test_pointerlock-api.html]
+skip-if = os == "linux" || os == "win" # Bug 1357082
 tags = fullscreen
 support-files =
   pointerlock_utils.js
   file_pointerlock-api.html
   file_pointerlockerror.html
   file_escapeKey.html
   file_withoutDOM.html
   file_removedFromDOM.html
--- a/dom/webidl/CSPDictionaries.webidl
+++ b/dom/webidl/CSPDictionaries.webidl
@@ -25,13 +25,14 @@ dictionary CSP {
   sequence<DOMString> form-action;
   sequence<DOMString> referrer;
   sequence<DOMString> manifest-src;
   sequence<DOMString> upgrade-insecure-requests;
   sequence<DOMString> child-src;
   sequence<DOMString> block-all-mixed-content;
   sequence<DOMString> require-sri-for;
   sequence<DOMString> sandbox;
+  sequence<DOMString> worker-src;
 };
 
 dictionary CSPPolicies {
   sequence<CSP> csp-policies;
 };
--- a/dom/webidl/ServiceWorker.webidl
+++ b/dom/webidl/ServiceWorker.webidl
@@ -21,14 +21,17 @@ interface ServiceWorker : EventTarget {
 
   [Throws]
   void postMessage(any message, optional sequence<object> transferable = []);
 };
 
 ServiceWorker implements AbstractWorker;
 
 enum ServiceWorkerState {
+  // https://github.com/w3c/ServiceWorker/issues/1162
+  "parsed",
+
   "installing",
   "installed",
   "activating",
   "activated",
   "redundant"
 };
new file mode 100644
--- /dev/null
+++ b/dom/workers/IPCServiceWorkerDescriptor.ipdlh
@@ -0,0 +1,24 @@
+/* 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 PBackgroundSharedTypes;
+
+using ServiceWorkerState from "mozilla/dom/ServiceWorkerIPCUtils.h";
+
+namespace mozilla {
+namespace dom {
+
+// IPC type with enough information to create a ServiceWorker DOM object
+// in a child process.  Note that the state may be slightly out-of-sync
+// with the parent and should be updated dynamically if necessary.
+struct IPCServiceWorkerDescriptor
+{
+  uint64_t id;
+  PrincipalInfo principalInfo;
+  nsCString scope;
+  ServiceWorkerState state;
+};
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/workers/ServiceWorkerDescriptor.cpp
@@ -0,0 +1,107 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "ServiceWorkerDescriptor.h"
+#include "mozilla/dom/IPCServiceWorkerDescriptor.h"
+#include "mozilla/dom/ServiceWorkerBinding.h"
+#include "mozilla/ipc/PBackgroundSharedTypes.h"
+
+namespace mozilla {
+namespace dom {
+
+ServiceWorkerDescriptor::ServiceWorkerDescriptor()
+  : mData(MakeUnique<IPCServiceWorkerDescriptor>())
+{
+}
+
+ServiceWorkerDescriptor::ServiceWorkerDescriptor(uint64_t aId,
+                                                 const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+                                                 const nsACString& aScope,
+                                                 ServiceWorkerState aState)
+  : mData(MakeUnique<IPCServiceWorkerDescriptor>(aId, aPrincipalInfo,
+                                                 nsCString(aScope), aState))
+{
+}
+
+ServiceWorkerDescriptor::ServiceWorkerDescriptor(const IPCServiceWorkerDescriptor& aDescriptor)
+  : mData(MakeUnique<IPCServiceWorkerDescriptor>(aDescriptor))
+{
+}
+
+ServiceWorkerDescriptor::ServiceWorkerDescriptor(const ServiceWorkerDescriptor& aRight)
+{
+  operator=(aRight);
+}
+
+ServiceWorkerDescriptor&
+ServiceWorkerDescriptor::operator=(const ServiceWorkerDescriptor& aRight)
+{
+  mData.reset();
+  mData = MakeUnique<IPCServiceWorkerDescriptor>(*aRight.mData);
+  return *this;
+}
+
+ServiceWorkerDescriptor::ServiceWorkerDescriptor(ServiceWorkerDescriptor&& aRight)
+  : mData(Move(aRight.mData))
+{
+}
+
+ServiceWorkerDescriptor&
+ServiceWorkerDescriptor::operator=(ServiceWorkerDescriptor&& aRight)
+{
+  mData.reset();
+  mData = Move(aRight.mData);
+  return *this;
+}
+
+ServiceWorkerDescriptor::~ServiceWorkerDescriptor()
+{
+}
+
+bool
+ServiceWorkerDescriptor::operator==(const ServiceWorkerDescriptor& aRight) const
+{
+  return *mData == *aRight.mData;
+}
+
+uint64_t
+ServiceWorkerDescriptor::Id() const
+{
+  return mData->id();
+}
+
+const mozilla::ipc::PrincipalInfo&
+ServiceWorkerDescriptor::PrincipalInfo() const
+{
+  return mData->principalInfo();
+}
+
+const nsCString&
+ServiceWorkerDescriptor::Scope() const
+{
+  return mData->scope();
+}
+
+ServiceWorkerState
+ServiceWorkerDescriptor::State() const
+{
+  return mData->state();
+}
+
+void
+ServiceWorkerDescriptor::SetState(ServiceWorkerState aState)
+{
+  mData->state() = aState;
+}
+
+const IPCServiceWorkerDescriptor&
+ServiceWorkerDescriptor::ToIPC() const
+{
+  return *mData;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/workers/ServiceWorkerDescriptor.h
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _mozilla_dom_ServiceWorkerDescriptor_h
+#define _mozilla_dom_ServiceWorkerDescriptor_h
+
+namespace mozilla {
+
+namespace ipc {
+class PrincipalInfo;
+} // namespace ipc
+
+namespace dom {
+
+class IPCServiceWorkerDescriptor;
+enum class ServiceWorkerState : uint8_t;
+
+// This class represents a snapshot of a particular ServiceWorkerInfo object.
+// It is threadsafe and can be transferred across processes.  This is useful
+// because most of its values are immutable and can be relied upon to be
+// accurate. Currently the only variable field is the ServiceWorkerState.
+class ServiceWorkerDescriptor final
+{
+  // This class is largely a wrapper wround an IPDL generated struct.  We
+  // need the wrapper class since IPDL generated code includes windows.h
+  // which is in turn incompatible with bindings code.
+  UniquePtr<IPCServiceWorkerDescriptor> mData;
+
+public:
+  ServiceWorkerDescriptor();
+
+  ServiceWorkerDescriptor(uint64_t aId,
+                          const mozilla::ipc::PrincipalInfo& aPrincipalInfo,
+                          const nsACString& aScope,
+                          ServiceWorkerState aState);
+
+  explicit ServiceWorkerDescriptor(const IPCServiceWorkerDescriptor& aDescriptor);
+
+  ServiceWorkerDescriptor(const ServiceWorkerDescriptor& aRight);
+
+  ServiceWorkerDescriptor&
+  operator=(const ServiceWorkerDescriptor& aRight);
+
+  ServiceWorkerDescriptor(ServiceWorkerDescriptor&& aRight);
+
+  ServiceWorkerDescriptor&
+  operator=(ServiceWorkerDescriptor&& aRight);
+
+  ~ServiceWorkerDescriptor();
+
+  bool
+  operator==(const ServiceWorkerDescriptor& aRight) const;
+
+  uint64_t
+  Id() const;
+
+  const mozilla::ipc::PrincipalInfo&
+  PrincipalInfo() const;
+
+  const nsCString&
+  Scope() const;
+
+  ServiceWorkerState
+  State() const;
+
+  void
+  SetState(ServiceWorkerState aState);
+
+  // Expose the underlying IPC type so that it can be passed via IPC.
+  const IPCServiceWorkerDescriptor&
+  ToIPC() const;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // _mozilla_dom_ServiceWorkerDescriptor_h
new file mode 100644
--- /dev/null
+++ b/dom/workers/ServiceWorkerIPCUtils.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef _mozilla_dom_ServiceWorkerIPCUtils_h
+#define _mozilla_dom_ServiceWorkerIPCUtils_h
+
+#include "ipc/IPCMessageUtils.h"
+#include "mozilla/dom/ServiceWorkerBinding.h"
+
+namespace IPC {
+
+  template<>
+  struct ParamTraits<mozilla::dom::ServiceWorkerState> :
+    public ContiguousEnumSerializer<mozilla::dom::ServiceWorkerState,
+                                    mozilla::dom::ServiceWorkerState::Parsed,
+                                    mozilla::dom::ServiceWorkerState::EndGuard_>
+  {};
+
+} // namespace IPC
+
+#endif // _mozilla_dom_ServiceWorkerIPCUtils_h
--- a/dom/workers/ServiceWorkerInfo.cpp
+++ b/dom/workers/ServiceWorkerInfo.cpp
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ServiceWorkerInfo.h"
 
 #include "ServiceWorkerScriptCache.h"
 
 BEGIN_WORKERS_NAMESPACE
 
+static_assert(nsIServiceWorkerInfo::STATE_PARSED == static_cast<uint16_t>(ServiceWorkerState::Parsed),
+              "ServiceWorkerState enumeration value should match state values from nsIServiceWorkerInfo.");
 static_assert(nsIServiceWorkerInfo::STATE_INSTALLING == static_cast<uint16_t>(ServiceWorkerState::Installing),
               "ServiceWorkerState enumeration value should match state values from nsIServiceWorkerInfo.");
 static_assert(nsIServiceWorkerInfo::STATE_INSTALLED == static_cast<uint16_t>(ServiceWorkerState::Installed),
               "ServiceWorkerState enumeration value should match state values from nsIServiceWorkerInfo.");
 static_assert(nsIServiceWorkerInfo::STATE_ACTIVATING == static_cast<uint16_t>(ServiceWorkerState::Activating),
               "ServiceWorkerState enumeration value should match state values from nsIServiceWorkerInfo.");
 static_assert(nsIServiceWorkerInfo::STATE_ACTIVATED == static_cast<uint16_t>(ServiceWorkerState::Activated),
               "ServiceWorkerState enumeration value should match state values from nsIServiceWorkerInfo.");
@@ -41,17 +43,17 @@ ServiceWorkerInfo::GetCacheName(nsAStrin
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ServiceWorkerInfo::GetState(uint16_t* aState)
 {
   MOZ_ASSERT(aState);
   AssertIsOnMainThread();
-  *aState = static_cast<uint16_t>(mState);
+  *aState = static_cast<uint16_t>(State());
   return NS_OK;
 }
 
 NS_IMETHODIMP
 ServiceWorkerInfo::GetDebugger(nsIWorkerDebugger** aResult)
 {
   if (NS_WARN_IF(!aResult)) {
     return NS_ERROR_FAILURE;
@@ -177,71 +179,78 @@ private:
 void
 ServiceWorkerInfo::UpdateState(ServiceWorkerState aState)
 {
   AssertIsOnMainThread();
 #ifdef DEBUG
   // Any state can directly transition to redundant, but everything else is
   // ordered.
   if (aState != ServiceWorkerState::Redundant) {
-    MOZ_ASSERT_IF(mState == ServiceWorkerState::EndGuard_, aState == ServiceWorkerState::Installing);
-    MOZ_ASSERT_IF(mState == ServiceWorkerState::Installing, aState == ServiceWorkerState::Installed);
-    MOZ_ASSERT_IF(mState == ServiceWorkerState::Installed, aState == ServiceWorkerState::Activating);
-    MOZ_ASSERT_IF(mState == ServiceWorkerState::Activating, aState == ServiceWorkerState::Activated);
+    MOZ_ASSERT_IF(State() == ServiceWorkerState::EndGuard_,
+                  aState == ServiceWorkerState::Installing);
+    MOZ_ASSERT_IF(State() == ServiceWorkerState::Installing,
+                  aState == ServiceWorkerState::Installed);
+    MOZ_ASSERT_IF(State() == ServiceWorkerState::Installed,
+                  aState == ServiceWorkerState::Activating);
+    MOZ_ASSERT_IF(State() == ServiceWorkerState::Activating,
+                  aState == ServiceWorkerState::Activated);
   }
   // Activated can only go to redundant.
-  MOZ_ASSERT_IF(mState == ServiceWorkerState::Activated, aState == ServiceWorkerState::Redundant);
+  MOZ_ASSERT_IF(State() == ServiceWorkerState::Activated,
+                aState == ServiceWorkerState::Redundant);
 #endif
   // Flush any pending functional events to the worker when it transitions to the
   // activated state.
   // TODO: Do we care that these events will race with the propagation of the
   //       state change?
-  if (aState == ServiceWorkerState::Activated && mState != aState) {
-    mServiceWorkerPrivate->Activated();
+  if (State() != aState) {
+    mServiceWorkerPrivate->UpdateState(aState);
   }
-  mState = aState;
-  nsCOMPtr<nsIRunnable> r = new ChangeStateUpdater(mInstances, mState);
+  mDescriptor.SetState(aState);
+  nsCOMPtr<nsIRunnable> r = new ChangeStateUpdater(mInstances, State());
   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r.forget()));
-  if (mState == ServiceWorkerState::Redundant) {
+  if (State() == ServiceWorkerState::Redundant) {
     serviceWorkerScriptCache::PurgeCache(mPrincipal, mCacheName);
   }
 }
 
 ServiceWorkerInfo::ServiceWorkerInfo(nsIPrincipal* aPrincipal,
                                      const nsACString& aScope,
                                      const nsACString& aScriptSpec,
                                      const nsAString& aCacheName,
                                      nsLoadFlags aImportsLoadFlags)
   : mPrincipal(aPrincipal)
-  , mScope(aScope)
   , mScriptSpec(aScriptSpec)
   , mCacheName(aCacheName)
-  , mState(ServiceWorkerState::EndGuard_)
   , mImportsLoadFlags(aImportsLoadFlags)
-  , mServiceWorkerID(GetNextID())
   , mCreationTime(PR_Now())
   , mCreationTimeStamp(TimeStamp::Now())
   , mInstalledTime(0)
   , mActivatedTime(0)
   , mRedundantTime(0)
   , mServiceWorkerPrivate(new ServiceWorkerPrivate(this))
   , mSkipWaitingFlag(false)
   , mHandlesFetch(Unknown)
 {
   MOZ_ASSERT(mPrincipal);
   // cache origin attributes so we can use them off main thread
   mOriginAttributes = mPrincipal->OriginAttributesRef();
-  MOZ_ASSERT(!mScope.IsEmpty());
   MOZ_ASSERT(!mScriptSpec.IsEmpty());
   MOZ_ASSERT(!mCacheName.IsEmpty());
 
   // Scripts of a service worker should always be loaded bypass service workers.
   // Otherwise, we might not be able to update a service worker correctly, if
   // there is a service worker generating the script.
   MOZ_DIAGNOSTIC_ASSERT(mImportsLoadFlags & nsIChannel::LOAD_BYPASS_SERVICE_WORKER);
+
+  PrincipalInfo principalInfo;
+  MOZ_ALWAYS_SUCCEEDS(PrincipalToPrincipalInfo(aPrincipal, &principalInfo));
+
+  mDescriptor = ServiceWorkerDescriptor(GetNextID(), principalInfo, aScope,
+                                        ServiceWorkerState::Parsed);
 }
 
 ServiceWorkerInfo::~ServiceWorkerInfo()
 {
   MOZ_ASSERT(mServiceWorkerPrivate);
   mServiceWorkerPrivate->NoteDeadServiceWorkerInfo();
 }
 
@@ -274,39 +283,39 @@ ServiceWorkerInfo::GetOrCreateInstance(n
   }
 
   return ref.forget();
 }
 
 void
 ServiceWorkerInfo::UpdateInstalledTime()
 {
-  MOZ_ASSERT(mState == ServiceWorkerState::Installed);
+  MOZ_ASSERT(State() == ServiceWorkerState::Installed);
   MOZ_ASSERT(mInstalledTime == 0);
 
   mInstalledTime =
     mCreationTime + static_cast<PRTime>((TimeStamp::Now() -
                                          mCreationTimeStamp).ToMicroseconds());
 }
 
 void
 ServiceWorkerInfo::UpdateActivatedTime()
 {
-  MOZ_ASSERT(mState == ServiceWorkerState::Activated);
+  MOZ_ASSERT(State() == ServiceWorkerState::Activated);
   MOZ_ASSERT(mActivatedTime == 0);
 
   mActivatedTime =
     mCreationTime + static_cast<PRTime>((TimeStamp::Now() -
                                          mCreationTimeStamp).ToMicroseconds());
 }
 
 void
 ServiceWorkerInfo::UpdateRedundantTime()
 {
-  MOZ_ASSERT(mState == ServiceWorkerState::Redundant);
+  MOZ_ASSERT(State() == ServiceWorkerState::Redundant);
   MOZ_ASSERT(mRedundantTime == 0);
 
   mRedundantTime =
     mCreationTime + static_cast<PRTime>((TimeStamp::Now() -
                                          mCreationTimeStamp).ToMicroseconds());
 }
 
 END_WORKERS_NAMESPACE
--- a/dom/workers/ServiceWorkerInfo.h
+++ b/dom/workers/ServiceWorkerInfo.h
@@ -23,36 +23,31 @@ class ServiceWorkerPrivate;
  * as the same thing; i.e. "Resolve foo with
  * _GetNewestWorker(serviceWorkerRegistration)", we represent the description
  * by this class and spawn a ServiceWorker in the right global when required.
  */
 class ServiceWorkerInfo final : public nsIServiceWorkerInfo
 {
 private:
   nsCOMPtr<nsIPrincipal> mPrincipal;
-  const nsCString mScope;
+  ServiceWorkerDescriptor mDescriptor;
   const nsCString mScriptSpec;
   const nsString mCacheName;
-  ServiceWorkerState mState;
   OriginAttributes mOriginAttributes;
 
   // This LoadFlags is only applied to imported scripts, since the main script
   // has already been downloaded when performing the bytecheck. This LoadFlag is
   // composed of three parts:
   //   1. nsIChannel::LOAD_BYPASS_SERVICE_WORKER
   //   2. (Optional) nsIRequest::VALIDATE_ALWAYS
   //      depends on ServiceWorkerUpdateViaCache of its registration.
   //   3. (optional) nsIRequest::LOAD_BYPASS_CACHE
   //      depends on whether the update timer is expired.
   const nsLoadFlags mImportsLoadFlags;
 
-  // This id is shared with WorkerPrivate to match requests issued by service
-  // workers to their corresponding serviceWorkerInfo.
-  uint64_t mServiceWorkerID;
-
   // Timestamp to track SW's state
   PRTime mCreationTime;
   TimeStamp mCreationTimeStamp;
 
   // The time of states are 0, if SW has not reached that state yet. Besides, we
   // update each of them after UpdateState() is called in SWRegistrationInfo.
   PRTime mInstalledTime;
   PRTime mActivatedTime;
@@ -101,17 +96,17 @@ public:
   ScriptSpec() const
   {
     return mScriptSpec;
   }
 
   const nsCString&
   Scope() const
   {
-    return mScope;
+    return mDescriptor.Scope();
   }
 
   bool SkipWaitingFlag() const
   {
     AssertIsOnMainThread();
     return mSkipWaitingFlag;
   }
 
@@ -125,17 +120,17 @@ public:
                     const nsACString& aScope,
                     const nsACString& aScriptSpec,
                     const nsAString& aCacheName,
                     nsLoadFlags aLoadFlags);
 
   ServiceWorkerState
   State() const
   {
-    return mState;
+    return mDescriptor.State();
   }
 
   const OriginAttributes&
   GetOriginAttributes() const
   {
     return mOriginAttributes;
   }
 
@@ -149,28 +144,34 @@ public:
   GetImportsLoadFlags() const
   {
     return mImportsLoadFlags;
   }
 
   uint64_t
   ID() const
   {
-    return mServiceWorkerID;
+    return mDescriptor.Id();
+  }
+
+  const ServiceWorkerDescriptor&
+  Descriptor() const
+  {
+    return mDescriptor;
   }
 
   void
   UpdateState(ServiceWorkerState aState);
 
   // Only used to set initial state when loading from disk!
   void
   SetActivateStateUncheckedWithoutEvent(ServiceWorkerState aState)
   {
     AssertIsOnMainThread();
-    mState = aState;
+    mDescriptor.SetState(aState);
   }
 
   void
   SetHandlesFetch(bool aHandlesFetch)
   {
     AssertIsOnMainThread();
     MOZ_DIAGNOSTIC_ASSERT(mHandlesFetch == Unknown);
     mHandlesFetch = aHandlesFetch ? Enabled : Disabled;
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -2537,19 +2537,21 @@ public:
     MOZ_ASSERT(aChannel);
   }
 
   void
   HandleError()
   {
     AssertIsOnMainThread();
     NS_WARNING("Unexpected error while dispatching fetch event!");
-    DebugOnly<nsresult> rv = mChannel->ResetInterception();
-    NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
-                         "Failed to resume intercepted network request");
+    nsresult rv = mChannel->ResetInterception();
+    if (NS_FAILED(rv)) {
+      NS_WARNING("Failed to resume intercepted network request");
+      mChannel->CancelInterception(rv);
+    }
   }
 
   NS_IMETHOD
   Run() override
   {
     AssertIsOnMainThread();
 
     nsCOMPtr<nsIChannel> channel;
--- a/dom/workers/ServiceWorkerPrivate.cpp
+++ b/dom/workers/ServiceWorkerPrivate.cpp
@@ -1551,18 +1551,20 @@ private:
       AssertIsOnMainThread();
 
       TimeStamp timeStamp = TimeStamp::Now();
       mChannel->SetHandleFetchEventEnd(timeStamp);
       mChannel->SetChannelResetEnd(timeStamp);
       mChannel->SaveTimeStamps();
 
       nsresult rv = mChannel->ResetInterception();
-      NS_WARNING_ASSERTION(NS_SUCCEEDED(rv),
-                           "Failed to resume intercepted network request");
+      if (NS_FAILED(rv)) {
+        NS_WARNING("Failed to resume intercepted network request");
+        mChannel->CancelInterception(rv);
+      }
       return rv;
     }
   };
 
   bool
   DispatchFetchEvent(JSContext* aCx, WorkerPrivate* aWorkerPrivate)
   {
     MOZ_ASSERT(aCx);
@@ -1679,25 +1681,33 @@ ServiceWorkerPrivate::SendFetchEvent(nsI
     swm->GetRegistration(mInfo->Principal(), mInfo->Scope());
 
   // Its possible the registration is removed between starting the interception
   // and actually dispatching the fetch event.  In these cases we simply
   // want to restart the original network request.  Since this is a normal
   // condition we handle the reset here instead of returning an error which
   // would in turn trigger a console report.
   if (!registration) {
-    aChannel->ResetInterception();
+    nsresult rv = aChannel->ResetInterception();
+    if (NS_FAILED(rv)) {
+      NS_WARNING("Failed to resume intercepted network request");
+      aChannel->CancelInterception(rv);
+    }
     return NS_OK;
   }
 
   // Handle Fetch algorithm - step 16. If the service worker didn't register
   // any fetch event handlers, then abort the interception and maybe trigger
   // the soft update algorithm.
   if (!mInfo->HandlesFetch()) {
-    aChannel->ResetInterception();
+    nsresult rv = aChannel->ResetInterception();
+    if (NS_FAILED(rv)) {
+      NS_WARNING("Failed to resume intercepted network request");
+      aChannel->CancelInterception(rv);
+    }
 
     // Trigger soft updates if necessary.
     registration->MaybeScheduleTimeCheckAndUpdate();
 
     return NS_OK;
   }
 
   // if the ServiceWorker script fails to load for some reason, just resume
@@ -1802,17 +1812,26 @@ ServiceWorkerPrivate::SpawnWorkerIfNeede
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   info.mResolvedScriptURI = info.mBaseURI;
   MOZ_ASSERT(!mInfo->CacheName().IsEmpty());
   info.mServiceWorkerCacheName = mInfo->CacheName();
-  info.mServiceWorkerID = mInfo->ID();
+
+  PrincipalInfo principalInfo;
+  rv = PrincipalToPrincipalInfo(mInfo->Principal(), &principalInfo);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  info.mServiceWorkerDescriptor.emplace(ServiceWorkerDescriptor(mInfo->ID(),
+                                                                principalInfo,
+                                                                mInfo->Scope(),
+                                                                mInfo->State()));
+
   info.mLoadGroup = aLoadGroup;
   info.mLoadFailedAsyncRunnable = aLoadFailedRunnable;
 
   // If we are loading a script for a ServiceWorker then we must not
   // try to intercept it.  If the interception matches the current
   // ServiceWorker's scope then we could deadlock the load.
   info.mLoadFlags = mInfo->GetImportsLoadFlags() |
                     nsIChannel::LOAD_BYPASS_SERVICE_WORKER;
@@ -1940,25 +1959,58 @@ ServiceWorkerPrivate::TerminateWorker()
 void
 ServiceWorkerPrivate::NoteDeadServiceWorkerInfo()
 {
   AssertIsOnMainThread();
   mInfo = nullptr;
   TerminateWorker();
 }
 
+namespace {
+
+class UpdateStateControlRunnable final : public MainThreadWorkerControlRunnable
+{
+  const ServiceWorkerState mState;
+
+  bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+  {
+    MOZ_DIAGNOSTIC_ASSERT(aWorkerPrivate);
+    aWorkerPrivate->UpdateServiceWorkerState(mState);
+    return true;
+  }
+
+public:
+  UpdateStateControlRunnable(WorkerPrivate* aWorkerPrivate,
+                             ServiceWorkerState aState)
+    : MainThreadWorkerControlRunnable(aWorkerPrivate)
+    , mState(aState)
+  {
+  }
+};
+
+} // anonymous namespace
+
 void
-ServiceWorkerPrivate::Activated()
+ServiceWorkerPrivate::UpdateState(ServiceWorkerState aState)
 {
   AssertIsOnMainThread();
 
-  // If we had to queue up events due to the worker activating, that means
-  // the worker must be currently running.  We should be called synchronously
-  // when the worker becomes activated.
-  MOZ_ASSERT_IF(!mPendingFunctionalEvents.IsEmpty(), mWorkerPrivate);
+  if (!mWorkerPrivate) {
+    MOZ_DIAGNOSTIC_ASSERT(mPendingFunctionalEvents.IsEmpty());
+    return;
+  }
+
+  RefPtr<WorkerRunnable> r =
+    new UpdateStateControlRunnable(mWorkerPrivate, aState);
+  Unused << r->Dispatch();
+
+  if (aState != ServiceWorkerState::Activated) {
+    return;
+  }
 
   nsTArray<RefPtr<WorkerRunnable>> pendingEvents;
   mPendingFunctionalEvents.SwapElements(pendingEvents);
 
   for (uint32_t i = 0; i < pendingEvents.Length(); ++i) {
     RefPtr<WorkerRunnable> r = pendingEvents[i].forget();
     if (NS_WARN_IF(!r->Dispatch())) {
       NS_WARNING("Failed to dispatch pending functional event!");
--- a/dom/workers/ServiceWorkerPrivate.h
+++ b/dom/workers/ServiceWorkerPrivate.h
@@ -140,17 +140,17 @@ public:
 
   void
   NoteDeadServiceWorkerInfo();
 
   void
   NoteStoppedControllingDocuments();
 
   void
-  Activated();
+  UpdateState(ServiceWorkerState aState);
 
   nsresult
   GetDebugger(nsIWorkerDebugger** aResult);
 
   nsresult
   AttachDebugger();
 
   nsresult
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -1782,17 +1782,16 @@ public:
 NS_IMPL_ISUPPORTS(WorkerEventTarget, nsIEventTarget,
                                             nsISerialEventTarget)
 
 END_WORKERS_NAMESPACE
 
 WorkerLoadInfo::WorkerLoadInfo()
   : mLoadFlags(nsIRequest::LOAD_NORMAL)
   , mWindowID(UINT64_MAX)
-  , mServiceWorkerID(0)
   , mReferrerPolicy(net::RP_Unset)
   , mFromWindow(false)
   , mEvalAllowed(false)
   , mReportCSPViolations(false)
   , mXHRParamsAllowed(false)
   , mPrincipalIsSystem(false)
   , mStorageAllowed(false)
   , mServiceWorkersTestingInWindow(false)
@@ -1839,19 +1838,19 @@ WorkerLoadInfo::StealFrom(WorkerLoadInfo
   aOther.mInterfaceRequestor.swap(mInterfaceRequestor);
 
   MOZ_ASSERT(!mPrincipalInfo);
   mPrincipalInfo = aOther.mPrincipalInfo.forget();
 
   mDomain = aOther.mDomain;
   mOrigin = aOther.mOrigin;
   mServiceWorkerCacheName = aOther.mServiceWorkerCacheName;
+  mServiceWorkerDescriptor = aOther.mServiceWorkerDescriptor;
   mLoadFlags = aOther.mLoadFlags;
   mWindowID = aOther.mWindowID;
-  mServiceWorkerID = aOther.mServiceWorkerID;
   mReferrerPolicy = aOther.mReferrerPolicy;
   mFromWindow = aOther.mFromWindow;
   mEvalAllowed = aOther.mEvalAllowed;
   mReportCSPViolations = aOther.mReportCSPViolations;
   mXHRParamsAllowed = aOther.mXHRParamsAllowed;
   mPrincipalIsSystem = aOther.mPrincipalIsSystem;
   mStorageAllowed = aOther.mStorageAllowed;
   mServiceWorkersTestingInWindow = aOther.mServiceWorkersTestingInWindow;
@@ -2822,17 +2821,17 @@ WorkerPrivateParent<Derived>::WorkerPriv
                                            bool aIsChromeWorker,
                                            WorkerType aWorkerType,
                                            const nsAString& aWorkerName,
                                            const nsACString& aServiceWorkerScope,
                                            WorkerLoadInfo& aLoadInfo)
 : mMutex("WorkerPrivateParent Mutex"),
   mCondVar(mMutex, "WorkerPrivateParent CondVar"),
   mParent(aParent), mScriptURL(aScriptURL),
-  mWorkerName(aWorkerName), mServiceWorkerScope(aServiceWorkerScope),
+  mWorkerName(aWorkerName),
   mLoadingWorkerScript(false), mBusyCount(0), mParentWindowPausedDepth(0),
   mParentStatus(Pending), mParentFrozen(false),
   mIsChromeWorker(aIsChromeWorker), mMainThreadObjectsForgotten(false),
   mIsSecureContext(false), mWorkerType(aWorkerType),
   mCreationTimeStamp(TimeStamp::Now()),
   mCreationTimeHighRes((double)PR_Now() / PR_USEC_PER_MSEC)
 {
   MOZ_ASSERT_IF(!IsDedicatedWorker(), NS_IsMainThread());
--- a/dom/workers/WorkerPrivate.h
+++ b/dom/workers/WorkerPrivate.h
@@ -214,18 +214,16 @@ protected:
   // Protected by mMutex.
   nsTArray<RefPtr<WorkerRunnable>> mPreStartRunnables;
 
 private:
   WorkerPrivate* mParent;
   nsString mScriptURL;
   // This is the worker name for shared workers and dedicated workers.
   nsString mWorkerName;
-  // This is the worker scope for service workers.
-  nsCString mServiceWorkerScope;
   LocationInfo mLocationInfo;
   // The lifetime of these objects within LoadInfo is managed explicitly;
   // they do not need to be cycle collected.
   WorkerLoadInfo mLoadInfo;
 
   Atomic<bool> mLoadingWorkerScript;
 
   // Only used for top level workers.
@@ -518,24 +516,23 @@ public:
   WindowID() const
   {
     return mLoadInfo.mWindowID;
   }
 
   uint64_t
   ServiceWorkerID() const
   {
-    return mLoadInfo.mServiceWorkerID;
+    return GetServiceWorkerDescriptor().Id();
   }
 
   const nsCString&
   ServiceWorkerScope() const
   {
-    MOZ_DIAGNOSTIC_ASSERT(IsServiceWorker());
-    return mServiceWorkerScope;
+    return GetServiceWorkerDescriptor().Scope();
   }
 
   nsIURI*
   GetBaseURI() const
   {
     AssertIsOnMainThread();
     return mLoadInfo.mBaseURI;
   }
@@ -548,21 +545,37 @@ public:
   {
     AssertIsOnMainThread();
     return mLoadInfo.mResolvedScriptURI;
   }
 
   const nsString&
   ServiceWorkerCacheName() const
   {
-    MOZ_ASSERT(IsServiceWorker());
+    MOZ_DIAGNOSTIC_ASSERT(IsServiceWorker());
     AssertIsOnMainThread();
     return mLoadInfo.mServiceWorkerCacheName;
   }
 
+  const ServiceWorkerDescriptor&
+  GetServiceWorkerDescriptor() const
+  {
+    MOZ_DIAGNOSTIC_ASSERT(IsServiceWorker());
+    MOZ_DIAGNOSTIC_ASSERT(mLoadInfo.mServiceWorkerDescriptor.isSome());
+    return mLoadInfo.mServiceWorkerDescriptor.ref();
+  }
+
+  void
+  UpdateServiceWorkerState(ServiceWorkerState aState)
+  {
+    MOZ_DIAGNOSTIC_ASSERT(IsServiceWorker());
+    MOZ_DIAGNOSTIC_ASSERT(mLoadInfo.mServiceWorkerDescriptor.isSome());
+    return mLoadInfo.mServiceWorkerDescriptor.ref().SetState(aState);
+  }
+
   const ChannelInfo&
   GetChannelInfo() const
   {
     return mLoadInfo.mChannelInfo;
   }
 
   void
   SetChannelInfo(const ChannelInfo& aChannelInfo)
--- a/dom/workers/Workers.h
+++ b/dom/workers/Workers.h
@@ -17,16 +17,17 @@
 #include "nsDebug.h"
 #include "nsString.h"
 #include "nsTArray.h"
 
 #include "nsILoadContext.h"
 #include "nsIWeakReferenceUtils.h"
 #include "nsIInterfaceRequestor.h"
 #include "mozilla/dom/ChannelInfo.h"
+#include "mozilla/dom/ServiceWorkerDescriptor.h"
 #include "mozilla/net/ReferrerPolicy.h"
 
 #define BEGIN_WORKERS_NAMESPACE \
   namespace mozilla { namespace dom { namespace workers {
 #define END_WORKERS_NAMESPACE \
   } /* namespace workers */ } /* namespace dom */ } /* namespace mozilla */
 #define USING_WORKERS_NAMESPACE \
   using namespace mozilla::dom::workers;
@@ -240,22 +241,22 @@ struct WorkerLoadInfo
   // Only set if we have a custom overriden load group
   RefPtr<InterfaceRequestor> mInterfaceRequestor;
 
   nsAutoPtr<mozilla::ipc::PrincipalInfo> mPrincipalInfo;
   nsCString mDomain;
   nsString mOrigin; // Derived from mPrincipal; can be used on worker thread.
 
   nsString mServiceWorkerCacheName;
+  Maybe<ServiceWorkerDescriptor> mServiceWorkerDescriptor;
 
   ChannelInfo mChannelInfo;
   nsLoadFlags mLoadFlags;
 
   uint64_t mWindowID;
-  uint64_t mServiceWorkerID;
 
   net::ReferrerPolicy mReferrerPolicy;
   bool mFromWindow;
   bool mEvalAllowed;
   bool mReportCSPViolations;
   bool mXHRParamsAllowed;
   bool mPrincipalIsSystem;
   bool mStorageAllowed;
--- a/dom/workers/moz.build
+++ b/dom/workers/moz.build
@@ -7,17 +7,19 @@
 with Files("**"):
     BUG_COMPONENT = ("Core", "DOM: Workers")
 
 # Public stuff.
 EXPORTS.mozilla.dom += [
     'FileReaderSync.h',
     'ServiceWorkerCommon.h',
     'ServiceWorkerContainer.h',
+    'ServiceWorkerDescriptor.h',
     'ServiceWorkerEvents.h',
+    'ServiceWorkerIPCUtils.h',
     'ServiceWorkerRegistrar.h',
     'ServiceWorkerRegistration.h',
     'WorkerLocation.h',
     'WorkerNavigator.h',
     'WorkerPrefs.h',
     'WorkerPrivate.h',
     'WorkerRunnable.h',
     'WorkerScope.h',
@@ -55,16 +57,17 @@ UNIFIED_SOURCES += [
     'Principal.cpp',
     'RegisterBindings.cpp',
     'RuntimeService.cpp',
     'ScriptLoader.cpp',
     'ServiceWorker.cpp',
     'ServiceWorkerClient.cpp',
     'ServiceWorkerClients.cpp',
     'ServiceWorkerContainer.cpp',
+    'ServiceWorkerDescriptor.cpp',
     'ServiceWorkerEvents.cpp',
     'ServiceWorkerInfo.cpp',
     'ServiceWorkerJob.cpp',
     'ServiceWorkerJobQueue.cpp',
     'ServiceWorkerManager.cpp',
     'ServiceWorkerManagerChild.cpp',
     'ServiceWorkerManagerParent.cpp',
     'ServiceWorkerManagerService.cpp',
@@ -86,16 +89,17 @@ UNIFIED_SOURCES += [
     'WorkerNavigator.cpp',
     'WorkerPrivate.cpp',
     'WorkerRunnable.cpp',
     'WorkerScope.cpp',
     'WorkerThread.cpp',
 ]
 
 IPDL_SOURCES += [
+    'IPCServiceWorkerDescriptor.ipdlh',
     'PServiceWorkerManager.ipdl',
     'PServiceWorkerUpdater.ipdl',
     'ServiceWorkerRegistrarTypes.ipdlh',
 ]
 
 LOCAL_INCLUDES += [
     '../base',
     '../system',
--- a/gfx/gl/AndroidSurfaceTexture.cpp
+++ b/gfx/gl/AndroidSurfaceTexture.cpp
@@ -9,44 +9,26 @@
 #include "AndroidSurfaceTexture.h"
 
 using namespace mozilla;
 
 namespace mozilla {
 namespace gl {
 
 void
-AndroidSurfaceTexture::GetTransformMatrix(java::sdk::SurfaceTexture::LocalRef aSurfaceTexture,
-                                          gfx::Matrix4x4& aMatrix)
+AndroidSurfaceTexture::GetTransformMatrix(java::sdk::SurfaceTexture::Param surfaceTexture,
+                                          gfx::Matrix4x4* outMatrix)
 {
   JNIEnv* const env = jni::GetEnvForThread();
 
   auto jarray = jni::FloatArray::LocalRef::Adopt(env, env->NewFloatArray(16));
-  aSurfaceTexture->GetTransformMatrix(jarray);
+  surfaceTexture->GetTransformMatrix(jarray);
 
   jfloat* array = env->GetFloatArrayElements(jarray.Get(), nullptr);
 
-  aMatrix._11 = array[0];
-  aMatrix._12 = array[1];
-  aMatrix._13 = array[2];
-  aMatrix._14 = array[3];
-
-  aMatrix._21 = array[4];
-  aMatrix._22 = array[5];
-  aMatrix._23 = array[6];
-  aMatrix._24 = array[7];
-
-  aMatrix._31 = array[8];
-  aMatrix._32 = array[9];
-  aMatrix._33 = array[10];
-  aMatrix._34 = array[11];
-
-  aMatrix._41 = array[12];
-  aMatrix._42 = array[13];
-  aMatrix._43 = array[14];
-  aMatrix._44 = array[15];
+  memcpy(&(outMatrix->_11), array, sizeof(float)*16);
 
   env->ReleaseFloatArrayElements(jarray.Get(), array, 0);
 }
 
 } // gl
 } // mozilla
 #endif // MOZ_WIDGET_ANDROID
--- a/gfx/gl/AndroidSurfaceTexture.h
+++ b/gfx/gl/AndroidSurfaceTexture.h
@@ -13,18 +13,18 @@
 
 typedef uint32_t AndroidSurfaceTextureHandle;
 
 namespace mozilla {
 namespace gl {
 
 class AndroidSurfaceTexture {
 public:
-  static void GetTransformMatrix(java::sdk::SurfaceTexture::LocalRef aSurfaceTexture,
-                                 mozilla::gfx::Matrix4x4& aMatrix);
+  static void GetTransformMatrix(java::sdk::SurfaceTexture::Param surfaceTexture,
+                                 mozilla::gfx::Matrix4x4* outMatrix);
 
 };
 
 } // gl
 } // mozilla
 
 #endif // MOZ_WIDGET_ANDROID
 #endif // AndroidSurfaceTexture_h__
--- a/gfx/gl/GLBlitHelper.cpp
+++ b/gfx/gl/GLBlitHelper.cpp
@@ -14,16 +14,17 @@
 #include "HeapCopyOfStackArray.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/gfx/Logging.h"
 #include "mozilla/gfx/Matrix.h"
 #include "mozilla/UniquePtr.h"
 #include "GPUVideoImage.h"
 
 #ifdef MOZ_WIDGET_ANDROID
+#include "GeneratedJNIWrappers.h"
 #include "AndroidSurfaceTexture.h"
 #include "GLImages.h"
 #include "GLLibraryEGL.h"
 #endif
 
 #ifdef XP_MACOSX
 #include "MacIOSurfaceImage.h"
 #include "GLContextCGL.h"
@@ -49,23 +50,24 @@ const char* const kFragHeader_Tex2DRect 
     #define SAMPLER sampler2DRect                                            \n\
     #if __VERSION__ >= 130                                                   \n\
         #define TEXTURE texture                                              \n\
     #else                                                                    \n\
         #define TEXTURE texture2DRect                                        \n\
     #endif                                                                   \n\
 ";
 const char* const kFragHeader_TexExt = "\
-    #extension GL_OES_EGL_image_external : require                           \n\
-    #define SAMPLER samplerExternalOES                                       \n\
     #if __VERSION__ >= 130                                                   \n\
+        #extension GL_OES_EGL_image_external_essl3 : require                 \n\
         #define TEXTURE texture                                              \n\
     #else                                                                    \n\
+        #extension GL_OES_EGL_image_external : require                       \n\
         #define TEXTURE texture2D                                            \n\
     #endif                                                                   \n\
+    #define SAMPLER samplerExternalOES                                       \n\
 ";
 
 const char* const kFragBody_RGBA = "\
     VARYING vec2 vTexCoord0;                                                 \n\
     uniform SAMPLER uTex0;                                                   \n\
                                                                              \n\
     void main(void)                                                          \n\
     {                                                                        \n\
@@ -752,24 +754,81 @@ GLBlitHelper::BlitImageToFramebuffer(lay
         return false;
     }
 }
 
 // -------------------------------------
 
 #ifdef MOZ_WIDGET_ANDROID
 bool
-GLBlitHelper::BlitImage(layers::SurfaceTextureImage* srcImage, const gfx::IntSize&,
-                        const OriginPos) const
+GLBlitHelper::BlitImage(layers::SurfaceTextureImage* srcImage, const gfx::IntSize& destSize,
+                        const OriginPos destOrigin) const
 {
-    // FIXME
+    AndroidSurfaceTextureHandle handle = srcImage->GetHandle();
+    const auto& surfaceTexture = java::GeckoSurfaceTexture::Lookup(handle);
+
+    if (!surfaceTexture) {
+        return false;
+    }
+
+    const ScopedBindTextureUnit boundTU(mGL, LOCAL_GL_TEXTURE0);
+
+    if (!surfaceTexture->IsAttachedToGLContext((int64_t)mGL)) {
+        GLuint tex;
+        mGL->MakeCurrent();
+        mGL->fGenTextures(1, &tex);
+
+        if (NS_FAILED(surfaceTexture->AttachToGLContext((int64_t)mGL, tex))) {
+            mGL->fDeleteTextures(1, &tex);
+            return false;
+        }
+    }
+
+    const ScopedBindTexture savedTex(mGL, surfaceTexture->GetTexName(), LOCAL_GL_TEXTURE_EXTERNAL);
+    surfaceTexture->UpdateTexImage();
+
+    gfx::Matrix4x4 transform4;
+    AndroidSurfaceTexture::GetTransformMatrix(java::sdk::SurfaceTexture::Ref::From(surfaceTexture),
+                                              &transform4);
+    Mat3 transform3;
+    transform3.at(0,0) = transform4._11;
+    transform3.at(0,1) = transform4._12;
+    transform3.at(0,2) = transform4._14;
+    transform3.at(1,0) = transform4._21;
+    transform3.at(1,1) = transform4._22;
+    transform3.at(1,2) = transform4._24;
+    transform3.at(2,0) = transform4._41;
+    transform3.at(2,1) = transform4._42;
+    transform3.at(2,2) = transform4._44;
+
+    // We don't do w-divison, so if these aren't what we expect, we're probably doing
+    // something wrong.
+    MOZ_ASSERT(transform3.at(0,2) == 0);
+    MOZ_ASSERT(transform3.at(1,2) == 0);
+    MOZ_ASSERT(transform3.at(2,2) == 1);
+
     const auto& srcOrigin = srcImage->GetOriginPos();
-    (void)srcOrigin;
-    gfxCriticalError() << "BlitImage(SurfaceTextureImage) not implemented.";
-    return false;
+
+    // I honestly have no idea why this logic is flipped, but changing the
+    // source origin would mean we'd have to flip it in the compositor
+    // which makes just as little sense as this.
+    const bool yFlip = (srcOrigin == destOrigin);
+
+    const auto& prog = GetDrawBlitProg({kFragHeader_TexExt, kFragBody_RGBA});
+    MOZ_RELEASE_ASSERT(prog);
+
+    // There is no padding on these images, so we can use the GetTransformMatrix directly.
+    const DrawBlitProg::BaseArgs baseArgs = { transform3, yFlip, destSize, Nothing() };
+    prog->Draw(baseArgs, nullptr);
+
+    if (surfaceTexture->IsSingleBuffer()) {
+        surfaceTexture->ReleaseTexImage();
+    }
+
+    return true;
 }
 #endif
 
 // -------------------------------------
 
 bool
 GuessDivisors(const gfx::IntSize& ySize, const gfx::IntSize& uvSize,
               gfx::IntSize* const out_divisors)
--- a/gfx/layers/SourceSurfaceSharedData.cpp
+++ b/gfx/layers/SourceSurfaceSharedData.cpp
@@ -8,16 +8,53 @@
 
 #include "mozilla/Likely.h"
 #include "mozilla/Types.h" // for decltype
 
 namespace mozilla {
 namespace gfx {
 
 bool
+SourceSurfaceSharedDataWrapper::Init(const IntSize& aSize,
+                                     int32_t aStride,
+                                     SurfaceFormat aFormat,
+                                     const SharedMemoryBasic::Handle& aHandle,
+                                     base::ProcessId aCreatorPid)
+{
+  MOZ_ASSERT(!mBuf);
+  mSize = aSize;
+  mStride = aStride;
+  mFormat = aFormat;
+  mCreatorPid = aCreatorPid;
+
+  size_t len = GetAlignedDataLength();
+  mBuf = MakeAndAddRef<SharedMemoryBasic>();
+  if (NS_WARN_IF(!mBuf->SetHandle(aHandle, ipc::SharedMemory::RightsReadOnly)) ||
+      NS_WARN_IF(!mBuf->Map(len))) {
+    mBuf = nullptr;
+    return false;
+  }
+
+  mBuf->CloseHandle();
+  return true;
+}
+
+void
+SourceSurfaceSharedDataWrapper::Init(SourceSurfaceSharedData* aSurface)
+{
+  MOZ_ASSERT(!mBuf);
+  MOZ_ASSERT(aSurface);
+  mSize = aSurface->mSize;
+  mStride = aSurface->mStride;
+  mFormat = aSurface->mFormat;
+  mCreatorPid = base::GetCurrentProcId();
+  mBuf = aSurface->mBuf;
+}
+
+bool
 SourceSurfaceSharedData::Init(const IntSize &aSize,
                               int32_t aStride,
                               SurfaceFormat aFormat)
 {
   mSize = aSize;
   mStride = aStride;
   mFormat = aFormat;
 
@@ -63,16 +100,17 @@ SourceSurfaceSharedData::GetDataInternal
   return static_cast<uint8_t*>(mBuf->memory());
 }
 
 nsresult
 SourceSurfaceSharedData::ShareToProcess(base::ProcessId aPid,
                                         SharedMemoryBasic::Handle& aHandle)
 {
   MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(mHandleCount > 0);
 
   if (mClosed) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   bool shared = mBuf->ShareToProcess(aPid, &aHandle);
   if (MOZ_UNLIKELY(!shared)) {
     return NS_ERROR_FAILURE;
@@ -82,29 +120,33 @@ SourceSurfaceSharedData::ShareToProcess(
 }
 
 void
 SourceSurfaceSharedData::CloseHandleInternal()
 {
   mMutex.AssertCurrentThreadOwns();
 
   if (mClosed) {
+    MOZ_ASSERT(mHandleCount == 0);
+    MOZ_ASSERT(mFinalized);
+    MOZ_ASSERT(mShared);
     return;
   }
 
   if (mFinalized && mShared) {
     mBuf->CloseHandle();
     mClosed = true;
   }
 }
 
 bool
 SourceSurfaceSharedData::ReallocHandle()
 {
   MutexAutoLock lock(mMutex);
+  MOZ_ASSERT(mHandleCount > 0);
   MOZ_ASSERT(mClosed);
   MOZ_ASSERT(mFinalized);
 
   size_t len = GetAlignedDataLength();
   RefPtr<SharedMemoryBasic> buf = new SharedMemoryBasic();
   if (NS_WARN_IF(!buf->Create(len)) ||
       NS_WARN_IF(!buf->Map(len))) {
     return false;
--- a/gfx/layers/SourceSurfaceSharedData.h
+++ b/gfx/layers/SourceSurfaceSharedData.h
@@ -9,39 +9,138 @@
 
 #include "mozilla/gfx/2D.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/ipc/SharedMemoryBasic.h"
 
 namespace mozilla {
 namespace gfx {
 
+class SourceSurfaceSharedData;
+
+/**
+ * This class is used to wrap shared (as in process) data buffers allocated by
+ * a SourceSurfaceSharedData object. It may live in the same process or a
+ * different process from the actual SourceSurfaceSharedData object.
+ *
+ * If it is in the same process, mBuf is the same object as that in the surface.
+ * It is a useful abstraction over just using the surface directly, because it
+ * can have a different lifetime from the surface; if the surface gets freed,
+ * consumers may continue accessing the data in the buffer. Releasing the
+ * original surface is a signal which feeds into SharedSurfacesParent to decide
+ * to release the SourceSurfaceSharedDataWrapper.
+ *
+ * If it is in a different process, mBuf is a new SharedMemoryBasic object which
+ * mapped in the given shared memory handle as read only memory.
+ */
+class SourceSurfaceSharedDataWrapper final : public DataSourceSurface
+{
+  typedef mozilla::ipc::SharedMemoryBasic SharedMemoryBasic;
+
+public:
+  MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceSharedDataWrapper, override)
+
+  SourceSurfaceSharedDataWrapper()
+    : mStride(0)
+    , mFormat(SurfaceFormat::UNKNOWN)
+  { }
+
+  bool Init(const IntSize& aSize,
+            int32_t aStride,
+            SurfaceFormat aFormat,
+            const SharedMemoryBasic::Handle& aHandle,
+            base::ProcessId aCreatorPid);
+
+  void Init(SourceSurfaceSharedData *aSurface);
+
+  base::ProcessId GetCreatorPid() const
+  {
+    return mCreatorPid;
+  }
+
+  int32_t Stride() override { return mStride; }
+
+  SurfaceType GetType() const override { return SurfaceType::DATA; }
+  IntSize GetSize() const override { return mSize; }
+  SurfaceFormat GetFormat() const override { return mFormat; }
+
+  uint8_t* GetData() override
+  {
+    return static_cast<uint8_t*>(mBuf->memory());
+  }
+
+  bool OnHeap() const override
+  {
+    return false;
+  }
+
+  bool Map(MapType, MappedSurface *aMappedSurface) override
+  {
+    aMappedSurface->mData = GetData();
+    aMappedSurface->mStride = mStride;
+    return true;
+  }
+
+  void Unmap() override
+  { }
+
+  bool AddConsumer()
+  {
+    return ++mConsumers == 1;
+  }
+
+  bool RemoveConsumer()
+  {
+    MOZ_ASSERT(mConsumers > 0);
+    return --mConsumers == 0;
+  }
+
+private:
+  size_t GetDataLength() const
+  {
+    return static_cast<size_t>(mStride) * mSize.height;
+  }
+
+  size_t GetAlignedDataLength() const
+  {
+    return mozilla::ipc::SharedMemory::PageAlignedSize(GetDataLength());
+  }
+
+  int32_t mStride;
+  uint32_t mConsumers;
+  IntSize mSize;
+  RefPtr<SharedMemoryBasic> mBuf;
+  SurfaceFormat mFormat;
+  base::ProcessId mCreatorPid;
+};
+
 /**
  * This class is used to wrap shared (as in process) data buffers used by a
  * source surface.
  */
 class SourceSurfaceSharedData final : public DataSourceSurface
 {
   typedef mozilla::ipc::SharedMemoryBasic SharedMemoryBasic;
 
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(SourceSurfaceSharedData, override)
 
   SourceSurfaceSharedData()
     : mMutex("SourceSurfaceSharedData")
     , mStride(0)
     , mMapCount(0)
+    , mHandleCount(0)
     , mFormat(SurfaceFormat::UNKNOWN)
     , mClosed(false)
     , mFinalized(false)
     , mShared(false)
   {
   }
 
-  bool Init(const IntSize &aSize,
+  bool Init(const IntSize& aSize,
             int32_t aStride,
             SurfaceFormat aFormat);
 
   uint8_t* GetData() override
   {
     MutexAutoLock lock(mMutex);
     return GetDataInternal();
   }
@@ -129,27 +228,75 @@ public:
    * Allocate a new shared memory buffer so that we can get a new handle for
    * sharing to new processes. ShareToProcess must have failed with
    * NS_ERROR_NOT_AVAILABLE in order for this to be safe to call. Returns true
    * if the operation succeeds. If it fails, there is no state change.
    */
   bool ReallocHandle();
 
   /**
-   * Indicates we have finished writing to the buffer and it may be marked as
+   * Signals we have finished writing to the buffer and it may be marked as
    * read only. May release the handle if possible (see CloseHandleInternal).
    */
   void Finalize();
 
+  /**
+   * Indicates whether or not the buffer can change. If this returns true, it is
+   * guaranteed to continue to do so for the remainder of the surface's life.
+   */
+  bool IsFinalized() const
+  {
+    MutexAutoLock lock(mMutex);
+    return mFinalized;
+  }
+
+  /**
+   * While a HandleLock exists for the given surface, the shared memory handle
+   * cannot be released.
+   */
+  class MOZ_STACK_CLASS HandleLock final {
+  public:
+    explicit HandleLock(SourceSurfaceSharedData* aSurface)
+      : mSurface(aSurface)
+    {
+      mSurface->LockHandle();
+    }
+
+    ~HandleLock()
+    {
+      mSurface->UnlockHandle();
+    }
+
+  private:
+    RefPtr<SourceSurfaceSharedData> mSurface;
+  };
+
 private:
+  friend class SourceSurfaceSharedDataWrapper;
+
   ~SourceSurfaceSharedData() override
   {
     MOZ_ASSERT(mMapCount == 0);
   }
 
+  void LockHandle()
+  {
+    MutexAutoLock lock(mMutex);
+    ++mHandleCount;
+  }
+
+  void UnlockHandle()
+  {
+    MutexAutoLock lock(mMutex);
+    MOZ_ASSERT(mHandleCount > 0);
+    --mHandleCount;
+    mShared = true;
+    CloseHandleInternal();
+  }
+
   uint8_t* GetDataInternal() const;
 
   size_t GetDataLength() const
   {
     return static_cast<size_t>(mStride) * mSize.height;
   }
 
   size_t GetAlignedDataLength() const
@@ -161,16 +308,17 @@ private:
    * Attempt to close the handle. Only if the buffer has been both finalized
    * and we have completed sharing will it be released.
    */
   void CloseHandleInternal();
 
   mutable Mutex mMutex;
   int32_t mStride;
   int32_t mMapCount;
+  int32_t mHandleCount;
   IntSize mSize;
   RefPtr<SharedMemoryBasic> mBuf;
   RefPtr<SharedMemoryBasic> mOldBuf;
   SurfaceFormat mFormat;
   bool mClosed : 1;
   bool mFinalized : 1;
   bool mShared : 1;
 };
--- a/gfx/layers/client/ContentClient.cpp
+++ b/gfx/layers/client/ContentClient.cpp
@@ -430,22 +430,23 @@ ContentClient::CalculateBufferForPaint(P
 
       // We need to validate the entire buffer, to make sure that only valid
       // pixels are sampled.
       neededRegion = destBufferRect;
     }
 
     // If we have an existing buffer, but the content type has changed or we
     // have transitioned into/out of component alpha, then we need to recreate it.
-    if (canReuseBuffer &&
+    if (canKeepBufferContents &&
+        mBuffer &&
         (contentType != BufferContentType() ||
         (mode == SurfaceMode::SURFACE_COMPONENT_ALPHA) != mBuffer->HaveBufferOnWhite()))
     {
       // Restart the decision process; we won't re-enter since we guard on
-      // being able to re-use the buffer.
+      // being able to keep the buffer contents.
       canReuseBuffer = false;
       canKeepBufferContents = false;
       validRegion.SetEmpty();
       continue;
     }
 
     break;
   }
--- a/gfx/layers/ipc/CompositorManagerChild.h
+++ b/gfx/layers/ipc/CompositorManagerChild.h
@@ -44,26 +44,52 @@ public:
                                const CompositorOptions& aOptions,
                                bool aUseExternalSurfaceSize,
                                const gfx::IntSize& aSurfaceSize);
 
   static already_AddRefed<CompositorBridgeChild>
   CreateSameProcessWidgetCompositorBridge(LayerManager* aLayerManager,
                                           uint32_t aNamespace);
 
+  static CompositorManagerChild* GetInstance()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    return sInstance;
+  }
+
+  bool CanSend() const
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    return mCanSend;
+  }
+
   uint32_t GetNextResourceId()
   {
+    MOZ_ASSERT(NS_IsMainThread());
     return ++mResourceId;
   }
 
   uint32_t GetNamespace() const
   {
     return mNamespace;
   }
 
+  bool OwnsExternalImageId(const wr::ExternalImageId& aId) const
+  {
+    return mNamespace == static_cast<uint32_t>(wr::AsUint64(aId) >> 32);
+  }
+
+  wr::ExternalImageId GetNextExternalImageId()
+  {
+    uint64_t id = GetNextResourceId();
+    MOZ_RELEASE_ASSERT(id != 0);
+    id |= (static_cast<uint64_t>(mNamespace) << 32);
+    return wr::ToExternalImageId(id);
+  }
+
   void ActorDestroy(ActorDestroyReason aReason) override;
 
   void HandleFatalError(const char* aName, const char* aMsg) const override;
 
   void ProcessingError(Result aCode, const char* aReason) override;
 
   PCompositorBridgeChild* AllocPCompositorBridgeChild(const CompositorBridgeOptions& aOptions) override;
 
@@ -81,22 +107,16 @@ private:
   CompositorManagerChild(Endpoint<PCompositorManagerChild>&& aEndpoint,
                          uint64_t aProcessToken,
                          uint32_t aNamespace);
 
   ~CompositorManagerChild() override
   {
   }
 
-  bool CanSend() const
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    return mCanSend;
-  }
-
   void DeallocPCompositorManagerChild() override;
 
   already_AddRefed<nsIEventTarget>
   GetSpecificMessageEventTarget(const Message& aMsg) override;
 
   void SetReplyTimeout();
 
   uint64_t mProcessToken;
--- a/gfx/layers/ipc/CompositorManagerParent.cpp
+++ b/gfx/layers/ipc/CompositorManagerParent.cpp
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/layers/CompositorManagerParent.h"
 #include "mozilla/gfx/GPUParent.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CrossProcessCompositorBridgeParent.h"
 #include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/SharedSurfacesParent.h"
 #include "nsAutoPtr.h"
 #include "VsyncSource.h"
 
 namespace mozilla {
 namespace layers {
 
 StaticRefPtr<CompositorManagerParent> CompositorManagerParent::sInstance;
 StaticMutex CompositorManagerParent::sMutex;
@@ -145,16 +146,18 @@ CompositorManagerParent::BindComplete()
   }
   sActiveActors->AppendElement(this);
 #endif
 }
 
 void
 CompositorManagerParent::ActorDestroy(ActorDestroyReason aReason)
 {
+  SharedSurfacesParent::DestroyProcess(OtherPid());
+
   StaticMutexAutoLock lock(sMutex);
   if (sInstance == this) {
     sInstance = nullptr;
   }
 }
 
 void
 CompositorManagerParent::DeallocPCompositorManagerParent()
@@ -268,10 +271,25 @@ CompositorManagerParent::AllocPComposito
 
 bool
 CompositorManagerParent::DeallocPCompositorBridgeParent(PCompositorBridgeParent* aActor)
 {
   static_cast<CompositorBridgeParentBase*>(aActor)->Release();
   return true;
 }
 
+mozilla::ipc::IPCResult
+CompositorManagerParent::RecvAddSharedSurface(const wr::ExternalImageId& aId,
+                                              const SurfaceDescriptorShared& aDesc)
+{
+  SharedSurfacesParent::Add(aId, aDesc, OtherPid());
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+CompositorManagerParent::RecvRemoveSharedSurface(const wr::ExternalImageId& aId)
+{
+  SharedSurfacesParent::Remove(aId);
+  return IPC_OK();
+}
+
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/ipc/CompositorManagerParent.h
+++ b/gfx/layers/ipc/CompositorManagerParent.h
@@ -35,16 +35,20 @@ public:
   static void Shutdown();
 
   static already_AddRefed<CompositorBridgeParent>
   CreateSameProcessWidgetCompositorBridge(CSSToLayoutDeviceScale aScale,
                                           const CompositorOptions& aOptions,
                                           bool aUseExternalSurfaceSize,
                                           const gfx::IntSize& aSurfaceSize);
 
+  mozilla::ipc::IPCResult RecvAddSharedSurface(const wr::ExternalImageId& aId,
+                                               const SurfaceDescriptorShared& aDesc) override;
+  mozilla::ipc::IPCResult RecvRemoveSharedSurface(const wr::ExternalImageId& aId) override;
+
   void BindComplete();
   void ActorDestroy(ActorDestroyReason aReason) override;
 
   bool DeallocPCompositorBridgeParent(PCompositorBridgeParent* aActor) override;
   PCompositorBridgeParent* AllocPCompositorBridgeParent(const CompositorBridgeOptions& aOpt) override;
 
 private:
   static StaticRefPtr<CompositorManagerParent> sInstance;
--- a/gfx/layers/ipc/CompositorThread.cpp
+++ b/gfx/layers/ipc/CompositorThread.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #include "CompositorThread.h"
 #include "MainThreadUtils.h"
 #include "nsThreadUtils.h"
 #include "CompositorBridgeParent.h"
 #include "mozilla/layers/ImageBridgeParent.h"
+#include "mozilla/layers/SharedSurfacesParent.h"
 #include "mozilla/media/MediaSystemResourceService.h"
 #ifdef MOZ_CRASHREPORTER
 #include "nsExceptionHandler.h"     // for CrashReporter
 #endif
 
 namespace mozilla {
 
 namespace gfx {
@@ -93,16 +94,17 @@ CompositorThreadHolder::~CompositorThrea
 /* static */ void
 CompositorThreadHolder::DestroyCompositorThread(base::Thread* aCompositorThread)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   MOZ_ASSERT(!sCompositorThreadHolder, "We shouldn't be destroying the compositor thread yet.");
 
   CompositorBridgeParent::Shutdown();
+  SharedSurfacesParent::Shutdown();
   delete aCompositorThread;
   sFinishedCompositorShutDown = true;
 }
 
 /* static */ base::Thread*
 CompositorThreadHolder::CreateCompositorThread()
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -127,16 +129,17 @@ CompositorThreadHolder::CreateCompositor
   options.message_loop_type = MessageLoop::TYPE_UI;
 #endif
 
   if (!compositorThread->StartWithOptions(options)) {
     delete compositorThread;
     return nullptr;
   }
 
+  SharedSurfacesParent::Initialize();
   CompositorBridgeParent::Setup();
   ImageBridgeParent::Setup();
 
   return compositorThread;
 }
 
 void
 CompositorThreadHolder::Start()
--- a/gfx/layers/ipc/ImageBridgeParent.cpp
+++ b/gfx/layers/ipc/ImageBridgeParent.cpp
@@ -37,17 +37,17 @@
 
 namespace mozilla {
 namespace layers {
 
 using namespace mozilla::ipc;
 using namespace mozilla::gfx;
 using namespace mozilla::media;
 
-std::map<base::ProcessId, ImageBridgeParent*> ImageBridgeParent::sImageBridges;
+ImageBridgeParent::ImageBridgeMap ImageBridgeParent::sImageBridges;
 
 StaticAutoPtr<mozilla::Monitor> sImageBridgesLock;
 
 static StaticRefPtr<ImageBridgeParent> sImageBridgeParentSingleton;
 
 // defined in CompositorBridgeParent.cpp
 CompositorThreadHolder* GetCompositorThreadHolder();
 
@@ -364,39 +364,45 @@ ImageBridgeParent::NotifyImageComposites
     uint32_t end = i + 1;
     MOZ_ASSERT(aNotifications[i].mNotification.compositable());
     ProcessId pid = aNotifications[i].mImageBridgeProcessId;
     while (end < aNotifications.Length() &&
            aNotifications[end].mImageBridgeProcessId == pid) {
       notifications.AppendElement(aNotifications[end].mNotification);
       ++end;
     }
-    GetInstance(pid)->SendPendingAsyncMessages();
-    if (!GetInstance(pid)->SendDidComposite(notifications)) {
+    RefPtr<ImageBridgeParent> bridge = GetInstance(pid);
+    bridge->SendPendingAsyncMessages();
+    if (!bridge->SendDidComposite(notifications)) {
       ok = false;
     }
     i = end;
   }
   return ok;
 }
 
 void
 ImageBridgeParent::DeferredDestroy()
 {
   mCompositorThreadHolder = nullptr;
   mSelfRef = nullptr; // "this" ImageBridge may get deleted here.
 }
 
-RefPtr<ImageBridgeParent>
+already_AddRefed<ImageBridgeParent>
 ImageBridgeParent::GetInstance(ProcessId aId)
 {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   MonitorAutoLock lock(*sImageBridgesLock);
-  NS_ASSERTION(sImageBridges.count(aId) == 1, "ImageBridgeParent for the process");
-  return sImageBridges[aId];
+  ImageBridgeMap::const_iterator i = sImageBridges.find(aId);
+  if (i == sImageBridges.end()) {
+    NS_ASSERTION(false, "Cannot find image bridge for process!");
+    return nullptr;
+  }
+  RefPtr<ImageBridgeParent> bridge = i->second;
+  return bridge.forget();
 }
 
 bool
 ImageBridgeParent::AllocShmem(size_t aSize,
                       ipc::SharedMemory::SharedMemoryType aType,
                       ipc::Shmem* aShmem)
 {
   if (mClosed) {
--- a/gfx/layers/ipc/ImageBridgeParent.h
+++ b/gfx/layers/ipc/ImageBridgeParent.h
@@ -110,17 +110,17 @@ public:
   virtual bool AllocUnsafeShmem(size_t aSize,
                                 ipc::SharedMemory::SharedMemoryType aType,
                                 ipc::Shmem* aShmem) override;
 
   virtual void DeallocShmem(ipc::Shmem& aShmem) override;
 
   virtual bool IsSameProcess() const override;
 
-  static RefPtr<ImageBridgeParent> GetInstance(ProcessId aId);
+  static already_AddRefed<ImageBridgeParent> GetInstance(ProcessId aId);
 
   static bool NotifyImageComposites(nsTArray<ImageCompositeNotificationInfo>& aNotifications);
 
   virtual bool UsesImageBridge() const override { return true; }
 
   virtual bool IPCOpen() const override { return !mClosed; }
 
 protected:
@@ -136,17 +136,18 @@ private:
   RefPtr<ImageBridgeParent> mSelfRef;
 
   bool mSetChildThreadPriority;
   bool mClosed;
 
   /**
    * Map of all living ImageBridgeParent instances
    */
-  static std::map<base::ProcessId, ImageBridgeParent*> sImageBridges;
+  typedef std::map<base::ProcessId, ImageBridgeParent*> ImageBridgeMap;
+  static ImageBridgeMap sImageBridges;
 
   RefPtr<CompositorThreadHolderDebug> mCompositorThreadHolder;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif // gfx_layers_ipc_ImageBridgeParent_h_
--- a/gfx/layers/ipc/LayersSurfaces.ipdlh
+++ b/gfx/layers/ipc/LayersSurfaces.ipdlh
@@ -7,16 +7,17 @@ using nsIntRegion from "nsRegion.h";
 using struct mozilla::layers::SurfaceDescriptorX11 from "gfxipc/ShadowLayerUtils.h";
 using mozilla::StereoMode from "ImageTypes.h";
 using mozilla::YUVColorSpace from "ImageTypes.h";
 using struct mozilla::null_t from "ipc/IPCMessageUtils.h";
 using mozilla::WindowsHandle from "ipc/IPCMessageUtils.h";
 using mozilla::gfx::SurfaceFormat from "mozilla/gfx/Types.h";
 using mozilla::gfx::IntRect from "mozilla/gfx/Rect.h";
 using mozilla::gfx::IntSize from "mozilla/gfx/Point.h";
+using mozilla::ipc::SharedMemoryBasic::Handle from "mozilla/ipc/SharedMemoryBasic.h";
 using gfxImageFormat from "gfxTypes.h";
 
 namespace mozilla {
 namespace layers {
 
 union OverlayHandle {
   int32_t;
   null_t;
@@ -124,25 +125,34 @@ union MemoryOrShmem {
   Shmem;
 };
 
 struct SurfaceDescriptorBuffer {
   BufferDescriptor desc;
   MemoryOrShmem data;
 };
 
+struct SurfaceDescriptorShared
+{
+  IntSize size;
+  int32_t stride;
+  SurfaceFormat format;
+  Handle handle;
+};
+
 union SurfaceDescriptor {
   SurfaceDescriptorBuffer;
   SurfaceDescriptorDIB;
   SurfaceDescriptorD3D10;
   SurfaceDescriptorFileMapping;
   SurfaceDescriptorDXGIYCbCr;
   SurfaceDescriptorX11;
   SurfaceTextureDescriptor;
   EGLImageDescriptor;
   SurfaceDescriptorMacIOSurface;
   SurfaceDescriptorSharedGLTexture;
   SurfaceDescriptorGPUVideo;
+  SurfaceDescriptorShared;
   null_t;
 };
 
 } // namespace
 } // namespace
--- a/gfx/layers/ipc/PCompositorManager.ipdl
+++ b/gfx/layers/ipc/PCompositorManager.ipdl
@@ -1,22 +1,27 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  * vim: sw=2 ts=8 et :
  */
 /* 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 PCompositorBridge;
+include LayersSurfaces;
+include "mozilla/GfxMessageUtils.h";
+include "mozilla/layers/WebRenderMessageUtils.h";
 
 using struct mozilla::void_t from "ipc/IPCMessageUtils.h";
 using mozilla::TimeDuration from "mozilla/TimeStamp.h";
 using mozilla::CSSToLayoutDeviceScale from "Units.h";
 using mozilla::gfx::IntSize from "mozilla/gfx/2D.h";
+using mozilla::ipc::SharedMemoryBasic::Handle from "mozilla/ipc/SharedMemoryBasic.h";
 using mozilla::layers::CompositorOptions from "mozilla/layers/CompositorOptions.h";
+using mozilla::wr::ExternalImageId from "mozilla/webrender/WebRenderTypes.h";
 
 namespace mozilla {
 namespace layers {
 
 struct WidgetCompositorOptions {
   CSSToLayoutDeviceScale scale;
   TimeDuration vsyncRate;
   CompositorOptions options;
@@ -62,12 +67,15 @@ parent:
    *   representing the drawable area for Web content.
    * - A "widget" PCompositorBridge is requested by the UI process for each
    *   "top level browser window" for chrome and such.
    * - A "same process widget" PCompositorBridge is requested by the combined
    *   GPU/UI process for each "top level browser window" as above.
    * See gfx/layers/ipc/PCompositorBridge.ipdl for more details.
    */
   async PCompositorBridge(CompositorBridgeOptions options);
+
+  async AddSharedSurface(ExternalImageId aId, SurfaceDescriptorShared aDesc);
+  async RemoveSharedSurface(ExternalImageId aId);
 };
 
 } // layers
 } // mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/SharedSurfacesChild.cpp
@@ -0,0 +1,214 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SharedSurfacesChild.h"
+#include "SharedSurfacesParent.h"
+#include "CompositorManagerChild.h"
+#include "mozilla/layers/SourceSurfaceSharedData.h"
+#include "mozilla/SystemGroup.h"        // for SystemGroup
+
+namespace mozilla {
+namespace layers {
+
+using namespace mozilla::gfx;
+
+class SharedSurfacesChild::SharedUserData final
+{
+public:
+  explicit SharedUserData(const wr::ExternalImageId& aId)
+    : mId(aId)
+    , mShared(false)
+  { }
+
+  ~SharedUserData()
+  {
+    if (mShared) {
+      mShared = false;
+      if (NS_IsMainThread()) {
+        SharedSurfacesChild::Unshare(mId);
+      } else {
+        wr::ExternalImageId id = mId;
+        SystemGroup::Dispatch(TaskCategory::Other,
+                              NS_NewRunnableFunction("DestroySharedUserData",
+                                                     [id]() -> void {
+          SharedSurfacesChild::Unshare(id);
+        }));
+      }
+    }
+  }
+
+  const wr::ExternalImageId& Id() const
+  {
+    return mId;
+  }
+
+  void SetId(const wr::ExternalImageId& aId)
+  {
+    mId = aId;
+    mShared = false;
+  }
+
+  bool IsShared() const
+  {
+    return mShared;
+  }
+
+  void MarkShared()
+  {
+    MOZ_ASSERT(!mShared);
+    mShared = true;
+  }
+
+private:
+  wr::ExternalImageId mId;
+  bool mShared : 1;
+};
+
+/* static */ void
+SharedSurfacesChild::DestroySharedUserData(void* aClosure)
+{
+  MOZ_ASSERT(aClosure);
+  auto data = static_cast<SharedUserData*>(aClosure);
+  delete data;
+}
+
+/* static */ nsresult
+SharedSurfacesChild::Share(SourceSurfaceSharedData* aSurface,
+                           wr::ExternalImageId& aId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  CompositorManagerChild* manager = CompositorManagerChild::GetInstance();
+  if (NS_WARN_IF(!manager || !manager->CanSend())) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  static UserDataKey sSharedKey;
+  SharedUserData* data =
+    static_cast<SharedUserData*>(aSurface->GetUserData(&sSharedKey));
+  if (!data) {
+    data = new SharedUserData(manager->GetNextExternalImageId());
+    aSurface->AddUserData(&sSharedKey, data, DestroySharedUserData);
+  } else if (!manager->OwnsExternalImageId(data->Id())) {
+    // If the id isn't owned by us, that means the bridge was reinitialized, due
+    // to the GPU process crashing. All previous mappings have been released.
+    MOZ_ASSERT(manager->OtherPid() != base::GetCurrentProcId());
+    data->SetId(manager->GetNextExternalImageId());
+  } else if (data->IsShared()) {
+    // It has already been shared with the GPU process, reuse the id.
+    aId = data->Id();
+    return NS_OK;
+  }
+
+  // Ensure that the handle doesn't get released until after we have finished
+  // sending the buffer to the GPU process and/or reallocating it.
+  // FinishedSharing is not a sufficient condition because another thread may
+  // decide we are done while we are in the processing of sharing our newly
+  // reallocated handle. Once it goes out of scope, it may release the handle.
+  SourceSurfaceSharedData::HandleLock lock(aSurface);
+
+  // If we live in the same process, then it is a simple matter of directly
+  // asking the parent instance to store a pointer to the same data, no need
+  // to map the data into our memory space twice.
+  auto pid = manager->OtherPid();
+  if (pid == base::GetCurrentProcId()) {
+    SharedSurfacesParent::AddSameProcess(data->Id(), aSurface);
+    data->MarkShared();
+    aId = data->Id();
+    return NS_OK;
+  }
+
+  // Attempt to share a handle with the GPU process. The handle may or may not
+  // be available -- it will only be available if it is either not yet finalized
+  // and/or if it has been finalized but never used for drawing in process.
+  ipc::SharedMemoryBasic::Handle handle = ipc::SharedMemoryBasic::NULLHandle();
+  nsresult rv = aSurface->ShareToProcess(pid, handle);
+  if (rv == NS_ERROR_NOT_AVAILABLE) {
+    // It is at least as expensive to copy the image to the GPU process if we
+    // have already closed the handle necessary to share, but if we reallocate
+    // the shared buffer to get a new handle, we can save some memory.
+    if (NS_WARN_IF(!aSurface->ReallocHandle())) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+
+    // Reattempt the sharing of the handle to the GPU process.
+    rv = aSurface->ShareToProcess(pid, handle);
+  }
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    MOZ_ASSERT(rv != NS_ERROR_NOT_AVAILABLE);
+    return rv;
+  }
+
+  SurfaceFormat format = aSurface->GetFormat();
+  MOZ_RELEASE_ASSERT(format == SurfaceFormat::B8G8R8X8 ||
+                     format == SurfaceFormat::B8G8R8A8, "bad format");
+
+  data->MarkShared();
+  aId = data->Id();
+  manager->SendAddSharedSurface(aId,
+                                SurfaceDescriptorShared(aSurface->GetSize(),
+                                                        aSurface->Stride(),
+                                                        format, handle));
+  return NS_OK;
+}
+
+/* static */ nsresult
+SharedSurfacesChild::Share(ImageContainer* aContainer,
+                           wr::ExternalImageId& aId,
+                           uint32_t& aGeneration)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aContainer);
+
+  if (aContainer->IsAsync()) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  AutoTArray<ImageContainer::OwningImage,4> images;
+  aContainer->GetCurrentImages(&images, &aGeneration);
+  if (images.IsEmpty()) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
+  RefPtr<gfx::SourceSurface> surface = images[0].mImage->GetAsSourceSurface();
+  if (!surface) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  if (surface->GetType() != SurfaceType::DATA_SHARED) {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
+  auto sharedSurface = static_cast<SourceSurfaceSharedData*>(surface.get());
+  return Share(sharedSurface, aId);
+}
+
+/* static */ void
+SharedSurfacesChild::Unshare(const wr::ExternalImageId& aId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  CompositorManagerChild* manager = CompositorManagerChild::GetInstance();
+  if (MOZ_UNLIKELY(!manager || !manager->CanSend())) {
+    return;
+  }
+
+  if (manager->OtherPid() == base::GetCurrentProcId()) {
+    // We are in the combined UI/GPU process. Call directly to it to remove its
+    // wrapper surface to free the underlying buffer.
+    MOZ_ASSERT(manager->OwnsExternalImageId(aId));
+    SharedSurfacesParent::RemoveSameProcess(aId);
+  } else if (manager->OwnsExternalImageId(aId)) {
+    // Only attempt to release current mappings in the GPU process. It is
+    // possible we had a surface that was previously shared, the GPU process
+    // crashed / was restarted, and then we freed the surface. In that case
+    // we know the mapping has already been freed.
+    manager->SendRemoveSharedSurface(aId);
+  }
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/SharedSurfacesChild.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_GFX_SHAREDSURFACESCHILD_H
+#define MOZILLA_GFX_SHAREDSURFACESCHILD_H
+
+#include <stddef.h>                     // for size_t
+#include <stdint.h>                     // for uint32_t, uint64_t
+#include "mozilla/Attributes.h"         // for override
+#include "mozilla/RefPtr.h"             // for already_AddRefed
+#include "mozilla/StaticPtr.h"          // for StaticRefPtr
+#include "mozilla/webrender/WebRenderTypes.h" // for wr::ExternalImageId
+
+namespace mozilla {
+namespace gfx {
+class SourceSurfaceSharedData;
+} // namespace gfx
+
+namespace layers {
+
+class CompositorManagerChild;
+
+class SharedSurfacesChild final
+{
+public:
+  static nsresult Share(gfx::SourceSurfaceSharedData* aSurface, wr::ExternalImageId& aId);
+  static nsresult Share(ImageContainer* aContainer, wr::ExternalImageId& aId, uint32_t& aGeneration);
+
+private:
+  SharedSurfacesChild() = delete;
+  ~SharedSurfacesChild() = delete;
+
+  class SharedUserData;
+
+  static void Unshare(const wr::ExternalImageId& aId);
+  static void DestroySharedUserData(void* aClosure);
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/SharedSurfacesParent.cpp
@@ -0,0 +1,193 @@
+/* vim: set ts=2 sw=2 et tw=80: */
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "SharedSurfacesParent.h"
+#include "mozilla/layers/SourceSurfaceSharedData.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/webrender/RenderSharedSurfaceTextureHost.h"
+#include "mozilla/webrender/RenderThread.h"
+
+namespace mozilla {
+namespace layers {
+
+using namespace mozilla::gfx;
+
+StaticAutoPtr<SharedSurfacesParent> SharedSurfacesParent::sInstance;
+
+SharedSurfacesParent::SharedSurfacesParent()
+{
+}
+
+SharedSurfacesParent::~SharedSurfacesParent()
+{
+}
+
+/* static */ void
+SharedSurfacesParent::Initialize()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (!sInstance) {
+    sInstance = new SharedSurfacesParent();
+  }
+}
+
+/* static */ void
+SharedSurfacesParent::Shutdown()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  sInstance = nullptr;
+}
+
+/* static */ already_AddRefed<DataSourceSurface>
+SharedSurfacesParent::Acquire(const wr::ExternalImageId& aId)
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  if (!sInstance) {
+    return nullptr;
+  }
+
+  RefPtr<SourceSurfaceSharedDataWrapper> surface;
+  sInstance->mSurfaces.Get(wr::AsUint64(aId), getter_AddRefs(surface));
+
+  if (surface) {
+    DebugOnly<bool> rv = surface->AddConsumer();
+    MOZ_ASSERT(!rv);
+  }
+
+  return surface.forget();
+}
+
+/* static */ bool
+SharedSurfacesParent::Release(const wr::ExternalImageId& aId)
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  if (!sInstance) {
+    return false;
+  }
+
+  uint64_t id = wr::AsUint64(aId);
+  RefPtr<SourceSurfaceSharedDataWrapper> surface;
+  sInstance->mSurfaces.Get(wr::AsUint64(aId), getter_AddRefs(surface));
+  if (!surface) {
+    return false;
+  }
+
+  if (surface->RemoveConsumer()) {
+    sInstance->mSurfaces.Remove(id);
+  }
+
+  return true;
+}
+
+/* static */ void
+SharedSurfacesParent::AddSameProcess(const wr::ExternalImageId& aId,
+                                     SourceSurfaceSharedData* aSurface)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // If the child bridge detects it is in the combined UI/GPU process, then it
+  // will insert a wrapper surface holding the shared memory buffer directly.
+  // This is good because we avoid mapping the same shared memory twice, but
+  // still allow the original surface to be freed and remove the wrapper from
+  // the table when it is no longer needed.
+  RefPtr<SourceSurfaceSharedDataWrapper> surface =
+    new SourceSurfaceSharedDataWrapper();
+  surface->Init(aSurface);
+
+  uint64_t id = wr::AsUint64(aId);
+  RefPtr<Runnable> task = NS_NewRunnableFunction(
+    "layers::SharedSurfacesParent::AddSameProcess",
+    [surface, id]() -> void {
+      if (!sInstance) {
+        return;
+      }
+
+      MOZ_ASSERT(!sInstance->mSurfaces.Contains(id));
+
+      RefPtr<wr::RenderSharedSurfaceTextureHost> texture =
+        new wr::RenderSharedSurfaceTextureHost(surface);
+      wr::RenderThread::Get()->RegisterExternalImage(id, texture.forget());
+
+      sInstance->mSurfaces.Put(id, surface);
+    });
+
+  CompositorThreadHolder::Loop()->PostTask(task.forget());
+}
+
+/* static */ void
+SharedSurfacesParent::RemoveSameProcess(const wr::ExternalImageId& aId)
+{
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+
+  const wr::ExternalImageId id(aId);
+  RefPtr<Runnable> task = NS_NewRunnableFunction(
+    "layers::SharedSurfacesParent::RemoveSameProcess",
+    [id]() -> void {
+      Remove(id);
+    });
+
+  CompositorThreadHolder::Loop()->PostTask(task.forget());
+}
+
+/* static */ void
+SharedSurfacesParent::DestroyProcess(base::ProcessId aPid)
+{
+  if (!sInstance) {
+    return;
+  }
+
+  // Note that the destruction of a parent may not be cheap if it still has a
+  // lot of surfaces still bound that require unmapping.
+  for (auto i = sInstance->mSurfaces.Iter(); !i.Done(); i.Next()) {
+    if (i.Data()->GetCreatorPid() == aPid) {
+      wr::RenderThread::Get()->UnregisterExternalImage(i.Key());
+      i.Remove();
+    }
+  }
+}
+
+/* static */ void
+SharedSurfacesParent::Add(const wr::ExternalImageId& aId,
+                          const SurfaceDescriptorShared& aDesc,
+                          base::ProcessId aPid)
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  MOZ_ASSERT(aPid != base::GetCurrentProcId());
+  if (!sInstance) {
+    return;
+  }
+
+  // Note that the surface wrapper maps in the given handle as read only.
+  RefPtr<SourceSurfaceSharedDataWrapper> surface =
+    new SourceSurfaceSharedDataWrapper();
+  if (NS_WARN_IF(!surface->Init(aDesc.size(), aDesc.stride(),
+                                aDesc.format(), aDesc.handle(),
+                                aPid))) {
+    return;
+  }
+
+  uint64_t id = wr::AsUint64(aId);
+  MOZ_ASSERT(!sInstance->mSurfaces.Contains(id));
+
+  RefPtr<wr::RenderSharedSurfaceTextureHost> texture =
+    new wr::RenderSharedSurfaceTextureHost(surface);
+  wr::RenderThread::Get()->RegisterExternalImage(id, texture.forget());
+
+  sInstance->mSurfaces.Put(id, surface.forget());
+}
+
+/* static */ void
+SharedSurfacesParent::Remove(const wr::ExternalImageId& aId)
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  DebugOnly<bool> rv = Release(aId);
+  MOZ_ASSERT(rv);
+}
+
+} // namespace layers
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/layers/ipc/SharedSurfacesParent.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_GFX_SHAREDSURFACESPARENT_H
+#define MOZILLA_GFX_SHAREDSURFACESPARENT_H
+
+#include <stdint.h>                     // for uint32_t
+#include "mozilla/Attributes.h"         // for override
+#include "mozilla/StaticPtr.h"          // for StaticAutoPtr
+#include "mozilla/RefPtr.h"             // for already_AddRefed
+#include "mozilla/ipc/SharedMemory.h"   // for SharedMemory, etc
+#include "mozilla/gfx/2D.h"             // for SurfaceFormat
+#include "mozilla/gfx/Point.h"          // for IntSize
+#include "mozilla/layers/LayersSurfaces.h"    // for SurfaceDescriptorShared
+#include "mozilla/webrender/WebRenderTypes.h" // for wr::ExternalImageId
+#include "nsRefPtrHashtable.h"
+
+namespace mozilla {
+namespace gfx {
+class DataSourceSurface;
+class SourceSurfaceSharedData;
+class SourceSurfaceSharedDataWrapper;
+} // namespace gfx
+
+namespace layers {
+
+class SharedSurfacesChild;
+
+class SharedSurfacesParent final
+{
+public:
+  static void Initialize();
+  static void Shutdown();
+
+  static already_AddRefed<gfx::DataSourceSurface>
+  Acquire(const wr::ExternalImageId& aId);
+
+  static bool Release(const wr::ExternalImageId& aId);
+
+  static void Add(const wr::ExternalImageId& aId,
+                  const SurfaceDescriptorShared& aDesc,
+                  base::ProcessId aPid);
+
+  static void Remove(const wr::ExternalImageId& aId);
+
+  static void DestroyProcess(base::ProcessId aPid);
+
+  ~SharedSurfacesParent();
+
+private:
+  friend class SharedSurfacesChild;
+
+  SharedSurfacesParent();
+
+  static void AddSameProcess(const wr::ExternalImageId& aId,
+                             gfx::SourceSurfaceSharedData* aSurface);
+  static void RemoveSameProcess(const wr::ExternalImageId& aId);
+
+  static StaticAutoPtr<SharedSurfacesParent> sInstance;
+
+  nsRefPtrHashtable<nsUint64HashKey, gfx::SourceSurfaceSharedDataWrapper> mSurfaces;
+};
+
+} // namespace layers
+} // namespace mozilla
+
+#endif
--- a/gfx/layers/moz.build
+++ b/gfx/layers/moz.build
@@ -189,16 +189,18 @@ EXPORTS.mozilla.layers += [
     'ipc/LayersMessageUtils.h',
     'ipc/LayerTransactionChild.h',
     'ipc/LayerTransactionParent.h',
     'ipc/LayerTreeOwnerTracker.h',
     'ipc/RemoteContentController.h',
     'ipc/ShadowLayers.h',
     'ipc/SharedPlanarYCbCrImage.h',
     'ipc/SharedRGBImage.h',
+    'ipc/SharedSurfacesChild.h',
+    'ipc/SharedSurfacesParent.h',
     'ipc/SynchronousTask.h',
     'ipc/TextureForwarder.h',
     'ipc/UiCompositorControllerChild.h',
     'ipc/UiCompositorControllerMessageTypes.h',
     'ipc/UiCompositorControllerParent.h',
     'ipc/VideoBridgeChild.h',
     'ipc/VideoBridgeParent.h',
     'LayerAttributes.h',
@@ -411,16 +413,18 @@ UNIFIED_SOURCES += [
     'ipc/LayerAnimationUtils.cpp',
     'ipc/LayerTransactionChild.cpp',
     'ipc/LayerTransactionParent.cpp',
     'ipc/LayerTreeOwnerTracker.cpp',
     'ipc/RemoteContentController.cpp',
     'ipc/ShadowLayers.cpp',
     'ipc/SharedPlanarYCbCrImage.cpp',
     'ipc/SharedRGBImage.cpp',
+    'ipc/SharedSurfacesChild.cpp',
+    'ipc/SharedSurfacesParent.cpp',
     'ipc/UiCompositorControllerChild.cpp',
     'ipc/UiCompositorControllerParent.cpp',
     'ipc/VideoBridgeChild.cpp',
     'ipc/VideoBridgeParent.cpp',
     'LayerScope.cpp',
     'LayersHelpers.cpp',
     'LayersLogging.cpp',
     'LayerSorter.cpp',
--- a/gfx/layers/opengl/TextureHostOGL.cpp
+++ b/gfx/layers/opengl/TextureHostOGL.cpp
@@ -400,17 +400,17 @@ SurfaceTextureSource::GetTextureTransfor
   gfx::Matrix4x4 ret;
 
   // GetTransformMatrix() returns the transform set by the producer side of
   // the SurfaceTexture. We should ignore this if we know the transform should
   // be identity but the producer couldn't set it correctly, like is the
   // case for AndroidNativeWindowTextureData.
   if (!mIgnoreTransform) {
     const auto& surf = java::sdk::SurfaceTexture::LocalRef(java::sdk::SurfaceTexture::Ref::From(mSurfTex));
-    AndroidSurfaceTexture::GetTransformMatrix(surf, ret);
+    AndroidSurfaceTexture::GetTransformMatrix(surf, &ret);
   }
 
   return ret;
 }
 
 void
 SurfaceTextureSource::DeallocateDeviceData()
 {
@@ -496,16 +496,24 @@ SurfaceTextureHost::Lock()
                                               mSurfTex,
                                               mFormat,
                                               target,
                                               wrapMode,
                                               mSize,
                                               mIgnoreTransform);
   }
 
+  if (!mSurfTex->IsAttachedToGLContext((int64_t)gl)) {
+    GLuint texName;
+    gl->fGenTextures(1, &texName);
+    if (NS_FAILED(mSurfTex->AttachToGLContext((int64_t)gl, texName))) {
+      return false;
+    }
+  }
+
   return true;
 }
 
 void
 SurfaceTextureHost::SetTextureSourceProvider(TextureSourceProvider* aProvider)
 {
   if (mProvider != aProvider) {
     if (!aProvider || !aProvider->GetGLContext()) {
--- a/gfx/layers/opengl/TexturePoolOGL.cpp
+++ b/gfx/layers/opengl/TexturePoolOGL.cpp
@@ -10,20 +10,16 @@
 #include "mozilla/Logging.h"
 #include "mozilla/Monitor.h"            // for Monitor, MonitorAutoLock
 #include "mozilla/mozalloc.h"           // for operator delete, etc
 #include "mozilla/layers/CompositorThread.h"
 #include "nsDebug.h"                    // for NS_ASSERTION, NS_ERROR, etc
 #include "nsDeque.h"                    // for nsDeque
 #include "nsThreadUtils.h"
 
-#ifdef MOZ_WIDGET_ANDROID
-#include "GeneratedJNINatives.h"
-#endif
-
 static const unsigned int TEXTURE_POOL_SIZE = 10;
 static const unsigned int TEXTURE_REFILL_THRESHOLD = TEXTURE_POOL_SIZE / 2;
 
 static mozilla::LazyLogModule gTexturePoolLog("TexturePoolOGL");
 #define LOG(arg, ...) MOZ_LOG(gTexturePoolLog, mozilla::LogLevel::Debug, ("TexturePoolOGL::%s: " arg, __func__, ##__VA_ARGS__))
 
 namespace mozilla {
 namespace gl {
@@ -37,29 +33,16 @@ enum class PoolState : uint8_t {
   NOT_INITIALIZE,
   INITIALIZED,
   SHUTDOWN
 };
 static PoolState sPoolState = PoolState::NOT_INITIALIZE;
 
 static bool sHasPendingFillTask = false;
 
-#ifdef MOZ_WIDGET_ANDROID
-
-class GeckoSurfaceTextureSupport final
-    : public java::GeckoSurfaceTexture::Natives<GeckoSurfaceTextureSupport>
-{
-public:
-  static int32_t NativeAcquireTexture() {
-    return TexturePoolOGL::AcquireTexture();
-  }
-};
-
-#endif // MOZ_WIDGET_ANDROID
-
 void TexturePoolOGL::MaybeFillTextures()
 {
   if (sTextures->GetSize() < TEXTURE_REFILL_THRESHOLD &&
       !sHasPendingFillTask) {
     LOG("need to refill the texture pool.");
     sHasPendingFillTask = true;
     MessageLoop* loop = mozilla::layers::CompositorThreadHolder::Loop();
     MOZ_ASSERT(loop);
@@ -167,21 +150,16 @@ GLContext* TexturePoolOGL::GetGLContext(
 }
 
 void TexturePoolOGL::Init()
 {
   MOZ_ASSERT(sPoolState != PoolState::INITIALIZED);
   sMonitor = new Monitor("TexturePoolOGL.sMonitor");
   sTextures = new nsDeque();
 
-#ifdef MOZ_WIDGET_ANDROID
-  if (jni::IsAvailable()) {
-    GeckoSurfaceTextureSupport::Init();
-  }
-#endif
   sPoolState = PoolState::INITIALIZED;
 }
 
 void TexturePoolOGL::Shutdown()
 {
   MOZ_ASSERT(sPoolState == PoolState::INITIALIZED);
   sPoolState = PoolState::SHUTDOWN;
   delete sMonitor;
--- a/gfx/layers/wr/AsyncImagePipelineManager.cpp
+++ b/gfx/layers/wr/AsyncImagePipelineManager.cpp
@@ -147,35 +147,32 @@ AsyncImagePipelineManager::UpdateAsyncIm
 
 Maybe<TextureHost::ResourceUpdateOp>
 AsyncImagePipelineManager::UpdateImageKeys(wr::ResourceUpdateQueue& aResources,
                                            AsyncImagePipeline* aPipeline,
                                            nsTArray<wr::ImageKey>& aKeys)
 {
   MOZ_ASSERT(aKeys.IsEmpty());
   MOZ_ASSERT(aPipeline);
-  if (!aPipeline->mInitialised) {
-    return Nothing();
-  }
 
   TextureHost* texture = aPipeline->mImageHost->GetAsTextureHostForComposite();
   TextureHost* previousTexture = aPipeline->mCurrentTexture.get();
 
-  if (!aPipeline->mIsChanged && texture == previousTexture) {
+  if (texture == previousTexture) {
     // The texture has not changed, just reuse previous ImageKeys.
-    // No need to update DisplayList.
+    aKeys = aPipeline->mKeys;
     return Nothing();
   }
 
   if (!texture) {
     // We don't have a new texture, there isn't much we can do.
+    aKeys = aPipeline->mKeys;
     return Nothing();
   }
 
-  aPipeline->mIsChanged = false;
   aPipeline->mCurrentTexture = texture;
 
   WebRenderTextureHost* wrTexture = texture->AsWebRenderTextureHost();
 
   bool useExternalImage = !gfxEnv::EnableWebRenderRecording() && wrTexture;
   aPipeline->mUseExternalImage = useExternalImage;
 
   // The non-external image code path falls back to converting the texture into
@@ -264,26 +261,31 @@ AsyncImagePipelineManager::ApplyAsyncIma
   for (auto iter = mAsyncImagePipelines.Iter(); !iter.Done(); iter.Next()) {
     wr::ResourceUpdateQueue resourceUpdates;
     wr::PipelineId pipelineId = wr::AsPipelineId(iter.Key());
     AsyncImagePipeline* pipeline = iter.Data();
 
     nsTArray<wr::ImageKey> keys;
     auto op = UpdateImageKeys(resourceUpdates, pipeline, keys);
 
-    if (op != Some(TextureHost::ADD_IMAGE)) {
+    bool updateDisplayList = pipeline->mInitialised &&
+                             (pipeline->mIsChanged || op == Some(TextureHost::ADD_IMAGE)) &&
+                             !!pipeline->mCurrentTexture;
+
+    if (!updateDisplayList) {
       // We don't need to update the display list, either because we can't or because
       // the previous one is still up to date.
       // We may, however, have updated some resources.
       mApi->UpdatePipelineResources(resourceUpdates, pipelineId, epoch);
       if (pipeline->mCurrentTexture) {
         HoldExternalImage(pipelineId, epoch, pipeline->mCurrentTexture->AsWebRenderTextureHost());
       }
       continue;
     }
+    pipeline->mIsChanged = false;
 
     wr::LayoutSize contentSize { pipeline->mScBounds.Width(), pipeline->mScBounds.Height() };
     wr::DisplayListBuilder builder(pipelineId, contentSize);
 
     MOZ_ASSERT(!keys.IsEmpty());
     MOZ_ASSERT(pipeline->mCurrentTexture.get());
 
     float opacity = 1.0f;
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -19,16 +19,17 @@
 #include "mozilla/layers/APZCTreeManager.h"
 #include "mozilla/layers/Compositor.h"
 #include "mozilla/layers/CompositorBridgeParent.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/CompositorVsyncScheduler.h"
 #include "mozilla/layers/ImageBridgeParent.h"
 #include "mozilla/layers/ImageDataSerializer.h"
 #include "mozilla/layers/IpcResourceUpdateQueue.h"
+#include "mozilla/layers/SharedSurfacesParent.h"
 #include "mozilla/layers/TextureHost.h"
 #include "mozilla/layers/AsyncImagePipelineManager.h"
 #include "mozilla/layers/WebRenderImageHost.h"
 #include "mozilla/layers/WebRenderTextureHost.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/Unused.h"
 #include "mozilla/webrender/RenderThread.h"
 #include "mozilla/widget/CompositorWidget.h"
@@ -353,37 +354,51 @@ bool
 WebRenderBridgeParent::AddExternalImage(wr::ExternalImageId aExtId, wr::ImageKey aKey,
                                         wr::ResourceUpdateQueue& aResources)
 {
   Range<wr::ImageKey> keys(&aKey, 1);
   // Check if key is obsoleted.
   if (keys[0].mNamespace != mIdNamespace) {
     return true;
   }
-  MOZ_ASSERT(mExternalImageIds.Get(wr::AsUint64(aExtId)).get());
 
-  RefPtr<WebRenderImageHost> host = mExternalImageIds.Get(wr::AsUint64(aExtId));
-  if (!host) {
-    NS_ERROR("CompositableHost does not exist");
-    return false;
-  }
-  if (!gfxEnv::EnableWebRenderRecording()) {
-    TextureHost* texture = host->GetAsTextureHostForComposite();
-    if (!texture) {
-      NS_ERROR("TextureHost does not exist");
+  RefPtr<DataSourceSurface> dSurf = SharedSurfacesParent::Acquire(aExtId);
+  if (dSurf) {
+    if (!gfxEnv::EnableWebRenderRecording()) {
+      wr::ImageDescriptor descriptor(dSurf->GetSize(), dSurf->Stride(),
+                                     dSurf->GetFormat());
+      aResources.AddExternalImage(aKey, descriptor, aExtId,
+                                  wr::WrExternalImageBufferType::ExternalBuffer,
+                                  0);
+      return true;
+    }
+  } else {
+    MOZ_ASSERT(mExternalImageIds.Get(wr::AsUint64(aExtId)).get());
+
+    RefPtr<WebRenderImageHost> host = mExternalImageIds.Get(wr::AsUint64(aExtId));
+    if (!host) {
+      NS_ERROR("CompositableHost does not exist");
       return false;
     }
-    WebRenderTextureHost* wrTexture = texture->AsWebRenderTextureHost();
-    if (wrTexture) {
-      wrTexture->PushResourceUpdates(aResources, TextureHost::ADD_IMAGE, keys,
-                                     wrTexture->GetExternalImageKey());
-      return true;
+    if (!gfxEnv::EnableWebRenderRecording()) {
+      TextureHost* texture = host->GetAsTextureHostForComposite();
+      if (!texture) {
+        NS_ERROR("TextureHost does not exist");
+        return false;
+      }
+      WebRenderTextureHost* wrTexture = texture->AsWebRenderTextureHost();
+      if (wrTexture) {
+        wrTexture->PushResourceUpdates(aResources, TextureHost::ADD_IMAGE, keys,
+                                       wrTexture->GetExternalImageKey());
+        return true;
+      }
     }
+    dSurf = host->GetAsSurface();
   }
-  RefPtr<DataSourceSurface> dSurf = host->GetAsSurface();
+
   if (!dSurf) {
     NS_ERROR("TextureHost does not return DataSourceSurface");
     return false;
   }
 
   DataSourceSurface::MappedSurface map;
   if (!dSurf->Map(gfx::DataSourceSurface::MapType::READ, &map)) {
     NS_ERROR("DataSourceSurface failed to map");
@@ -839,16 +854,21 @@ WebRenderBridgeParent::RecvAddExternalIm
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvRemoveExternalImageId(const ExternalImageId& aImageId)
 {
   if (mDestroyed) {
     return IPC_OK();
   }
+
+  if (SharedSurfacesParent::Release(aImageId)) {
+    return IPC_OK();
+  }
+
   WebRenderImageHost* wrHost = mExternalImageIds.Get(wr::AsUint64(aImageId)).get();
   if (!wrHost) {
     return IPC_OK();
   }
 
   wrHost->ClearWrBridge();
   mExternalImageIds.Remove(wr::AsUint64(aImageId));
 
--- a/gfx/layers/wr/WebRenderUserData.cpp
+++ b/gfx/layers/wr/WebRenderUserData.cpp
@@ -6,16 +6,17 @@
 
 #include "WebRenderUserData.h"
 
 #include "mozilla/layers/ImageClient.h"
 #include "mozilla/layers/WebRenderBridgeChild.h"
 #include "mozilla/layers/WebRenderLayerManager.h"
 #include "mozilla/layers/WebRenderMessages.h"
 #include "mozilla/layers/IpcResourceUpdateQueue.h"
+#include "mozilla/layers/SharedSurfacesChild.h"
 #include "nsDisplayListInvalidation.h"
 #include "WebRenderCanvasRenderer.h"
 
 namespace mozilla {
 namespace layers {
 
 WebRenderUserData::WebRenderUserData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem)
   : mWRManager(aWRManager)
@@ -45,16 +46,17 @@ WebRenderUserData::RemoveFromTable()
 WebRenderBridgeChild*
 WebRenderUserData::WrBridge() const
 {
   return mWRManager->WrBridge();
 }
 
 WebRenderImageData::WebRenderImageData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem)
   : WebRenderUserData(aWRManager, aItem)
+  , mGeneration(0)
 {
 }
 
 WebRenderImageData::~WebRenderImageData()
 {
   ClearCachedResources();
 }
 
@@ -77,46 +79,65 @@ WebRenderImageData::ClearCachedResources
   }
 }
 
 Maybe<wr::ImageKey>
 WebRenderImageData::UpdateImageKey(ImageContainer* aContainer,
                                    wr::IpcResourceUpdateQueue& aResources,
                                    bool aForceUpdate)
 {
-  CreateImageClientIfNeeded();
-  CreateExternalImageIfNeeded();
+  MOZ_ASSERT(aContainer);
 
   if (mContainer != aContainer) {
     mContainer = aContainer;
   }
 
-  if (!mImageClient || !mExternalImageId) {
-    return Nothing();
-  }
+  wr::ExternalImageId externalId;
+  uint32_t generation;
+  nsresult rv = SharedSurfacesChild::Share(aContainer, externalId, generation);
+  if (NS_SUCCEEDED(rv)) {
+    if (mExternalImageId.isSome() && mExternalImageId.ref() == externalId) {
+      // The image container has the same surface as before, we can reuse the
+      // key if the generation matches and the caller allows us.
+      if (mKey && mGeneration == generation && !aForceUpdate) {
+        return mKey;
+      }
+    } else {
+      // The image container has a new surface, generate a new image key.
+      mExternalImageId = Some(externalId);
+    }
 
-  MOZ_ASSERT(mImageClient->AsImageClientSingle());
-  MOZ_ASSERT(aContainer);
-
-  ImageClientSingle* imageClient = mImageClient->AsImageClientSingle();
-  uint32_t oldCounter = imageClient->GetLastUpdateGenerationCounter();
+    mGeneration = generation;
+  } else if (rv == NS_ERROR_NOT_IMPLEMENTED) {
+    CreateImageClientIfNeeded();
+    CreateExternalImageIfNeeded();
 
-  bool ret = imageClient->UpdateImage(aContainer, /* unused */0);
-  if (!ret || imageClient->IsEmpty()) {
-    // Delete old key
-    if (mKey) {
-      mWRManager->AddImageKeyForDiscard(mKey.value());
-      mKey = Nothing();
+    if (!mImageClient || !mExternalImageId) {
+      return Nothing();
     }
-    return Nothing();
-  }
+
+    MOZ_ASSERT(mImageClient->AsImageClientSingle());
+
+    ImageClientSingle* imageClient = mImageClient->AsImageClientSingle();
+    uint32_t oldCounter = imageClient->GetLastUpdateGenerationCounter();
 
-  // Reuse old key if generation is not updated.
-  if (!aForceUpdate && oldCounter == imageClient->GetLastUpdateGenerationCounter() && mKey) {
-    return mKey;
+    bool ret = imageClient->UpdateImage(aContainer, /* unused */0);
+    if (!ret || imageClient->IsEmpty()) {
+      // Delete old key
+      if (mKey) {
+        mWRManager->AddImageKeyForDiscard(mKey.value());
+        mKey = Nothing();
+      }
+      return Nothing();
+    }
+
+    // Reuse old key if generation is not updated.
+    if (!aForceUpdate && oldCounter == imageClient->GetLastUpdateGenerationCounter() && mKey) {
+      return mKey;
+    }
   }
 
   // Delete old key, we are generating a new key.
   // TODO(nical): noooo... we need to reuse image keys.
   if (mKey) {
     mWRManager->AddImageKeyForDiscard(mKey.value());
   }
 
--- a/gfx/layers/wr/WebRenderUserData.h
+++ b/gfx/layers/wr/WebRenderUserData.h
@@ -103,16 +103,17 @@ public:
 protected:
   void CreateExternalImageIfNeeded();
 
   wr::MaybeExternalImageId mExternalImageId;
   Maybe<wr::ImageKey> mKey;
   RefPtr<ImageClient> mImageClient;
   Maybe<wr::PipelineId> mPipelineId;
   RefPtr<ImageContainer> mContainer;
+  uint32_t mGeneration;
 };
 
 class WebRenderFallbackData : public WebRenderImageData
 {
 public:
   explicit WebRenderFallbackData(WebRenderLayerManager* aWRManager, nsDisplayItem* aItem);
   virtual ~WebRenderFallbackData();
 
--- a/gfx/thebes/gfxCoreTextShaper.cpp
+++ b/gfx/thebes/gfxCoreTextShaper.cpp
@@ -522,18 +522,17 @@ gfxCoreTextShaper::SetGlyphsFromRun(gfxS
                                                      glyphs[glyphStart]);
         } else {
             // collect all glyphs in a list to be assigned to the first char;
             // there must be at least one in the clump, and we already measured its advance,
             // hence the placement of the loop-exit test and the measurement of the next glyph
             while (true) {
                 gfxTextRun::DetailedGlyph *details = detailedGlyphs.AppendElement();
                 details->mGlyphID = glyphs[glyphStart];
-                details->mXOffset = 0;
-                details->mYOffset = -positions[glyphStart].y * appUnitsPerDevUnit;
+                details->mOffset.y = -positions[glyphStart].y * appUnitsPerDevUnit;
                 details->mAdvance = advance;
                 if (++glyphStart >= glyphEnd) {
                    break;
                 }
                 if (glyphStart < numGlyphs-1) {
                     toNextGlyph = positions[glyphStart+1].x - positions[glyphStart].x;
                 } else {
                     toNextGlyph = positions[0].x + runWidth - positions[glyphStart].x;
--- a/gfx/thebes/gfxFT2Fonts.cpp
+++ b/gfx/thebes/gfxFT2Fonts.cpp
@@ -146,18 +146,16 @@ gfxFT2Font::AddRange(const char16_t *aTe
             // gid = 0 only happens when the glyph is missing from the font
             aShapedText->SetMissingGlyph(aOffset, ch, this);
         } else {
             gfxTextRun::DetailedGlyph details;
             details.mGlyphID = gid;
             NS_ASSERTION(details.mGlyphID == gid,
                          "Seriously weird glyph ID detected!");
             details.mAdvance = advance;
-            details.mXOffset = 0;
-            details.mYOffset = 0;
             gfxShapedText::CompressedGlyph g;
             g.SetComplex(charGlyphs[aOffset].IsClusterStart(), true, 1);
             aShapedText->SetGlyphs(aOffset, g, &details);
         }
     }
 }
 
 gfxFT2Font::gfxFT2Font(const RefPtr<UnscaledFontFreeType>& aUnscaledFont,
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -680,18 +680,16 @@ gfxShapedText::SetMissingGlyph(uint32_t 
         details->mAdvance = 0;
     } else {
         gfxFloat width =
             std::max(aFont->GetMetrics(gfxFont::eHorizontal).aveCharWidth,
                      gfxFloat(gfxFontMissingGlyphs::GetDesiredMinWidth(aChar,
                                 mAppUnitsPerDevUnit)));
         details->mAdvance = uint32_t(width * mAppUnitsPerDevUnit);
     }
-    details->mXOffset = 0;
-    details->mYOffset = 0;
     GetCharacterGlyphs()[aIndex].SetMissing(1);
 }
 
 bool
 gfxShapedText::FilterIfIgnorable(uint32_t aIndex, uint32_t aCh)
 {
     if (IsIgnorable(aCh)) {
         // There are a few default-ignorables of Letter category (currently,
@@ -702,18 +700,16 @@ gfxShapedText::FilterIfIgnorable(uint32_
         if (GetGenCategory(aCh) == nsUGenCategory::kLetter &&
             aIndex + 1 < GetLength() &&
             !GetCharacterGlyphs()[aIndex + 1].IsClusterStart()) {
             return false;
         }
         DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
         details->mGlyphID = aCh;
         details->mAdvance = 0;
-        details->mXOffset = 0;
-        details->mYOffset = 0;
         GetCharacterGlyphs()[aIndex].SetMissing(1);
         return true;
     }
     return false;
 }
 
 void
 gfxShapedText::AdjustAdvancesForSyntheticBold(float aSynBoldOffset,
@@ -728,17 +724,17 @@ gfxShapedText::AdjustAdvancesForSyntheti
              // simple glyphs ==> just add the advance
              int32_t advance = glyphData->GetSimpleAdvance() + synAppUnitOffset;
              if (CompressedGlyph::IsSimpleAdvance(advance)) {
                  glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph());
              } else {
                  // rare case, tested by making this the default
                  uint32_t glyphIndex = glyphData->GetSimpleGlyph();
                  glyphData->SetComplex(true, true, 1);
-                 DetailedGlyph detail = {glyphIndex, advance, 0, 0};
+                 DetailedGlyph detail = { glyphIndex, advance, gfx::Point() };
                  SetGlyphs(i, *glyphData, &detail);
              }
          } else {
              // complex glyphs ==> add offset at cluster/ligature boundaries
              uint32_t detailedLength = glyphData->GetGlyphCount();
              if (detailedLength) {
                  DetailedGlyph *details = GetDetailedGlyphs(i);
                  if (!details) {
@@ -1936,24 +1932,17 @@ gfxFont::DrawGlyphs(const gfxShapedText*
                     }
                     if (glyphData->IsMissing()) {
                         if (!DrawMissingGlyph(aBuffer.mRunParams,
                                               aBuffer.mFontParams,
                                               details, *aPt)) {
                             return false;
                         }
                     } else {
-                        gfx::Point glyphPt(*aPt);
-                        if (aBuffer.mFontParams.isVerticalFont) {
-                            glyphPt.x += details->mYOffset;
-                            glyphPt.y += details->mXOffset;
-                        } else {
-                            glyphPt.x += details->mXOffset;
-                            glyphPt.y += details->mYOffset;
-                        }
+                        gfx::Point glyphPt(*aPt + details->mOffset);
                         DrawOneGlyph<FC>(details->mGlyphID, glyphPt, aBuffer,
                                          &emittedGlyphs);
                     }
                     if (!aBuffer.mRunParams.isRTL) {
                         inlineCoord += advance;
                     }
                 }
             }
@@ -2556,58 +2545,50 @@ gfxFont::Measure(const gfxTextRun *aText
                     UnionRange(x + direction*extentsWidth, &advanceMin, &advanceMax);
                 } else {
                     gfxRect glyphRect;
                     if (!extents->GetTightGlyphExtentsAppUnits(this,
                             aRefDrawTarget, glyphIndex, &glyphRect)) {
                         glyphRect = gfxRect(0, metrics.mBoundingBox.Y(),
                             advance, metrics.mBoundingBox.Height());
                     }
-                    if (orientation == eVertical) {
-                        Swap(glyphRect.x, glyphRect.y);
-                        Swap(glyphRect.width, glyphRect.height);
+                    if (isRTL) {
+                        glyphRect.x -= advance;
                     }
-                    if (isRTL) {
-                        glyphRect -= gfxPoint(advance, 0);
-                    }
-                    glyphRect += gfxPoint(x, 0);
+                    glyphRect.x += x;
                     metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect);
                 }
             }
             x += direction*advance;
         } else {
             allGlyphsInvisible = false;
             uint32_t glyphCount = glyphData->GetGlyphCount();
             if (glyphCount > 0) {
                 const gfxTextRun::DetailedGlyph *details =
                     aTextRun->GetDetailedGlyphs(i);
                 NS_ASSERTION(details != nullptr,
                              "detailedGlyph record should not be missing!");
                 uint32_t j;
                 for (j = 0; j < glyphCount; ++j, ++details) {
                     uint32_t glyphIndex = details->mGlyphID;
-                    gfxPoint glyphPt(x + details->mXOffset, details->mYOffset);
                     double advance = details->mAdvance;
                     gfxRect glyphRect;
                     if (glyphData->IsMissing() || !extents ||
                         !extents->GetTightGlyphExtentsAppUnits(this,
                                 aRefDrawTarget, glyphIndex, &glyphRect)) {
                         // We might have failed to get glyph extents due to
                         // OOM or something
                         glyphRect = gfxRect(0, -metrics.mAscent,
                             advance, metrics.mAscent + metrics.mDescent);
                     }
-                    if (orientation == eVertical) {
-                        Swap(glyphRect.x, glyphRect.y);
-                        Swap(glyphRect.width, glyphRect.height);
+                    if (isRTL) {
+                        glyphRect.x -= advance;
                     }
-                    if (isRTL) {
-                        glyphRect -= gfxPoint(advance, 0);
-                    }
-                    glyphRect += glyphPt;
+                    glyphRect.x += x + details->mOffset.x;
+                    glyphRect.y += details->mOffset.y;
                     metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect);
                     x += direction*advance;
                 }
             }
         }
         // Every other glyph type is ignored
         if (aSpacing) {
             double space = aSpacing[i - aStart].mAfter;
--- a/gfx/thebes/gfxFont.h
+++ b/gfx/thebes/gfxFont.h
@@ -952,25 +952,27 @@ public:
     virtual const CompressedGlyph *GetCharacterGlyphs() const = 0;
     virtual CompressedGlyph *GetCharacterGlyphs() = 0;
 
     /**
      * When the glyphs for a character don't fit into a CompressedGlyph record
      * in SimpleGlyph format, we use an array of DetailedGlyphs instead.
      */
     struct DetailedGlyph {
-        /** The glyphID, or the Unicode character
-         * if this is a missing glyph */
+        // The glyphID, or the Unicode character if this is a missing glyph
         uint32_t mGlyphID;
-        /** The advance, x-offset and y-offset of the glyph, in appunits
-         *  mAdvance is in the text direction (RTL or LTR)
-         *  mXOffset is always from left to right
-         *  mYOffset is always from top to bottom */   
+        // The advance of the glyph, in appunits.
+        // mAdvance is in the text direction (RTL or LTR),
+        // and will normally be non-negative (although this is not guaranteed)
         int32_t  mAdvance;
-        float    mXOffset, mYOffset;
+        // The offset from the glyph's default position, in line-relative
+        // coordinates (so mOffset.x is an offset in the line-right direction,
+        // and mOffset.y is an offset in line-downwards direction).
+        // These values are in floating-point appUnits.
+        mozilla::gfx::Point mOffset;
     };
 
     void SetGlyphs(uint32_t aCharIndex, CompressedGlyph aGlyph,
                    const DetailedGlyph *aGlyphs);
 
     void SetMissingGlyph(uint32_t aIndex, uint32_t aChar, gfxFont *aFont);
 
     void SetIsSpace(uint32_t aIndex) {
@@ -1078,17 +1080,17 @@ protected:
     // it to record specific characters that layout may need to detect.
     void EnsureComplexGlyph(uint32_t aIndex, CompressedGlyph& aGlyph)
     {
         MOZ_ASSERT(GetCharacterGlyphs() + aIndex == &aGlyph);
         if (aGlyph.IsSimpleGlyph()) {
             DetailedGlyph details = {
                 aGlyph.GetSimpleGlyph(),
                 (int32_t) aGlyph.GetSimpleAdvance(),
-                0, 0
+                mozilla::gfx::Point()
             };
             SetGlyphs(aIndex, CompressedGlyph().SetComplex(true, true, 1),
                       &details);
         }
     }
 
     // For characters whose glyph data does not fit the "simple" glyph criteria
     // in CompressedGlyph, we use a sorted array to store the association
--- a/gfx/thebes/gfxGraphiteShaper.cpp
+++ b/gfx/thebes/gfxGraphiteShaper.cpp
@@ -334,27 +334,26 @@ gfxGraphiteShaper::SetGlyphsFromSegment(
             charGlyphs[offs].SetSimpleGlyph(appAdvance, gids[c.baseGlyph]);
         } else {
             // not a one-to-one mapping with simple metrics: use DetailedGlyph
             AutoTArray<gfxShapedText::DetailedGlyph,8> details;
             float clusterLoc;
             for (uint32_t j = c.baseGlyph; j < c.baseGlyph + c.nGlyphs; ++j) {
                 gfxShapedText::DetailedGlyph* d = details.AppendElement();
                 d->mGlyphID = gids[j];
-                d->mYOffset = roundY ? NSToIntRound(-yLocs[j]) * dev2appUnits
-                                     : -yLocs[j] * dev2appUnits;
+                d->mOffset.y = roundY ? NSToIntRound(-yLocs[j]) * dev2appUnits
+                                      : -yLocs[j] * dev2appUnits;
                 if (j == c.baseGlyph) {
-                    d->mXOffset = 0;
                     d->mAdvance = appAdvance;
                     clusterLoc = xLocs[j];
                 } else {
                     float dx = rtl ? (xLocs[j] - clusterLoc) :
                                      (xLocs[j] - clusterLoc - adv);
-                    d->mXOffset = roundX ? NSToIntRound(dx) * dev2appUnits
-                                         : dx * dev2appUnits;
+                    d->mOffset.x = roundX ? NSToIntRound(dx) * dev2appUnits
+                                          : dx * dev2appUnits;
                     d->mAdvance = 0;
                 }
             }
             gfxShapedText::CompressedGlyph g;
             g.SetComplex(charGlyphs[offs].IsClusterStart(),
                          true, details.Length());
             aShapedText->SetGlyphs(aOffset + offs, g, details.Elements());
         }
--- a/gfx/thebes/gfxHarfBuzzShaper.cpp
+++ b/gfx/thebes/gfxHarfBuzzShaper.cpp
@@ -1731,22 +1731,29 @@ gfxHarfBuzzShaper::SetGlyphsFromRun(gfxS
             // there must be at least one in the clump, and we already measured
             // its advance, hence the placement of the loop-exit test and the
             // measurement of the next glyph.
             while (1) {
                 gfxTextRun::DetailedGlyph* details =
                     detailedGlyphs.AppendElement();
                 details->mGlyphID = ginfo[glyphStart].codepoint;
 
-                details->mXOffset = iOffset;
                 details->mAdvance = advance;
 
-                details->mYOffset = bPos -
-                    (roundB ? appUnitsPerDevUnit * FixedToIntRound(b_offset)
-                     : floor(hb2appUnits * b_offset + 0.5));
+                if (aVertical) {
+                    details->mOffset.x = bPos -
+                        (roundB ? appUnitsPerDevUnit * FixedToIntRound(b_offset)
+                         : floor(hb2appUnits * b_offset + 0.5));
+                    details->mOffset.y = iOffset;
+                } else {
+                    details->mOffset.x = iOffset;
+                    details->mOffset.y = bPos -
+                        (roundB ? appUnitsPerDevUnit * FixedToIntRound(b_offset)
+                         : floor(hb2appUnits * b_offset + 0.5));
+                }
 
                 if (b_advance != 0) {
                     bPos -=
                         roundB ? appUnitsPerDevUnit * FixedToIntRound(b_advance)
                         : floor(hb2appUnits * b_advance + 0.5);
                 }
                 if (++glyphStart >= glyphEnd) {
                     break;
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -613,17 +613,17 @@ private:
   DECL_GFX_PREF(Once, "layers.mlgpu.enable-cpu-occlusion",     AdvancedLayersEnableCPUOcclusion, bool, true);
   DECL_GFX_PREF(Once, "layers.mlgpu.enable-depth-buffer",      AdvancedLayersEnableDepthBuffer, bool, false);
   DECL_GFX_PREF(Live, "layers.mlgpu.enable-invalidation",      AdvancedLayersUseInvalidation, bool, true);
   DECL_GFX_PREF(Once, "layers.mlgpu.enable-on-windows7",       AdvancedLayersEnableOnWindows7, bool, false);
   DECL_GFX_PREF(Once, "layers.mlgpu.enable-container-resizing", AdvancedLayersEnableContainerResizing, bool, true);
   DECL_GFX_PREF(Once, "layers.offmainthreadcomposition.force-disabled", LayersOffMainThreadCompositionForceDisabled, bool, false);
   DECL_GFX_PREF(Live, "layers.offmainthreadcomposition.frame-rate", LayersCompositionFrameRate, int32_t,-1);
   DECL_GFX_PREF(Live, "layers.omtp.force-sync",                LayersOMTPForceSync, bool, false);
-  DECL_GFX_PREF(Live, "layers.omtp.release-capture-on-main-thread", LayersOMTPReleaseCaptureOnMainThread, bool, true);
+  DECL_GFX_PREF(Live, "layers.omtp.release-capture-on-main-thread", LayersOMTPReleaseCaptureOnMainThread, bool, false);
   DECL_GFX_PREF(Live, "layers.orientation.sync.timeout",       OrientationSyncMillis, uint32_t, (uint32_t)0);
   DECL_GFX_PREF(Once, "layers.prefer-opengl",                  LayersPreferOpenGL, bool, false);
   DECL_GFX_PREF(Live, "layers.progressive-paint",              ProgressivePaint, bool, false);
   DECL_GFX_PREF(Live, "layers.shared-buffer-provider.enabled", PersistentBufferProviderSharedEnabled, bool, false);
   DECL_GFX_PREF(Live, "layers.single-tile.enabled",            LayersSingleTileEnabled, bool, true);
   DECL_GFX_PREF(Once, "layers.stereo-video.enabled",           StereoVideoEnabled, bool, false);
   DECL_GFX_PREF(Live, "layers.force-synchronous-resize",       LayersForceSynchronousResize, bool, true);
 
--- a/gfx/thebes/gfxTextRun.cpp
+++ b/gfx/thebes/gfxTextRun.cpp
@@ -2753,17 +2753,16 @@ gfxFontGroup::InitScriptRun(DrawTarget* 
                         if (gfxShapedText::CompressedGlyph::IsSimpleAdvance(advance)) {
                             aTextRun->GetCharacterGlyphs()[aOffset + index].
                                 SetSimpleGlyph(advance,
                                                mainFont->GetSpaceGlyph());
                         } else {
                             gfxTextRun::DetailedGlyph detailedGlyph;
                             detailedGlyph.mGlyphID = mainFont->GetSpaceGlyph();
                             detailedGlyph.mAdvance = advance;
-                            detailedGlyph.mXOffset = detailedGlyph.mYOffset = 0;
                             gfxShapedText::CompressedGlyph g;
                             g.SetComplex(true, true, 1);
                             aTextRun->SetGlyphs(aOffset + index,
                                                 g, &detailedGlyph);
                         }
                         continue;
                     }
                 }
--- a/gfx/webrender_bindings/RenderBufferTextureHost.cpp
+++ b/gfx/webrender_bindings/RenderBufferTextureHost.cpp
@@ -39,35 +39,35 @@ RenderBufferTextureHost::RenderBufferTex
   }
 }
 
 RenderBufferTextureHost::~RenderBufferTextureHost()
 {
   MOZ_COUNT_DTOR_INHERITED(RenderBufferTextureHost, RenderTextureHost);
 }
 
-bool
-RenderBufferTextureHost::Lock()
+wr::WrExternalImage
+RenderBufferTextureHost::Lock(uint8_t aChannelIndex, gl::GLContext* aGL)
 {
   if (!mLocked) {
     if (!GetBuffer()) {
       // We hit some problems to get the shmem.
-      return false;
+      return RawDataToWrExternalImage(nullptr, 0);
     }
     if (mFormat != gfx::SurfaceFormat::YUV) {
       mSurface = gfx::Factory::CreateWrappingDataSourceSurface(GetBuffer(),
                                                                layers::ImageDataSerializer::GetRGBStride(mDescriptor.get_RGBDescriptor()),
                                                                mSize,
                                                                mFormat);
       if (NS_WARN_IF(!mSurface)) {
-        return false;
+        return RawDataToWrExternalImage(nullptr, 0);
       }
       if (NS_WARN_IF(!mSurface->Map(gfx::DataSourceSurface::MapType::READ_WRITE, &mMap))) {
         mSurface = nullptr;
-        return false;
+        return RawDataToWrExternalImage(nullptr, 0);
       }
     } else {
       const layers::YCbCrDescriptor& desc = mDescriptor.get_YCbCrDescriptor();
 
       mYSurface = gfx::Factory::CreateWrappingDataSourceSurface(layers::ImageDataSerializer::GetYChannel(GetBuffer(), desc),
                                                                 desc.yStride(),
                                                                 desc.ySize(),
                                                                 gfx::SurfaceFormat::A8);
@@ -76,29 +76,30 @@ RenderBufferTextureHost::Lock()
                                                                  desc.cbCrSize(),
                                                                  gfx::SurfaceFormat::A8);
       mCrSurface = gfx::Factory::CreateWrappingDataSourceSurface(layers::ImageDataSerializer::GetCrChannel(GetBuffer(), desc),
                                                                  desc.cbCrStride(),
                                                                  desc.cbCrSize(),
                                                                  gfx::SurfaceFormat::A8);
       if (NS_WARN_IF(!mYSurface || !mCbSurface || !mCrSurface)) {
         mYSurface = mCbSurface = mCrSurface = nullptr;
-        return false;
+        return RawDataToWrExternalImage(nullptr, 0);
       }
       if (NS_WARN_IF(!mYSurface->Map(gfx::DataSourceSurface::MapType::READ_WRITE, &mYMap) ||
                      !mCbSurface->Map(gfx::DataSourceSurface::MapType::READ_WRITE, &mCbMap) ||
                      !mCrSurface->Map(gfx::DataSourceSurface::MapType::READ_WRITE, &mCrMap))) {
         mYSurface = mCbSurface = mCrSurface = nullptr;
-        return false;
+        return RawDataToWrExternalImage(nullptr, 0);
       }
     }
     mLocked = true;
   }
 
-  return true;
+  RenderBufferData data = GetBufferDataForRender(aChannelIndex);
+  return RawDataToWrExternalImage(data.mData, data.mBufferSize);
 }
 
 void
 RenderBufferTextureHost::Unlock()
 {
   if (mLocked) {
     if (mSurface) {
       mSurface->Unmap();
--- a/gfx/webrender_bindings/RenderBufferTextureHost.h
+++ b/gfx/webrender_bindings/RenderBufferTextureHost.h
@@ -13,23 +13,18 @@ namespace mozilla {
 namespace wr {
 
 class RenderBufferTextureHost final : public RenderTextureHost
 {
 public:
   RenderBufferTextureHost(uint8_t* aBuffer,
                           const layers::BufferDescriptor& aDescriptor);
 
-  virtual bool Lock() override;
-  virtual void Unlock() override;
-
-  virtual RenderBufferTextureHost* AsBufferTextureHost() override
-  {
-    return this;
-  }
+  wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL) override;
+  void Unlock() override;
 
   class RenderBufferData
   {
   public:
     RenderBufferData(uint8_t* aData, size_t aBufferSize)
       : mData(aData)
       , mBufferSize(aBufferSize)
     {
--- a/gfx/webrender_bindings/RenderD3D11TextureHostOGL.cpp
+++ b/gfx/webrender_bindings/RenderD3D11TextureHostOGL.cpp
@@ -55,27 +55,16 @@ RenderDXGITextureHostOGL::RenderDXGIText
 }
 
 RenderDXGITextureHostOGL::~RenderDXGITextureHostOGL()
 {
   MOZ_COUNT_DTOR_INHERITED(RenderDXGITextureHostOGL, RenderTextureHostOGL);
   DeleteTextureHandle();
 }
 
-void
-RenderDXGITextureHostOGL::SetGLContext(gl::GLContext* aContext)
-{
-  if (mGL.get() != aContext) {
-    // Release the texture handle in the previous gl context.
-    DeleteTextureHandle();
-    mGL = aContext;
-    mGL->MakeCurrent();
-  }
-}
-
 bool
 RenderDXGITextureHostOGL::EnsureLockable()
 {
   if (mTextureHandle[0]) {
     return true;
   }
 
   const auto& egl = &gl::sEGLLibrary;
@@ -178,37 +167,44 @@ RenderDXGITextureHostOGL::EnsureLockable
     // Now, we could get the NV12 gl handle from the stream.
     egl->fStreamConsumerAcquireKHR(egl->Display(), mStream);
     MOZ_ASSERT(egl->fGetError() == LOCAL_EGL_SUCCESS);
   }
 
   return true;
 }
 
-bool
-RenderDXGITextureHostOGL::Lock()
+wr::WrExternalImage
+RenderDXGITextureHostOGL::Lock(uint8_t aChannelIndex, gl::GLContext* aGL)
 {
-  if (!EnsureLockable()) {
-    return false;
+  if (mGL.get() != aGL) {
+    // Release the texture handle in the previous gl context.
+    DeleteTextureHandle();
+    mGL = aGL;
+    mGL->MakeCurrent();
   }
 
-  if (mLocked) {
-    return true;
+  if (!EnsureLockable()) {
+    return NativeTextureToWrExternalImage(0, 0, 0, 0, 0);
   }
 
-  if (mKeyedMutex) {
-    HRESULT hr = mKeyedMutex->AcquireSync(0, 100);
-    if (hr != S_OK) {
-      gfxCriticalError() << "RenderDXGITextureHostOGL AcquireSync timeout, hr=" << gfx::hexa(hr);
-      return false;
+  if (!mLocked) {
+    if (mKeyedMutex) {
+      HRESULT hr = mKeyedMutex->AcquireSync(0, 100);
+      if (hr != S_OK) {
+        gfxCriticalError() << "RenderDXGITextureHostOGL AcquireSync timeout, hr=" << gfx::hexa(hr);
+        return NativeTextureToWrExternalImage(0, 0, 0, 0, 0);
+      }
     }
+    mLocked = true;
   }
-  mLocked = true;
 
-  return true;
+  gfx::IntSize size = GetSize(aChannelIndex);
+  return NativeTextureToWrExternalImage(GetGLHandle(aChannelIndex), 0, 0,
+                                        size.width, size.height);
 }
 
 void
 RenderDXGITextureHostOGL::Unlock()
 {
   if (mLocked) {
     if (mKeyedMutex) {
       mKeyedMutex->ReleaseSync(0);
@@ -285,27 +281,16 @@ RenderDXGIYCbCrTextureHostOGL::RenderDXG
 }
 
 RenderDXGIYCbCrTextureHostOGL::~RenderDXGIYCbCrTextureHostOGL()
 {
   MOZ_COUNT_CTOR_INHERITED(RenderDXGIYCbCrTextureHostOGL, RenderTextureHostOGL);
   DeleteTextureHandle();
 }
 
-void
-RenderDXGIYCbCrTextureHostOGL::SetGLContext(gl::GLContext* aContext)
-{
-  if (mGL.get() != aContext) {
-    // Release the texture handle in the previous gl context.
-    DeleteTextureHandle();
-    mGL = aContext;
-    mGL->MakeCurrent();
-  }
-}
-
 bool
 RenderDXGIYCbCrTextureHostOGL::EnsureLockable()
 {
   if (mTextureHandles[0]) {
     return true;
   }
 
   const auto& egl = &gl::sEGLLibrary;
@@ -363,39 +348,46 @@ RenderDXGIYCbCrTextureHostOGL::EnsureLoc
     // Now, we could get the R8 gl handle from the stream.
     egl->fStreamConsumerAcquireKHR(egl->Display(), mStreams[i]);
     MOZ_ASSERT(egl->fGetError() == LOCAL_EGL_SUCCESS);
   }
 
   return true;
 }
 
-bool
-RenderDXGIYCbCrTextureHostOGL::Lock()
+wr::WrExternalImage
+RenderDXGIYCbCrTextureHostOGL::Lock(uint8_t aChannelIndex, gl::GLContext* aGL)
 {
-  if (!EnsureLockable()) {
-    return false;
+  if (mGL.get() != aGL) {
+    // Release the texture handle in the previous gl context.
+    DeleteTextureHandle();
+    mGL = aGL;
+    mGL->MakeCurrent();
   }
 
-  if (mLocked) {
-    return true;
+  if (!EnsureLockable()) {
+    return NativeTextureToWrExternalImage(0, 0, 0, 0, 0);
   }
 
-  if (mKeyedMutexs[0]) {
-    for (const auto& mutex : mKeyedMutexs) {
-      HRESULT hr = mutex->AcquireSync(0, 100);
-      if (hr != S_OK) {
-        gfxCriticalError() << "RenderDXGIYCbCrTextureHostOGL AcquireSync timeout, hr=" << gfx::hexa(hr);
-        return false;
+  if (!mLocked) {
+    if (mKeyedMutexs[0]) {
+      for (const auto& mutex : mKeyedMutexs) {
+        HRESULT hr = mutex->AcquireSync(0, 100);
+        if (hr != S_OK) {
+          gfxCriticalError() << "RenderDXGIYCbCrTextureHostOGL AcquireSync timeout, hr=" << gfx::hexa(hr);
+          return NativeTextureToWrExternalImage(0, 0, 0, 0, 0);
+        }
       }
     }
+    mLocked = true;
   }
-  mLocked = true;
 
-  return true;
+  gfx::IntSize size = GetSize(aChannelIndex);
+  return NativeTextureToWrExternalImage(GetGLHandle(aChannelIndex), 0, 0,
+                                        size.width, size.height);
 }
 
 void
 RenderDXGIYCbCrTextureHostOGL::Unlock()
 {
   if (mLocked) {
     if (mKeyedMutexs[0]) {
       for (const auto& mutex : mKeyedMutexs) {
--- a/gfx/webrender_bindings/RenderD3D11TextureHostOGL.h
+++ b/gfx/webrender_bindings/RenderD3D11TextureHostOGL.h
@@ -19,20 +19,18 @@ namespace wr {
 
 class RenderDXGITextureHostOGL final : public RenderTextureHostOGL
 {
 public:
   explicit RenderDXGITextureHostOGL(WindowsHandle aHandle,
                                     gfx::SurfaceFormat aFormat,
                                     gfx::IntSize aSize);
 
-  virtual void SetGLContext(gl::GLContext* aContext) override;
-
-  virtual bool Lock() override;
-  virtual void Unlock() override;
+  wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL) override;
+  void Unlock() override;
 
   virtual gfx::IntSize GetSize(uint8_t aChannelIndex) const;
   virtual GLuint GetGLHandle(uint8_t aChannelIndex) const;
 
 private:
   virtual ~RenderDXGITextureHostOGL();
 
   bool EnsureLockable();
@@ -59,19 +57,17 @@ private:
 };
 
 class RenderDXGIYCbCrTextureHostOGL final : public RenderTextureHostOGL
 {
 public:
   explicit RenderDXGIYCbCrTextureHostOGL(WindowsHandle (&aHandles)[3],
                                          gfx::IntSize aSize);
 
-  virtual void SetGLContext(gl::GLContext* aContext) override;
-
-  virtual bool Lock() override;
+  wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL) override;
   virtual void Unlock() override;
 
   virtual gfx::IntSize GetSize(uint8_t aChannelIndex) const;
   virtual GLuint GetGLHandle(uint8_t aChannelIndex) const;
 
 private:
   virtual ~RenderDXGIYCbCrTextureHostOGL();
 
--- a/gfx/webrender_bindings/RenderMacIOSurfaceTextureHostOGL.cpp
+++ b/gfx/webrender_bindings/RenderMacIOSurfaceTextureHostOGL.cpp
@@ -67,55 +67,53 @@ RenderMacIOSurfaceTextureHostOGL::GetSiz
 
   if (!mSurface) {
     return gfx::IntSize();
   }
   return gfx::IntSize(mSurface->GetDevicePixelWidth(aChannelIndex),
                       mSurface->GetDevicePixelHeight(aChannelIndex));
 }
 
-bool
-RenderMacIOSurfaceTextureHostOGL::Lock()
+wr::WrExternalImage
+RenderMacIOSurfaceTextureHostOGL::Lock(uint8_t aChannelIndex, gl::GLContext* aGL)
 {
+  if (mGL.get() != aGL) {
+    // release the texture handle in the previous gl context
+    DeleteTextureHandle();
+    mGL = aGL;
+    mGL->MakeCurrent();
+  }
+
   if (!mSurface || !mGL || !mGL->MakeCurrent()) {
-    return false;
+    return NativeTextureToWrExternalImage(0, 0, 0, 0, 0);
   }
 
   if (!mTextureHandles[0]) {
     MOZ_ASSERT(gl::GLContextCGL::Cast(mGL.get())->GetCGLContext());
 
     // The result of GetPlaneCount() is 0 for single plane format, but it will
     // be 2 if the format has 2 planar data.
     CreateTextureForPlane(0, mGL, mSurface, &(mTextureHandles[0]));
     for (size_t i = 1; i < mSurface->GetPlaneCount(); ++i) {
       CreateTextureForPlane(i, mGL, mSurface, &(mTextureHandles[i]));
     }
   }
 
-  return true;
+  gfx::IntSize size = GetSize(aChannelIndex);
+  return NativeTextureToWrExternalImage(GetGLHandle(aChannelIndex), 0, 0,
+                                        size.width, size.height);
 }
 
 void
 RenderMacIOSurfaceTextureHostOGL::Unlock()
 {
 
 }
 
 void
-RenderMacIOSurfaceTextureHostOGL::SetGLContext(gl::GLContext* aContext)
-{
-  if (mGL.get() != aContext) {
-    // release the texture handle in the previous gl context
-    DeleteTextureHandle();
-    mGL = aContext;
-    mGL->MakeCurrent();
-  }
-}
-
-void
 RenderMacIOSurfaceTextureHostOGL::DeleteTextureHandle()
 {
   if (mTextureHandles[0] != 0 && mGL && mGL->MakeCurrent()) {
     // Calling glDeleteTextures on 0 isn't an error. So, just make them a single
     // call.
     mGL->fDeleteTextures(3, mTextureHandles);
     for (size_t i = 0; i < 3; ++i) {
       mTextureHandles[i] = 0;
--- a/gfx/webrender_bindings/RenderMacIOSurfaceTextureHostOGL.h
+++ b/gfx/webrender_bindings/RenderMacIOSurfaceTextureHostOGL.h
@@ -19,20 +19,18 @@ class SurfaceDescriptorMacIOSurface;
 
 namespace wr {
 
 class RenderMacIOSurfaceTextureHostOGL final : public RenderTextureHostOGL
 {
 public:
   explicit RenderMacIOSurfaceTextureHostOGL(MacIOSurface* aSurface);
 
-  virtual bool Lock() override;
-  virtual void Unlock() override;
-
-  virtual void SetGLContext(gl::GLContext* aContext) override;
+  wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL) override;
+  void Unlock() override;
 
   virtual gfx::IntSize GetSize(uint8_t aChannelIndex) const override;
   virtual GLuint GetGLHandle(uint8_t aChannelIndex) const override;
 
 private:
   virtual ~RenderMacIOSurfaceTextureHostOGL();
   void DeleteTextureHandle();
 
new file mode 100644
--- /dev/null
+++ b/gfx/webrender_bindings/RenderSharedSurfaceTextureHost.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "RenderSharedSurfaceTextureHost.h"
+
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/layers/ImageDataSerializer.h"
+#include "mozilla/layers/SourceSurfaceSharedData.h"
+
+namespace mozilla {
+namespace wr {
+
+RenderSharedSurfaceTextureHost::RenderSharedSurfaceTextureHost(gfx::SourceSurfaceSharedDataWrapper* aSurface)
+  : mSurface(aSurface)
+  , mLocked(false)
+{
+  MOZ_COUNT_CTOR_INHERITED(RenderSharedSurfaceTextureHost, RenderTextureHost);
+  MOZ_ASSERT(aSurface);
+}
+
+RenderSharedSurfaceTextureHost::~RenderSharedSurfaceTextureHost()
+{
+  MOZ_COUNT_DTOR_INHERITED(RenderSharedSurfaceTextureHost, RenderTextureHost);
+}
+
+wr::WrExternalImage
+RenderSharedSurfaceTextureHost::Lock(uint8_t aChannelIndex, gl::GLContext* aGL)
+{
+  if (!mLocked) {
+    if (NS_WARN_IF(!mSurface->Map(gfx::DataSourceSurface::MapType::READ_WRITE,
+                                  &mMap))) {
+      return RawDataToWrExternalImage(nullptr, 0);
+    }
+    mLocked = true;
+  }
+
+  return RawDataToWrExternalImage(mMap.mData,
+                                  mMap.mStride * mSurface->GetSize().height);
+}
+
+void
+RenderSharedSurfaceTextureHost::Unlock()
+{
+  if (mLocked) {
+    mSurface->Unmap();
+    mLocked = false;
+  }
+}
+
+} // namespace wr
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/gfx/webrender_bindings/RenderSharedSurfaceTextureHost.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_GFX_RENDERSHAREDSURFACETEXTUREHOST_H
+#define MOZILLA_GFX_RENDERSHAREDSURFACETEXTUREHOST_H
+
+#include "RenderTextureHost.h"
+
+namespace mozilla {
+namespace gfx {
+class SourceSurfaceSharedDataWrapper;
+}
+
+namespace wr {
+
+/**
+ * This class allows for surfaces managed by SharedSurfacesParent to be inserted
+ * into the render texture cache by wrapping an existing surface wrapper. These
+ * surfaces are backed by BGRA/X shared memory buffers.
+ */
+class RenderSharedSurfaceTextureHost final : public RenderTextureHost
+{
+public:
+  explicit RenderSharedSurfaceTextureHost(gfx::SourceSurfaceSharedDataWrapper* aSurface);
+
+  wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL) override;
+  void Unlock() override;
+
+private:
+  ~RenderSharedSurfaceTextureHost() override;
+
+  RefPtr<gfx::SourceSurfaceSharedDataWrapper> mSurface;
+  gfx::DataSourceSurface::MappedSurface mMap;
+  bool mLocked;
+};
+
+} // namespace wr
+} // namespace mozilla
+
+#endif // MOZILLA_GFX_RENDERSHAREDSURFACETEXTUREHOST_H
--- a/gfx/webrender_bindings/RenderTextureHost.h
+++ b/gfx/webrender_bindings/RenderTextureHost.h
@@ -8,34 +8,36 @@
 #define MOZILLA_GFX_RENDERTEXTUREHOST_H
 
 #include "nsISupportsImpl.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/layers/LayersSurfaces.h"
 #include "mozilla/RefPtr.h"
 
 namespace mozilla {
+
+namespace gl {
+class GLContext;
+}
+
 namespace wr {
 
 class RenderBufferTextureHost;
 class RenderTextureHostOGL;
 
 class RenderTextureHost
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RenderTextureHost)
 
 public:
   RenderTextureHost();
 
-  virtual bool Lock() = 0;
+  virtual wr::WrExternalImage Lock(uint8_t aChannelIndex, gl::GLContext* aGL) = 0;
   virtual void Unlock() = 0;
 
-  virtual RenderBufferTextureHost* AsBufferTextureHost() { return nullptr; }
-  virtual RenderTextureHostOGL* AsTextureHostOGL() { return nullptr; }
-
 protected:
   virtual ~RenderTextureHost();
 };
 
 } // namespace wr
 } // namespace mozilla
 
 #endif // MOZILLA_GFX_RENDERTEXTUREHOST_H
--- a/gfx/webrender_bindings/RenderTextureHostOGL.h
+++ b/gfx/webrender_bindings/RenderTextureHostOGL.h
@@ -17,23 +17,19 @@ class GLContext;
 
 namespace wr {
 
 class RenderTextureHostOGL : public RenderTextureHost
 {
 public:
   RenderTextureHostOGL();
 
-  virtual void SetGLContext(gl::GLContext* aContext) = 0;
-
   virtual gfx::IntSize GetSize(uint8_t aChannelIndex) const = 0;
   virtual GLuint GetGLHandle(uint8_t aChannelIndex) const = 0;
 
-  virtual RenderTextureHostOGL* AsTextureHostOGL()  override { return this; }
-
 protected:
   virtual ~RenderTextureHostOGL();
 };
 
 } // namespace wr
 } // namespace mozilla
 
 #endif // MOZILLA_GFX_RENDERTEXTUREHOSTOGL_H
--- a/gfx/webrender_bindings/RendererOGL.cpp
+++ b/gfx/webrender_bindings/RendererOGL.cpp
@@ -18,47 +18,18 @@
 
 namespace mozilla {
 namespace wr {
 
 wr::WrExternalImage LockExternalImage(void* aObj, wr::WrExternalImageId aId, uint8_t aChannelIndex)
 {
   RendererOGL* renderer = reinterpret_cast<RendererOGL*>(aObj);
   RenderTextureHost* texture = renderer->GetRenderTexture(aId);
-
-  if (texture->AsBufferTextureHost()) {
-    RenderBufferTextureHost* bufferTexture = texture->AsBufferTextureHost();
-    MOZ_ASSERT(bufferTexture);
-
-    if (bufferTexture->Lock()) {
-      RenderBufferTextureHost::RenderBufferData data =
-          bufferTexture->GetBufferDataForRender(aChannelIndex);
-
-      return RawDataToWrExternalImage(data.mData, data.mBufferSize);
-    } else {
-      return RawDataToWrExternalImage(nullptr, 0);
-    }
-  } else {
-    // texture handle case
-    RenderTextureHostOGL* textureOGL = texture->AsTextureHostOGL();
-    MOZ_ASSERT(textureOGL);
-
-    textureOGL->SetGLContext(renderer->mGL);
-    gfx::IntSize size = textureOGL->GetSize(aChannelIndex);
-    if (textureOGL->Lock()) {
-      return NativeTextureToWrExternalImage(textureOGL->GetGLHandle(aChannelIndex),
-                                            0, 0,
-                                            size.width, size.height);
-    } else {
-      // Just use 0 for the gl handle if the lock() was failed.
-      return NativeTextureToWrExternalImage(0,
-                                            0, 0,
-                                            size.width, size.height);
-    }
-  }
+  MOZ_ASSERT(texture);
+  return texture->Lock(aChannelIndex, renderer->mGL);
 }
 
 void UnlockExternalImage(void* aObj, wr::WrExternalImageId aId, uint8_t aChannelIndex)
 {
   RendererOGL* renderer = reinterpret_cast<RendererOGL*>(aObj);
   RenderTextureHost* texture = renderer->GetRenderTexture(aId);
   MOZ_ASSERT(texture);
   texture->Unlock();
--- a/gfx/webrender_bindings/moz.build
+++ b/gfx/webrender_bindings/moz.build
@@ -5,29 +5,31 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 with Files('**'):
     BUG_COMPONENT = ('Core', 'Graphics: WebRender')
 
 EXPORTS.mozilla.webrender += [
     'RenderBufferTextureHost.h',
     'RendererOGL.h',
+    'RenderSharedSurfaceTextureHost.h',
     'RenderTextureHost.h',
     'RenderTextureHostOGL.h',
     'RenderThread.h',
     'webrender_ffi.h',
     'webrender_ffi_generated.h',
     'WebRenderAPI.h',
     'WebRenderTypes.h',
 ]
 
 UNIFIED_SOURCES += [
     'Moz2DImageRenderer.cpp',
     'RenderBufferTextureHost.cpp',
     'RendererOGL.cpp',
+    'RenderSharedSurfaceTextureHost.cpp',
     'RenderTextureHost.cpp',
     'RenderTextureHostOGL.cpp',
     'RenderThread.cpp',
     'WebRenderAPI.cpp',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     EXPORTS.mozilla.webrender += [
--- a/ipc/glue/IPCMessageUtils.h
+++ b/ipc/glue/IPCMessageUtils.h
@@ -421,19 +421,25 @@ struct ParamTraits<nsAString>
       aResult->SetIsVoid(true);
       return true;
     }
 
     uint32_t length;
     if (!ReadParam(aMsg, aIter, &length)) {
       return false;
     }
+
     aResult->SetLength(length);
 
-    return aMsg->ReadBytesInto(aIter, aResult->BeginWriting(), length * sizeof(char16_t));
+    mozilla::CheckedInt<uint32_t> byteLength = mozilla::CheckedInt<uint32_t>(length) * sizeof(char16_t);
+    if (!byteLength.isValid()) {
+      return false;
+    }
+
+    return aMsg->ReadBytesInto(aIter, aResult->BeginWriting(), byteLength.value());
   }
 
   static void Log(const paramType& aParam, std::wstring* aLog)
   {
     if (aParam.IsVoid())
       aLog->append(L"(NULL)");
     else {
 #ifdef WCHAR_T_IS_UTF16
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -652,17 +652,17 @@ js::Nursery::collect(JS::gcreason::Reaso
     const float promotionRate = calcPromotionRate(&validPromotionRate);
     uint32_t pretenureCount = 0;
     if (validPromotionRate) {
         if (promotionRate > 0.8 || IsFullStoreBufferReason(reason)) {
             JSContext* cx = TlsContext.get();
             for (auto& entry : tenureCounts.entries) {
                 if (entry.count >= 3000) {
                     ObjectGroup* group = entry.group;
-                    if (group->canPreTenure()) {
+                    if (group->canPreTenure() && group->zone()->group()->canEnterWithoutYielding(cx)) {
                         AutoCompartment ac(cx, group);
                         group->setShouldPreTenure(cx);
                         pretenureCount++;
                     }
                 }
             }
         }
     }
--- a/js/src/gc/ZoneGroup.cpp
+++ b/js/src/gc/ZoneGroup.cpp
@@ -90,16 +90,22 @@ ZoneGroup::leave()
 {
     MOZ_ASSERT(ownedByCurrentThread());
     MOZ_ASSERT(enterCount);
     if (--enterCount == 0)
         ownerContext_ = CooperatingContext(nullptr);
 }
 
 bool
+ZoneGroup::canEnterWithoutYielding(JSContext* cx)
+{
+    return ownerContext().context() == cx || ownerContext().context() == nullptr;
+}
+
+bool
 ZoneGroup::ownedByCurrentThread()
 {
     MOZ_ASSERT(TlsContext.get());
     return ownerContext().context() == TlsContext.get();
 }
 
 ZoneGroup::IonBuilderList&
 ZoneGroup::ionLazyLinkList()
--- a/js/src/gc/ZoneGroup.h
+++ b/js/src/gc/ZoneGroup.h
@@ -44,16 +44,17 @@ class ZoneGroup
     UnprotectedData<bool> useExclusiveLocking_;
 
   public:
     CooperatingContext& ownerContext() { return ownerContext_.ref(); }
     void* addressOfOwnerContext() { return &ownerContext_.ref().cx; }
 
     void enter(JSContext* cx);
     void leave();
+    bool canEnterWithoutYielding(JSContext* cx);
     bool ownedByCurrentThread();
 
     // All zones in the group.
   private:
     ZoneGroupOrGCTaskData<ZoneVector> zones_;
   public:
     ZoneVector& zones() { return zones_.ref(); }
 
--- a/js/src/tests/user.js
+++ b/js/src/tests/user.js
@@ -17,17 +17,17 @@ user_pref("browser.warnOnQuit", false);
 user_pref("browser.cache.check_doc_frequency", 1);
 user_pref("extensions.checkCompatibility", false);
 user_pref("extensions.checkUpdateSecurity", false);
 user_pref("browser.EULA.override", true);
 user_pref("javascript.options.strict", false);
 user_pref("javascript.options.werror", false);
 user_pref("toolkit.startup.max_resumed_crashes", -1);
 user_pref("security.turn_off_all_security_so_that_viruses_can_take_over_this_computer", true);
-user_pref("toolkit.telemetry.enabled", false);
+user_pref("datareporting.healthreport.uploadEnabled", false);
 user_pref("browser.safebrowsing.phishing.enabled", false);
 user_pref("browser.safebrowsing.malware.enabled", false);
 user_pref("browser.safebrowsing.blockedURIs.enabled", false);
 user_pref("browser.safebrowsing.passwords.enabled", false);
 user_pref("plugins.flashBlock.enabled", false);
 user_pref("privacy.trackingprotection.annotate_channels", false);
 user_pref("privacy.trackingprotection.enabled", false);
 user_pref("privacy.trackingprotection.pbmode.enabled", false);
--- a/layout/base/tests/mochitest.ini
+++ b/layout/base/tests/mochitest.ini
@@ -23,17 +23,17 @@ support-files = border_radius_hit_testin
 [test_bug369950.html]
 skip-if = true # Bug 492575
 support-files = bug369950-subframe.xml
 [test_bug370436.html]
 skip-if = toolkit == 'android' # Bug 1355815
 [test_bug386575.xhtml]
 [test_bug388019.html]
 [test_bug394057.html]
-skip-if = toolkit == 'android' # Bug 1355817
+skip-if = toolkit == 'android' || os == "linux" # Bug 1355817, linux Bug 1369411
 [test_bug399284.html]
 [test_bug399951.html]
 [test_bug404209.xhtml]
 [test_bug416896.html]
 [test_bug423523.html]
 [test_bug435293-interaction.html]
 [test_bug435293-scale.html]
 [test_bug435293-skew.html]
--- a/layout/generic/nsTextRunTransformations.cpp
+++ b/layout/generic/nsTextRunTransformations.cpp
@@ -156,18 +156,16 @@ MergeCharactersInTextRun(gfxTextRun* aDe
     uint32_t stringEnd = iter.GetStringEnd();
     for (uint32_t k = iter.GetStringStart(); k < stringEnd; ++k) {
       const gfxTextRun::CompressedGlyph g = srcGlyphs[k];
       if (g.IsSimpleGlyph()) {
         if (!anyMissing) {
           gfxTextRun::DetailedGlyph details;
           details.mGlyphID = g.GetSimpleGlyph();
           details.mAdvance = g.GetSimpleAdvance();
-          details.mXOffset = 0;
-          details.mYOffset = 0;
           glyphs.AppendElement(details);
         }
       } else {
         if (g.IsMissing()) {
           anyMissing = true;
           glyphs.Clear();
         }
         if (g.GetGlyphCount() > 0) {
--- a/layout/mathml/nsMathMLChar.cpp
+++ b/layout/mathml/nsMathMLChar.cpp
@@ -583,17 +583,16 @@ nsOpenTypeTable::MakeTextRun(DrawTarget*
                               // We don't care about CSS writing mode here;
                               // math runs are assumed to be horizontal.
   gfxTextRun::DetailedGlyph detailedGlyph;
   detailedGlyph.mGlyphID = aGlyph.glyphID;
   detailedGlyph.mAdvance =
     NSToCoordRound(aAppUnitsPerDevPixel *
                    aFontGroup->GetFirstValidFont()->
                    GetGlyphHAdvance(aDrawTarget, aGlyph.glyphID));
-  detailedGlyph.mXOffset = detailedGlyph.mYOffset = 0;
   gfxShapedText::CompressedGlyph g;
   g.SetComplex(true, true, 1);
   textRun->SetGlyphs(0, g, &detailedGlyph);
 
   return textRun.forget();
 }
 
 // -----------------------------------------------------------------------------
--- a/layout/painting/RetainedDisplayListBuilder.cpp
+++ b/layout/painting/RetainedDisplayListBuilder.cpp
@@ -388,16 +388,24 @@ RetainedDisplayListBuilder::MergeDisplay
         }
         newItem->Destroy(&mBuilder);
       } else {
         // The new item has a matching counterpart in the old list, so copy all valid
         // items from the old list into the merged list until we get to the matched item.
         nsDisplayItem* old = nullptr;
         while ((old = aOldList->RemoveBottom()) && !IsSameItem(newItem, old)) {
           if (!IsAnyAncestorModified(old->FrameForInvalidation())) {
+            // Recurse into the child list (without a matching new list) to
+            // ensure that we find and remove any invalidated items.
+            if (old->GetChildren()) {
+              nsDisplayList empty(&mBuilder);
+              MergeDisplayLists(&empty, old->GetChildren(),
+                                old->GetChildren());
+              old->UpdateBounds(&mBuilder);
+            }
             ReuseItem(old);
           } else {
             oldListLookup.Remove({ old->Frame(), old->GetPerFrameKey() });
             old->Destroy(&mBuilder);
           }
         }
         MOZ_ASSERT(old && IsSameItem(newItem, old));
         MOZ_ASSERT(old == oldItem);
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1562,17 +1562,17 @@ needs-focus == 568441.html 568441-ref.ht
 == 574898-1.html 574898-ref.html
 # 574907 is a windows-only issue, result on other platforms depends on details of font support
 random-if(!winWidget) random-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)) == 574907-1.html 574907-1-ref.html # Bug 1258240
 random-if(!winWidget) random-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu)) == 574907-2.html 574907-2-ref.html # Bug 1258240
 # 574907-3 only worked under directwrite, and even there it now depends on the rendering mode; marking as random for now
 random-if(!winWidget) fails-if(winWidget&&!dwrite) random-if(winWidget&&dwrite) != 574907-3.html 574907-3-notref.html
 == 577838-1.html 577838-1-ref.html
 == 577838-2.html 577838-2-ref.html
-fails-if(webrender) == 579323-1.html 579323-1-ref.html
+== 579323-1.html 579323-1-ref.html
 == 579349-1.html 579349-1-ref.html
 == 579655-1.html 579655-1-ref.html
 skip-if(!haveTestPlugin) fails-if(Android) HTTP == 579808-1.html 579808-1-ref.html
 fails-if(Android) random-if(layersGPUAccelerated) fuzzy-if(skiaContent,1,10000) == 579985-1.html 579985-1-ref.html # this bug was only for a regression in BasicLayers anyway
 skip-if(Android) == 580160-1.html 580160-1-ref.html # bug 920927 for Android; issues without the test-plugin
 fuzzy-if(asyncPan&&!layersGPUAccelerated,255,141) == 580863-1.html 580863-1-ref.html
 fails-if(Android) random-if(layersGPUAccelerated) fuzzy-if(skiaContent,1,6436) == 581317-1.html 581317-1-ref.html
 == 581579-1.html 581579-1-ref.html
index b3041fd9aea6396e58cf7ee2576a9c1513983d88..3434adad8d63a91b91f51e17daf9068177fb8447
GIT binary patch
literal 4648
zc%1DRYj6|C`P(~5XJlg}8(Fqw)0GduCHri^0-J|qupHY292*;nXn?aV%NS#8j4-$f
z9c-s*P{L~neaxhtwh)?OD04O>VW#brHtBOn%4AyqXlYBE{>TtQI+?W0WI*b-cjre*
zAJg_<hr8L`@BP}_z1sr<0NL0XKneu5J^o~X+Xi682Kh~!8Us)aR>Wh7FKg=PZ20Su
zKX)R267jCB9aS}5Qv)9ZkgE*u*(b+ZJ_>vYz^%lV+cO%cpkAgwVaN5wqWhLysrnW=
z+l*)=D({a$8VG3bG2~}N2gmwuocZR90D=K^p6-vxy=j+LSE2p2$gk~3f^dz07x6ga
z%lr4m4`uumo=002#0v+9dt}plMj3VAL)@`XJ`^Kf=s@`|BTk3peG%^FA5Q=*J_5j>
zj}7mSr_O?e{!1A@HWG;`A5|<x{VRx9j-o%~ovaq*GkeQNnZH*Aj4vyL*#}lTY;T;s
z@YP*Q*8T-53~IK&*s+rNgPHvAABMuzWqyVqKr{_Ftr|>+A4pw>Fv=G~IX|OXT6h^%
zN(O9&Y!&Aa=O72>1qqTqMmgZV!d*xFgf^1*VFg*l6ikcetP%49(`|i?z2F0yO7XE&
z48j=O<Enl2QX^6wYE8|k28xhc2<>XGPAE1W+e%&JX*FhmTbdn`g<PWYc`&Pd1F$*H
zRI6YDD!~>Q+j$E<$xEeJ?HA&ycW)S`jnBS91>=XGs!WF3_v)CyboGYu;8d!O#k2*q
z#^M^=sWle&Ezla%{UlFo`rK&qvk0SPIU}Aq=bdH}UI)w68ANmDSs)!p)BN0!d0X=r
z(F*qed@0~oAiGs5Zwo3dqag*v4Y`V`BDgN3(hi5{FBOtqnd?!AoBmqKsPHJ9yR|*o
zD27}fg?I1CrHZdD=u&(kk795$RhQ`cZ1A_vw?a;o3qEju5ORtx#aIzk0;3_75elI_
zqkD09het8FrwYk)=$$?n4m%Y<GlF|+xk~cQk<#2*R=TFjqbzdM!_3K>Xof0$QL{)D
zLvf1&ZNWq&AyWpdcDh_4XF|o=HH<klYtdPp78hDfchh&&bTZtuN)al;K}ws%fIL8h
zw6|L`!Q>V*&*+*aXj38}iwT+#Rd*sYuJ}+A^J4_X7hwqMWU8Ll&E&dVPCApoGN4j3
zM$@Us=2FF$xJ5dnI~QrNwZrLBNGO=Vgqp>KNE6KoQDzF73L89%g-t^i#%*N@Fd*xp
z1QKiz<$>J`GhwRPZcHNa1y;6}UNIpkv@N*Sc?~&B-Iu{fd<_kx^{NGWK<$}uXOQ*n
zL9rX77aN>75U~L(?`scE;|ku`(=bga!9k%tN?s&?)|bt#AQkI^od=(@t0eV#g&c!c
zC9N$uMac2cRDjKfV!;P(X*&W(LIqg0a4@7St(bc}v*+$&xJ1^HpL2zr!mkImR&iG&
zHQWVUqPQ!pu)3@zE39H6vE03WoxAX$tB~tsQZ;0pf16xKDO~N@E}N@J@Ye7z-S6dJ
z8uybg+H7VYSfB`Z{2$}4%B0M&6!M?|O2Ca9(@Izk>%b3<&<ywnBsOuC-7W}d$Y>Hw
zLP@Q+)+>1JOE{}wwdde16qgj2*mDF^Nj4)fk2tRK+JzD$BJ5Tfc7bI1{TAoX$cE$(
zNZ7Z1_aRffsk5A;^-f=_-|Z{&IM!GT#EMtVxlYS@A0?-f!(__G*vS(rg6+1?XLa(s
z{%|MVY?-$4M<T;*X+x2CjCV>_$8@rWlDnC?Vv)O`w5ZhmjMHMyC2uC}lq;h->B$C4
zewXg7w$ayZ-_7B6+#jR-G}^&x7pAVLZ$Paci}8EH2DxfYDxebg*;=TB4X_cKpp|Xd
zdR>j$AYgieUZ)i%q1ZcHsFETraqqt=OqW`c4dedg4f1mGN%Hczf4qKuef^TW>U!Sc
zTy4zFuQGkcY%!5KR>9fYO#5lQ!#`h}_ZgF4#HRROPENYN_CaD9_u}Zq*UagbQx1c{
zaqzJ=yHM`m+<!6oB}z^s`QD@2{13G;Lwo8je~DkI*CrFPzz!~4yX8<dUkOZvAn3J<
z)@&?6$%-6rj@?#RQtZX$V-i*|F$T2r;rr+KFs6FdpM0CNCofUIpD!pd_<2Gs#d&#L
zSAl_F#>F0-<YEOpU%;g>72m4lVHY`<oVMALr^)f;2$RVo2I8~li#e#y`+Pn&z@xc*
z^ql`!wE@T_#dDvI?DeL>1gqrHVGiF)bvn%Bx42)24OqoK9X4W3pVDEIu1naLDc1G{
z9VYm8`k@Zb+GMZ+=i(h5*6iTdyMdh_cED{NCg32ObeMxovQvk7Xd#0-Y=Cm|nhqPG
zklfN?)0{4zC1DH4=`hB_wdwG@O)J`=+-@CK?Q9&0$ni)o?H;2|gJZG&?R)m_sidX-
z@p!DRsw&Dz2N`L2BwAIr=|HS;sOO0T@<==qZ;wO|49X*c;h}f{xe;1ZDbYIGE$@%?
zAq~N5ucx}&QzJ=h{!WqVno^}yT~AA+2ditml6Q@_Y;x;lVlqD2IN30{dE!UM?wq_h
zae3nQ#LbCUC%!k{@Xvxe(R=0_XP!Cs?9uNWA3WW4^xVVvtUFu-BM<=@;@J1%S5Y^N
zVc!IUIL4qKw!<FSi>(r=rHIGzj^RvJ;T@fmIyfgajP|0)D}zmN0C|ltgdb&3AS@$?
zd5Pn>E`l-#P@B;Ls56ArfUX-sPc>*s!m&<`NQV7L?bGEnYBhNAnXAUzgVGY-YyO2t
z`jAgawO<YOYF1H<zZ$K0k>*7^UMQ0k>9+JM>AZAS`nD9^8t%48oze@^=cMh@E{Xe3
zYjNtWqry|g9rrqNx_%QX+yDH-_^0BVJG`a7t{pJ{!NuXpk;Bt0Kf8v1l2m@r#y395
zzsW}RzkqhG2X>a%Pu*-?8Wya3z#US8^<dZ(*t`uK{qj&$?e~CVuRJ7!LpzkCC-wh>
zEDy%<{0jQt2!lSRVKb=^QBUT?0%Rr$>OTj7X8v_p@H0X4jGSxyfOeCw>C{?n_#6F+
Bdr1HQ
--- a/layout/tools/reftest/reftest-preferences.js
+++ b/layout/tools/reftest/reftest-preferences.js
@@ -81,17 +81,18 @@ user_pref("dom.allow_XUL_XBL_for_file", 
 user_pref("security.view-source.reachable-from-inner-protocol", true);
 
 // Skip data reporting policy notifications.
 user_pref("datareporting.policy.dataSubmissionPolicyBypassNotification", true);
 
 // Ensure that telemetry is disabled, so we don't connect to the telemetry
 // server in the middle of the tests.
 user_pref("toolkit.telemetry.enabled", false);
-user_pref("toolkit.telemetry.unified", false);
+user_pref("datareporting.healthreport.uploadEnabled", false);
+user_pref("experiments.enabled", false);
 // Likewise for safebrowsing.
 user_pref("browser.safebrowsing.phishing.enabled", false);
 user_pref("browser.safebrowsing.malware.enabled", false);
 user_pref("browser.safebrowsing.blockedURIs.enabled", false);
 user_pref("browser.safebrowsing.passwords.enabled", false);
 user_pref("browser.safebrowsing.downloads.remote.url", "http://127.0.0.1/safebrowsing-dummy/gethash");
 user_pref("browser.safebrowsing.provider.google.gethashURL", "http://127.0.0.1/safebrowsing-dummy/gethash");
 user_pref("browser.safebrowsing.provider.google.updateURL", "http://127.0.0.1/safebrowsing-dummy/update");
--- a/media/webrtc/trunk/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java
+++ b/media/webrtc/trunk/webrtc/modules/video_capture/android/java/src/org/webrtc/videoengine/VideoCaptureAndroid.java
@@ -80,17 +80,17 @@ public class VideoCaptureAndroid impleme
     this.info = new Camera.CameraInfo();
     Camera.getCameraInfo(id, info);
   }
 
   // Return the global application context.
   @WebRTCJNITarget
   private static native Context GetContext();
 
-  private class CameraThread extends Thread {
+  private static final class CameraThread extends Thread {
     private Exchanger<Handler> handlerExchanger;
     public CameraThread(Exchanger<Handler> handlerExchanger) {
       this.handlerExchanger = handlerExchanger;
     }
 
     @Override public void run() {
       Looper.prepare();
       exchange(handlerExchanger, new Handler());
--- a/mfbt/lz4.h
+++ b/mfbt/lz4.h
@@ -414,19 +414,21 @@ union LZ4_streamDecode_u {
    it is generally possible to disable them,
    typically with -Wno-deprecated-declarations for gcc
    or _CRT_SECURE_NO_WARNINGS in Visual.
    Otherwise, it's also possible to define LZ4_DISABLE_DEPRECATE_WARNINGS */
 #ifdef LZ4_DISABLE_DEPRECATE_WARNINGS
 #  define LZ4_DEPRECATED(message)   /* disable deprecation warnings */
 #else
 #  define LZ4_GCC_VERSION (__GNUC__ * 100 + __GNUC_MINOR__)
-#  if defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */
+#  if defined(__clang__) /* clang doesn't handle mixed C++11 and CNU attributes */
+#    define LZ4_DEPRECATED(message) __attribute__((deprecated(message)))
+#  elif defined (__cplusplus) && (__cplusplus >= 201402) /* C++14 or greater */
 #    define LZ4_DEPRECATED(message) [[deprecated(message)]]
-#  elif (LZ4_GCC_VERSION >= 405) || defined(__clang__)
+#  elif (LZ4_GCC_VERSION >= 405)
 #    define LZ4_DEPRECATED(message) __attribute__((deprecated(message)))
 #  elif (LZ4_GCC_VERSION >= 301)
 #    define LZ4_DEPRECATED(message) __attribute__((deprecated))
 #  elif defined(_MSC_VER)
 #    define LZ4_DEPRECATED(message) __declspec(deprecated(message))
 #  else
 #    pragma message("WARNING: You need to implement LZ4_DEPRECATED for this compiler")
 #    define LZ4_DEPRECATED(message)
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -3160,17 +3160,17 @@ public class BrowserApp extends GeckoApp
         mBrowserSearch.setUserVisibleHint(false);
 
         getWindow().setBackgroundDrawable(null);
     }
 
     /**
      * Hides certain UI elements (e.g. button toast) when the user touches the main layout.
      */
-    private class HideOnTouchListener implements TouchEventInterceptor {
+    private static final class HideOnTouchListener implements TouchEventInterceptor {
         @Override
         public boolean onInterceptTouchEvent(View view, MotionEvent event) {
             if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
                 SnackbarBuilder.dismissCurrentSnackbar();
             }
             return false;
         }
 
--- a/mobile/android/base/java/org/mozilla/gecko/DoorHangerPopup.java
+++ b/mobile/android/base/java/org/mozilla/gecko/DoorHangerPopup.java
@@ -84,17 +84,17 @@ public class DoorHangerPopup extends Anc
         updatePopup();
     }
 
     @Override
     public void handleMessage(final String event, final GeckoBundle geckoObject,
                               final EventCallback callback) {
         if (event.equals("Doorhanger:Add")) {
             final DoorhangerConfig config = makeConfigFromBundle(geckoObject);
-            addDoorHanger(config, callback);
+            addDoorHanger(config, callback, (Integer) geckoObject.get("defaultCallback"));
 
         } else if (event.equals("Doorhanger:Remove")) {
             final int tabId = geckoObject.getInt("tabID", -1);
             final String value = geckoObject.getString("value");
 
             DoorHanger doorHanger = getDoorHanger(tabId, value);
             if (doorHanger == null) {
                 return;
@@ -161,17 +161,18 @@ public class DoorHangerPopup extends Anc
         }
     }
 
     /**
      * Adds a doorhanger.
      *
      * This method must be called on the UI thread.
      */
-    private void addDoorHanger(final DoorhangerConfig config, final EventCallback callback) {
+    private void addDoorHanger(final DoorhangerConfig config, final EventCallback callback,
+                               final Integer defaultCallback) {
         final int tabId = config.getTabId();
         // Don't add a doorhanger for a tab that doesn't exist
         if (isGeckoApp && Tabs.getInstance().getTab(tabId) == null) {
             return;
         }
 
         // Replace the doorhanger if it already exists
         DoorHanger oldDoorHanger = getDoorHanger(tabId, config.getId());
@@ -180,16 +181,17 @@ public class DoorHangerPopup extends Anc
         }
 
         if (!mInflated) {
             init();
         }
 
         final DoorHanger newDoorHanger = DoorHanger.Get(mContext, config);
         newDoorHanger.callback = callback;
+        newDoorHanger.defaultCallback = defaultCallback;
 
         mDoorHangers.add(newDoorHanger);
         mContent.addView(newDoorHanger);
 
         // Only update the popup if we're adding a notification to the selected tab
         if (!isGeckoApp || tabId == Tabs.getInstance().getSelectedTab().getId()) {
             updatePopup();
         }
@@ -201,16 +203,20 @@ public class DoorHangerPopup extends Anc
      */
     @Override
     public void onButtonClick(final GeckoBundle response, DoorHanger doorhanger) {
         if (isGeckoApp) {
             EventDispatcher.getInstance().dispatch("Doorhanger:Reply", response);
         } else {
             doorhanger.callback.sendSuccess(response);
         }
+
+        // Don't call default callback because a button was clicked.
+        doorhanger.defaultCallback = null;
+
         removeDoorHanger(doorhanger);
         updatePopup();
     }
 
     /**
      * Gets a doorhanger.
      *
      * This method must be called on the UI thread.
@@ -226,16 +232,27 @@ public class DoorHangerPopup extends Anc
     }
 
     /**
      * Removes a doorhanger.
      *
      * This method must be called on the UI thread.
      */
     private void removeDoorHanger(final DoorHanger doorHanger) {
+        // Call default callback if requested.
+        if (doorHanger.defaultCallback != null) {
+            final GeckoBundle response = new GeckoBundle(1);
+            response.putInt("callback", doorHanger.defaultCallback);
+            if (isGeckoApp) {
+                EventDispatcher.getInstance().dispatch("Doorhanger:Reply", response);
+            } else {
+                doorHanger.callback.sendSuccess(response);
+            }
+        }
+
         mDoorHangers.remove(doorHanger);
         mContent.removeView(doorHanger);
     }
 
     /**
      * Removes doorhangers for a given tab.
      * @param tabId identifier of the tab to remove doorhangers from
      * @param forceRemove boolean for force-removing tabs. If true, all doorhangers associated
--- a/mobile/android/base/java/org/mozilla/gecko/EditBookmarkDialog.java
+++ b/mobile/android/base/java/org/mozilla/gecko/EditBookmarkDialog.java
@@ -34,17 +34,17 @@ public class EditBookmarkDialog {
 
     public EditBookmarkDialog(Context context) {
         mContext = context;
     }
 
     /**
      * A private struct to make it easier to pass bookmark data across threads
      */
-    private class Bookmark {
+    private static final class Bookmark {
         final int id;
         final String title;
         final String url;
         final String keyword;
 
         public Bookmark(int aId, String aTitle, String aUrl, String aKeyword) {
             id = aId;
             title = aTitle;
@@ -56,17 +56,17 @@ public class EditBookmarkDialog {
     /**
      * This text watcher to enable or disable the OK button if the dialog contains
      * valid information. This class is overridden to do data checking on different fields.
      * By itself, it always enables the button.
      *
      * Callers can also assign a paired partner to the TextWatcher, and callers will check
      * that both are enabled before enabling the ok button.
      */
-    private class EditBookmarkTextWatcher implements TextWatcher {
+    private static class EditBookmarkTextWatcher implements TextWatcher {
         // A stored reference to the dialog containing the text field being watched
         protected AlertDialog mDialog;
 
         // A stored text watcher to do the real verification of a field
         protected EditBookmarkTextWatcher mPairedTextWatcher;
 
         // Whether or not the ok button should be enabled.
         protected boolean mEnabled = true;
@@ -96,34 +96,34 @@ public class EditBookmarkDialog {
         @Override
         public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
     }
 
     /**
      * A version of the EditBookmarkTextWatcher for the url field of the dialog.
      * Only checks if the field is empty or not.
      */
-    private class LocationTextWatcher extends EditBookmarkTextWatcher {
+    private static final class LocationTextWatcher extends EditBookmarkTextWatcher {
         public LocationTextWatcher(AlertDialog aDialog) {
             super(aDialog);
         }
 
         // Disables the ok button if the location field is empty.
         @Override
         public void onTextChanged(CharSequence s, int start, int before, int count) {
             mEnabled = (s.toString().trim().length() > 0);
             super.onTextChanged(s, start, before, count);
         }
     }
 
     /**
      * A version of the EditBookmarkTextWatcher for the keyword field of the dialog.
      * Checks if the field has any (non leading or trailing) spaces.
      */
-    private class KeywordTextWatcher extends EditBookmarkTextWatcher {
+    private static final class KeywordTextWatcher extends EditBookmarkTextWatcher {
         public KeywordTextWatcher(AlertDialog aDialog) {
             super(aDialog);
         }
 
         @Override
         public void onTextChanged(CharSequence s, int start, int before, int count) {
             // Disable if the keyword contains spaces
             mEnabled = (s.toString().trim().indexOf(' ') == -1);
--- a/mobile/android/base/java/org/mozilla/gecko/FormAssistPopup.java
+++ b/mobile/android/base/java/org/mozilla/gecko/FormAssistPopup.java
@@ -393,17 +393,17 @@ public class FormAssistPopup extends Rel
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 positionAndShowPopup(aMetrics);
             }
         });
     }
 
-    private class AutoCompleteListAdapter extends ArrayAdapter<Pair<String, String>> {
+    private static final class AutoCompleteListAdapter extends ArrayAdapter<Pair<String, String>> {
         private final LayoutInflater mInflater;
         private final int mTextViewResourceId;
 
         public AutoCompleteListAdapter(Context context, int textViewResourceId) {
             super(context, textViewResourceId);
 
             mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
             mTextViewResourceId = textViewResourceId;
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -342,17 +342,17 @@ public abstract class GeckoApp extends G
 
     abstract public View getDoorhangerOverlay();
 
     protected void processTabQueue() {};
 
     protected void openQueuedTabs() {};
 
     @SuppressWarnings("serial")
-    class SessionRestoreException extends Exception {
+    static final class SessionRestoreException extends Exception {
         public SessionRestoreException(Exception e) {
             super(e);
         }
 
         public SessionRestoreException(String message) {
             super(message);
         }
     }
--- a/mobile/android/base/java/org/mozilla/gecko/LauncherActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/LauncherActivity.java
@@ -10,16 +10,17 @@ import android.content.Context;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
 import android.support.annotation.NonNull;
 import android.support.customtabs.CustomTabsIntent;
 import android.util.Log;
 
 import org.mozilla.gecko.home.HomeConfig;
+import org.mozilla.gecko.mma.MmaDelegate;
 import org.mozilla.gecko.switchboard.SwitchBoard;
 import org.mozilla.gecko.webapps.WebAppActivity;
 import org.mozilla.gecko.webapps.WebAppIndexer;
 import org.mozilla.gecko.customtabs.CustomTabsActivity;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.mozglue.SafeIntent;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.tabqueue.TabQueueHelper;
@@ -185,19 +186,26 @@ public class LauncherActivity extends Ac
         boolean actionMatched = intent.getAction().equals(Intent.ACTION_VIEW);
         return schemeMatched && actionMatched;
     }
 
     private void dispatchDeepLink(SafeIntent intent) {
         if (intent == null || intent.getData() == null || intent.getData().getHost() == null) {
             return;
         }
-        final String host = intent.getData().getHost();
+        final String deepLink = intent.getData().getHost();
+        final String uid = intent.getData().getQueryParameter("uid");
+        final String localUid = getSharedPreferences(BrowserApp.class.getName(), MODE_PRIVATE).getString(MmaDelegate.KEY_ANDROID_PREF_STRING_LEANPLUM_DEVICE_ID, null);
+        final boolean isMmaDeepLink = uid != null && localUid != null && uid.equals(localUid);
 
-        switch (host) {
+        if (!validateMmaDeepLink(deepLink, isMmaDeepLink)) {
+            return;
+        }
+
+        switch (deepLink) {
             case LINK_DEFAULT_BROWSER:
                 GeckoSharedPrefs.forApp(this).edit().putBoolean(GeckoPreferences.PREFS_DEFAULT_BROWSER, true).apply();
 
                 if (AppConstants.Versions.feature24Plus) {
                     // We are special casing the link to set the default browser here: On old Android versions we
                     // link to a SUMO page but on new Android versions we can link to the default app settings where
                     // the user can actually set a default browser (Bug 1312686).
                     final Intent changeDefaultApps = new Intent("android.settings.MANAGE_DEFAULT_APPS_SETTINGS");
@@ -229,27 +237,36 @@ public class LauncherActivity extends Ac
                 break;
             case LINK_PREFERENCES_GENERAL:
             case LINK_PREFERENCES_HOME:
             case LINK_PREFERENCES_PRIAVACY:
             case LINK_PREFERENCES_SEARCH:
             case LINK_PREFERENCES_NOTIFICATIONS:
             case LINK_PREFERENCES_ACCESSIBILITY:
                 settingsIntent = new Intent(this, GeckoPreferences.class);
-                GeckoPreferences.setResourceToOpen(settingsIntent, host);
+                GeckoPreferences.setResourceToOpen(settingsIntent, deepLink);
                 startActivityForResult(settingsIntent, ACTIVITY_REQUEST_PREFERENCES);
                 break;
             case LINK_FXA_SIGNIN:
                 dispatchAccountsDeepLink(intent);
                 break;
             default:
                 Log.w(TAG, "Unrecognized deep link");
         }
     }
 
+    private boolean validateMmaDeepLink(@NonNull final String deepLink, boolean isMmaDeepLink) {
+        // LINK_FXA_SIGNING is skipped here because it doesn't come from leanplum/mma
+        if (deepLink.contains(LINK_FXA_SIGNIN) && !isMmaDeepLink) {
+            return true;
+        } else {
+            return isMmaDeepLink;
+        }
+    }
+
     private void dispatchAccountsDeepLink(final SafeIntent safeIntent) {
         final Intent intent = new Intent(Intent.ACTION_VIEW);
 
         final Uri intentUri = safeIntent.getData();
 
         final String accountsToken = intentUri.getQueryParameter(DeepLinkContract.ACCOUNTS_TOKEN_PARAM);
         final String entryPoint = intentUri.getQueryParameter(DeepLinkContract.ACCOUNTS_ENTRYPOINT_PARAM);
 
--- a/mobile/android/base/java/org/mozilla/gecko/SessionParser.java
+++ b/mobile/android/base/java/org/mozilla/gecko/SessionParser.java
@@ -15,17 +15,17 @@ import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.util.Log;
 
 public abstract class SessionParser {
     private static final String LOGTAG = "GeckoSessionParser";
 
-    public class SessionTab {
+    public static final class SessionTab {
         final private String mTitle;
         final private String mUrl;
         final private JSONObject mTabObject;
         private boolean mIsSelected;
 
         private SessionTab(String title, String url, boolean isSelected, JSONObject tabObject) {
             mTitle = title;
             mUrl = url;
--- a/mobile/android/base/java/org/mozilla/gecko/animation/PropertyAnimator.java
+++ b/mobile/android/base/java/org/mozilla/gecko/animation/PropertyAnimator.java
@@ -28,17 +28,17 @@ public class PropertyAnimator implements
         TRANSLATION_X,
         TRANSLATION_Y,
         SCROLL_X,
         SCROLL_Y,
         WIDTH,
         HEIGHT
     }
 
-    private class ElementHolder {
+    private static final class ElementHolder {
         View view;
         Property property;
         float from;
         float to;
     }
 
     public static interface PropertyAnimationListener {
         public void onPropertyAnimationStart();
--- a/mobile/android/base/java/org/mozilla/gecko/bookmarks/BookmarkEditFragment.java
+++ b/mobile/android/base/java/org/mozilla/gecko/bookmarks/BookmarkEditFragment.java
@@ -491,17 +491,17 @@ public class BookmarkEditFragment extend