Backed out changeset 4f7d58a640b8 (bug 1483173) for browser_jsterm_screenshot_command_clipboard.js failures CLOSED TREE
authorCiure Andrei <aciure@mozilla.com>
Tue, 21 Aug 2018 22:03:10 +0300
changeset 487771 6c479457064ffd32edfdb78fd43538a87f17dd55
parent 487770 568230ce860e86538ee9d567fdd6fcec46142c8d
child 487772 7e2003fdace0e0df5db6dc1e6e95d2b526884692
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1483173
milestone63.0a1
backs out4f7d58a640b8d4872fad4c2589f8c934f7e0b246
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out changeset 4f7d58a640b8 (bug 1483173) for browser_jsterm_screenshot_command_clipboard.js failures CLOSED TREE
devtools/client/definitions.js
devtools/client/inspector/inspector.js
devtools/client/webconsole/components/JSTerm.js
devtools/server/actors/moz.build
devtools/server/actors/screenshot.js
devtools/server/actors/webconsole/moz.build
devtools/server/actors/webconsole/screenshot.js
devtools/server/actors/webconsole/utils.js
devtools/server/main.js
devtools/shared/fronts/moz.build
devtools/shared/fronts/screenshot.js
devtools/shared/moz.build
devtools/shared/screenshot/capture.js
devtools/shared/screenshot/moz.build
devtools/shared/screenshot/save.js
devtools/shared/specs/moz.build
devtools/shared/specs/screenshot.js
devtools/shared/webconsole/moz.build
devtools/shared/webconsole/screenshot-helper.js
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -28,17 +28,16 @@ loader.lazyGetter(this, "AccessibilityPa
 loader.lazyGetter(this, "ApplicationPanel", () => require("devtools/client/application/panel").ApplicationPanel);
 
 // Other dependencies
 loader.lazyRequireGetter(this, "AccessibilityStartup", "devtools/client/accessibility/accessibility-startup", true);
 loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
 loader.lazyRequireGetter(this, "CommandState", "devtools/shared/gcli/command-state", true);
 loader.lazyRequireGetter(this, "ResponsiveUIManager", "devtools/client/responsive.html/manager", true);
 loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
-loader.lazyRequireGetter(this, "getScreenshotFront", "resource://devtools/shared/fronts/screenshot", true);
 
 const {MultiLocalizationHelper} = require("devtools/shared/l10n");
 const L10N = new MultiLocalizationHelper(
   "devtools/client/locales/startup.properties",
   "devtools/startup/locales/key-shortcuts.properties"
 );
 
 var Tools = {};
@@ -581,27 +580,26 @@ exports.ToolboxButtons = [
     },
     teardown(toolbox, onChange) {
       ResponsiveUIManager.off("on", onChange);
       ResponsiveUIManager.off("off", onChange);
     }
   },
   { id: "command-button-screenshot",
     description: l10n("toolbox.buttons.screenshot"),
-    isTargetSupported: target => !target.chrome && target.hasActor("screenshot"),
-    async onClick(event, toolbox) {
+    isTargetSupported: target => target.isLocalTab,
+    onClick(event, toolbox) {
       // Special case for screenshot button to check for clipboard preference
       const clipboardEnabled = Services.prefs
         .getBoolPref("devtools.screenshot.clipboard.enabled");
-      const args = { fullpage: true, file: true };
+      let args = "--fullpage --file";
       if (clipboardEnabled) {
-        args.clipboard = true;
+        args += " --clipboard";
       }
-      const screenshotFront = getScreenshotFront(toolbox.target);
-      await screenshotFront.captureAndSave(toolbox.win, args);
+      CommandUtils.executeOnTarget(toolbox.target, "screenshot " + args);
     }
   },
   { id: "command-button-rulers",
     description: l10n("toolbox.buttons.rulers"),
     isTargetSupported: target => target.isLocalTab,
     onClick(event, toolbox) {
       CommandUtils.executeOnTarget(toolbox.target, "rulers");
     },
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -28,20 +28,19 @@ loader.lazyRequireGetter(this, "KeyShort
 loader.lazyRequireGetter(this, "InspectorSearch", "devtools/client/inspector/inspector-search", true);
 loader.lazyRequireGetter(this, "ToolSidebar", "devtools/client/inspector/toolsidebar", true);
 loader.lazyRequireGetter(this, "MarkupView", "devtools/client/inspector/markup/markup");
 loader.lazyRequireGetter(this, "HighlightersOverlay", "devtools/client/inspector/shared/highlighters-overlay");
 loader.lazyRequireGetter(this, "nodeConstants", "devtools/shared/dom-node-constants");
 loader.lazyRequireGetter(this, "Menu", "devtools/client/framework/menu");
 loader.lazyRequireGetter(this, "MenuItem", "devtools/client/framework/menu-item");
 loader.lazyRequireGetter(this, "ExtensionSidebar", "devtools/client/inspector/extensions/extension-sidebar");
+loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
 loader.lazyRequireGetter(this, "clipboardHelper", "devtools/shared/platform/clipboard");
 loader.lazyRequireGetter(this, "openContentLink", "devtools/client/shared/link", true);
-loader.lazyRequireGetter(this, "getScreenshotFront", "devtools/shared/fronts/screenshot", true);
-loader.lazyRequireGetter(this, "saveScreenshot", "devtools/shared/screenshot/save");
 
 loader.lazyImporter(this, "DeferredTask", "resource://gre/modules/DeferredTask.jsm");
 
 const {LocalizationHelper, localizeMarkup} = require("devtools/shared/l10n");
 const INSPECTOR_L10N =
   new LocalizationHelper("devtools/client/locales/inspector.properties");
 loader.lazyGetter(this, "TOOLBOX_L10N", function() {
   return new LocalizationHelper("devtools/client/locales/toolbox.properties");
@@ -2307,32 +2306,31 @@ Inspector.prototype = {
       clipboardHelper.copyString(path);
     }).catch(console.error);
   },
 
   /**
    * Initiate gcli screenshot command on selected node.
    */
   async screenshotNode() {
+    const command = Services.prefs.getBoolPref("devtools.screenshot.clipboard.enabled") ?
+      "screenshot --file --clipboard --selector" :
+      "screenshot --file --selector";
+
     // Bug 1332936 - it's possible to call `screenshotNode` while the BoxModel highlighter
     // is still visible, therefore showing it in the picture.
     // To avoid that, we have to hide it before taking the screenshot. The `hideBoxModel`
     // will do that, calling `hide` for the highlighter only if previously shown.
     await this.highlighter.hideBoxModel();
 
-    const clipboardEnabled = Services.prefs
-      .getBoolPref("devtools.screenshot.clipboard.enabled");
-    const args = {
-      file: true,
-      selector: this.selectionCssSelector,
-      clipboard: clipboardEnabled
-    };
-    const screenshotFront = getScreenshotFront(this.target);
-    const screenshot = await screenshotFront.capture(args);
-    await saveScreenshot(this.panelWin, args, screenshot);
+    // Bug 1180314 -  CssSelector might contain white space so need to make sure it is
+    // passed to screenshot as a single parameter.  More work *might* be needed if
+    // CssSelector could contain escaped single- or double-quotes, backslashes, etc.
+    CommandUtils.executeOnTarget(this._target,
+      `${command} '${this.selectionCssSelector}'`);
   },
 
   /**
    * Scroll the node into view.
    */
   scrollNodeIntoView: function() {
     if (!this.selection.isNode()) {
       return;
--- a/devtools/client/webconsole/components/JSTerm.js
+++ b/devtools/client/webconsole/components/JSTerm.js
@@ -13,17 +13,17 @@ loader.lazyServiceGetter(this, "clipboar
 loader.lazyRequireGetter(this, "Debugger", "Debugger");
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 loader.lazyRequireGetter(this, "AutocompletePopup", "devtools/client/shared/autocomplete-popup");
 loader.lazyRequireGetter(this, "PropTypes", "devtools/client/shared/vendor/react-prop-types");
 loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
 loader.lazyRequireGetter(this, "KeyCodes", "devtools/client/shared/keycodes", true);
 loader.lazyRequireGetter(this, "Editor", "devtools/client/sourceeditor/editor");
 loader.lazyRequireGetter(this, "Telemetry", "devtools/client/shared/telemetry");
-loader.lazyRequireGetter(this, "saveScreenshot", "devtools/shared/screenshot/save");
+loader.lazyRequireGetter(this, "processScreenshot", "devtools/shared/webconsole/screenshot-helper");
 
 const l10n = require("devtools/client/webconsole/webconsole-l10n");
 
 const HELP_URL = "https://developer.mozilla.org/docs/Tools/Web_Console/Helpers";
 
 function gSequenceId() {
   return gSequenceId.n++;
 }
@@ -442,17 +442,17 @@ class JSTerm extends Component {
         case "help":
           this.hud.owner.openLink(HELP_URL);
           break;
         case "copyValueToClipboard":
           clipboardHelper.copyString(helperResult.value);
           break;
         case "screenshotOutput":
           const { args, value } = helperResult;
-          const results = await saveScreenshot(this.hud.window, args, value);
+          const results = await processScreenshot(this.hud.window, args, value);
           this.screenshotNotify(results);
           // early return as screenshot notify has dispatched all necessary messages
           return null;
       }
     }
 
     // Hide undefined results coming from JSTerm helper functions.
     if (!errorMessage && result && typeof result == "object" &&
--- a/devtools/server/actors/moz.build
+++ b/devtools/server/actors/moz.build
@@ -52,17 +52,16 @@ DevToolsModules(
     'performance-recording.js',
     'performance.js',
     'preference.js',
     'pretty-print-worker.js',
     'process.js',
     'promises.js',
     'reflow.js',
     'root.js',
-    'screenshot.js',
     'source.js',
     'storage.js',
     'string.js',
     'styles.js',
     'stylesheets.js',
     'thread.js',
     'timeline.js',
     'webaudio.js',
deleted file mode 100644
--- a/devtools/server/actors/screenshot.js
+++ /dev/null
@@ -1,20 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const protocol = require("devtools/shared/protocol");
-const {captureScreenshot} = require("devtools/shared/screenshot/capture");
-const {screenshotSpec} = require("devtools/shared/specs/screenshot");
-
-exports.ScreenshotActor = protocol.ActorClassWithSpec(screenshotSpec, {
-  initialize: function(conn, targetActor) {
-    protocol.Actor.prototype.initialize.call(this, conn);
-    this.document = targetActor.window.document;
-  },
-
-  capture: function(args) {
-    return captureScreenshot(args, this.document);
-  }
-});
--- a/devtools/server/actors/webconsole/moz.build
+++ b/devtools/server/actors/webconsole/moz.build
@@ -8,11 +8,12 @@ DIRS += [
     'listeners',
 ]
 
 DevToolsModules(
     'commands.js',
     'content-process-forward.js',
     'eval-with-debugger.js',
     'message-manager-mock.js',
+    'screenshot.js',
     'utils.js',
     'worker-listeners.js',
 )
rename from devtools/shared/screenshot/capture.js
rename to devtools/server/actors/webconsole/screenshot.js
--- a/devtools/shared/screenshot/capture.js
+++ b/devtools/server/actors/webconsole/screenshot.js
@@ -1,70 +1,59 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
+
 const { Cu } = require("chrome");
+const { getRect } = require("devtools/shared/layout/utils");
 const { LocalizationHelper } = require("devtools/shared/l10n");
-const Services = require("Services");
 
 const CONTAINER_FLASHING_DURATION = 500;
 const STRINGS_URI = "devtools/shared/locales/screenshot.properties";
 const L10N = new LocalizationHelper(STRINGS_URI);
 
-loader.lazyRequireGetter(this, "getRect", "devtools/shared/layout/utils", true);
+exports.screenshot = function takeAsyncScreenshot(owner, args = {}) {
+  if (args.help) {
+    // Early return as help will be handled on the client side.
+    return null;
+  }
+  return captureScreenshot(args, owner.window.document);
+};
 
 /**
  * This function is called to simulate camera effects
- * @param object document
- *        The target document.
  */
 function simulateCameraFlash(document) {
   const window = document.defaultView;
   const frames = Cu.cloneInto({ opacity: [ 0, 1 ] }, window);
   document.documentElement.animate(frames, CONTAINER_FLASHING_DURATION);
 }
 
 /**
- * This function is called to simulate camera effects
- *
- * @param object document
- *        The target document.
- */
-function simulateCameraShutter(document) {
-  const window = document.defaultView;
-  if (Services.prefs.getBoolPref("devtools.screenshot.audio.enabled")) {
-    const audioCamera = new window.Audio("resource://devtools/client/themes/audio/shutter.wav");
-    audioCamera.play();
-  }
-}
-
-/**
  * This function simply handles the --delay argument before calling
  * createScreenshotData
  */
 function captureScreenshot(args, document) {
   if (args.delay > 0) {
     return new Promise((resolve, reject) => {
       document.defaultView.setTimeout(() => {
-        createScreenshotDataURL(document, args).then(resolve, reject);
+        createScreenshotData(document, args).then(resolve, reject);
       }, args.delay * 1000);
     });
   }
-  return createScreenshotDataURL(document, args);
+  return createScreenshotData(document, args);
 }
 
-exports.captureScreenshot = captureScreenshot;
-
 /**
  * This does the dirty work of creating a base64 string out of an
  * area of the browser window
  */
-function createScreenshotDataURL(document, args) {
+function createScreenshotData(document, args) {
   const window = document.defaultView;
   let left = 0;
   let top = 0;
   let width;
   let height;
   const currentX = window.scrollX;
   const currentY = window.scrollY;
 
@@ -107,29 +96,26 @@ function createScreenshotDataURL(documen
   const data = canvas.toDataURL("image/png", "");
 
   // See comment above on bug 961832
   if (args.fullpage) {
     window.scrollTo(currentX, currentY);
   }
 
   simulateCameraFlash(document);
-  simulateCameraShutter(document);
 
   return Promise.resolve({
     destinations: [],
     data: data,
     height: height,
     width: width,
     filename: filename,
   });
 }
 
-exports.createScreenshotDataURL = createScreenshotDataURL;
-
 /**
  * We may have a filename specified in args, or we might have to generate
  * one.
  */
 function getFilename(defaultName) {
   // Create a name for the file if not present
   if (defaultName) {
     return defaultName;
@@ -147,9 +133,8 @@ function getFilename(defaultName) {
 
   const timeString = date.toTimeString().replace(/:/g, ".").split(" ")[0];
   return L10N.getFormatStr(
     "screenshotGeneratedFilename",
     dateString,
     timeString
   ) + ".png";
 }
-
--- a/devtools/server/actors/webconsole/utils.js
+++ b/devtools/server/actors/webconsole/utils.js
@@ -2,20 +2,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {Ci, Cu} = require("chrome");
 
-// Note that this is only used in WebConsoleCommands, see $0, screenshot and pprint().
+loader.lazyRequireGetter(this, "screenshot", "devtools/server/actors/webconsole/screenshot", true);
+
+// Note that this is only used in WebConsoleCommands, see $0 and pprint().
 if (!isWorker) {
   loader.lazyImporter(this, "VariablesView", "resource://devtools/client/shared/widgets/VariablesView.jsm");
-  loader.lazyRequireGetter(this, "captureScreenshot", "devtools/shared/screenshot/capture", true);
 }
 
 const CONSOLE_WORKER_IDS = exports.CONSOLE_WORKER_IDS = [
   "SharedWorker",
   "ServiceWorker",
   "Worker"
 ];
 
@@ -593,23 +594,20 @@ WebConsoleCommands._registerOriginal("co
 
 /**
  * Take a screenshot of a page.
  *
  * @param object args
  *               The arguments to be passed to the screenshot
  * @return void
  */
-WebConsoleCommands._registerOriginal("screenshot", function(owner, args = {}) {
+WebConsoleCommands._registerOriginal("screenshot", function(owner, args) {
   owner.helperResult = (async () => {
     // creates data for saving the screenshot
-    // help is handled on the client side
-    const value = args.help ?
-      null :
-      await captureScreenshot(args, owner.window.document);
+    const value = await screenshot(owner, args);
     return {
       type: "screenshotOutput",
       value,
       // pass args through to the client, so that the client can take care of copying
       // and saving the screenshot data on the client machine instead of on the
       // remote machine
       args
     };
--- a/devtools/server/main.js
+++ b/devtools/server/main.js
@@ -421,21 +421,16 @@ var DebuggerServer = {
       constructor: "WebExtensionInspectedWindowActor",
       type: { target: true }
     });
     this.registerModule("devtools/server/actors/accessibility", {
       prefix: "accessibility",
       constructor: "AccessibilityActor",
       type: { target: true }
     });
-    this.registerModule("devtools/server/actors/screenshot", {
-      prefix: "screenshot",
-      constructor: "ScreenshotActor",
-      type: { target: true }
-    });
   },
 
   /**
    * Passes a set of options to the AddonTargetActors for the given ID.
    *
    * @param id string
    *        The ID of the add-on to pass the options to
    * @param options object
--- a/devtools/shared/fronts/moz.build
+++ b/devtools/shared/fronts/moz.build
@@ -26,17 +26,16 @@ DevToolsModules(
     'memory.js',
     'node.js',
     'perf.js',
     'performance-recording.js',
     'performance.js',
     'preference.js',
     'promises.js',
     'reflow.js',
-    'screenshot.js',
     'storage.js',
     'string.js',
     'styles.js',
     'stylesheets.js',
     'timeline.js',
     'webaudio.js',
     'webgl.js'
 )
deleted file mode 100644
--- a/devtools/shared/fronts/screenshot.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-"use strict";
-
-const {screenshotSpec} = require("devtools/shared/specs/screenshot");
-const saveScreenshot = require("devtools/shared/screenshot/save");
-const protocol = require("devtools/shared/protocol");
-
-const ScreenshotFront = protocol.FrontClassWithSpec(screenshotSpec, {
-  initialize: function(client, form) {
-    protocol.Front.prototype.initialize.call(this, client);
-    this.actorID = form.screenshotActor;
-    this.manage(this);
-  },
-
-  async captureAndSave(window, args) {
-    const screenshot = await this.capture(args);
-    return saveScreenshot(window, args, screenshot);
-  }
-});
-
-// A cache of created fronts: WeakMap<Client, Front>
-const knownFronts = new WeakMap();
-
-/**
- * Create a screenshot front only when needed
- */
-function getScreenshotFront(target) {
-  let front = knownFronts.get(target.client);
-  if (front == null && target.form.screenshotActor != null) {
-    front = new ScreenshotFront(target.client, target.form);
-    knownFronts.set(target.client, front);
-  }
-  return front;
-}
-
-exports.getScreenshotFront = getScreenshotFront;
--- a/devtools/shared/moz.build
+++ b/devtools/shared/moz.build
@@ -20,17 +20,16 @@ DIRS += [
     'jsbeautify',
     'layout',
     'locales',
     'node-properties',
     'performance',
     'platform',
     'pretty-fast',
     'qrcode',
-    'screenshot',
     'security',
     'sourcemap',
     'sprintfjs',
     'specs',
     'transport',
     'webconsole',
     'worker',
 ]
deleted file mode 100644
--- a/devtools/shared/screenshot/moz.build
+++ /dev/null
@@ -1,10 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-DevToolsModules(
-    'capture.js',
-    'save.js',
-)
deleted file mode 100644
--- a/devtools/shared/screenshot/save.js
+++ /dev/null
@@ -1,251 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-const { Cc, Ci } = require("chrome");
-const { LocalizationHelper } = require("devtools/shared/l10n");
-const Services = require("Services");
-
-loader.lazyImporter(this, "Downloads", "resource://gre/modules/Downloads.jsm");
-loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm");
-loader.lazyImporter(this, "FileUtils", "resource://gre/modules/FileUtils.jsm");
-
-const STRINGS_URI = "devtools/shared/locales/screenshot.properties";
-const L10N = new LocalizationHelper(STRINGS_URI);
-
-const screenshotDescription = L10N.getStr("screenshotDesc");
-const screenshotGroupOptions = L10N.getStr("screenshotGroupOptions");
-const screenshotCommandParams = [
-  {
-    name: "clipboard",
-    type: "boolean",
-    description: L10N.getStr("screenshotClipboardDesc"),
-    manual: L10N.getStr("screenshotClipboardManual")
-  },
-  {
-    name: "delay",
-    type: "number",
-    description: L10N.getStr("screenshotDelayDesc"),
-    manual: L10N.getStr("screenshotDelayManual")
-  },
-  {
-    name: "dpr",
-    type: "number",
-    description: L10N.getStr("screenshotDPRDesc"),
-    manual: L10N.getStr("screenshotDPRManual")
-  },
-  {
-    name: "fullpage",
-    type: "boolean",
-    description: L10N.getStr("screenshotFullPageDesc"),
-    manual: L10N.getStr("screenshotFullPageManual")
-  },
-  {
-    name: "selector",
-    type: "string",
-    description: L10N.getStr("inspectNodeDesc"),
-    manual: L10N.getStr("inspectNodeManual")
-  },
-  {
-    name: "file",
-    type: "boolean",
-    description: L10N.getStr("screenshotFileDesc"),
-    manual: L10N.getStr("screenshotFileManual"),
-  },
-  {
-    name: "filename",
-    type: "string",
-    description: L10N.getStr("screenshotFilenameDesc"),
-    manual: L10N.getStr("screenshotFilenameManual")
-  }
-];
-
-/**
- * Creates a string from an object for use when screenshot is passed the `--help` argument
- *
- * @param object param
- *        The param object to be formatted.
- * @return string
- *         The formatted information from the param object as a string
- */
-function formatHelpField(param) {
-  const padding = " ".repeat(5);
-  return Object.entries(param).map(([key, value]) => {
-    if (key === "name") {
-      const name = `${padding}--${value}`;
-      return name;
-    }
-    return `${padding.repeat(2)}${key}: ${value}`;
-  }).join("\n");
-}
-
-/**
- * Creates a string response from the screenshot options for use when
- * screenshot is passed the `--help` argument
- *
- * @return string
- *         The formatted information from the param object as a string
- */
-function getFormattedHelpData() {
-  const formattedParams = screenshotCommandParams
-    .map(formatHelpField)
-    .join("\n\n");
-
-  return `${screenshotDescription}\n${screenshotGroupOptions}\n\n${formattedParams}`;
-}
-
-/**
- * Main entry point in this file; Takes the original arguments that `:screenshot` was
- * called with and the image value from the server, and uses the client window to add
- * and audio effect.
- *
- * @param object window
- *        The Debugger Client window.
- *
- * @param object args
- *        The original args with which the screenshot
- *        was called.
- * @param object value
- *        an object with a image value and file name
- *
- * @return string[]
- *         Response messages from processing the screenshot
- */
-function saveScreenshot(window, args = {}, value) {
-  if (args.help) {
-    const message = getFormattedHelpData();
-    // Wrap message in an array so that the return value is consistant with save
-    return [message];
-  }
-  simulateCameraShutter(window);
-  return save(args, value);
-}
-
-/**
- * This function is called to simulate camera effects
- *
- * @param object document
- *        The Debugger Client document.
- */
-function simulateCameraShutter(window) {
-  if (Services.prefs.getBoolPref("devtools.screenshot.audio.enabled")) {
-    const audioCamera = new window.Audio("resource://devtools/client/themes/audio/shutter.wav");
-    audioCamera.play();
-  }
-}
-
-/**
- * Save the captured screenshot to one of several destinations.
- *
- * @param object args
- *        The original args with which the screenshot was called.
- *
- * @param object image
- *        The image object that was sent from the server.
- *
- * @return string[]
- *         Response messages from processing the screenshot.
- */
-async function save(args, image) {
-  const fileNeeded = args.filename ||
-    !args.clipboard || args.file;
-  const results = [];
-
-  if (args.clipboard) {
-    const result = saveToClipboard(image.data);
-    results.push(result);
-  }
-
-  if (fileNeeded) {
-    const result = await saveToFile(image);
-    results.push(result);
-  }
-  return results;
-}
-
-/**
- * Save the image data to the clipboard. This returns a promise, so it can
- * be treated exactly like file processing.
- *
- * @param string base64URI
- *        The image data encoded in a base64 URI that was sent from the server.
- *
- * @return string
- *         Response message from processing the screenshot.
- */
-function saveToClipboard(base64URI) {
-  try {
-    const imageTools = Cc["@mozilla.org/image/tools;1"]
-                       .getService(Ci.imgITools);
-
-    const base64Data = base64URI.replace("data:image/png;base64,", "");
-
-    const image = atob(base64Data);
-    const imgPtr = Cc["@mozilla.org/supports-interface-pointer;1"]
-                   .createInstance(Ci.nsISupportsInterfacePointer);
-    imgPtr.data = imageTools.decodeImageFromBuffer(image, image.length, "image/png");
-
-    const transferable = Cc["@mozilla.org/widget/transferable;1"]
-                     .createInstance(Ci.nsITransferable);
-    transferable.init(null);
-    transferable.addDataFlavor("image/png");
-    transferable.setTransferData("image/png", imgPtr, -1);
-
-    Services.clipboard.setData(transferable, null, Services.clipboard.kGlobalClipboard);
-    return L10N.getStr("screenshotCopied");
-  } catch (ex) {
-    console.error(ex);
-    return L10N.getStr("screenshotErrorCopying");
-  }
-}
-
-/**
- * Save the screenshot data to disk, returning a promise which is resolved on
- * completion.
- *
- * @param object image
- *        The image object that was sent from the server.
- *
- * @return string
- *         Response message from processing the screenshot.
- */
-async function saveToFile(image) {
-  let filename = image.filename;
-
-  // Check there is a .png extension to filename
-  if (!filename.match(/.png$/i)) {
-    filename += ".png";
-  }
-
-  const downloadsDir = await Downloads.getPreferredDownloadsDirectory();
-  const downloadsDirExists = await OS.File.exists(downloadsDir);
-  if (downloadsDirExists) {
-    // If filename is absolute, it will override the downloads directory and
-    // still be applied as expected.
-    filename = OS.Path.join(downloadsDir, filename);
-  }
-
-  const sourceURI = Services.io.newURI(image.data);
-  const targetFile = new FileUtils.File(filename);
-
-  // Create download and track its progress.
-  try {
-    const download = await Downloads.createDownload({
-      source: sourceURI,
-      target: targetFile
-    });
-    const list = await Downloads.getList(Downloads.ALL);
-    // add the download to the download list in the Downloads list in the Browser UI
-    list.add(download);
-    // Await successful completion of the save via the download manager
-    await download.start();
-    return L10N.getFormatStr("screenshotSavedToFile", filename);
-  } catch (ex) {
-    console.error(ex);
-    return L10N.getFormatStr("screenshotErrorSavingToFile", filename);
-  }
-}
-
-module.exports = saveScreenshot;
--- a/devtools/shared/specs/moz.build
+++ b/devtools/shared/specs/moz.build
@@ -37,17 +37,16 @@ DevToolsModules(
     'object.js',
     'perf.js',
     'performance-recording.js',
     'performance.js',
     'preference.js',
     'promises.js',
     'property-iterator.js',
     'reflow.js',
-    'screenshot.js',
     'script.js',
     'source.js',
     'storage.js',
     'string.js',
     'styles.js',
     'stylesheets.js',
     'symbol-iterator.js',
     'symbol.js',
deleted file mode 100644
--- a/devtools/shared/specs/screenshot.js
+++ /dev/null
@@ -1,32 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-"use strict";
-
-const {RetVal, Arg, generateActorSpec, types} = require("devtools/shared/protocol");
-
-types.addDictType("screenshot.args", {
-  fullpage: "nullable:boolean",
-  file: "nullable:boolean",
-  clipboard: "nullable:boolean",
-  selector: "nullable:string",
-  dpr: "nullable:string",
-  delay: "nullable:string"
-});
-
-const screenshotSpec = generateActorSpec({
-  typeName: "screenshot",
-
-  methods: {
-    capture: {
-      request: {
-        args: Arg(0, "screenshot.args")
-      },
-      response: {
-        value: RetVal("json")}
-    },
-  },
-});
-
-exports.screenshotSpec = screenshotSpec;
-
--- a/devtools/shared/webconsole/moz.build
+++ b/devtools/shared/webconsole/moz.build
@@ -7,10 +7,11 @@
 if CONFIG['OS_TARGET'] != 'Android':
     MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
     XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
 
 DevToolsModules(
     'client.js',
     'js-property-provider.js',
     'network-helper.js',
+    'screenshot-helper.js',
     'throttle.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/shared/webconsole/screenshot-helper.js
@@ -0,0 +1,261 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci } = require("chrome");
+const { LocalizationHelper } = require("devtools/shared/l10n");
+const Services = require("Services");
+
+loader.lazyImporter(this, "Downloads", "resource://gre/modules/Downloads.jsm");
+loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm");
+loader.lazyImporter(this, "FileUtils", "resource://gre/modules/FileUtils.jsm");
+
+const STRINGS_URI = "devtools/shared/locales/screenshot.properties";
+const L10N = new LocalizationHelper(STRINGS_URI);
+
+const screenshotDescription = L10N.getStr("screenshotDesc");
+const screenshotGroupOptions = L10N.getStr("screenshotGroupOptions");
+const screenshotCommandParams = [
+  {
+    name: "clipboard",
+    type: "boolean",
+    description: L10N.getStr("screenshotClipboardDesc"),
+    manual: L10N.getStr("screenshotClipboardManual")
+  },
+  {
+    name: "delay",
+    type: "number",
+    description: L10N.getStr("screenshotDelayDesc"),
+    manual: L10N.getStr("screenshotDelayManual")
+  },
+  {
+    name: "dpr",
+    type: "number",
+    description: L10N.getStr("screenshotDPRDesc"),
+    manual: L10N.getStr("screenshotDPRManual")
+  },
+  {
+    name: "fullpage",
+    type: "boolean",
+    description: L10N.getStr("screenshotFullPageDesc"),
+    manual: L10N.getStr("screenshotFullPageManual")
+  },
+  {
+    name: "selector",
+    type: "string",
+    description: L10N.getStr("inspectNodeDesc"),
+    manual: L10N.getStr("inspectNodeManual")
+  },
+  {
+    name: "file",
+    type: "boolean",
+    description: L10N.getStr("screenshotFileDesc"),
+    manual: L10N.getStr("screenshotFileManual"),
+  },
+  {
+    name: "filename",
+    type: "string",
+    description: L10N.getStr("screenshotFilenameDesc"),
+    manual: L10N.getStr("screenshotFilenameManual")
+  }
+];
+
+/**
+ * Creates a string from an object for use when screenshot is passed the `--help` argument
+ *
+ * @param object param
+ *        The param object to be formatted.
+ * @return string
+ *         The formatted information from the param object as a string
+ */
+function formatHelpField(param) {
+  const padding = " ".repeat(5);
+  return Object.entries(param).map(([key, value]) => {
+    if (key === "name") {
+      const name = `${padding}--${value}`;
+      return name;
+    }
+    return `${padding.repeat(2)}${key}: ${value}`;
+  }).join("\n");
+}
+
+/**
+ * Creates a string response from the screenshot options for use when
+ * screenshot is passed the `--help` argument
+ *
+ * @return string
+ *         The formatted information from the param object as a string
+ */
+function getFormattedHelpData() {
+  const formattedParams = screenshotCommandParams
+    .map(formatHelpField)
+    .join("\n\n");
+
+  return `${screenshotDescription}\n${screenshotGroupOptions}\n\n${formattedParams}`;
+}
+
+/**
+ * Main entry point in this file; Takes the original arguments that `:screenshot` was
+ * called with and the image value from the server, and uses the client window to save
+ * the screenshot to the remote debugging machine's memory or clipboard.
+ *
+ * @param object window
+ *        The Debugger Client window.
+ *
+ * @param object args
+ *        The original args with which the screenshot
+ *        was called.
+ * @param object value
+ *        an object with a image value and file name
+ *
+ * @return string[]
+ *         Response messages from processing the screenshot
+ */
+function processScreenshot(window, args = {}, value) {
+  if (args.help) {
+    const message = getFormattedHelpData();
+    // Wrap meesage in an array so that the return value is consistant with saveScreenshot
+    return [message];
+  }
+  simulateCameraShutter(window.document);
+  return saveScreenshot(window, args, value);
+}
+
+/**
+ * This function is called to simulate camera effects
+ *
+ * @param object document
+ *        The Debugger Client document.
+ */
+function simulateCameraShutter(document) {
+  const window = document.defaultView;
+  if (Services.prefs.getBoolPref("devtools.screenshot.audio.enabled")) {
+    const audioCamera = new window.Audio("resource://devtools/client/themes/audio/shutter.wav");
+    audioCamera.play();
+  }
+}
+
+/**
+ * Save the captured screenshot to one of several destinations.
+ *
+ * @param object window
+ *        The Debugger Client window.
+ *
+ * @param object args
+ *        The original args with which the screenshot was called.
+ *
+ * @param object image
+ *        The image object that was sent from the server.
+ *
+ * @return string[]
+ *         Response messages from processing the screenshot.
+ */
+async function saveScreenshot(window, args, image) {
+  const fileNeeded = args.filename ||
+    !args.clipboard || args.file;
+  const results = [];
+
+  if (args.clipboard) {
+    const result = saveToClipboard(window, image.data);
+    results.push(result);
+  }
+
+  if (fileNeeded) {
+    const result = await saveToFile(window, image);
+    results.push(result);
+  }
+  return results;
+}
+
+/**
+ * Save the image data to the clipboard. This returns a promise, so it can
+ * be treated exactly like file processing.
+ *
+ * @param object window
+ *        The Debugger Client window.
+ *
+ * @param string base64URI
+ *        The image data encoded in a base64 URI that was sent from the server.
+ *
+ * @return string
+ *         Response message from processing the screenshot.
+ */
+function saveToClipboard(window, base64URI) {
+  try {
+    const imageTools = Cc["@mozilla.org/image/tools;1"]
+                       .getService(Ci.imgITools);
+
+    const base64Data = base64URI.replace("data:image/png;base64,", "");
+
+    const image = atob(base64Data);
+    const imgPtr = Cc["@mozilla.org/supports-interface-pointer;1"]
+                   .createInstance(Ci.nsISupportsInterfacePointer);
+    imgPtr.data = imageTools.decodeImageFromBuffer(image, image.length, "image/png");
+
+    const transferable = Cc["@mozilla.org/widget/transferable;1"]
+                     .createInstance(Ci.nsITransferable);
+    transferable.init(null);
+    transferable.addDataFlavor("image/png");
+    transferable.setTransferData("image/png", imgPtr, -1);
+
+    Services.clipboard.setData(transferable, null, Services.clipboard.kGlobalClipboard);
+    return L10N.getStr("screenshotCopied");
+  } catch (ex) {
+    console.error(ex);
+    return L10N.getStr("screenshotErrorCopying");
+  }
+}
+
+/**
+ * Save the screenshot data to disk, returning a promise which is resolved on
+ * completion.
+ *
+ * @param object window
+ *        The Debugger Client window.
+ *
+ * @param object image
+ *        The image object that was sent from the server.
+ *
+ * @return string
+ *         Response message from processing the screenshot.
+ */
+async function saveToFile(window, image) {
+  let filename = image.filename;
+
+  // Check there is a .png extension to filename
+  if (!filename.match(/.png$/i)) {
+    filename += ".png";
+  }
+
+  const downloadsDir = await Downloads.getPreferredDownloadsDirectory();
+  const downloadsDirExists = await OS.File.exists(downloadsDir);
+  if (downloadsDirExists) {
+    // If filename is absolute, it will override the downloads directory and
+    // still be applied as expected.
+    filename = OS.Path.join(downloadsDir, filename);
+  }
+
+  const sourceURI = Services.io.newURI(image.data);
+  const targetFile = new FileUtils.File(filename);
+
+  // Create download and track its progress.
+  try {
+    const download = await Downloads.createDownload({
+      source: sourceURI,
+      target: targetFile
+    });
+    const list = await Downloads.getList(Downloads.ALL);
+    // add the download to the download list in the Downloads list in the Browser UI
+    list.add(download);
+    // Await successful completion of the save via the download manager
+    await download.start();
+    return L10N.getFormatStr("screenshotSavedToFile", filename);
+  } catch (ex) {
+    console.error(ex);
+    return L10N.getFormatStr("screenshotErrorSavingToFile", filename);
+  }
+}
+
+module.exports = processScreenshot;