Bug 1456129 - Extend HAR toolbar with import; r=davidwalsh
MozReview-Commit-ID: 2g5sVon7xpk
--- a/devtools/client/locales/en-US/netmonitor.properties
+++ b/devtools/client/locales/en-US/netmonitor.properties
@@ -954,16 +954,36 @@ netmonitor.context.copyAllAsHar.accesske
# LOCALIZATION NOTE (netmonitor.context.saveAllAsHar): This is the label displayed
# on the context menu that saves all as HAR format
netmonitor.context.saveAllAsHar=Save All As HAR
# LOCALIZATION NOTE (netmonitor.context.saveAllAsHar.accesskey): This is the access key
# for the Save All As HAR menu item displayed in the context menu for a network panel
netmonitor.context.saveAllAsHar.accesskey=H
+# LOCALIZATION NOTE (netmonitor.context.importHar): This is the label displayed
+# on the context menu that imports HAR files
+netmonitor.context.importHar=Import…
+
+# LOCALIZATION NOTE (netmonitor.context.importHar.accesskey): This is the access key
+# for the Import HAR menu item displayed in the context menu for a network panel
+netmonitor.context.importHar.accesskey=I
+
+# LOCALIZATION NOTE (netmonitor.har.importHarDialogTitle): This is a label
+# used for import file open dialog
+netmonitor.har.importHarDialogTitle=Import HAR File
+
+# LOCALIZATION NOTE (netmonitor.har.importDialogHARFilter):
+# This string is displayed as a filter for importing HAR file
+netmonitor.har.importDialogHARFilter=HAR Files
+
+# LOCALIZATION NOTE (netmonitor.har.importDialogAllFilter):
+# This string is displayed as a filter for importing HAR file
+netmonitor.har.importDialogAllFilter=All Files
+
# LOCALIZATION NOTE (netmonitor.context.editAndResend): This is the label displayed
# on the context menu that opens a form to edit and resend the currently
# displayed request
netmonitor.context.editAndResend=Edit and Resend
# LOCALIZATION NOTE (netmonitor.context.editAndResend.accesskey): This is the access key
# for the "Edit and Resend" menu item displayed in the context menu for a request
netmonitor.context.editAndResend.accesskey=E
--- a/devtools/client/netmonitor/src/components/App.js
+++ b/devtools/client/netmonitor/src/components/App.js
@@ -59,17 +59,19 @@ class App extends Component {
return (
div({className: "network-monitor"},
!statisticsOpen ?
DropHarHandler({
actions,
openSplitConsole,
},
MonitorPanel({
+ actions,
connector,
+ openSplitConsole,
sourceMapService,
openLink,
})
) : StatisticsPanel({
connector
}),
)
);
--- a/devtools/client/netmonitor/src/components/DropHarHandler.js
+++ b/devtools/client/netmonitor/src/components/DropHarHandler.js
@@ -5,19 +5,18 @@
"use strict";
const { Component } = require("devtools/client/shared/vendor/react");
const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const { L10N } = require("../utils/l10n");
-loader.lazyGetter(this, "HarImporter", function() {
- return require("../har/har-importer").HarImporter;
-});
+loader.lazyRequireGetter(this, "HarMenuUtils",
+ "devtools/client/netmonitor/src/har/har-menu-utils", true);
const { div } = dom;
const DROP_HAR_FILES = L10N.getStr("netmonitor.label.dropHarFiles");
/**
* Helper component responsible for handling and importing
* dropped *.har files.
@@ -65,44 +64,34 @@ class DropHarHandler extends Component {
event.preventDefault();
stopDragging(findDOMNode(this));
let files = event.dataTransfer.files;
if (!files) {
return;
}
+ let {
+ actions,
+ openSplitConsole,
+ } = this.props;
+
// Import only the first dragged file for now
// See also:
// https://bugzilla.mozilla.org/show_bug.cgi?id=1438792
if (files.length) {
let file = files[0];
readFile(file).then(har => {
if (har) {
- this.appendPreview(har);
+ HarMenuUtils.appendPreview(har, actions, openSplitConsole);
}
});
}
}
- appendPreview(har) {
- let {
- openSplitConsole
- } = this.props;
-
- try {
- let importer = new HarImporter(this.props.actions);
- importer.import(har);
- } catch (err) {
- if (openSplitConsole) {
- openSplitConsole("Error while processing HAR file: " + err.message);
- }
- }
- }
-
// Rendering
render() {
return (
div({
onDragEnter: this.onDragEnter,
onDragOver: this.onDragOver,
onDragExit: this.onDragExit,
--- a/devtools/client/netmonitor/src/components/MonitorPanel.js
+++ b/devtools/client/netmonitor/src/components/MonitorPanel.js
@@ -37,20 +37,23 @@ const MediaQuerySingleRow = window.match
/**
* Monitor panel component
* The main panel for displaying various network request information
*/
class MonitorPanel extends Component {
static get propTypes() {
return {
+ actions: PropTypes.object.isRequired,
connector: PropTypes.object.isRequired,
isEmpty: PropTypes.bool.isRequired,
networkDetailsOpen: PropTypes.bool.isRequired,
openNetworkDetails: PropTypes.func.isRequired,
+ // Callback for opening split console.
+ openSplitConsole: PropTypes.func,
onNetworkDetailsResized: PropTypes.func.isRequired,
request: PropTypes.object,
selectedRequestVisible: PropTypes.bool.isRequired,
sourceMapService: PropTypes.object,
openLink: PropTypes.func,
updateRequest: PropTypes.func.isRequired,
};
}
@@ -113,32 +116,36 @@ class MonitorPanel extends Component {
return this.props.onNetworkDetailsResized(
isVerticalSpliter ? width : null,
isVerticalSpliter ? null : height
);
}
render() {
let {
+ actions,
connector,
isEmpty,
networkDetailsOpen,
openLink,
+ openSplitConsole,
sourceMapService,
} = this.props;
let initialWidth = Services.prefs.getIntPref(
"devtools.netmonitor.panes-network-details-width");
let initialHeight = Services.prefs.getIntPref(
"devtools.netmonitor.panes-network-details-height");
return (
div({ className: "monitor-panel" },
Toolbar({
+ actions,
connector,
+ openSplitConsole,
singleRow: this.state.isSingleRow,
}),
SplitBox({
className: "devtools-responsive-container",
initialWidth: initialWidth,
initialHeight: initialHeight,
minSize: "50px",
maxSize: "80%",
--- a/devtools/client/netmonitor/src/components/Toolbar.js
+++ b/devtools/client/netmonitor/src/components/Toolbar.js
@@ -53,16 +53,17 @@ loader.lazyRequireGetter(this, "HarMenuU
* Network monitor toolbar component.
*
* Toolbar contains a set of useful tools to control network requests
* as well as set of filters for filtering the content.
*/
class Toolbar extends Component {
static get propTypes() {
return {
+ actions: PropTypes.object.isRequired,
connector: PropTypes.object.isRequired,
toggleRecording: PropTypes.func.isRequired,
recording: PropTypes.bool.isRequired,
clearRequests: PropTypes.func.isRequired,
// List of currently displayed requests (i.e. filtered & sorted).
displayedRequests: PropTypes.array.isRequired,
requestFilterTypes: PropTypes.object.isRequired,
setRequestFilterText: PropTypes.func.isRequired,
@@ -72,16 +73,18 @@ class Toolbar extends Component {
disableBrowserCache: PropTypes.func.isRequired,
toggleBrowserCache: PropTypes.func.isRequired,
browserCacheDisabled: PropTypes.bool.isRequired,
toggleRequestFilterType: PropTypes.func.isRequired,
filteredRequests: PropTypes.array.isRequired,
// Set to true if there is enough horizontal space
// and the toolbar needs just one row.
singleRow: PropTypes.bool.isRequired,
+ // Callback for opening split console.
+ openSplitConsole: PropTypes.func,
};
}
constructor(props) {
super(props);
this.autocompleteProvider = this.autocompleteProvider.bind(this);
this.onSearchBoxFocus = this.onSearchBoxFocus.bind(this);
this.toggleRequestFilterType = this.toggleRequestFilterType.bind(this);
@@ -269,23 +272,34 @@ class Toolbar extends Component {
onClick: evt => {
this.showHarMenu(evt.target);
},
});
}
showHarMenu(menuButton) {
const {
+ actions,
connector,
- displayedRequests
+ displayedRequests,
+ openSplitConsole,
} = this.props;
let menuItems = [];
menuItems.push({
+ id: "request-list-context-import-har",
+ label: L10N.getStr("netmonitor.context.importHar"),
+ accesskey: L10N.getStr("netmonitor.context.importHar.accesskey"),
+ click: () => HarMenuUtils.openHarFile(actions, openSplitConsole),
+ });
+
+ menuItems.push("-");
+
+ menuItems.push({
id: "request-list-context-save-all-as-har",
label: L10N.getStr("netmonitor.context.saveAllAsHar"),
accesskey: L10N.getStr("netmonitor.context.saveAllAsHar.accesskey"),
disabled: !displayedRequests.length,
click: () => HarMenuUtils.saveAllAsHar(displayedRequests, connector),
});
menuItems.push({
--- a/devtools/client/netmonitor/src/har/har-menu-utils.js
+++ b/devtools/client/netmonitor/src/har/har-menu-utils.js
@@ -1,17 +1,24 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint-disable mozilla/reject-some-requires */
"use strict";
-loader.lazyRequireGetter(this, "HarExporter", "devtools/client/netmonitor/src/har/har-exporter", true);
+const { L10N } = require("../utils/l10n");
+
+loader.lazyRequireGetter(this, "HarExporter",
+ "devtools/client/netmonitor/src/har/har-exporter", true);
+
+loader.lazyGetter(this, "HarImporter", function() {
+ return require("../har/har-importer").HarImporter;
+});
/**
* Helper object with HAR related context menu actions.
*/
var HarMenuUtils = {
/**
* Copy HAR from the network panel content to the clipboard.
*/
@@ -25,18 +32,66 @@ var HarMenuUtils = {
saveAllAsHar(requests, connector) {
// This will not work in launchpad
// document.execCommand(‘cut’/‘copy’) was denied because it was not called from
// inside a short running user-generated event handler.
// https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Interact_with_the_clipboard
return HarExporter.save(this.getDefaultHarOptions(requests, connector));
},
+ /**
+ * Import HAR file and preview its content in the Network panel.
+ */
+ openHarFile(actions, openSplitConsole) {
+ let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+ fp.init(window, L10N.getStr("netmonitor.har.importHarDialogTitle"),
+ Ci.nsIFilePicker.modeOpen);
+
+ // Append file filters
+ fp.appendFilter(L10N.getStr("netmonitor.har.importDialogHARFilter"), "*.har");
+ fp.appendFilter(L10N.getStr("netmonitor.har.importDialogAllFilter"), "*.*");
+
+ fp.open(rv => {
+ if (rv == Ci.nsIFilePicker.returnOK) {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(fp.file.path);
+ readFile(file).then(har => {
+ if (har) {
+ this.appendPreview(har, actions, openSplitConsole);
+ }
+ });
+ }
+ });
+ },
+
+ appendPreview(har, actions, openSplitConsole) {
+ try {
+ let importer = new HarImporter(actions);
+ importer.import(har);
+ } catch (err) {
+ if (openSplitConsole) {
+ openSplitConsole("Error while processing HAR file: " + err.message);
+ }
+ }
+ },
+
getDefaultHarOptions(requests, connector) {
return {
connector: connector,
items: requests,
};
},
};
+// Helpers
+
+function readFile(file) {
+ return new Promise(resolve => {
+ const { OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
+ OS.File.read(file.path).then(data => {
+ let decoder = new TextDecoder();
+ resolve(decoder.decode(data));
+ });
+ });
+}
+
// Exports from this module
exports.HarMenuUtils = HarMenuUtils;
--- a/devtools/client/netmonitor/src/utils/menu.js
+++ b/devtools/client/netmonitor/src/utils/menu.js
@@ -19,16 +19,20 @@ const MenuItem = require("devtools/clien
function showMenu(items, options) {
if (items.length === 0) {
return;
}
// Build the menu object from provided menu items.
let menu = new Menu();
items.forEach((item) => {
+ if (item == "-") {
+ item = { type: "separator" };
+ }
+
let menuItem = new MenuItem(item);
let subItems = item.submenu;
if (subItems) {
let subMenu = new Menu();
subItems.forEach((subItem) => {
subMenu.append(new MenuItem(subItem));
});