Merge autoland to central, a=merge CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Tue, 31 Jan 2017 15:11:13 -0800
changeset 360858 c22bc80b4d44f8b710c154eed2b52a73a167b037
parent 360838 ee975d32deb9eaa5641f45428cd6a4b5b555a8f5 (current diff)
parent 360857 b5acbffc8ebf1472c5e6d6f85acdf52e142b0d31 (diff)
child 360897 1d025ac534a6333a8170a59a95a8a3673d4028ee
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone54.0a1
Merge autoland to central, a=merge CLOSED TREE MozReview-Commit-ID: Br0VTCuffaz
--- a/browser/components/extensions/ext-theme.js
+++ b/browser/components/extensions/ext-theme.js
@@ -1,69 +1,127 @@
-/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                   "resource://gre/modules/Preferences.jsm");
 
-let themeExtensions = new WeakSet();
+// WeakMap[Extension -> Theme]
+let themeMap = new WeakMap();
 
-/* eslint-disable mozilla/balanced-listeners */
-extensions.on("manifest_theme", (type, directive, extension, manifest) => {
-  let enabled = Preferences.get("extensions.webextensions.themes.enabled");
-
-  if (!enabled || !manifest || !manifest.theme) {
-    return;
+/** Class representing a theme. */
+class Theme {
+  /**
+   * Creates a theme instance.
+   */
+  constructor() {
+    // A dictionary of light weight theme styles.
+    this.lwtStyles = {};
   }
-  // Apply theme only if themes are enabled.
-  let lwtStyles = {footerURL: ""};
-  if (manifest.theme.colors) {
-    let colors = manifest.theme.colors;
-    for (let color of Object.getOwnPropertyNames(colors)) {
-      let val = colors[color];
-      // Since values are optional, they may be `null`.
-      if (val === null) {
-        continue;
-      }
+
+  /**
+   * Loads a theme by reading the properties from the extension's manifest.
+   * This method will override any currently applied theme.
+   *
+   * @param {Object} details Theme part of the manifest. Supported
+   *   properties can be found in the schema under ThemeType.
+   */
+  load(details) {
+    if (details.colors) {
+      this.loadColors(details.colors);
+    }
+
+    if (details.images) {
+      this.loadImages(details.images);
+    }
 
-      if (color == "accentcolor") {
-        lwtStyles.accentcolor = val;
-        continue;
-      }
-      if (color == "textcolor") {
-        lwtStyles.textcolor = val;
-      }
+    // Lightweight themes require all properties to be defined.
+    if (this.lwtStyles.headerURL &&
+        this.lwtStyles.accentcolor &&
+        this.lwtStyles.textcolor) {
+      Services.obs.notifyObservers(null,
+        "lightweight-theme-styling-update",
+        JSON.stringify(this.lwtStyles));
+    }
+  }
+
+  /**
+   * Helper method for loading colors found in the extension's manifest.
+   *
+   * @param {Object} colors Dictionary mapping color properties to values.
+   */
+  loadColors(colors) {
+    let {accentcolor, textcolor} = colors;
+
+    if (accentcolor) {
+      this.lwtStyles.accentcolor = accentcolor;
+    }
+
+    if (textcolor) {
+      this.lwtStyles.textcolor = textcolor;
     }
   }
 
-  if (manifest.theme.images) {
-    let images = manifest.theme.images;
-    for (let image of Object.getOwnPropertyNames(images)) {
-      let val = images[image];
-      if (val === null) {
-        continue;
-      }
+  /**
+   * Helper method for loading images found in the extension's manifest.
+   *
+   * @param {Object} images Dictionary mapping image properties to values.
+   */
+  loadImages(images) {
+    let {headerURL} = images;
 
-      if (image == "headerURL") {
-        lwtStyles.headerURL = val;
-      }
+    if (headerURL) {
+      this.lwtStyles.headerURL = headerURL;
     }
   }
 
-  if (lwtStyles.headerURL &&
-      lwtStyles.accentcolor &&
-      lwtStyles.textcolor) {
-    themeExtensions.add(extension);
+  /**
+   * Unloads the currently applied theme.
+   */
+  unload() {
     Services.obs.notifyObservers(null,
       "lightweight-theme-styling-update",
-      JSON.stringify(lwtStyles));
+      null);
   }
+}
+
+/* eslint-disable mozilla/balanced-listeners */
+extensions.on("manifest_theme", (type, directive, extension, manifest) => {
+  if (!Preferences.get("extensions.webextensions.themes.enabled")) {
+    // Return early if themes are disabled.
+    return;
+  }
+
+  let theme = new Theme();
+  theme.load(manifest.theme);
+  themeMap.set(extension, theme);
 });
 
 extensions.on("shutdown", (type, extension) => {
-  if (themeExtensions.has(extension)) {
-    Services.obs.notifyObservers(null, "lightweight-theme-styling-update", null);
+  let theme = themeMap.get(extension);
+
+  // We won't have a theme if theme's aren't enabled.
+  if (!theme) {
+    return;
   }
+
+  theme.unload();
 });
 /* eslint-enable mozilla/balanced-listeners */
+
+extensions.registerSchemaAPI("theme", "addon_parent", context => {
+  let {extension} = context;
+  return {
+    theme: {
+      update(details) {
+        let theme = themeMap.get(extension);
+
+        // We won't have a theme if theme's aren't enabled.
+        if (!theme) {
+          return;
+        }
+
+        theme.load(details);
+      },
+    },
+  };
+});
--- a/browser/components/extensions/schemas/theme.json
+++ b/browser/components/extensions/schemas/theme.json
@@ -41,10 +41,30 @@
         "properties": {
           "theme": {
             "optional": true,
             "$ref": "ThemeType"
           }
         }
       }
     ]
+  },
+  {
+    "namespace": "theme",
+    "description": "The theme API allows customizing of visual elements of the browser.",
+    "permissions": ["manifest:theme"],
+    "functions": [
+      {
+        "name": "update",
+        "type": "function",
+        "async": true,
+        "description": "Make complete or partial updates to the theme. Resolves when the update has completed.",
+        "parameters": [
+          {
+            "name": "details",
+            "$ref": "manifest.ThemeType",
+            "description": "The properties of the theme to update."
+          }
+        ]
+      }
+    ]
   }
 ]
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -102,16 +102,17 @@ support-files =
 [browser_ext_tabs_query.js]
 [browser_ext_tabs_reload.js]
 [browser_ext_tabs_reload_bypass_cache.js]
 [browser_ext_tabs_sendMessage.js]
 [browser_ext_tabs_cookieStoreId.js]
 [browser_ext_tabs_update.js]
 [browser_ext_tabs_zoom.js]
 [browser_ext_tabs_update_url.js]
+[browser_ext_themes_dynamic_updates.js]
 [browser_ext_themes_lwtsupport.js]
 [browser_ext_topwindowid.js]
 [browser_ext_url_overrides.js]
 [browser_ext_webRequest.js]
 [browser_ext_webNavigation_frameId0.js]
 [browser_ext_webNavigation_getFrames.js]
 [browser_ext_webNavigation_urlbar_transitions.js]
 [browser_ext_windows.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_themes_dynamic_updates.js
@@ -0,0 +1,87 @@
+"use strict";
+
+// PNG image data for a simple red dot.
+const BACKGROUND_1 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
+const ACCENT_COLOR_1 = "#a14040";
+const TEXT_COLOR_1 = "#fac96e";
+
+// PNG image data for the Mozilla dino head.
+const BACKGROUND_2 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
+const ACCENT_COLOR_2 = "#03fe03";
+const TEXT_COLOR_2 = "#0ef325";
+
+function hexToRGB(hex) {
+  hex = parseInt((hex.indexOf("#") > -1 ? hex.substring(1) : hex), 16);
+  return [hex >> 16, (hex & 0x00FF00) >> 8, (hex & 0x0000FF)];
+}
+
+function validateTheme(backgroundImage, accentColor, textColor) {
+  let docEl = window.document.documentElement;
+  let style = window.getComputedStyle(docEl);
+
+  Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
+  Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
+    "LWT text color attribute should be set");
+
+  Assert.equal(style.backgroundImage, 'url("' + backgroundImage.replace(/"/g, '\\"') + '")',
+    "Expected correct background image");
+  Assert.equal(style.backgroundColor, "rgb(" + hexToRGB(accentColor).join(", ") + ")",
+    "Expected correct accent color");
+  Assert.equal(style.color, "rgb(" + hexToRGB(textColor).join(", ") + ")",
+    "Expected correct text color");
+}
+
+add_task(function* setup() {
+  yield SpecialPowers.pushPrefEnv({
+    set: [["extensions.webextensions.themes.enabled", true]],
+  });
+});
+
+add_task(function* test_dynamic_theme_updates() {
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "theme": {
+        "images": {
+          "headerURL": BACKGROUND_1,
+        },
+        "colors": {
+          "accentcolor": ACCENT_COLOR_1,
+          "textcolor": TEXT_COLOR_1,
+        },
+      },
+    },
+    background() {
+      browser.test.onMessage.addListener((msg, details) => {
+        if (msg != "update-theme") {
+          browser.test.fail("expected 'update-theme' message");
+        }
+
+        browser.theme.update(details);
+        browser.test.sendMessage("theme-updated");
+      });
+    },
+  });
+
+  yield extension.startup();
+
+  validateTheme(BACKGROUND_1, ACCENT_COLOR_1, TEXT_COLOR_1);
+
+  extension.sendMessage("update-theme", {
+    "images": {
+      "headerURL": BACKGROUND_2,
+    },
+    "colors": {
+      "accentcolor": ACCENT_COLOR_2,
+      "textcolor": TEXT_COLOR_2,
+    },
+  });
+
+  yield extension.awaitMessage("theme-updated");
+
+  validateTheme(BACKGROUND_2, ACCENT_COLOR_2, TEXT_COLOR_2);
+
+  yield extension.unload();
+
+  let docEl = window.document.documentElement;
+  Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
+});
--- a/browser/components/extensions/test/browser/browser_ext_themes_lwtsupport.js
+++ b/browser/components/extensions/test/browser/browser_ext_themes_lwtsupport.js
@@ -1,87 +1,87 @@
 "use strict";
 
-const kBackground = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
-const kAccentColor = "#a14040";
-const kTextColor = "#fac96e";
+const BACKGROUND = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==";
+const ACCENT_COLOR = "#a14040";
+const TEXT_COLOR = "#fac96e";
 
 function hexToRGB(hex) {
   hex = parseInt((hex.indexOf("#") > -1 ? hex.substring(1) : hex), 16);
   return [hex >> 16, (hex & 0x00FF00) >> 8, (hex & 0x0000FF)];
 }
 
 add_task(function* setup() {
   yield SpecialPowers.pushPrefEnv({
     set: [["extensions.webextensions.themes.enabled", true]],
   });
 });
 
-add_task(function* testSupportLWTProperties() {
+add_task(function* test_support_LWT_properties() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": kBackground,
+          "headerURL": BACKGROUND,
         },
         "colors": {
-          "accentcolor": kAccentColor,
-          "textcolor": kTextColor,
+          "accentcolor": ACCENT_COLOR,
+          "textcolor": TEXT_COLOR,
         },
       },
     },
   });
 
   yield extension.startup();
 
   let docEl = window.document.documentElement;
   let style = window.getComputedStyle(docEl);
 
   Assert.ok(docEl.hasAttribute("lwtheme"), "LWT attribute should be set");
   Assert.equal(docEl.getAttribute("lwthemetextcolor"), "bright",
     "LWT text color attribute should be set");
 
