Bug 1571342 - unify save paths for console and netmonitor and fix the latter to not do docshell loads, r=Honza,nchevobbe
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Fri, 16 Aug 2019 11:13:24 +0000
changeset 488494 73659f7163fcaf46fa9b61cd0c7cd2d0a52fa973
parent 488493 f24a38d1b40a971a2de2ec06e144c1d7b4220c78
child 488495 68f95d43440d89d12526d5984e0c00a6861bb347
push id36444
push userccoroiu@mozilla.com
push dateFri, 16 Aug 2019 16:24:18 +0000
treeherdermozilla-central@8a9e9189cd98 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersHonza, nchevobbe
bugs1571342
milestone70.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1571342 - unify save paths for console and netmonitor and fix the latter to not do docshell loads, r=Honza,nchevobbe Differential Revision: https://phabricator.services.mozilla.com/D41858
devtools/client/netmonitor/src/har/har-exporter.js
devtools/client/netmonitor/src/widgets/RequestListContextMenu.js
devtools/client/shared/file-saver.js
devtools/client/shared/moz.build
devtools/client/webconsole/test/browser/browser_webconsole_context_menu_export_console_output.js
devtools/client/webconsole/utils/context-menu.js
devtools/shared/DevToolsUtils.js
devtools/shared/tests/unit/test_console_save-file.js
devtools/shared/tests/unit/xpcshell.ini
--- a/devtools/client/netmonitor/src/har/har-exporter.js
+++ b/devtools/client/netmonitor/src/har/har-exporter.js
@@ -1,16 +1,16 @@
 /* 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 Services = require("Services");
-const FileSaver = require("devtools/client/shared/file-saver");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const JSZip = require("devtools/client/shared/vendor/jszip");
 const clipboardHelper = require("devtools/shared/platform/clipboard");
 const { HarBuilder } = require("./har-builder");
 
 var uid = 1;
 
 // Helper tracer. Should be generic sharable by other modules (bug 1171927)
 const trace = {
@@ -81,26 +81,24 @@ const HarExporter = {
     );
 
     if (compress) {
       data = await JSZip()
         .file(fileName, data)
         .generateAsync({
           compression: "DEFLATE",
           platform: Services.appinfo.OS === "WINNT" ? "DOS" : "UNIX",
-          type: "blob",
+          type: "uint8array",
         });
+    } else {
+      data = new TextEncoder().encode(data);
     }
 
     fileName = `${fileName}${compress ? ".zip" : ""}`;
-    const blob = compress
-      ? data
-      : new Blob([data], { type: "application/json" });
-
-    FileSaver.saveAs(blob, fileName, document);
+    DevToolsUtils.saveAs(window, data, fileName);
   },
 
   formatDate(date) {
     const year = String(date.getFullYear() % 100).padStart(2, "0");
     const month = String(date.getMonth() + 1).padStart(2, "0");
     const day = String(date.getDate()).padStart(2, "0");
     const hour = String(date.getHours()).padStart(2, "0");
     const minutes = String(date.getMinutes()).padStart(2, "0");
--- a/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js
+++ b/devtools/client/netmonitor/src/widgets/RequestListContextMenu.js
@@ -10,22 +10,17 @@ const { L10N } = require("../utils/l10n"
 const {
   formDataURI,
   getUrlQuery,
   getUrlBaseName,
   parseQueryString,
 } = require("../utils/request-utils");
 
 loader.lazyRequireGetter(this, "Curl", "devtools/client/shared/curl", true);
-loader.lazyRequireGetter(
-  this,
-  "saveAs",
-  "devtools/client/shared/file-saver",
-  true
-);
+loader.lazyRequireGetter(this, "saveAs", "devtools/shared/DevToolsUtils", true);
 loader.lazyRequireGetter(
   this,
   "copyString",
   "devtools/shared/platform/clipboard",
   true
 );
 loader.lazyRequireGetter(
   this,
@@ -605,19 +600,19 @@ class RequestListContextMenu {
     let data;
     if (encoding === "base64") {
       const decoded = atob(text);
       data = new Uint8Array(decoded.length);
       for (let i = 0; i < decoded.length; ++i) {
         data[i] = decoded.charCodeAt(i);
       }
     } else {
-      data = text;
+      data = new TextEncoder().encode(text);
     }
-    saveAs(new Blob([data]), fileName, document);
+    saveAs(window, data, fileName);
   }
 
   /**
    * Copy response data as a string.
    */
   async copyResponse(id, responseContent) {
     responseContent =
       responseContent ||
deleted file mode 100644
--- a/devtools/client/shared/file-saver.js
+++ /dev/null
@@ -1,30 +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/. */
-
-/* eslint-env browser */
-
-"use strict";
-
-/**
- * HTML5 file saver to provide a standard download interface with a "Save As"
- * dialog
- *
- * @param {object} blob - A blob object will be downloaded
- * @param {string} filename - Given a file name which will display in "Save As" dialog
- * @param {object} document - Optional. A HTML document for creating a temporary anchor
- *                            for triggering a file download.
- */
-function saveAs(blob, filename = "", doc = document) {
-  const url = URL.createObjectURL(blob);
-  const a = doc.createElement("a");
-  doc.body.appendChild(a);
-  a.style = "display: none";
-  a.href = url;
-  a.download = filename;
-  a.click();
-  URL.revokeObjectURL(url);
-  a.remove();
-}
-
-exports.saveAs = saveAs;
--- a/devtools/client/shared/moz.build
+++ b/devtools/client/shared/moz.build
@@ -29,17 +29,16 @@ DevToolsModules(
     'browser-loader.js',
     'css-angle.js',
     'curl.js',
     'demangle.js',
     'devices.js',
     'DOMHelpers.jsm',
     'enum.js',
     'events.js',
-    'file-saver.js',
     'focus.js',
     'getjson.js',
     'inplace-editor.js',
     'key-shortcuts.js',
     'keycodes.js',
     'link.js',
     'natural-sort.js',
     'node-attribute-parser.js',
--- a/devtools/client/webconsole/test/browser/browser_webconsole_context_menu_export_console_output.js
+++ b/devtools/client/webconsole/test/browser/browser_webconsole_context_menu_export_console_output.js
@@ -150,16 +150,17 @@ async function exportAllToFile(hud, mess
   const exportFile = menuPopup.querySelector("#console-menu-export-file");
   ok(exportFile, "copy menu item is enabled");
 
   const nsiFile = FileUtils.getFile("TmpD", [
     `export_console_${Date.now()}.log`,
   ]);
   MockFilePicker.setFiles([nsiFile]);
   exportFile.click();
+  info("Exporting to file");
 
   // The file may not be ready yet.
   await waitFor(() => OS.File.exists(nsiFile.path));
   const buffer = await OS.File.read(nsiFile.path);
   return new TextDecoder().decode(buffer);
 }
 
 /**
--- a/devtools/client/webconsole/utils/context-menu.js
+++ b/devtools/client/webconsole/utils/context-menu.js
@@ -9,28 +9,17 @@
 const Menu = require("devtools/client/framework/menu");
 const MenuItem = require("devtools/client/framework/menu-item");
 
 const { MESSAGE_SOURCE } = require("devtools/client/webconsole/constants");
 
 const clipboardHelper = require("devtools/shared/platform/clipboard");
 const { l10n } = require("devtools/client/webconsole/utils/messages");
 
-loader.lazyRequireGetter(
-  this,
-  "showSaveFileDialog",
-  "devtools/shared/DevToolsUtils",
-  true
-);
-loader.lazyRequireGetter(
-  this,
-  "saveFileStream",
-  "devtools/shared/DevToolsUtils",
-  true
-);
+loader.lazyRequireGetter(this, "saveAs", "devtools/shared/DevToolsUtils", true);
 loader.lazyRequireGetter(
   this,
   "openContentLink",
   "devtools/client/shared/link",
   true
 );
 loader.lazyRequireGetter(
   this,
@@ -250,32 +239,26 @@ function createContextMenu(
   );
 
   // Export to file
   exportSubmenu.append(
     new MenuItem({
       id: "console-menu-export-file",
       label: l10n.getStr("webconsole.menu.exportSubmenu.exportFile.label"),
       disabled: false,
-      click: async () => {
+      // Note: not async, but returns a promise for the actual save.
+      click: () => {
         const date = new Date();
         const suggestedName =
           `console-export-${date.getFullYear()}-` +
           `${date.getMonth() + 1}-${date.getDate()}_${date.getHours()}-` +
           `${date.getMinutes()}-${date.getSeconds()}.txt`;
-        const returnFile = await showSaveFileDialog(win, suggestedName);
-        const converter = Cc[
-          "@mozilla.org/intl/scriptableunicodeconverter"
-        ].createInstance(Ci.nsIScriptableUnicodeConverter);
-        converter.charset = "UTF-8";
         const webconsoleOutput = parentNode.querySelector(".webconsole-output");
-        const istream = converter.convertToInputStream(
-          getElementText(webconsoleOutput)
-        );
-        return saveFileStream(returnFile, istream);
+        const data = new TextEncoder().encode(getElementText(webconsoleOutput));
+        return saveAs(window, data, suggestedName);
       },
     })
   );
 
   menu.append(
     new MenuItem({
       id: "console-menu-export",
       label: l10n.getStr("webconsole.menu.exportSubmenu.label"),
--- a/devtools/shared/DevToolsUtils.js
+++ b/devtools/shared/DevToolsUtils.js
@@ -776,31 +776,35 @@ exports.openFileStream = function(filePa
 
         resolve(stream);
       }
     );
   });
 };
 
 /**
- * Open the file at the given path for writing.
+ * Save the given data to disk after asking the user where to do so.
  *
- * @param {String} filePath
+ * @param {Window} parentWindow
+ *        The parent window to use to display the filepicker.
+ * @param {UInt8Array} dataArray
+ *        The data to write to the file.
+ * @param {String} fileName
+ *        The suggested filename.
  */
-exports.saveFileStream = function(filePath, istream) {
-  return new Promise((resolve, reject) => {
-    const ostream = FileUtils.openSafeFileOutputStream(filePath);
-    NetUtil.asyncCopy(istream, ostream, status => {
-      if (!components.isSuccessCode(status)) {
-        reject(new Error(`Could not save "${filePath}"`));
-        return;
-      }
-      FileUtils.closeSafeFileOutputStream(ostream);
-      resolve();
-    });
+exports.saveAs = async function(parentWindow, dataArray, fileName = "") {
+  let returnFile;
+  try {
+    returnFile = await exports.showSaveFileDialog(parentWindow, fileName);
+  } catch (ex) {
+    return;
+  }
+
+  await OS.File.writeAtomic(returnFile.path, dataArray, {
+    tmpPath: returnFile.path + ".tmp",
   });
 };
 
 /**
  * Show file picker and return the file user selected.
  *
  * @param nsIWindow parentWindow
  *        Optional parent window. If null the parent window of the file picker
deleted file mode 100644
--- a/devtools/shared/tests/unit/test_console_save-file.js
+++ /dev/null
@@ -1,48 +0,0 @@
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-"use strict";
-
-// Tests for DevToolsUtils.saveFileStream file:
-
-const { FileUtils } = ChromeUtils.import(
-  "resource://gre/modules/FileUtils.jsm"
-);
-
-/**
- * Tests that a file is properly saved using the saveFileStream function
- */
-add_task(async function test_save_file() {
-  const testText = "This is a text";
-  const fileName = "test_console_save-file-" + Math.random();
-  const file = FileUtils.getFile("TmpD", [fileName]);
-  info("Test creating temporary file: " + file.path);
-  await DevToolsUtils.saveFileStream(file, convertToInputStream(testText));
-  Assert.ok(file.exists(), "Checking if test file exists");
-  const { content } = await DevToolsUtils.fetch(file.path);
-  deepEqual(content, testText, "The content was correct.");
-  cleanup(fileName);
-});
-
-/**
- * Converts a string to an input stream.
- * @param String content
- * @return nsIInputStream
- */
-function convertToInputStream(content) {
-  const converter = Cc[
-    "@mozilla.org/intl/scriptableunicodeconverter"
-  ].createInstance(Ci.nsIScriptableUnicodeConverter);
-  converter.charset = "UTF-8";
-  return converter.convertToInputStream(content);
-}
-
-/**
- * Removes the temporary file after the test completes.
- */
-function cleanup(fileName) {
-  const file = FileUtils.getFile("TmpD", [fileName]);
-  registerCleanupFunction(() => {
-    file.remove(false);
-  });
-}
--- a/devtools/shared/tests/unit/xpcshell.ini
+++ b/devtools/shared/tests/unit/xpcshell.ini
@@ -28,17 +28,16 @@ run-if = nightly_build
 [test_flatten.js]
 [test_indentation.js]
 [test_independent_loaders.js]
 [test_invisible_loader.js]
 [test_isSet.js]
 [test_safeErrorString.js]
 [test_defineLazyPrototypeGetter.js]
 [test_console_filtering.js]
-[test_console_save-file.js]
 [test_pluralForm-english.js]
 [test_pluralForm-makeGetter.js]
 [test_prettifyCSS.js]
 [test_require_lazy.js]
 [test_require_raw.js]
 [test_require.js]
 [test_sprintfjs.js]
 [test_stack.js]