Bug 1436665 - Use Net monitor app object for WebExtension API; r=ochameau
MozReview-Commit-ID: H82IJX9tAXe
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -66,16 +66,18 @@ loader.lazyRequireGetter(this, "SourceMa
loader.lazyRequireGetter(this, "HUDService",
"devtools/client/webconsole/hudservice", true);
loader.lazyRequireGetter(this, "viewSource",
"devtools/client/shared/view-source");
loader.lazyRequireGetter(this, "StyleSheetsFront",
"devtools/shared/fronts/stylesheets", true);
loader.lazyRequireGetter(this, "buildHarLog",
"devtools/client/netmonitor/src/har/har-builder-utils", true);
+loader.lazyRequireGetter(this, "NetMonitorApp",
+ "devtools/client/netmonitor/src/app", true);
loader.lazyGetter(this, "domNodeConstants", () => {
return require("devtools/shared/dom-node-constants");
});
loader.lazyGetter(this, "registerHarOverlay", () => {
return require("devtools/client/netmonitor/src/har/toolbox-overlay").register;
});
@@ -109,19 +111,16 @@ function Toolbox(target, selectedTool, h
this._initInspector = null;
this._inspector = null;
this._styleSheets = null;
// Map of frames (id => frame-info) and currently selected frame id.
this.frameMap = new Map();
this.selectedFrameId = null;
- // List of listeners for `devtools.network.onRequestFinished` WebExt API
- this._requestFinishedListeners = new Set();
-
this._toolRegistered = this._toolRegistered.bind(this);
this._toolUnregistered = this._toolUnregistered.bind(this);
this._onWillNavigate = this._onWillNavigate.bind(this);
this._refreshHostTitle = this._refreshHostTitle.bind(this);
this._toggleNoAutohide = this._toggleNoAutohide.bind(this);
this.showFramesMenu = this.showFramesMenu.bind(this);
this.handleKeyDownOnFramesButton = this.handleKeyDownOnFramesButton.bind(this);
this.showFramesMenuOnKeyDown = this.showFramesMenuOnKeyDown.bind(this);
@@ -2697,17 +2696,16 @@ Toolbox.prototype = {
this._applyServiceWorkersTestingSettings);
this._lastFocusedElement = null;
if (this._sourceMapURLService) {
this._sourceMapURLService.destroy();
this._sourceMapURLService = null;
}
-
if (this._sourceMapService) {
this._sourceMapService.stopSourceMapWorker();
this._sourceMapService = null;
}
if (this.webconsolePanel) {
this._saveSplitConsoleHeight();
this.webconsolePanel.removeEventListener("resize",
@@ -2784,16 +2782,21 @@ Toolbox.prototype = {
this._telemetry.destroy();
// Finish all outstanding tasks (which means finish destroying panels and
// then destroying the host, successfully or not) before destroying the
// target.
deferred.resolve(settleAll(outstanding)
.catch(console.error)
.then(() => {
+ let app = this._netMonitorApp;
+ this._netMonitorApp = null;
+ return app ? app.destroy() : null;
+ }, console.error)
+ .then(() => {
this._removeHostListeners();
// `location` may already be 'invalid' if the toolbox document is
// already in process of destruction. Otherwise if it is still
// around, ensure releasing toolbox document and triggering cleanup
// thanks to unload event. We do that precisely here, before
// nullifying the target as various cleanup code depends on the
// target attribute to be still
@@ -3042,64 +3045,79 @@ Toolbox.prototype = {
* @see devtools/client/shared/source-utils.js
*/
viewSource: function(sourceURL, sourceLine) {
return viewSource.viewSource(this, sourceURL, sourceLine);
},
// Support for WebExtensions API (`devtools.network.*`)
+ getNetMonitorAppWhenReady: async function() {
+ if (this._netMonitorApp) {
+ return this._netMonitorApp;
+ }
+
+ // Create and initialize Network monitor application object.
+ // This object is only connected to the backend not to the UI.
+ // It's supposed to be used by WebExtension API only.
+ this._netMonitorApp = new NetMonitorApp();
+ await this._netMonitorApp.connect({toolbox: this});
+
+ return this._netMonitorApp;
+ },
+
/**
* Returns data (HAR) collected by the Network panel.
*/
getHARFromNetMonitor: async function() {
let netPanel = this.getPanel("netmonitor");
-
- // The panel doesn't have to exist (it must be selected
- // by the user at least once to be created).
- // Return default empty HAR log in such case.
- if (!netPanel) {
- let har = await buildHarLog(Services.appinfo);
- return har.log;
- }
+ let har;
// Use Netmonitor object to get the current HAR log.
- let har = await netPanel.panelWin.Netmonitor.getHar();
+ // If Netmonitor panel doesn't exist (not initialized yet)
+ // Use NetMonitorApp object created specifically for
+ // HAR export.
+ if (netPanel) {
+ har = await netPanel.panelWin.Netmonitor.getHar();
+ } if (this._netMonitorApp) {
+ har = await this._netMonitorApp.getHar();
+ }
+
+ // Return default empty HAR file.
+ if (!har) {
+ har = buildHarLog(Services.appinfo);
+ }
// Return the log directly to be compatible with
// Chrome WebExtension API.
return har.log;
},
/**
* Add listener for `onRequestFinished` events.
*
* @param {Object} listener
* The listener to be called it's expected to be
* a function that takes ({harEntry, requestId})
* as first argument.
*/
- addRequestFinishedListener: function(listener) {
- // Log console message informing the extension developer
- // that the Network panel needs to be selected at least
- // once in order to receive `onRequestFinished` events.
- let message = "The Network panel needs to be selected at least" +
- " once in order to receive 'onRequestFinished' events.";
- this.target.logWarningInPage(message, "har");
-
- // Add the listener into internal list.
- this._requestFinishedListeners.add(listener);
+ addRequestFinishedListener: async function(listener) {
+ let app = await this.getNetMonitorAppWhenReady();
+ app.addRequestFinishedListener(listener);
},
- removeRequestFinishedListener: function(listener) {
- this._requestFinishedListeners.delete(listener);
- },
-
- getRequestFinishedListeners: function() {
- return this._requestFinishedListeners;
+ removeRequestFinishedListener: async function(listener) {
+ let app = await this.getNetMonitorAppWhenReady();
+ app.removeRequestFinishedListener(listener);
+
+ // Destroy Net Monitor object if there is no listener.
+ if (!app.hasRequestFinishedListeners()) {
+ this._netMonitorApp.destroy();
+ this._netMonitorApp = null;
+ }
},
/**
* Used to lazily fetch HTTP response content within
* `onRequestFinished` event listener.
*
* @param {String} requestId
* Id of the request for which the response content
--- a/devtools/client/netmonitor/src/app.js
+++ b/devtools/client/netmonitor/src/app.js
@@ -39,16 +39,19 @@ const {
*/
function NetMonitorApp() {
EventEmitter.decorate(this);
// Configure store/state object.
this.connector = new Connector();
this.store = configureStore(this.connector);
+ // List of listeners for `devtools.network.onRequestFinished` WebExt API
+ this._requestFinishedListeners = new Set();
+
// Bind event handlers
this.onRequestAdded = this.onRequestAdded.bind(this);
this.actions = bindActionCreators(Actions, this.store.dispatch);
}
NetMonitorApp.prototype = {
bootstrap({ toolbox, panel, document }) {
// Get the root element for mounting.
@@ -165,52 +168,76 @@ NetMonitorApp.prototype = {
return this.harExportConnector;
},
/**
* Support for `devtools.network.onRequestFinished`. A hook for
* every finished HTTP request used by WebExtensions API.
*/
async onRequestAdded(event, requestId) {
- let listeners = this.toolbox.getRequestFinishedListeners();
- if (!listeners.size) {
+ if (!this._requestFinishedListeners.size) {
return;
}
let { HarExporter } = require("devtools/client/netmonitor/src/har/har-exporter");
let connector = await this.getHarExportConnector();
let request = getDisplayedRequestById(this.store.getState(), requestId);
+ if (!request) {
+ console.error("HAR: request not found " + requestId);
+ return;
+ }
+
let options = {
connector,
includeResponseBodies: false,
items: [request],
};
// Build HAR for specified request only.
let har = await HarExporter.getHar(options);
// There is page so remove the page reference.
let harEntry = har.log.entries[0];
delete harEntry.pageref;
- listeners.forEach(listener => listener({
+ this._requestFinishedListeners.forEach(listener => listener({
harEntry,
requestId,
}));
},
/**
* Support for `Request.getContent` WebExt API (lazy loading response body)
*/
fetchResponseContent(requestId) {
return this.connector.requestData(requestId, "responseContent");
},
/**
+ * Add listener for `onRequestFinished` events.
+ *
+ * @param {Object} listener
+ * The listener to be called it's expected to be
+ * a function that takes ({harEntry, requestId})
+ * as first argument.
+ */
+ addRequestFinishedListener: function(listener) {
+ this._requestFinishedListeners.add(listener);
+ },
+
+ removeRequestFinishedListener: function(listener) {
+ this._requestFinishedListeners.delete(listener);
+ },
+
+ hasRequestFinishedListeners: function() {
+ return this._requestFinishedListeners.size > 0;
+ },
+
+ /**
* Selects the specified request in the waterfall and opens the details view.
* This is a firefox toolbox specific API, which providing an ability to inspect
* a network request directly from other internal toolbox panel.
*
* @param {string} requestId The actor ID of the request to inspect.
* @return {object} A promise resolved once the task finishes.
*/
inspectRequest(requestId) {
--- a/devtools/client/netmonitor/src/connector/firefox-connector.js
+++ b/devtools/client/netmonitor/src/connector/firefox-connector.js
@@ -56,17 +56,20 @@ class FirefoxConnector {
// Listener for `will-navigate` event is (un)registered outside
// of the `addListeners` and `removeListeners` methods since
// these are used to pause/resume the connector.
// Paused network panel should be automatically resumed when page
// reload, so `will-navigate` listener needs to be there all the time.
this.tabTarget.on("will-navigate", this.willNavigate);
this.tabTarget.on("navigate", this.navigate);
- this.displayCachedEvents();
+ // Displaying cache events is only intended for the UI panel.
+ if (this.panel) {
+ this.displayCachedEvents();
+ }
}
async disconnect() {
this.actions.batchReset();
await this.removeListeners();
if (this.tabTarget) {