-  Assert.equal(style.backgroundImage, 'url("' + kBackground.replace(/"/g, '\\"') + '")',
+  Assert.equal(style.backgroundImage, 'url("' + BACKGROUND.replace(/"/g, '\\"') + '")',
     "Expected background image");
-  Assert.equal(style.backgroundColor, "rgb(" + hexToRGB(kAccentColor).join(", ") + ")",
+  Assert.equal(style.backgroundColor, "rgb(" + hexToRGB(ACCENT_COLOR).join(", ") + ")",
     "Expected correct background color");
-  Assert.equal(style.color, "rgb(" + hexToRGB(kTextColor).join(", ") + ")",
+  Assert.equal(style.color, "rgb(" + hexToRGB(TEXT_COLOR).join(", ") + ")",
     "Expected correct text color");
 
   yield extension.unload();
 
   Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
 });
 
-add_task(function* testLWTRequiresAllPropertiesDefinedImageOnly() {
+add_task(function* test_LWT_requires_all_properties_defined_image_only() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "images": {
-          "headerURL": kBackground,
+          "headerURL": BACKGROUND,
         },
       },
     },
   });
 
   yield extension.startup();
 
   let docEl = window.document.documentElement;
   Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
   yield extension.unload();
   Assert.ok(!docEl.hasAttribute("lwtheme"), "LWT attribute should not be set");
 });
 
