diff --git a/devtools/client/jsonview/converter-child.js b/devtools/client/jsonview/converter-child.js index bf8eaa73ca38..560847a1055d 100644 --- a/devtools/client/jsonview/converter-child.js +++ b/devtools/client/jsonview/converter-child.js @@ -289,11 +289,8 @@ Converter.prototype = { break; case "save": - // The window ID is needed when the JSON Viewer is inside an iframe. - let windowID = win.QueryInterface(Ci.nsIInterfaceRequestor) - .getInterface(Ci.nsIDOMWindowUtils).outerWindowID; childProcessMessageManager.sendAsyncMessage( - "devtools:jsonview:save", {url: value, windowID: windowID}); + "devtools:jsonview:save", value); } }, diff --git a/devtools/client/jsonview/json-viewer.js b/devtools/client/jsonview/json-viewer.js index 979e127ee12f..b8208291a381 100644 --- a/devtools/client/jsonview/json-viewer.js +++ b/devtools/client/jsonview/json-viewer.js @@ -15,7 +15,6 @@ define(function (require, exports, module) { const headers = document.getElementById("headers"); let jsonData; - let prettyURL; try { jsonData = JSON.parse(json.textContent); @@ -46,10 +45,7 @@ define(function (require, exports, module) { }, onSaveJson: function () { - if (input.prettified && !prettyURL) { - prettyURL = URL.createObjectURL(new Blob([input.jsonPretty])); - } - dispatchEvent("save", input.prettified ? prettyURL : null); + dispatchEvent("save", input.prettified ? input.jsonPretty : input.jsonText); }, onCopyHeaders: function () { diff --git a/devtools/client/jsonview/main.js b/devtools/client/jsonview/main.js index b2bed7f333ff..4164ca00d58c 100644 --- a/devtools/client/jsonview/main.js +++ b/devtools/client/jsonview/main.js @@ -7,17 +7,11 @@ "use strict"; -const { Cu, Cc, Ci } = require("chrome"); +const { Cu } = require("chrome"); const Services = require("Services"); const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}); -XPCOMUtils.defineLazyGetter(this, "chrome", function () { - return Cc["@mozilla.org/appshell/window-mediator;1"] - .getService(Ci.nsIWindowMediator) - .getMostRecentWindow("navigator:browser"); -}); - XPCOMUtils.defineLazyGetter(this, "JsonViewUtils", function () { return require("devtools/client/jsonview/utils"); }); @@ -56,28 +50,9 @@ var JsonView = { * in the parent process. */ onSave: function (message) { - let browser = chrome.gBrowser.selectedBrowser; - if (message.data.url === null) { - // Save original contents - chrome.saveBrowser(browser, false, message.data.windowID); - } else { - // The following code emulates saveBrowser, but: - // - Uses the given blob URL containing the custom contents to save. - // - Obtains the file name from the URL of the document, not the blob. - let persistable = browser.QueryInterface(Ci.nsIFrameLoaderOwner) - .frameLoader.QueryInterface(Ci.nsIWebBrowserPersistable); - persistable.startPersistence(message.data.windowID, { - onDocumentReady(doc) { - let uri = chrome.makeURI(doc.documentURI, doc.characterSet); - let filename = chrome.getDefaultFileName(undefined, uri, doc, null); - chrome.internalSave(message.data.url, doc, filename, null, doc.contentType, - false, null, null, null, doc, false, null, undefined); - }, - onError(status) { - throw new Error("JSON Viewer's onSave failed in startPersistence"); - } - }); - } + JsonViewUtils.getTargetFile().then(file => { + JsonViewUtils.saveToFile(file, message.data); + }, () => {}); } }; diff --git a/devtools/client/jsonview/utils.js b/devtools/client/jsonview/utils.js index fce7a5b0ce8a..5c2bffe57067 100644 --- a/devtools/client/jsonview/utils.js +++ b/devtools/client/jsonview/utils.js @@ -6,9 +6,86 @@ "use strict"; -const { Cu } = require("chrome"); +const { Cu, Cc, Ci } = require("chrome"); const Services = require("Services"); +const OPEN_FLAGS = { + RDONLY: parseInt("0x01", 16), + WRONLY: parseInt("0x02", 16), + CREATE_FILE: parseInt("0x08", 16), + APPEND: parseInt("0x10", 16), + TRUNCATE: parseInt("0x20", 16), + EXCL: parseInt("0x80", 16) +}; + +let filePickerShown = false; + +/** + * Open File Save As dialog and let the user to pick proper file location. + */ +exports.getTargetFile = function () { + return new Promise((resolve, reject) => { + if (filePickerShown) { + reject(null); + return; + } + + filePickerShown = true; + + let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker); + + let win = Services.wm.getMostRecentWindow("navigator:browser"); + fp.init(win, null, Ci.nsIFilePicker.modeSave); + fp.appendFilter("JSON Files", "*.json; *.jsonp;"); + fp.appendFilters(Ci.nsIFilePicker.filterText); + fp.appendFilters(Ci.nsIFilePicker.filterAll); + fp.filterIndex = 0; + + fp.open(rv => { + filePickerShown = false; + + if (rv == Ci.nsIFilePicker.returnOK || rv == Ci.nsIFilePicker.returnReplace) { + resolve(fp.file); + } else { + reject(null); + } + }); + }); +}; + +/** + * Save JSON to a file + */ +exports.saveToFile = function (file, jsonString) { + let foStream = Cc["@mozilla.org/network/file-output-stream;1"] + .createInstance(Ci.nsIFileOutputStream); + + // write, create, truncate + let openFlags = OPEN_FLAGS.WRONLY | OPEN_FLAGS.CREATE_FILE | + OPEN_FLAGS.TRUNCATE; + + let permFlags = parseInt("0666", 8); + foStream.init(file, openFlags, permFlags, 0); + + let converter = Cc["@mozilla.org/intl/converter-output-stream;1"] + .createInstance(Ci.nsIConverterOutputStream); + + converter.init(foStream, "UTF-8", 0, 0); + + // The entire jsonString can be huge so, write the data in chunks. + let chunkLength = 1024 * 1204; + for (let i = 0; i <= jsonString.length; i++) { + let data = jsonString.substr(i, chunkLength + 1); + if (data) { + converter.writeString(data); + } + i = i + chunkLength; + } + + // this closes foStream + converter.close(); +}; + /** * Get the current theme from preferences. */