-add_task(function* testLWTRequiresAllPropertiesDefinedColorsOnly() {
+add_task(function* test_LWT_requires_all_properties_defined_colors_only() {
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "theme": {
         "colors": {
-          "accentcolor": kAccentColor,
-          "textcolor": kTextColor,
+          "accentcolor": ACCENT_COLOR,
+          "textcolor": TEXT_COLOR,
         },
       },
     },
   });
 
   yield extension.startup();
 
   let docEl = window.document.documentElement;
--- a/browser/components/migration/ChromeProfileMigrator.js
+++ b/browser/components/migration/ChromeProfileMigrator.js
@@ -435,34 +435,45 @@ function GetWindowsPasswordsResource(aPr
       // If the promise was rejected we will have already called aCallback,
       // so we can just return here.
       if (!rows) {
         return;
       }
       let crypto = new OSCrypto();
 
       for (let row of rows) {
-        let loginInfo = {
-          username: row.getResultByName("username_value"),
-          password: crypto.
-                    decryptData(crypto.arrayToString(row.getResultByName("password_value")),
-                                                     null),
-          hostname: NetUtil.newURI(row.getResultByName("origin_url")).prePath,
-          formSubmitURL: null,
-          httpRealm: null,
-          usernameElement: row.getResultByName("username_element"),
-          passwordElement: row.getResultByName("password_element"),
-          timeCreated: chromeTimeToDate(row.getResultByName("date_created") + 0).getTime(),
-          timesUsed: row.getResultByName("times_used") + 0,
-        };
+        try {
+          let origin_url = NetUtil.newURI(row.getResultByName("origin_url"));
+          // Ignore entries for non-http(s)/ftp URLs because we likely can't
+          // use them anyway.
+          const kValidSchemes = new Set(["https", "http", "ftp"]);
+          if (!kValidSchemes.has(origin_url.scheme)) {
+            continue;
+          }
+          let loginInfo = {
+            username: row.getResultByName("username_value"),
+            password: crypto.
+                      decryptData(crypto.arrayToString(row.getResultByName("password_value")),
+                                                       null),
+            hostname: origin_url.prePath,
+            formSubmitURL: null,
+            httpRealm: null,
+            usernameElement: row.getResultByName("username_element"),
+            passwordElement: row.getResultByName("password_element"),
+            timeCreated: chromeTimeToDate(row.getResultByName("date_created") + 0).getTime(),
+            timesUsed: row.getResultByName("times_used") + 0,
+          };
 
-        try {
           switch (row.getResultByName("scheme")) {
             case AUTH_TYPE.SCHEME_HTML:
-              loginInfo.formSubmitURL = NetUtil.newURI(row.getResultByName("action_url")).prePath;
+              let action_url = NetUtil.newURI(row.getResultByName("action_url"));
+              if (!kValidSchemes.has(action_url.scheme)) {
+                continue; // This continues the outer for loop.
+              }
+              loginInfo.formSubmitURL = action_url.prePath;
               break;
             case AUTH_TYPE.SCHEME_BASIC:
             case AUTH_TYPE.SCHEME_DIGEST:
               // signon_realm format is URIrealm, so we need remove URI
               loginInfo.httpRealm = row.getResultByName("signon_realm")
                                        .substring(loginInfo.hostname.length + 1);
               break;
             default:
--- a/browser/modules/E10SUtils.jsm
+++ b/browser/modules/E10SUtils.jsm
@@ -66,19 +66,21 @@ this.E10SUtils = {
       aURL = "about:blank";
     }
 
     // Javascript urls can load in any process, they apply to the current document
     if (aURL.startsWith("javascript:")) {
       return aPreferredRemoteType;
     }
 
-    // We need data: URIs to load in any remote process, because some of our
-    // tests rely on this.
-    if (aURL.startsWith("data:")) {
+    // We need data: URI's to load in a remote process, because some of our
+    // tests rely on this. For blob: URI's, load them in their originating
+    // process unless it is non-remote. In that case, favor a remote (sandboxed)
+    // process with fewer privileges to limit exposure.
+    if (aURL.startsWith("data:") || aURL.startsWith("blob:")) {
       return aPreferredRemoteType == NOT_REMOTE ? DEFAULT_REMOTE_TYPE
                                                 : aPreferredRemoteType;
     }
 
     if (aURL.startsWith("file:")) {
       return Services.prefs.getBoolPref("browser.tabs.remote.separateFileUriProcess")
              ? FILE_REMOTE_TYPE : DEFAULT_REMOTE_TYPE;
     }
--- a/devtools/server/actors/frame.js
+++ b/devtools/server/actors/frame.js
@@ -63,18 +63,20 @@ let FrameActor = ActorClassWithSpec(fram
 
     if (this.frame.environment) {
       let envActor = threadActor.createEnvironmentActor(
         this.frame.environment,
         this.frameLifetimePool
       );
       form.environment = envActor.form();
     }
-    form.this = createValueGrip(this.frame.this, threadActor._pausePool,
-      threadActor.objectGrip);
+    if (this.frame.type != "wasmcall") {
+      form.this = createValueGrip(this.frame.this, threadActor._pausePool,
+        threadActor.objectGrip);
+    }
     form.arguments = this._args();
     if (this.frame.script) {
       let generatedLocation = this.threadActor.sources.getFrameLocation(this.frame);
       form.where = {
         source: generatedLocation.generatedSourceActor.form(),
         line: generatedLocation.generatedLine,
         column: generatedLocation.generatedColumn
       };
--- a/devtools/server/actors/script.js
+++ b/devtools/server/actors/script.js
@@ -2185,20 +2185,25 @@ Object.assign(PauseScopedObjectActor.pro
 function hackDebugger(Debugger) {
   // TODO: Improve native code instead of hacking on top of it
 
   /**
    * Override the toString method in order to get more meaningful script output
    * for debugging the debugger.
    */
   Debugger.Script.prototype.toString = function () {
+    if (this.type == "wasm") {
+      return "[wasm]";
+    }
+
     let output = "";
     if (this.url) {
       output += this.url;
     }
+
     if (typeof this.staticLevel != "undefined") {
       output += ":L" + this.staticLevel;
     }
     if (typeof this.startLine != "undefined") {
       output += ":" + this.startLine;
       if (this.lineCount && this.lineCount > 1) {
         output += "-" + (this.startLine + this.lineCount - 1);
       }
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/unit/test_frameactor_wasm-01.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Verify that wasm frame(s) can be requested from the client.
+ */
+
+var gDebuggee;
+var gClient;
+var gThreadClient;
+var gOldPref;
+
+function run_test()
+{
+  gOldPref = Services.prefs.getBoolPref("javascript.options.wasm");
+  Services.prefs.setBoolPref("javascript.options.wasm", true);
+
+  if (typeof WebAssembly == "undefined") {
+    return; // wasm is not enabled for this platform
+  }
+
+  initTestDebuggerServer();
+  gDebuggee = addTestGlobal("test-stack");
+  gClient = new DebuggerClient(DebuggerServer.connectPipe());
+  gClient.connect().then(function () {
+    attachTestTabAndResume(gClient, "test-stack", function (aResponse, aTabClient, aThreadClient) {
+      gThreadClient = aThreadClient;
+      gThreadClient.reconfigure({ observeAsmJS: true }, function (aResponse) {
+        do_check_eq(!!aResponse.error, false);
+        test_pause_frame();
+      });
+    });
+  });
+  do_test_pending();
+}
+
+function test_pause_frame()
+{
+  gThreadClient.addOneTimeListener("paused", function (aEvent, aPacket1) {
+    gThreadClient.getFrames(0, null, function (aFrameResponse) {
+      do_check_eq(aFrameResponse.frames.length, 4);
+
+      let wasmFrame = aFrameResponse.frames[1];
+      do_check_eq(wasmFrame.type, "wasmcall");
+      do_check_eq(wasmFrame.this, undefined);
+
+      let location = wasmFrame.where;
+      do_check_eq(location.line > 0, true);
+      do_check_eq(location.column > 0, true);
+      do_check_eq(location.source.url.endsWith(" > wasm"), true);
+
+      Services.prefs.setBoolPref("javascript.options.wasm", gOldPref);
+      finishClient(gClient);
+    });
+  });
+
+  gDebuggee.eval("(" + function () {
+    // WebAssembly bytecode was generated by running:
+    // js -e 'print(wasmTextToBinary("(module(import \"a\" \"b\")(func(export \"c\")call 0))"))'
+    var m = new WebAssembly.Module(new Uint8Array([
+      0,97,115,109,13,0,0,0,1,132,128,128,128,0,1,96,0,0,2,135,128,128,128,0,1,1,97,1,
+      98,0,0,3,130,128,128,128,0,1,0,6,129,128,128,128,0,0,7,133,128,128,128,0,1,1,99,
+      0,1,10,138,128,128,128,0,1,132,128,128,128,0,0,16,0,11
+    ]));
+    var i = new WebAssembly.Instance(m, {a: {b: () => {
+      debugger;
+    }}});
+    i.exports.c();
+  } + ")()");
+}
--- a/devtools/server/tests/unit/xpcshell.ini
+++ b/devtools/server/tests/unit/xpcshell.ini
@@ -59,16 +59,17 @@ support-files =
 [test_blackboxing-05.js]
 [test_blackboxing-06.js]
 [test_blackboxing-07.js]
 [test_frameactor-01.js]
 [test_frameactor-02.js]
 [test_frameactor-03.js]
 [test_frameactor-04.js]
 [test_frameactor-05.js]
+[test_frameactor_wasm-01.js]
 [test_framearguments-01.js]
 [test_getRuleText.js]
 [test_getTextAtLineColumn.js]
 [test_pauselifetime-01.js]
 [test_pauselifetime-02.js]
 [test_pauselifetime-03.js]
 [test_pauselifetime-04.js]
 [test_threadlifetime-01.js]
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1064,23 +1064,16 @@ ContentChild::DeallocPCycleCollectWithLo
 {
   // ...so when we get here, there's nothing for us to do.
   //
   // Also, we're already in ~CycleCollectWithLogsChild (q.v.) at
   // this point, so we shouldn't touch the actor in any case.
   return true;
 }
 
-mozilla::plugins::PPluginModuleParent*
-ContentChild::AllocPPluginModuleParent(mozilla::ipc::Transport* aTransport,
-                                       base::ProcessId aOtherProcess)
-{
-  return plugins::PluginModuleContentParent::Initialize(aTransport, aOtherProcess);
-}
-
 PContentBridgeChild*
 ContentChild::AllocPContentBridgeChild(mozilla::ipc::Transport* aTransport,
                                        base::ProcessId aOtherProcess)
 {
   return ContentBridgeChild::Create(aTransport, aOtherProcess);
 }
 
 PContentBridgeParent*
@@ -2560,20 +2553,23 @@ ContentChild::RecvGatherProfile()
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 ContentChild::RecvLoadPluginResult(const uint32_t& aPluginId,
                                    const bool& aResult)
 {
   nsresult rv;
-  bool finalResult = aResult && SendConnectPluginBridge(aPluginId, &rv) &&
+  Endpoint<PPluginModuleParent> endpoint;
+  bool finalResult = aResult &&
+                     SendConnectPluginBridge(aPluginId, &rv, &endpoint) &&
                      NS_SUCCEEDED(rv);
   plugins::PluginModuleContentParent::OnLoadPluginResult(aPluginId,
-                                                         finalResult);
+                                                         finalResult,
+                                                         Move(endpoint));
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 ContentChild::RecvAssociatePluginId(const uint32_t& aPluginId,
                                     const base::ProcessId& aProcessId)
 {
   plugins::PluginModuleContentParent::AssociatePluginId(aPluginId, aProcessId);
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -142,20 +142,16 @@ public:
     MOZ_ASSERT(mLastBridge);
     ContentBridgeParent* parent = mLastBridge;
     mLastBridge = nullptr;
     return parent;
   }
 
   RefPtr<ContentBridgeParent> mLastBridge;
 
-  PPluginModuleParent *
-  AllocPPluginModuleParent(mozilla::ipc::Transport* transport,
-                           base::ProcessId otherProcess) override;
-
   PContentBridgeParent*
   AllocPContentBridgeParent(mozilla::ipc::Transport* transport,
                             base::ProcessId otherProcess) override;
   PContentBridgeChild*
   AllocPContentBridgeChild(mozilla::ipc::Transport* transport,
                            base::ProcessId otherProcess) override;
 
   mozilla::ipc::IPCResult
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -791,20 +791,24 @@ ContentParent::RecvCreateGMPService()
     MOZ_ASSERT(false, "SendInitGMPService failed");
     return IPC_FAIL_NO_REASON(this);
   }
 
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-ContentParent::RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv, uint32_t* aRunID)
+ContentParent::RecvLoadPlugin(const uint32_t& aPluginId,
+                              nsresult* aRv,
+                              uint32_t* aRunID,
+                              Endpoint<PPluginModuleParent>* aEndpoint)
 {
   *aRv = NS_OK;
-  if (!mozilla::plugins::SetupBridge(aPluginId, this, false, aRv, aRunID)) {
+  if (!mozilla::plugins::SetupBridge(aPluginId, this, false, aRv, aRunID,
+                                     aEndpoint)) {
     return IPC_FAIL_NO_REASON(this);
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 ContentParent::RecvUngrabPointer(const uint32_t& aTime)
 {
@@ -821,25 +825,27 @@ mozilla::ipc::IPCResult
 ContentParent::RecvRemovePermission(const IPC::Principal& aPrincipal,
                                     const nsCString& aPermissionType,
                                     nsresult* aRv) {
   *aRv = Permissions::RemovePermission(aPrincipal, aPermissionType.get());
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
-ContentParent::RecvConnectPluginBridge(const uint32_t& aPluginId, nsresult* aRv)
+ContentParent::RecvConnectPluginBridge(const uint32_t& aPluginId,
+                                       nsresult* aRv,
+                                       Endpoint<PPluginModuleParent>* aEndpoint)
 {
   *aRv = NS_OK;
   // We don't need to get the run ID for the plugin, since we already got it
   // in the first call to SetupBridge in RecvLoadPlugin, so we pass in a dummy
   // pointer and just throw it away.
   uint32_t dummy = 0;
-  if (!mozilla::plugins::SetupBridge(aPluginId, this, true, aRv, &dummy)) {
-    return IPC_FAIL_NO_REASON(this);
+  if (!mozilla::plugins::SetupBridge(aPluginId, this, true, aRv, &dummy, aEndpoint)) {
+    return IPC_FAIL(this, "SetupBridge failed");
   }
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 ContentParent::RecvGetBlocklistState(const uint32_t& aPluginId,
                                      uint32_t* aState)
 {
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -252,20 +252,22 @@ public:
                                                          bool* aIsForBrowser,
                                                          TabId* aTabId) override;
 
   virtual mozilla::ipc::IPCResult RecvBridgeToChildProcess(const ContentParentId& aCpId) override;
 
   virtual mozilla::ipc::IPCResult RecvCreateGMPService() override;
 
   virtual mozilla::ipc::IPCResult RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv,
-                                                 uint32_t* aRunID) override;
+                                                 uint32_t* aRunID,
+                                                 Endpoint<PPluginModuleParent>* aEndpoint) override;
 
   virtual mozilla::ipc::IPCResult RecvConnectPluginBridge(const uint32_t& aPluginId,
-                                                          nsresult* aRv) override;
+                                                          nsresult* aRv,
+                                                          Endpoint<PPluginModuleParent>* aEndpoint) override;
 
   virtual mozilla::ipc::IPCResult RecvGetBlocklistState(const uint32_t& aPluginId,
                                                         uint32_t* aIsBlocklisted) override;
 
   virtual mozilla::ipc::IPCResult RecvFindPlugins(const uint32_t& aPluginEpoch,
                                                   nsresult* aRv,
                                                   nsTArray<PluginTag>* aPlugins,
                                                   uint32_t* aNewPluginEpoch) override;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -343,18 +343,16 @@ struct GfxInfoFeatureStatus
 
 /**
  * The PContent protocol is a top-level protocol between the UI process
  * and a content process. There is exactly one PContentParent/PContentChild pair
  * for each content process.
  */
 nested(upto inside_cpow) sync protocol PContent
 {
-    parent spawns PPluginModule;
-
     manages PBlob;
     manages PBrowser;
     manages PContentPermissionRequest;
     manages PCrashReporter;
     manages PCycleCollectWithLogs;
     manages PDeviceStorageRequest;
     manages PPSMContentDownloader;
     manages PExternalHelperApp;
@@ -679,30 +677,31 @@ parent:
                             ProcessPriority priority,
                             TabId openerTabId)
         returns (ContentParentId cpId, bool isForBrowser, TabId tabId);
     sync BridgeToChildProcess(ContentParentId cpId);
 
     async CreateGMPService();
 
     /**
-     * This call connects the content process to a plugin process. While this
-     * call runs, a new PluginModuleParent will be created in the ContentChild
-     * via bridging. The corresponding PluginModuleChild will live in the plugin
-     * process.
+     * This call connects the content process to a plugin process. This call
+     * returns an endpoint for a new PluginModuleParent. The corresponding
+     * PluginModuleChild will be started up in the plugin process.
      */
-    sync LoadPlugin(uint32_t aPluginId) returns (nsresult aResult, uint32_t aRunID);
+    sync LoadPlugin(uint32_t aPluginId)
+        returns (nsresult aResult, uint32_t aRunID, Endpoint<PPluginModuleParent> aEndpoint);
 
     /**
      * This call is used by asynchronous plugin instantiation to notify the
      * content parent that it is now safe to initiate the plugin bridge for
-     * the specified plugin id. When this call returns, the requested bridge
-     * connection has been made.
+     * the specified plugin id. The endpoint for the content process part of the
+     * bridge is returned.
      */
-    sync ConnectPluginBridge(uint32_t aPluginId) returns (nsresult rv);
+    sync ConnectPluginBridge(uint32_t aPluginId)
+        returns (nsresult rv, Endpoint<PPluginModuleParent> aEndpoint);
 
     /**
      * Return the current blocklist state for a particular plugin.
      */
     sync GetBlocklistState(uint32_t aPluginId) returns (uint32_t aState);
 
     /**
      * This call returns the set of plugins loaded in the chrome
--- a/dom/plugins/ipc/PPluginModule.ipdl
+++ b/dom/plugins/ipc/PPluginModule.ipdl
@@ -31,18 +31,16 @@ struct PluginSettings
 
   // These settings come from elsewhere.
   nsCString userAgent;
   bool nativeCursorsSupported;
 };
 
 intr protocol PPluginModule
 {
-  bridges PContent, PPluginModule;
-
   manages PPluginInstance;
   manages PCrashReporter;
 
 both:
   // Window-specific message which instructs the interrupt mechanism to enter
   // a nested event loop for the current interrupt call.
   async ProcessNativeEventsInInterruptCall();
 
@@ -104,16 +102,18 @@ child:
   async StopProfiler();
 
   async GatherProfile();
 
   async SettingChanged(PluginSettings settings);
 
   async NPP_SetValue_NPNVaudioDeviceChangeDetails(NPAudioDeviceChangeDetailsIPC changeDetails);
 
+  async InitPluginModuleChild(Endpoint<PPluginModuleChild> endpoint);
+
 parent:
   async NP_InitializeResult(NPError aError);
 
   /**
    * This message is only used on X11 platforms.
    *
    * Send a dup of the plugin process's X socket to the parent
    * process.  In theory, this scheme keeps the plugin's X resources
--- a/dom/plugins/ipc/PluginBridge.h
+++ b/dom/plugins/ipc/PluginBridge.h
@@ -10,21 +10,29 @@
 #include "base/process.h"
 
 namespace mozilla {
 
 namespace dom {
 class ContentParent;
 } // namespace dom
 
+namespace ipc {
+template<class PFooSide>
+class Endpoint;
+} // namespace ipc
+
 namespace plugins {
 
+class PPluginModuleParent;
+
 bool
 SetupBridge(uint32_t aPluginId, dom::ContentParent* aContentParent,
-            bool aForceBridgeNow, nsresult* aResult, uint32_t* aRunID);
+            bool aForceBridgeNow, nsresult* aResult, uint32_t* aRunID,
+            ipc::Endpoint<PPluginModuleParent>* aEndpoint);
 
 nsresult
 FindPluginsForContent(uint32_t aPluginEpoch,
                       nsTArray<PluginTag>* aPlugins,
                       uint32_t* aNewPluginEpoch);
 
 void
 TakeFullMinidump(uint32_t aPluginId,
--- a/dom/plugins/ipc/PluginModuleChild.cpp
+++ b/dom/plugins/ipc/PluginModuleChild.cpp
@@ -92,27 +92,21 @@ typedef BOOL (WINAPI *GetWindowInfoPtr)(
 static GetWindowInfoPtr sGetWindowInfoPtrStub = nullptr;
 static HWND sBrowserHwnd = nullptr;
 // sandbox process doesn't get current key states.  So we need get it on chrome.
 typedef SHORT (WINAPI *GetKeyStatePtr)(int);
 static GetKeyStatePtr sGetKeyStatePtrStub = nullptr;
 #endif
 
 /* static */
-PluginModuleChild*
-PluginModuleChild::CreateForContentProcess(mozilla::ipc::Transport* aTransport,
-                                           base::ProcessId aOtherPid)
+bool
+PluginModuleChild::CreateForContentProcess(Endpoint<PPluginModuleChild>&& aEndpoint)
 {
     auto* child = new PluginModuleChild(false);
-
-    if (!child->InitForContent(aOtherPid, XRE_GetIOMessageLoop(), aTransport)) {
-        return nullptr;
-    }
-
-    return child;
+    return child->InitForContent(Move(aEndpoint));
 }
 
 PluginModuleChild::PluginModuleChild(bool aIsChrome)
   : mLibrary(0)
   , mPluginFilename("")
   , mQuirks(QUIRKS_NOT_INITIALIZED)
   , mIsChrome(aIsChrome)
   , mHasShutdown(false)
@@ -163,46 +157,38 @@ PluginModuleChild::GetChrome()
 {
     // A special PluginModuleChild instance that talks to the chrome process
     // during startup and shutdown. Synchronous messages to or from this actor
     // should be avoided because they may lead to hangs.
     MOZ_ASSERT(gChromeInstance);
     return gChromeInstance;
 }
 
-bool
-PluginModuleChild::CommonInit(base::ProcessId aParentPid,
-                              MessageLoop* aIOLoop,
-                              IPC::Channel* aChannel)
+void
+PluginModuleChild::CommonInit()
 {
     PLUGIN_LOG_DEBUG_METHOD;
 
     // Request Windows message deferral behavior on our channel. This
     // applies to the top level and all sub plugin protocols since they
     // all share the same channel.
     // Bug 1090573 - Don't do this for connections to content processes.
     GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION);
 
-    if (!Open(aChannel, aParentPid, aIOLoop)) {
-        return false;
-    }
-
     memset((void*) &mFunctions, 0, sizeof(mFunctions));
     mFunctions.size = sizeof(mFunctions);
     mFunctions.version = (NP_VERSION_MAJOR << 8) | NP_VERSION_MINOR;
-
-    return true;
 }
 
 bool
-PluginModuleChild::InitForContent(base::ProcessId aParentPid,
-                                  MessageLoop* aIOLoop,
-                                  IPC::Channel* aChannel)
+PluginModuleChild::InitForContent(Endpoint<PPluginModuleChild>&& aEndpoint)
 {
-    if (!CommonInit(aParentPid, aIOLoop, aChannel)) {
+    CommonInit();
+
+    if (!aEndpoint.Bind(this)) {
         return false;
     }
 
     mLibrary = GetChrome()->mLibrary;
     mFunctions = GetChrome()->mFunctions;
 
     return true;
 }
@@ -272,17 +258,19 @@ PluginModuleChild::InitForChrome(const s
 #endif
     {
         nsresult rv = pluginFile.LoadPlugin(&mLibrary);
         if (NS_FAILED(rv))
             return false;
     }
     NS_ASSERTION(mLibrary, "couldn't open shared object");
 
-    if (!CommonInit(aParentPid, aIOLoop, aChannel)) {
+    CommonInit();
+
+    if (!Open(aChannel, aParentPid, aIOLoop)) {
         return false;
     }
 
     GetIPCChannel()->SetAbortOnError(true);
 
     // TODO: use PluginPRLibrary here
 
 #if defined(OS_LINUX) || defined(OS_BSD)
@@ -717,21 +705,23 @@ PluginModuleChild::RecvSetAudioSessionDa
     NS_ENSURE_SUCCESS(rv, IPC_OK()); // Bail early if this fails
 
     // Ignore failures here; we can't really do anything about them
     mozilla::widget::StartAudioSession();
     return IPC_OK();
 #endif
 }
 
-PPluginModuleChild*
-PluginModuleChild::AllocPPluginModuleChild(mozilla::ipc::Transport* aTransport,
-                                           base::ProcessId aOtherPid)
+mozilla::ipc::IPCResult
+PluginModuleChild::RecvInitPluginModuleChild(Endpoint<PPluginModuleChild>&& aEndpoint)
 {
-    return PluginModuleChild::CreateForContentProcess(aTransport, aOtherPid);
+    if (!CreateForContentProcess(Move(aEndpoint))) {
+        return IPC_FAIL(this, "CreateForContentProcess failed");
+    }
+    return IPC_OK();
 }
 
 PCrashReporterChild*
 PluginModuleChild::AllocPCrashReporterChild(mozilla::dom::NativeThreadId* id,
                                             uint32_t* processType)
 {
     return new CrashReporterChild();
 }
--- a/dom/plugins/ipc/PluginModuleChild.h
+++ b/dom/plugins/ipc/PluginModuleChild.h
@@ -73,19 +73,18 @@ protected:
     virtual mozilla::ipc::IPCResult RecvDisableFlashProtectedMode() override;
     virtual mozilla::ipc::IPCResult AnswerNP_GetEntryPoints(NPError* rv) override;
     virtual mozilla::ipc::IPCResult AnswerNP_Initialize(const PluginSettings& aSettings, NPError* rv) override;
     virtual mozilla::ipc::IPCResult RecvAsyncNP_Initialize(const PluginSettings& aSettings) override;
     virtual mozilla::ipc::IPCResult AnswerSyncNPP_New(PPluginInstanceChild* aActor, NPError* rv)
                                    override;
     virtual mozilla::ipc::IPCResult RecvAsyncNPP_New(PPluginInstanceChild* aActor) override;
 
-    virtual PPluginModuleChild*
-    AllocPPluginModuleChild(mozilla::ipc::Transport* aTransport,
-                            base::ProcessId aOtherProcess) override;
+    virtual mozilla::ipc::IPCResult
+    RecvInitPluginModuleChild(Endpoint<PPluginModuleChild>&& endpoint) override;
 
     virtual PPluginInstanceChild*
     AllocPPluginInstanceChild(const nsCString& aMimeType,
                               const uint16_t& aMode,
                               const InfallibleTArray<nsCString>& aNames,
                               const InfallibleTArray<nsCString>& aValues)
                               override;
 
@@ -145,33 +144,28 @@ protected:
     virtual mozilla::ipc::IPCResult RecvGatherProfile() override;
 
     virtual mozilla::ipc::IPCResult
     AnswerModuleSupportsAsyncRender(bool* aResult) override;
 public:
     explicit PluginModuleChild(bool aIsChrome);
     virtual ~PluginModuleChild();
 
-    bool CommonInit(base::ProcessId aParentPid,
-                    MessageLoop* aIOLoop,
-                    IPC::Channel* aChannel);
+    void CommonInit();
 
     // aPluginFilename is UTF8, not native-charset!
     bool InitForChrome(const std::string& aPluginFilename,
                        base::ProcessId aParentPid,
                        MessageLoop* aIOLoop,
                        IPC::Channel* aChannel);
 
-    bool InitForContent(base::ProcessId aParentPid,
-                        MessageLoop* aIOLoop,
-                        IPC::Channel* aChannel);
+    bool InitForContent(Endpoint<PPluginModuleChild>&& aEndpoint);
 
-    static PluginModuleChild*
-    CreateForContentProcess(mozilla::ipc::Transport* aTransport,
-                            base::ProcessId aOtherProcess);
+    static bool
+    CreateForContentProcess(Endpoint<PPluginModuleChild>&& aEndpoint);
 
     void CleanUp();
 
     NPError NP_Shutdown();
 
     const char* GetUserAgent();
 
     static const NPNetscapeFuncs sBrowserFuncs;
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -93,17 +93,18 @@ static const char kHangUIMinDisplayPref[
 #define CHILD_TIMEOUT_PREF kChildTimeoutPref
 #endif
 
 bool
 mozilla::plugins::SetupBridge(uint32_t aPluginId,
                               dom::ContentParent* aContentParent,
                               bool aForceBridgeNow,
                               nsresult* rv,
-                              uint32_t* runID)
+                              uint32_t* runID,
+                              ipc::Endpoint<PPluginModuleParent>* aEndpoint)
 {
     PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
     if (NS_WARN_IF(!rv) || NS_WARN_IF(!runID)) {
         return false;
     }
 
     PluginModuleChromeParent::ClearInstantiationFlag();
     RefPtr<nsPluginHost> host = nsPluginHost::GetInst();
@@ -125,17 +126,34 @@ mozilla::plugins::SetupBridge(uint32_t a
     if (chromeParent->IsStartingAsync()) {
         chromeParent->SetContentParent(aContentParent);
     }
     if (!aForceBridgeNow && chromeParent->IsStartingAsync() &&
         PluginModuleChromeParent::DidInstantiate()) {
         // We'll handle the bridging asynchronously
         return true;
     }
-    *rv = PPluginModule::Bridge(aContentParent, chromeParent);
+
+    ipc::Endpoint<PPluginModuleParent> parent;
+    ipc::Endpoint<PPluginModuleChild> child;
+
+    *rv = PPluginModule::CreateEndpoints(aContentParent->OtherPid(),
+                                         chromeParent->OtherPid(),
+                                         &parent, &child);
+    if (NS_FAILED(*rv)) {
+        return true;
+    }
+
+    *aEndpoint = Move(parent);
+
+    if (!chromeParent->SendInitPluginModuleChild(Move(child))) {
+        *rv = NS_ERROR_BRIDGE_OPEN_CHILD;
+        return true;
+    }
+
     return true;
 }
 
 #ifdef MOZ_CRASHREPORTER_INJECTOR
 
 /**
  * Use for executing CreateToolhelp32Snapshot off main thread
  */
@@ -411,21 +429,23 @@ PluginModuleContentParent::LoadModule(ui
      * message. Before it sends its response, it sends a message to create
      * PluginModuleParent instance. That message is handled by
      * PluginModuleContentParent::Initialize, which saves the instance in
      * its module mapping. We fetch it from there after LoadPlugin finishes.
      */
     dom::ContentChild* cp = dom::ContentChild::GetSingleton();
     nsresult rv;
     uint32_t runID;
+    Endpoint<PPluginModuleParent> endpoint;
     TimeStamp sendLoadPluginStart = TimeStamp::Now();
-    if (!cp->SendLoadPlugin(aPluginId, &rv, &runID) ||
+    if (!cp->SendLoadPlugin(aPluginId, &rv, &runID, &endpoint) ||
         NS_FAILED(rv)) {
         return nullptr;
     }
+    Initialize(Move(endpoint));
     TimeStamp sendLoadPluginEnd = TimeStamp::Now();
 
     PluginModuleContentParent* parent = mapping->GetModule();
     MOZ_ASSERT(parent);
     parent->mTimeBlocked += (sendLoadPluginEnd - sendLoadPluginStart);
 
     if (!mapping->IsChannelOpened()) {
         // mapping is linked into PluginModuleMapping::sModuleListHead and is
@@ -444,51 +464,49 @@ PluginModuleContentParent::LoadModule(ui
 PluginModuleContentParent::AssociatePluginId(uint32_t aPluginId,
                                              base::ProcessId aOtherPid)
 {
     DebugOnly<PluginModuleMapping*> mapping =
         PluginModuleMapping::AssociateWithProcessId(aPluginId, aOtherPid);
     MOZ_ASSERT(mapping);
 }
 
-/* static */ PluginModuleContentParent*
-PluginModuleContentParent::Initialize(mozilla::ipc::Transport* aTransport,
-                                      base::ProcessId aOtherPid)
+/* static */ void
+PluginModuleContentParent::Initialize(Endpoint<PPluginModuleParent>&& aEndpoint)
 {
     nsAutoPtr<PluginModuleMapping> moduleMapping(
-        PluginModuleMapping::Resolve(aOtherPid));
+        PluginModuleMapping::Resolve(aEndpoint.OtherPid()));
     MOZ_ASSERT(moduleMapping);
     PluginModuleContentParent* parent = moduleMapping->GetModule();
     MOZ_ASSERT(parent);
 
-    DebugOnly<bool> ok = parent->Open(aTransport, aOtherPid,
-                                      XRE_GetIOMessageLoop(),
-                                      mozilla::ipc::ParentSide);
+    DebugOnly<bool> ok = aEndpoint.Bind(parent);
     MOZ_ASSERT(ok);
 
     moduleMapping->SetChannelOpened();
 
     // Request Windows message deferral behavior on our channel. This
     // applies to the top level and all sub plugin protocols since they
     // all share the same channel.
     parent->GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION);
 
     TimeoutChanged(kContentTimeoutPref, parent);
 
     // moduleMapping is linked into PluginModuleMapping::sModuleListHead and is
     // needed later, so since this function is returning successfully we
     // forget it here.
     moduleMapping.forget();
-    return parent;
 }
 
 /* static */ void
 PluginModuleContentParent::OnLoadPluginResult(const uint32_t& aPluginId,
-                                              const bool& aResult)
+                                              const bool& aResult,
+                                              Endpoint<PPluginModuleParent>&& aEndpoint)
 {
+    Initialize(Move(aEndpoint));
     nsAutoPtr<PluginModuleMapping> moduleMapping(
         PluginModuleMapping::FindModuleByPluginId(aPluginId));
     MOZ_ASSERT(moduleMapping);
     PluginModuleContentParent* parent = moduleMapping->GetModule();
     MOZ_ASSERT(parent);
     parent->RecvNP_InitializeResult(aResult ? NPERR_NO_ERROR
                                             : NPERR_GENERIC_ERROR);
 }
--- a/dom/plugins/ipc/PluginModuleParent.h
+++ b/dom/plugins/ipc/PluginModuleParent.h
@@ -374,29 +374,31 @@ protected:
 
 class PluginModuleContentParent : public PluginModuleParent
 {
   public:
     explicit PluginModuleContentParent(bool aAllowAsyncInit);
 
     static PluginLibrary* LoadModule(uint32_t aPluginId, nsPluginTag* aPluginTag);
 
-    static PluginModuleContentParent* Initialize(mozilla::ipc::Transport* aTransport,
-                                                 base::ProcessId aOtherProcess);
+    static void OnLoadPluginResult(const uint32_t& aPluginId,
+                                   const bool& aResult,
+                                   Endpoint<PPluginModuleParent>&& aEndpoint);
 
-    static void OnLoadPluginResult(const uint32_t& aPluginId, const bool& aResult);
     static void AssociatePluginId(uint32_t aPluginId, base::ProcessId aProcessId);
 
     virtual ~PluginModuleContentParent();
 
 #if defined(XP_WIN) || defined(XP_MACOSX)
     nsresult NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error) override;
 #endif
 
   private:
+    static void Initialize(Endpoint<PPluginModuleParent>&& aEndpoint);
+
     virtual bool ShouldContinueFromReplyTimeout() override;
     virtual void OnExitedSyncSend() override;
 
 #ifdef MOZ_CRASHREPORTER_INJECTOR
     void OnCrash(DWORD processID) override {}
 #endif
 
     static PluginModuleContentParent* sSavedModuleParent;
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -5728,31 +5728,38 @@ nsLayoutUtils::AppUnitBoundsOfString(con
     totalMetrics += metrics;
     aLength -= len;
     aString += len;
   }
   return totalMetrics;
 }
 
 void
-nsLayoutUtils::DrawString(const nsIFrame*       aFrame,
-                          nsFontMetrics&        aFontMetrics,
-                          nsRenderingContext*   aContext,
-                          const char16_t*      aString,
-                          int32_t               aLength,
-                          nsPoint               aPoint,
-                          nsStyleContext*       aStyleContext)
+nsLayoutUtils::DrawString(const nsIFrame*     aFrame,
+                          nsFontMetrics&      aFontMetrics,
+                          nsRenderingContext* aContext,
+                          const char16_t*     aString,
+                          int32_t             aLength,
+                          nsPoint             aPoint,
+                          nsStyleContext*     aStyleContext,
+                          DrawStringFlags     aFlags)
 {
   nsresult rv = NS_ERROR_FAILURE;
 
   // If caller didn't pass a style context, use the frame's.
   if (!aStyleContext) {
     aStyleContext = aFrame->StyleContext();
   }
-  aFontMetrics.SetVertical(WritingMode(aStyleContext).IsVertical());
+
+  if (aFlags & DrawStringFlags::eForceHorizontal) {
+    aFontMetrics.SetVertical(false);
+  } else {
+    aFontMetrics.SetVertical(WritingMode(aStyleContext).IsVertical());
+  }
+
   aFontMetrics.SetTextOrientation(
     aStyleContext->StyleVisibility()->mTextOrientation);
 
   nsPresContext* presContext = aFrame->PresContext();
   if (presContext->BidiEnabled()) {
     nsBidiLevel level =
       nsBidiPresUtils::BidiLevelFromStyle(aStyleContext);
     rv = nsBidiPresUtils::RenderText(aString, aLength, level,
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -112,16 +112,23 @@ struct DisplayPortMarginsPropertyData {
 } // namespace mozilla
 
 // For GetDisplayPort
 enum class RelativeTo {
   ScrollPort,
   ScrollFrame
 };
 
+// Flags to customize the behavior of nsLayoutUtils::DrawString.
+enum class DrawStringFlags {
+  eDefault         = 0x0,
+  eForceHorizontal = 0x1 // Forces the text to be drawn horizontally.
+};
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DrawStringFlags)
+
 /**
  * nsLayoutUtils is a namespace class used for various helper
  * functions that are useful in multiple places in layout.  The goal
  * is not to define multiple copies of the same static helper.
  */
 class nsLayoutUtils
 {
   typedef mozilla::dom::DOMRectList DOMRectList;
@@ -1601,23 +1608,24 @@ public:
                                        DrawTarget* aDrawTarget,
                                        nscoord aWidth);
 
   static nsBoundingMetrics AppUnitBoundsOfString(const char16_t* aString,
                                                  uint32_t aLength,
                                                  nsFontMetrics& aFontMetrics,
                                                  DrawTarget* aDrawTarget);
 
-  static void DrawString(const nsIFrame*       aFrame,
-                         nsFontMetrics&        aFontMetrics,
-                         nsRenderingContext*   aContext,
-                         const char16_t*      aString,
-                         int32_t               aLength,
-                         nsPoint               aPoint,
-                         nsStyleContext*       aStyleContext = nullptr);
+  static void DrawString(const nsIFrame*     aFrame,
+                         nsFontMetrics&      aFontMetrics,
+                         nsRenderingContext* aContext,
+                         const char16_t*     aString,
+                         int32_t             aLength,
+                         nsPoint             aPoint,
+                         nsStyleContext*     aStyleContext = nullptr,
+                         DrawStringFlags     aFlags = DrawStringFlags::eDefault);
 
   /**
    * Supports only LTR or RTL. Bidi (mixed direction) is not supported.
    */
   static void DrawUniDirString(const char16_t* aString,
                                uint32_t aLength,
                                nsPoint aPoint,
                                nsFontMetrics& aFontMetrics,
--- a/layout/generic/nsCanvasFrame.h
+++ b/layout/generic/nsCanvasFrame.h
@@ -39,26 +39,19 @@ public:
 
   NS_DECL_QUERYFRAME_TARGET(nsCanvasFrame)
   NS_DECL_QUERYFRAME
   NS_DECL_FRAMEARENA_HELPERS
 
 
   virtual void DestroyFrom(nsIFrame* aDestructRoot) override;
 
-  virtual mozilla::WritingMode GetWritingMode() const override
+  mozilla::WritingMode GetWritingMode() const override
   {
-    nsIContent* rootElem = GetContent();
-    if (rootElem) {
-      nsIFrame* rootElemFrame = rootElem->GetPrimaryFrame();
-      if (rootElemFrame) {
-        return rootElemFrame->GetWritingMode();
-      }
-    }
-    return nsIFrame::GetWritingMode();
+    return nsFrame::GetWritingModeDeferringToRootElem();
   }
 
 #ifdef DEBUG
   virtual void SetInitialChildList(ChildListID     aListID,
                                    nsFrameList&    aChildList) override;
   virtual void AppendFrames(ChildListID     aListID,
                             nsFrameList&    aFrameList) override;
   virtual void InsertFrames(ChildListID     aListID,
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -3015,16 +3015,29 @@ nsFrame::FireDOMEvent(const nsAString& a
   if (target) {
     RefPtr<AsyncEventDispatcher> asyncDispatcher =
       new AsyncEventDispatcher(target, aDOMEventName, true, false);
     DebugOnly<nsresult> rv = asyncDispatcher->PostDOMEvent();
     NS_ASSERTION(NS_SUCCEEDED(rv), "AsyncEventDispatcher failed to dispatch");
   }
 }
 
+WritingMode
+nsFrame::GetWritingModeDeferringToRootElem() const
+{
+  Element* rootElem = PresContext()->Document()->GetRootElement();
+  if (rootElem) {
+    nsIFrame* primaryFrame = rootElem->GetPrimaryFrame();
+    if (primaryFrame) {
+      return primaryFrame->GetWritingMode();
+    }
+  }
+  return nsIFrame::GetWritingMode();
+}
+
 nsresult
 nsFrame::HandleEvent(nsPresContext* aPresContext, 
                      WidgetGUIEvent* aEvent,
                      nsEventStatus* aEventStatus)
 {
 
   if (aEvent->mMessage == eMouseMove) {
     // XXX If the second argument of HandleDrag() is WidgetMouseEvent,
--- a/layout/generic/nsFrame.h
+++ b/layout/generic/nsFrame.h
@@ -683,16 +683,18 @@ protected:
   void GetBoxName(nsAutoString& aName) override;
 #endif
 
   nsBoxLayoutMetrics* BoxMetrics() const;
 
   // Fire DOM event. If no aContent argument use frame's mContent.
   void FireDOMEvent(const nsAString& aDOMEventName, nsIContent *aContent = nullptr);
 
+  mozilla::WritingMode GetWritingModeDeferringToRootElem() const;
+
 private:
   void BoxReflow(nsBoxLayoutState& aState,
                  nsPresContext*    aPresContext,
                  ReflowOutput&     aDesiredSize,
                  nsRenderingContext* aRenderingContext,
                  nscoord aX,
                  nscoord aY,
                  nscoord aWidth,
--- a/layout/generic/nsPageContentFrame.cpp
+++ b/layout/generic/nsPageContentFrame.cpp
@@ -106,17 +106,17 @@ nsPageContentFrame::Reflow(nsPresContext
   FinishAndStoreOverflow(&aDesiredSize);
 
   NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
 }
 
 nsIAtom*
 nsPageContentFrame::GetType() const
 {
-  return nsGkAtoms::pageContentFrame; 
+  return nsGkAtoms::pageContentFrame;
 }
 
 #ifdef DEBUG_FRAME_DUMP
 nsresult
 nsPageContentFrame::GetFrameName(nsAString& aResult) const
 {
   return MakeFrameName(NS_LITERAL_STRING("PageContent"), aResult);
 }
--- a/layout/generic/nsPageContentFrame.h
+++ b/layout/generic/nsPageContentFrame.h
@@ -38,17 +38,22 @@ public:
   virtual bool HasTransformGetter() const override { return true; }
 
   /**
    * Get the "type" of the frame
    *
    * @see nsGkAtoms::pageContentFrame
    */
   virtual nsIAtom* GetType() const override;
-  
+
+  mozilla::WritingMode GetWritingMode() const override
+  {
+    return nsFrame::GetWritingModeDeferringToRootElem();
+  }
+
 #ifdef DEBUG_FRAME_DUMP
   // Debugging
   virtual nsresult  GetFrameName(nsAString& aResult) const override;
 #endif
 
 protected:
   explicit nsPageContentFrame(nsStyleContext* aContext) : ViewportFrame(aContext) {}
 
--- a/layout/generic/nsPageFrame.cpp
+++ b/layout/generic/nsPageFrame.cpp
@@ -167,17 +167,17 @@ nsPageFrame::Reflow(nsPresContext*      
   PR_PL(("[%d,%d]\n", aReflowInput.AvailableWidth(), aReflowInput.AvailableHeight()));
 
   NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aDesiredSize);
 }
 
 nsIAtom*
 nsPageFrame::GetType() const
 {
-  return nsGkAtoms::pageFrame; 
+  return nsGkAtoms::pageFrame;
 }
 
 #ifdef DEBUG_FRAME_DUMP
 nsresult
 nsPageFrame::GetFrameName(nsAString& aResult) const
 {
   return MakeFrameName(NS_LITERAL_STRING("Page"), aResult);
 }
@@ -388,17 +388,19 @@ nsPageFrame::DrawHeaderFooter(nsRenderin
 
     // set up new clip and draw the text
     gfx->Save();
     gfx->Clip(NSRectToSnappedRect(aRect, PresContext()->AppUnitsPerDevPixel(),
                                   *drawTarget));
     gfx->SetColor(Color(0.f, 0.f, 0.f));
     nsLayoutUtils::DrawString(this, aFontMetrics, &aRenderingContext,
                               str.get(), str.Length(),
-                              nsPoint(x, y + aAscent));
+                              nsPoint(x, y + aAscent),
+                              nullptr,
+                              DrawStringFlags::eForceHorizontal);
     gfx->Restore();
   }
 }
 
 /**
  * Remove all leaf display items that are not for descendants of
  * aBuilder->GetReferenceFrame() from aList.
  * @param aPage the page we're constructing the display list for
@@ -730,17 +732,17 @@ nsPageBreakFrame::Reflow(nsPresContext* 
   // DidReflow will always get called before the next Reflow() call.
   mHaveReflowed = true;
   aStatus = NS_FRAME_COMPLETE; 
 }
 
 nsIAtom*
 nsPageBreakFrame::GetType() const
 {
-  return nsGkAtoms::pageBreakFrame; 
+  return nsGkAtoms::pageBreakFrame;
 }
 
 #ifdef DEBUG_FRAME_DUMP
 nsresult
 nsPageBreakFrame::GetFrameName(nsAString& aResult) const
 {
   return MakeFrameName(NS_LITERAL_STRING("PageBreak"), aResult);
 }
--- a/layout/generic/nsPageFrame.h
+++ b/layout/generic/nsPageFrame.h
@@ -27,23 +27,28 @@ public:
                       ReflowOutput& aDesiredSize,
                       const ReflowInput& aMaxSize,
                       nsReflowStatus&      aStatus) override;
 
   virtual void BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                                 const nsRect&           aDirtyRect,
                                 const nsDisplayListSet& aLists) override;
 
+  mozilla::WritingMode GetWritingMode() const override
+  {
+    return nsFrame::GetWritingModeDeferringToRootElem();
+  }
+
   /**
    * Get the "type" of the frame
    *
    * @see nsGkAtoms::pageFrame
    */
   virtual nsIAtom* GetType() const override;
-  
+
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult  GetFrameName(nsAString& aResult) const override;
 #endif
 
   //////////////////
   // For Printing
   //////////////////
 
--- a/layout/generic/nsSimplePageSequenceFrame.cpp
+++ b/layout/generic/nsSimplePageSequenceFrame.cpp
@@ -81,24 +81,29 @@ NS_QUERYFRAME_TAIL_INHERITING(nsContaine
 //----------------------------------------------------------------------
 
 void
 nsSimplePageSequenceFrame::SetDesiredSize(ReflowOutput& aDesiredSize,
                                           const ReflowInput& aReflowInput,
                                           nscoord aWidth,
                                           nscoord aHeight)
 {
-    // Aim to fill the whole size of the document, not only so we
-    // can act as a background in print preview but also handle overflow
-    // in child page frames correctly.
-    // Use availableWidth so we don't cause a needless horizontal scrollbar.
-    aDesiredSize.Width() = std::max(aReflowInput.AvailableWidth(),
-                                nscoord(aWidth * PresContext()->GetPrintPreviewScale()));
-    aDesiredSize.Height() = std::max(aReflowInput.ComputedHeight(),
-                                 nscoord(aHeight * PresContext()->GetPrintPreviewScale()));
+  // Aim to fill the whole size of the document, not only so we
+  // can act as a background in print preview but also handle overflow
+  // in child page frames correctly.
+  // Use availableISize so we don't cause a needless horizontal scrollbar.
+  WritingMode wm = aReflowInput.GetWritingMode();
+  nscoord scaledWidth = aWidth * PresContext()->GetPrintPreviewScale();
+  nscoord scaledHeight = aHeight * PresContext()->GetPrintPreviewScale();
+
+  nscoord scaledISize = (wm.IsVertical() ? scaledHeight : scaledWidth);
+  nscoord scaledBSize = (wm.IsVertical() ? scaledWidth : scaledHeight);
+
+  aDesiredSize.ISize(wm) = std::max(scaledISize, aReflowInput.AvailableISize());
+  aDesiredSize.BSize(wm) = std::max(scaledBSize, aReflowInput.ComputedBSize());
 }
 
 // Helper function to compute the offset needed to center a child
 // page-frame's margin-box inside our content-box.
 nscoord
 nsSimplePageSequenceFrame::ComputeCenteringMargin(
   nscoord aContainerContentBoxWidth,
   nscoord aChildPaddingBoxWidth,
@@ -128,21 +133,26 @@ nsSimplePageSequenceFrame::ComputeCenter
 
   // To center the child, we want to give it an additional left-margin of half
   // of the extra space.  And then, we have to scale that space back down, so
   // that it'll produce the correct scaled-up amount when we render (because
   // rendering will scale it back up):
   return NSToCoordRound(scaledExtraSpace * 0.5 / ppScale);
 }
 
+/*
+ * Note: we largely position/size out our children (page frames) using
+ * \*physical\* x/y/width/height values, because the print preview UI is always
+ * arranged in the same orientation, regardless of writing mode.
+ */
 void
-nsSimplePageSequenceFrame::Reflow(nsPresContext*          aPresContext,
-                                  ReflowOutput&     aDesiredSize,
+nsSimplePageSequenceFrame::Reflow(nsPresContext*     aPresContext,
+                                  ReflowOutput&      aDesiredSize,
                                   const ReflowInput& aReflowInput,
-                                  nsReflowStatus&          aStatus)
+                                  nsReflowStatus&    aStatus)
 {
   MarkInReflow();
   NS_PRECONDITION(aPresContext->IsRootPaginatedDocument(),
                   "A Page Sequence is only for real pages");
   DO_GLOBAL_REFLOW_COUNT("nsSimplePageSequenceFrame");
   DISPLAY_REFLOW(aPresContext, this, aReflowInput, aDesiredSize, aStatus);
   NS_FRAME_TRACE_REFLOW_IN("nsSimplePageSequenceFrame::Reflow");
 
@@ -158,17 +168,17 @@ nsSimplePageSequenceFrame::Reflow(nsPres
 
     if (GetRect().Width() != aDesiredSize.Width()) {
       // Our width is changing; we need to re-center our children (our pages).
       for (nsFrameList::Enumerator e(mFrames); !e.AtEnd(); e.Next()) {
         nsIFrame* child = e.get();
         nsMargin pageCSSMargin = child->GetUsedMargin();
         nscoord centeringMargin =
           ComputeCenteringMargin(aReflowInput.ComputedWidth(),
-                                 child->GetRect().width,
+                                 child->GetRect().Width(),
                                  pageCSSMargin);
         nscoord newX = pageCSSMargin.left + centeringMargin;
 
         // Adjust the child's x-position:
         child->MovePositionBy(nsPoint(newX - child->GetNormalPosition().x, 0));
       }
     }
     return;
@@ -237,23 +247,25 @@ nsSimplePageSequenceFrame::Reflow(nsPres
   for (nsFrameList::Enumerator e(mFrames); !e.AtEnd(); e.Next()) {
     nsIFrame* kidFrame = e.get();
     // Set the shared data into the page frame before reflow
     nsPageFrame * pf = static_cast<nsPageFrame*>(kidFrame);
     pf->SetSharedPageData(mPageData);
 
     // Reflow the page
     ReflowInput kidReflowInput(aPresContext, aReflowInput, kidFrame,
-                                     LogicalSize(kidFrame->GetWritingMode(),
+                               LogicalSize(kidFrame->GetWritingMode(),
                                                  pageSize));
     nsReflowStatus  status;
 
-    kidReflowInput.SetComputedWidth(kidReflowInput.AvailableWidth());
+    kidReflowInput.SetComputedISize(kidReflowInput.AvailableISize());
     //kidReflowInput.SetComputedHeight(kidReflowInput.AvailableHeight());
-    PR_PL(("AV W: %d   H: %d\n", kidReflowInput.AvailableWidth(), kidReflowInput.AvailableHeight()));
+    PR_PL(("AV ISize: %d   BSize: %d\n",
+           kidReflowInput.AvailableISize(),
+           kidReflowInput.AvailableBSize()));
 
     nsMargin pageCSSMargin = kidReflowInput.ComputedPhysicalMargin();
     y += pageCSSMargin.top;
 
     nscoord x = pageCSSMargin.left;
 
     // Place and size the page.
     ReflowChild(kidFrame, aPresContext, kidSize, kidReflowInput, x, y, 0, status);
--- a/layout/generic/nsSimplePageSequenceFrame.h
+++ b/layout/generic/nsSimplePageSequenceFrame.h
@@ -55,16 +55,21 @@ class nsSimplePageSequenceFrame : public
                                   public nsIPageSequenceFrame {
 public:
   friend nsSimplePageSequenceFrame* NS_NewSimplePageSequenceFrame(nsIPresShell* aPresShell,
                                                                   nsStyleContext* aContext);
 
   NS_DECL_QUERYFRAME
   NS_DECL_FRAMEARENA_HELPERS
 
+  mozilla::WritingMode GetWritingMode() const override
+  {
+    return nsFrame::GetWritingModeDeferringToRootElem();
+  }
+
   // nsIFrame
   void Reflow(nsPresContext* aPresContext,
               ReflowOutput& aDesiredSize,
               const ReflowInput& aMaxSize,
               nsReflowStatus& aStatus) override;
 
   void BuildDisplayList(nsDisplayListBuilder*   aBuilder,
                         const nsRect&           aDirtyRect,
new file mode 100644
--- /dev/null
+++ b/layout/reftests/printing/1166147-ref.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<!--
+This test passes if the pages generated are identical for both vertical-lr and
+horizontal-tb writing-modes by means of making the size of the content we are
+printing equal to exactly the same number of pages being generated in each
+writing mode.
+This sizing is calculated like so:
+
+1. It is important to note that irrespective of the writing-mode, the print
+   UI always lays out printed pages vertically. Therefore, it is possible
+   to equate the printed content of two different writing modes if both
+   cases generate the exact same number of pages (and of course no text on
+   the pages, which is why we use background color since it is independent
+   of writing mode).
+2. To avoid an unnecessary vertical scrollbar (since scrolled content will be
+   clipped anyway in the reftest snapshot), the maximum number of 5X3in pages
+   that we should generate for the reftest snapshot are equal to 3.
+3. Considering a margin of 0.5in on each side of the 5X3in page, we get the
+   size of the page content area to be 4X2in per page and to generate exactly 3
+   printed pages from this in horizontal-tb writing mode, we need a printable
+   area of 4X6in (as used below).
+
+Similarly, the size for the test case printable area is calculated, only
+considering a vertical-rl writing mode.
+
+It is important to note here that when printing this test outside of the test
+harness, the background color will not show since we omit printing and
+previewing of background colors by default via the browser printing path.
+-->
+<html class="reftest-print">
+  <body style="margin:0;">
+    <div style="background: teal; width:4in; height:6in;">
+    </div>
+  </body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/layout/reftests/printing/1166147.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<!--
+This test checks if content is paginated correctly in the horizontal
+direction when printing. The test passes if the pages generated
+are identical for both vertical-lr and horizontal-tb writing-modes by means of
+making the size of the content we are printing equal to exactly the same number
+of pages being generated in each writing mode.
+This sizing is calculated like so:
+
+1. It is important to note that irrespective of the writing-mode, the print
+   UI always lays out printed pages vertically. Therefore, it is possible
+   to equate the printed content of two different writing modes if both
+   cases generate the exact same number of pages (and of course no text on
+   the pages, which is why we use background color since it is independent
+   of writing mode).
+2. To avoid an unnecessary vertical scrollbar (since scrolled content will be
+   clipped anyway in the reftest snapshot), the maximum number of 5X3in pages
+   that we should generate for the reftest snapshot are equal to 3.
+3. Considering a margin of 0.5in on each side of the 5X3in page, we get the
+   size of the page content area to be 4X2in per page and to generate exactly 3
+   printed pages from this in the vertical-rl writing mode, we need a printable
+   area of 12X2in (as used below).
+
+Similarly, the size for the reference printable area is calculated, only
+considering a horizontal-tb writing mode.
+
+It is important to note here that when printing this test outside of the test
+harness, the background color will not show since we omit printing and
+previewing of background colors by default via the browser printing path.
+-->
+<html class="reftest-print" style="writing-mode: vertical-rl;">
+  <body style="margin:0;">
+    <div style="background: teal; width:12in; height:2in;">
+    </div>
+  </body>
+</html>
\ No newline at end of file
--- a/layout/reftests/printing/reftest.list
+++ b/layout/reftests/printing/reftest.list
@@ -31,9 +31,10 @@ fuzzy-if(cocoaWidget,1,5000) == 745025-1
 == 820496-1.html 820496-1-ref.html
 
 # NOTE: These tests don't yet rigorously test what they're
 # trying to test (shrink-to-fit behavior), due to bug 967311.
 == 960822.html 960822-ref.html
 == 966419-1.html 966419-1-ref.html
 == 966419-2.html 966419-2-ref.html
 # asserts(3) HTTP(..) fails 1108104.html 1108104-ref.html # bug 1067755, 1135556
+== 1166147.html 1166147-ref.html
 == 1321803-1a.html 1321803-1-ref.html
--- a/testing/mozharness/configs/releases/bouncer_firefox_beta.py
+++ b/testing/mozharness/configs/releases/bouncer_firefox_beta.py
@@ -67,29 +67,16 @@ config = {
             "add-locales": True,
             "paths": {
                 "win32": {
                     "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20Stub%%20%(version)s.exe",
                     "bouncer-platform": "win",
                 },
             },
         },
-        "sha1-installer": {
-            "product-name": "Firefox-%(version)s-sha1",
-            "check_uptake": True,
-            "alias": "firefox-beta-sha1",
-            "ssl-only": True,
-            "add-locales": True,
-            "paths": {
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/win32-sha1/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win",
-                },
-            },
-        },
         "complete-mar": {
             "product-name": "Firefox-%(version)s-Complete",
             "check_uptake": True,
             "ssl-only": False,
             "add-locales": True,
             "paths": {
                 "linux": {
                     "path": "/firefox/releases/%(version)s/update/linux-i686/:lang/firefox-%(version)s.complete.mar",
--- a/testing/mozharness/configs/releases/bouncer_firefox_esr.py
+++ b/testing/mozharness/configs/releases/bouncer_firefox_esr.py
@@ -56,20 +56,19 @@ config = {
                 "win64": {
                     "path": "/firefox/releases/%(version)s/win64/:lang/Firefox%%20Setup%%20%(version)s.exe",
                     "bouncer-platform": "win64",
                 },
             },
         },
         "sha1-installer": {
             "product-name": "Firefox-%(version)s-sha1",
-            # TODO: enable when sha1 installers are automated in bug 1290179
-            "check_uptake": False,
-            # TODO: enable when sha1 installers are automated in bug 1290179
-            # "alias": "firefox-esr-sha1",
+            "check_uptake": True,
+            # XP/Vista Release users are redicted to ESR52
+            "alias": "firefox-sha1",
             "ssl-only": True,
             "add-locales": True,
             "paths": {
                 "win32": {
                     "path": "/firefox/releases/%(version)s/win32-sha1/:lang/Firefox%%20Setup%%20%(version)s.exe",
                     "bouncer-platform": "win",
                 },
             },
--- a/testing/mozharness/configs/releases/bouncer_firefox_release.py
+++ b/testing/mozharness/configs/releases/bouncer_firefox_release.py
@@ -99,29 +99,16 @@ config = {
             "add-locales": True,
             "paths": {
                 "win32": {
                     "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20Stub%%20%(version)s.exe",
                     "bouncer-platform": "win",
                 },
             },
         },
-        "sha1-installer": {
-            "product-name": "Firefox-%(version)s-sha1",
-            "check_uptake": True,
-            "alias": "firefox-sha1",
-            "ssl-only": True,
-            "add-locales": True,
-            "paths": {
-                "win32": {
-                    "path": "/firefox/releases/%(version)s/win32-sha1/:lang/Firefox%%20Setup%%20%(version)s.exe",
-                    "bouncer-platform": "win",
-                },
-            },
-        },
         "complete-mar": {
             "product-name": "Firefox-%(version)s-Complete",
             "check_uptake": True,
             "ssl-only": False,
             "add-locales": True,
             "paths": {
                 "linux": {
                     "path": "/firefox/releases/%(version)s/update/linux-i686/:lang/firefox-%(version)s.complete.mar",
--- a/toolkit/components/telemetry/Scalars.yaml
+++ b/toolkit/components/telemetry/Scalars.yaml
@@ -10,275 +10,321 @@ browser.engagement:
       The count of maximum number of tabs open during a subsession,
       across all windows, including tabs in private windows and restored
       at startup.
     expires: "55"
     kind: uint
     notification_emails:
       - rweiss@mozilla.com
     release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
 
   tab_open_event_count:
     bug_numbers:
       - 1271304
     description: >
       The count of tab open events per subsession, across all windows, after the
       session has been restored. This includes tab open events from private windows
       and from manual session restorations (i.e. after crashes and from about:home).
     expires: "55"
     kind: uint
     notification_emails:
       - rweiss@mozilla.com
     release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
 
   max_concurrent_window_count:
     bug_numbers:
       - 1271304
     description: >
       The count of maximum number of browser windows open during a subsession. This
       includes private windows and the ones opened when starting the browser.
     expires: "55"
     kind: uint
     notification_emails:
       - rweiss@mozilla.com
     release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
 
   window_open_event_count:
     bug_numbers:
       - 1271304
     description: >
       The count of browser window open events per subsession, after the session
       has been restored. The count includes private windows and the ones from manual
       session restorations (i.e. after crashes and from about:home).
     expires: "55"
     kind: uint
     notification_emails:
       - rweiss@mozilla.com
     release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
 
   total_uri_count:
     bug_numbers:
       - 1271313
     description: >
       The count of the total non-unique http(s) URIs visited in a subsession, including
       page reloads, after the session has been restored. This does not include background
       page requests and URIs from embedded pages or private browsing.
     expires: "55"
     kind: uint
     notification_emails:
       - rweiss@mozilla.com
     release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
 
   unfiltered_uri_count:
     bug_numbers:
       - 1304647
     description: >
       The count of the total non-unique URIs visited in a subsession, not restricted to
       a specific protocol, including page reloads and about:* pages (other than initial
       pages such as about:blank, ...), after the session has been restored. This does
       not include background page requests and URIs from embedded pages or private browsing.
     expires: "55"
     kind: uint
     notification_emails:
       - bcolloran@mozilla.com
     release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
 
   unique_domains_count:
     bug_numbers:
       - 1271310
     description: >
       The count of the unique domains visited in a subsession, after the session
       has been restored. Subdomains under eTLD are aggregated after the first level
       (i.e. test.example.com and other.example.com are only counted once).
       This does not include background page requests and domains from embedded pages
       or private browsing. The count is limited to 100 unique domains.
     expires: "55"
     kind: uint
     notification_emails:
       - rweiss@mozilla.com
     release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
 
 # The following section contains the browser engagement scalars.
 browser.engagement.navigation:
   urlbar:
     bug_numbers:
       - 1271313
     description: >
       The count URI loads triggered in a subsession from the urlbar (awesomebar),
       broken down by the originating action.
     expires: "55"
     kind: uint
     keyed: true
     notification_emails:
       - bcolloran@mozilla.com
     release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
 
   searchbar:
     bug_numbers:
       - 1271313
     description: >
       The count URI loads triggered in a subsession from the searchbar,
       broken down by the originating action.
     expires: "55"
     kind: uint
     keyed: true
     notification_emails:
       - bcolloran@mozilla.com
     release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
 
   about_home:
     bug_numbers:
       - 1271313
     description: >
       The count URI loads triggered in a subsession from about:home,
       broken down by the originating action.
     expires: "55"
     kind: uint
     keyed: true
     notification_emails:
       - bcolloran@mozilla.com
     release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
 
   about_newtab:
     bug_numbers:
       - 1271313
     description: >
       The count URI loads triggered in a subsession from about:newtab,
       broken down by the originating action.
     expires: "55"
     kind: uint
     keyed: true
     notification_emails:
       - bcolloran@mozilla.com
     release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
 
   contextmenu:
     bug_numbers:
       - 1271313
     description: >
       The count URI loads triggered in a subsession from the contextmenu,
       broken down by the originating action.
     expires: "55"
     kind: uint
     keyed: true
     notification_emails:
       - bcolloran@mozilla.com
     release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
 
 # The following section is for probes testing the Telemetry system. They will not be
 # submitted in pings and are only used for testing.
 telemetry.test:
   unsigned_int_kind:
     bug_numbers:
       - 1276190
     description: >
       This is a test uint type with a really long description, maybe spanning even multiple
       lines, to just prove a point: everything works just fine.
     expires: never
     kind: uint
     notification_emails:
       - telemetry-client-dev@mozilla.com
+    record_in_processes:
+      - 'main'
 
   string_kind:
     bug_numbers:
       - 1276190
     description: A string test type with a one line comment that works just fine!
     expires: never
     kind: string
     notification_emails:
       - telemetry-client-dev@mozilla.com
+    record_in_processes:
+      - 'main'
 
   boolean_kind:
     bug_numbers:
       - 1281214
     description: A boolean test type with a one line comment that works just fine!
     expires: never
     kind: boolean
     notification_emails:
       - telemetry-client-dev@mozilla.com
+    record_in_processes:
+      - 'main'
 
   expired:
     bug_numbers:
       - 1276190
     description: This is an expired testing scalar; not meant to be touched.
     expires: 4.0a1
     kind: uint
     notification_emails:
       - telemetry-client-dev@mozilla.com
+    record_in_processes:
+      - 'main'
 
   unexpired:
     bug_numbers:
       - 1276190
     description: This is an unexpired testing scalar; not meant to be touched.
     expires: "375.0"
     kind: uint
     notification_emails:
       - telemetry-client-dev@mozilla.com
+    record_in_processes:
+      - 'main'
 
   release_optin:
     bug_numbers:
       - 1276190
     description: A testing scalar; not meant to be touched.
     expires: never
     kind: uint
     notification_emails:
       - telemetry-client-dev@mozilla.com
     release_channel_collection: opt-in
+    record_in_processes:
+      - 'main'
 
   release_optout:
     bug_numbers:
       - 1276190
     description: A testing scalar; not meant to be touched.
     expires: never
     kind: uint
     notification_emails:
       - telemetry-client-dev@mozilla.com
     release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
 
   keyed_release_optin:
     bug_numbers:
       - 1277806
     description: A testing scalar; not meant to be touched.
     expires: never
     kind: uint
     keyed: true
     notification_emails:
       - telemetry-client-dev@mozilla.com
     release_channel_collection: opt-in
+    record_in_processes:
+      - 'main'
 
   keyed_release_optout:
     bug_numbers:
       - 1277806
     description: A testing scalar; not meant to be touched.
     expires: never
     kind: uint
     keyed: true
     notification_emails:
       - telemetry-client-dev@mozilla.com
     release_channel_collection: opt-out
+    record_in_processes:
+      - 'main'
 
   keyed_expired:
     bug_numbers:
       - 1277806
     description: This is an expired testing scalar; not meant to be touched.
     expires: 4.0a1
     kind: uint
     keyed: true
     notification_emails:
       - telemetry-client-dev@mozilla.com
+    record_in_processes:
+      - 'main'
 
   keyed_unsigned_int:
     bug_numbers:
       - 1277806
     description: A testing keyed uint scalar; not meant to be touched.
     expires: never
     kind: uint
     keyed: true
     notification_emails:
       - telemetry-client-dev@mozilla.com
+    record_in_processes:
+      - 'main'
 
   keyed_boolean_kind:
     bug_numbers:
       - 1277806
     description: A testing keyed boolean scalar; not meant to be touched.
     expires: never
     kind: boolean
     keyed: true
--- a/toolkit/components/telemetry/docs/collection/scalars.rst
+++ b/toolkit/components/telemetry/docs/collection/scalars.rst
@@ -94,30 +94,30 @@ A probe can be defined as follows:
 Required Fields
 ---------------
 
 - ``bug_numbers``: A list of unsigned integers representing the number of the bugs the probe was introduced in.
 - ``description``: A single or multi-line string describing what data the probe collects and when it gets collected.
 - ``expires``: The version number in which the scalar expires, e.g. "30"; a version number of type "N" and "N.0" is automatically converted to "N.0a1" in order to expire the scalar also in the development channels. A telemetry probe acting on an expired scalar will print a warning into the browser console. For scalars that never expire the value ``never`` can be used.
 - ``kind``: A string representing the scalar type. Allowed values are ``uint``, ``string`` and ``boolean``.
 - ``notification_emails``: A list of email addresses to notify with alerts of expiring probes. More importantly, these are used by the data steward to verify that the probe is still useful.
+- ``record_in_processes``: A list of processes the scalar is allowed to record in. Currently supported values are:
+
+  - ``main``;
+  - ``content``;
+  - ``gpu``;
+  - ``all_child`` (record in all the child processes);
+  - ``all`` (record in all the processes).
 
 Optional Fields
 ---------------
 
 - ``cpp_guard``: A string that gets inserted as an ``#ifdef`` directive around the automatically generated C++ declaration. This is typically used for platform-specific scalars, e.g. ``ANDROID``.
 - ``release_channel_collection``: This can be either ``opt-in`` (default) or ``opt-out``. With the former the scalar is submitted by default on pre-release channels; on the release channel only if the user opted into additional data collection. With the latter the scalar is submitted by default on release and pre-release channels, unless the user opted out.
 - ``keyed``: A boolean that determines whether this is a keyed scalar. It defaults to ``False``.
-- ``record_in_processes``: A list of processes the scalar is allowed to record in. Currently supported values are:
-
-  - ``main`` (the default value);
-  - ``content``;
-  - ``gpu``;
-  - ``all_child`` (record in all the child processes);
-  - ``all`` (record in all the processes).
 
 String type restrictions
 ------------------------
 To prevent abuses, the content of a string scalar is limited to 50 characters in length. Trying
 to set a longer string will result in an error and no string being set.
 
 Keyed Scalars
 -------------
--- a/toolkit/components/telemetry/parse_scalars.py
+++ b/toolkit/components/telemetry/parse_scalars.py
@@ -86,24 +86,24 @@ class ScalarType:
         """
 
         # The required and optional fields in a scalar type definition.
         REQUIRED_FIELDS = {
             'bug_numbers': list, # This contains ints. See LIST_FIELDS_CONTENT.
             'description': basestring,
             'expires': basestring,
             'kind': basestring,
-            'notification_emails': list # This contains strings. See LIST_FIELDS_CONTENT.
+            'notification_emails': list, # This contains strings. See LIST_FIELDS_CONTENT.
+            'record_in_processes': list,
         }
 
         OPTIONAL_FIELDS = {
             'cpp_guard': basestring,
             'release_channel_collection': basestring,
             'keyed': bool,
-            'record_in_processes': list,
         }
 
         # The types for the data within the fields that hold lists.
         LIST_FIELDS_CONTENT = {
             'bug_numbers': int,
             'notification_emails': basestring,
             'record_in_processes': basestring,
         }
@@ -224,17 +224,17 @@ class ScalarType:
     @property
     def notification_emails(self):
         """Get the list of notification emails"""
         return self._definition['notification_emails']
 
     @property
     def record_in_processes(self):
         """Get the non-empty list of processes to record data in"""
-        return self._definition.get('record_in_processes', ['main'])
+        return self._definition['record_in_processes']
 
     @property
     def record_in_processes_enum(self):
         """Get the non-empty list of flags representing the processes to record data in"""
         return [KNOWN_PROCESS_FLAGS.get(p) for p in self.record_in_processes]
 
     @property
     def dataset(self):
--- a/toolkit/content/aboutTelemetry.js
+++ b/toolkit/content/aboutTelemetry.js
@@ -1122,19 +1122,27 @@ var CapturedStacks = {
                   capturedStacks.stacks.length > 0;
     setHasData("captured-stacks-section", hasData);
     if (!hasData) {
       return;
     }
 
     let stacks = capturedStacks.stacks;
     let memoryMap = capturedStacks.memoryMap;
+    let captures = capturedStacks.captures;
 
-    StackRenderer.renderStacks("captured-stacks", stacks, memoryMap, () => {});
+    StackRenderer.renderStacks("captured-stacks", stacks, memoryMap,
+                              (index) => this.renderCaptureHeader(index, captures));
   },
+
+  renderCaptureHeader: function CaptureStacks_renderCaptureHeader(index, captures) {
+    let key = captures[index][0];
+    let cardinality = captures[index][2];
+    StackRenderer.renderHeader("captured-stacks", [key, cardinality]);
+  }
 };
 
 var ThreadHangStats = {
 
   /**
    * Renders raw thread hang stats data
    */
   render(aPayload) {
@@ -1830,30 +1838,28 @@ function setupListeners() {
 
   document.getElementById("captured-stacks-fetch-symbols").addEventListener("click",
     function() {
       if (!gPingData) {
         return;
       }
       let capturedStacks = gPingData.payload.processes.parent.capturedStacks;
       let req = new SymbolicationRequest("captured-stacks",
-                                         CapturedStacks.render,
+                                         CapturedStacks.renderCaptureHeader,
                                          capturedStacks.memoryMap,
                                          capturedStacks.stacks,
-                                         null);
+                                         capturedStacks.captures);
       req.fetchSymbols();
   });
 
   document.getElementById("captured-stacks-hide-symbols").addEventListener("click",
     function() {
-      if (!gPingData) {
-        return;
+      if (gPingData) {
+        CapturedStacks.render(gPingData.payload);
       }
-
-      CapturedStacks.render(gPingData);
   });
 
   document.getElementById("late-writes-fetch-symbols").addEventListener("click",
     function() {
       if (!gPingData) {
         return;
       }
 
--- a/toolkit/locales/en-US/chrome/global/aboutTelemetry.dtd
+++ b/toolkit/locales/en-US/chrome/global/aboutTelemetry.dtd
@@ -152,21 +152,21 @@ Ping
   (No data collected)
 ">
 
 <!ENTITY aboutTelemetry.fullSqlWarning "
   NOTE: Slow SQL debugging is enabled. Full SQL strings may be displayed below but they will not be submitted to Telemetry.
 ">
 
 <!ENTITY aboutTelemetry.fetchSymbols "
-  Fetch function names for hang stacks
+  Fetch function names for stacks
 ">
 
 <!ENTITY aboutTelemetry.hideSymbols "
-  Show raw data from hangs
+  Show raw stack data
 ">
 
 <!ENTITY aboutTelemetry.filterText "
   Filter (strings or /regexp/)
 ">
 
 <!ENTITY aboutTelemetry.payloadChoiceHeader "
   Payload
--- a/toolkit/locales/en-US/chrome/global/aboutTelemetry.properties
+++ b/toolkit/locales/en-US/chrome/global/aboutTelemetry.properties
@@ -40,16 +40,21 @@ slowSqlAverage = Avg. Time (ms)
 slowSqlStatement = Statement
 
 # Note to translators:
 # - The %1$S will be replaced with the number of the hang
 # - The %2$S will be replaced with the duration of the hang
 chrome-hangs-title = Hang Report #%1$S (%2$S seconds)
 
 # Note to translators:
+# - The %1$S will be replaced with the string key for this stack.
+# - The %2$S will be replaced with the number of times this stack was captured.
+captured-stacks-title = %1$S (capture count: %2$S)
+
+# Note to translators:
 # - The %1$S will be replaced with the number of the late write
 late-writes-title = Late Write #%1$S
 
 stackTitle = Stack:
 
 memoryMapTitle = Memory map:
 
 errorFetchingSymbols = An error occurred while fetching symbols. Check that you are connected to the Internet and try again.