Skip to content

Commit c643b9c

Browse files
committed
#11 No toolbox initial commit
1 parent 9be466b commit c643b9c

File tree

4 files changed

+278
-18
lines changed

4 files changed

+278
-18
lines changed

lib/main.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ const { Cu, Ci } = require("chrome");
1111
const { Trace, TraceError } = require("firebug.sdk/lib/core/trace.js").get(module.id);
1212
const { ToolboxChrome } = require("firebug.sdk/lib/toolbox-chrome.js");
1313

14-
// HARExportTrigger overlays
14+
// HARExportTrigger
1515
const { TriggerToolboxOverlay } = require("./trigger-toolbox-overlay.js");
16+
const { TriggerToolbox } = require("./trigger-toolbox.js");
1617

1718
/**
1819
* Entry point of the extension. Both 'main' and 'onUnload' methods are
@@ -22,6 +23,7 @@ function main(options, callbacks) {
2223
ToolboxChrome.initialize(options);
2324

2425
ToolboxChrome.registerToolboxOverlay(TriggerToolboxOverlay);
26+
TriggerToolbox.initialize();
2527
}
2628

2729
/**
@@ -30,6 +32,7 @@ function main(options, callbacks) {
3032
*/
3133
function onUnload(reason) {
3234
ToolboxChrome.unregisterToolboxOverlay(TriggerToolboxOverlay);
35+
TriggerToolbox.shutdown(reason);
3336

3437
ToolboxChrome.shutdown(reason);
3538
}

lib/trigger-toolbox-overlay.js

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ const TriggerToolboxOverlay = Class(
6060
ToolboxOverlay.prototype.initialize.apply(this, arguments);
6161

6262
Trace.sysout("TriggerToolboxOverlay.initialize;", options);
63+
64+
this.automation = options.automation;
6365
},
6466

6567
destroy: function() {
@@ -83,8 +85,6 @@ const TriggerToolboxOverlay = Class(
8385
return;
8486
}
8587

86-
let harOverlay = getHarOverlay(this.toolbox);
87-
8888
// Call make remote to make sure the target.client exists.
8989
let target = this.toolbox.target;
9090
target.makeRemote().then(() => {
@@ -94,14 +94,19 @@ const TriggerToolboxOverlay = Class(
9494
// But, if users want to use HAR content API to trigger HAR export
9595
// when needed, HAR automation needs to be activated. Let's do it now
9696
// if 'extensions.netmonitor.har.enableAutomation' preference is true.
97-
if (prefs.enableAutomation && !harOverlay.automation) {
97+
if (prefs.enableAutomation && !this.automation) {
9898
Trace.sysout("TriggerToolboxOverlay.onReady; Init automation");
9999

100100
// Initialize automation.
101-
harOverlay.initAutomation();
102-
this.patchAutomation(harOverlay.automation);
101+
let harOverlay = getHarOverlay(this.toolbox);
102+
if (!harOverlay.automation) {
103+
harOverlay.initAutomation();
104+
}
105+
this.automation = harOverlay.automation;
103106
}
104107

108+
this.patchAutomation(this.automation);
109+
105110
// This is a bit hacky, but the HarAutomation starts monitoring
106111
// after target.makeRemote() promise is resolved.
107112
// It's resolved after the parent target.makeRemote() (we are just within)
@@ -113,9 +118,8 @@ const TriggerToolboxOverlay = Class(
113118
// created when the page load begins, but the toolbox can be opened
114119
// in the middle of page session (after page load event).
115120
// And HAR API consumer might want to export any time.
116-
let automation = harOverlay.automation;
117-
if (automation && !automation.collector) {
118-
automation.resetCollector();
121+
if (this.automation && !this.automation.collector) {
122+
this.automation.resetCollector();
119123
}
120124
});
121125

@@ -129,7 +133,11 @@ const TriggerToolboxOverlay = Class(
129133
* xxxHonza: this needs better platform API.
130134
* See also: https://github.com/firebug/har-export-trigger/issues/10
131135
*/
132-
patchAutomation: function(automation){
136+
patchAutomation: function(automation) {
137+
if (!automation) {
138+
return;
139+
}
140+
133141
let self = this;
134142
automation.pageLoadDone = function(response) {
135143
Trace.sysout("HarAutomation.patchAutomation;", response);
@@ -204,28 +212,37 @@ const TriggerToolboxOverlay = Class(
204212
triggerExport: function(data) {
205213
Trace.sysout("TriggerToolboxOverlay.triggerExport;", data);
206214

207-
var harOverlay = getHarOverlay(this.toolbox);
208-
if (!harOverlay.automation) {
215+
if (!this.automation) {
209216
let pref1 = "devtools.netmonitor.har.enableAutoExportToFile";
210217
let pref2 = "extensions.netmonitor.har.enableAutomation";
211218

212-
if (!harOverlay.automation) {
219+
if (!this.automation) {
213220
Cu.reportError("You need to set '" + pref1 + "' or '" + pref2 +
214221
"' pref to enable HAR export through the API " +
215222
"(browser restart is required)");
216223
}
217224
return;
218225
}
219226

220-
if (!harOverlay.automation.collector) {
227+
if (!this.automation.collector) {
221228
Cu.reportError("The HAR collector doesn't exist. Page reload required.");
222229
return;
223230
}
224231

225232
// Trigger HAR export now! Use executeExport() not triggerExport()
226233
// since we don't want to have the default name automatically provided.
227-
harOverlay.automation.executeExport(data).then(jsonString => {
228-
Trace.sysout("TriggerToolboxOverlay.triggerExport; DONE", jsonString);
234+
this.automation.executeExport(data).then(jsonString => {
235+
var har = jsonString;
236+
try {
237+
if (jsonString) {
238+
har = JSON.parse(jsonString);
239+
}
240+
} catch (err) {
241+
Trace.sysout("TriggerToolboxOverlay.triggerExport; ERROR " +
242+
"Failed to parse HAR log " + err);
243+
}
244+
245+
Trace.sysout("TriggerToolboxOverlay.triggerExport; DONE", har);
229246

230247
// Send event back to the backend notifying that it has
231248
// finished. If 'getData' is true include also the HAR string.
@@ -246,11 +263,12 @@ const TriggerToolboxOverlay = Class(
246263
* needs to be cleared.
247264
*/
248265
clear: function() {
266+
Trace.sysout("TriggerToolboxOverlay.clear;");
267+
249268
let panel = this.toolbox.getPanel("netmonitor");
250269

251270
// Clean up also the HAR collector.
252-
var harOverlay = getHarOverlay(this.toolbox);
253-
harOverlay.automation.resetCollector();
271+
this.automation.resetCollector();
254272

255273
// Clear the Network panel content. The panel doesn't
256274
// have to exist if the user doesn't select it yet.

lib/trigger-toolbox.js

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
/* See license.txt for terms of usage */
2+
3+
"use strict";
4+
5+
module.metadata = {
6+
"stability": "stable"
7+
};
8+
9+
// Add-on SDK
10+
const options = require("@loader/options");
11+
const { Cu } = require("chrome");
12+
const { Class } = require("sdk/core/heritage");
13+
const { prefs } = require("sdk/simple-prefs");
14+
const { defer, resolve } = require("sdk/core/promise");
15+
const { emit } = require("sdk/event/core");
16+
17+
// Firebug.SDK
18+
const { DebuggerServer, DebuggerClient, devtools } = require("firebug.sdk/lib/core/devtools.js");
19+
const { Trace, TraceError } = require("firebug.sdk/lib/core/trace.js").get(module.id);
20+
const { TriggerToolboxOverlay } = require("./trigger-toolbox-overlay.js");
21+
22+
// Platform
23+
const { HarAutomation } = devtools.require("devtools/client/netmonitor/har/har-automation");
24+
25+
// Constants
26+
const TargetFactory = devtools.TargetFactory;
27+
28+
/**
29+
* TODO: docs
30+
*/
31+
const TriggerToolbox =
32+
/** @lends TriggerToolbox */
33+
{
34+
// Initialization
35+
36+
initialize: function() {
37+
this.onTabListChanged = this.onTabListChanged.bind(this);
38+
39+
// Map<tab, TriggerToolboxOverlay>
40+
this.overlays = new Map();
41+
42+
if (!prefs.autoConnect) {
43+
return;
44+
}
45+
46+
this.connect().then(client => {
47+
this.onReady(client);
48+
});
49+
},
50+
51+
onReady: function(client) {
52+
Trace.sysout("TriggerToolbox.onReady;", client);
53+
54+
this.client = client;
55+
this.client.addListener("tabListChanged", this.onTabListChanged);
56+
57+
// Ensure that initial connection for the default tab is created.
58+
this.onTabListChanged();
59+
},
60+
61+
shutdown: function() {
62+
this.close();
63+
},
64+
65+
// Connect/close
66+
67+
connect: function() {
68+
let deferred = defer();
69+
70+
if (!DebuggerServer.initialized) {
71+
DebuggerServer.init();
72+
DebuggerServer.addBrowserActors();
73+
}
74+
75+
let client = new DebuggerClient(DebuggerServer.connectPipe());
76+
client.connect(() => {
77+
Trace.sysout("TriggerToolbox.connect; DONE", client);
78+
deferred.resolve(client);
79+
});
80+
81+
return deferred.promise;
82+
},
83+
84+
close: function() {
85+
Trace.sysout("TriggerToolbox.close;");
86+
87+
if (!this.target) {
88+
return resolve();
89+
}
90+
91+
if (this.destroyer) {
92+
return this.destroyer.promise;
93+
}
94+
95+
this.destroyer = defer();
96+
97+
this.client.close(() => {
98+
this.destroyer.resolve();
99+
});
100+
101+
return this.destroyer.promise;
102+
},
103+
104+
// Events
105+
106+
/**
107+
* Handle 'tabListChanged' event and attach the selected tab.
108+
* Note that there is an extra connection created for each tab.
109+
* So, network events ('tabListChanged' and 'networkEventUpdate')
110+
* are sent only to the attached automation.collector object.
111+
*
112+
* xxxHonza: if we remove the check in HarCollector.onNetworkEventUpdate
113+
* method (labeled as: 'Skip events from unknown actors') we might
114+
* do everything through one connection. But this needs testing.
115+
*/
116+
onTabListChanged: function(eventId, packet) {
117+
Trace.sysout("TriggerToolbox.onTabListChanged;", arguments);
118+
119+
// Execute 'listTabs' to make sure that 'tabListChanged' event
120+
// will be sent the next time (this is historical complexity
121+
// of the backend). This must be done after every 'tabListChanged'.
122+
this.client.listTabs(response => {
123+
if (response.error) {
124+
Trace.sysout("TriggerToolbox.onTabListChanged; ERROR " +
125+
response.message, response);
126+
return;
127+
}
128+
129+
let currentTab = response.tabs[response.selected];
130+
Trace.sysout("TriggerToolbox.onTabListChanged; " +
131+
"(initial connection): " + currentTab.actor, response);
132+
133+
// Bail out if the tab already has its own connection.
134+
if (this.overlays.has(currentTab.actor)) {
135+
return;
136+
}
137+
138+
// Create new connection for the current tab.
139+
this.connect().then(client => {
140+
// Execute list of tabs for the new connection (it'll maintain
141+
// it's own tab actors on the backend).
142+
client.listTabs(response => {
143+
let tabForm = response.tabs[response.selected];
144+
let tabActor = tabForm.actor;
145+
146+
Trace.sysout("TriggerToolbox.onTabListChanged; " +
147+
"current tab: " + tabActor, tabForm);
148+
149+
// Attach to the current tab using the new connection.
150+
this.attachTab(tabForm, client).then(result => {
151+
this.overlays.set(currentTab.actor, result);
152+
153+
Trace.sysout("TriggerToolbox.onTabListChanged; tab attached: " +
154+
currentTab.actor, this.overlays);
155+
});
156+
});
157+
});
158+
});
159+
},
160+
161+
onTabNavigated: function(packet) {
162+
Trace.sysout("TriggerToolbox.onTabNavigated; " + packet.from, packet);
163+
},
164+
165+
onTabDetached: function(packet) {
166+
Trace.sysout("TriggerToolbox.onTabDetached; " + packet.from, packet);
167+
168+
var tabActor = packet.from;
169+
170+
// Destroy the automation object and close its connection.
171+
var entry = this.overlays.get(tabActor);
172+
if (entry) {
173+
entry.overlay.destroy();
174+
entry.automation.destroy();
175+
entry.client.close();
176+
177+
this.overlays.delete(tabActor);
178+
}
179+
},
180+
181+
/**
182+
* Attach to given tab.
183+
*/
184+
attachTab: function(tab, client) {
185+
Trace.sysout("TriggerToolbox.attachTab; " + tab.actor);
186+
187+
let config = {
188+
form: tab,
189+
client: client,
190+
chrome: false,
191+
};
192+
193+
// Create target, automation object and the toolbox overlay object
194+
// This is what the real Toolbox does (but the Toolbox
195+
// isn't available at the moment).
196+
return TargetFactory.forRemoteTab(config).then(target => {
197+
Trace.sysout("TriggerToolbox.attachTab; target", target);
198+
199+
// Simulate the Toolbox object since the TriggerToolboxOverlay
200+
// is based on it.
201+
// xxxHonza: If TriggerToolboxOverlay is based on the target
202+
// things would be easier.
203+
var toolbox = {
204+
target: target,
205+
getPanel: function() {},
206+
on: function() {}
207+
};
208+
209+
var automation = new HarAutomation(toolbox);
210+
211+
// Create toolbox overlay (just like for the real Toolbox).
212+
let options = {
213+
toolbox: toolbox,
214+
automation: automation,
215+
}
216+
217+
// Instantiate the toolbox overlay and simulate onReady event.
218+
var overlay = new TriggerToolboxOverlay(options);
219+
overlay.onReady({});
220+
221+
Trace.sysout("TriggerToolbox.onTabSelected; New automation", options);
222+
223+
return {
224+
overlay: overlay,
225+
automation: automation,
226+
client: client
227+
};
228+
});
229+
}
230+
};
231+
232+
// Exports from this module
233+
exports.TriggerToolbox = TriggerToolbox;

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@
3333
"description": "Set to true to expose HAR trigger API into the content",
3434
"type": "string",
3535
"value": ""
36+
}, {
37+
"name": "autoConnect",
38+
"title": "Automatically connect to the backend",
39+
"description": "Automatically connect to the backend, the Toolbox doesn't have to be open.",
40+
"type": "bool",
41+
"value": false
3642
}, {
3743
"name": "enableAutomation",
3844
"title": "Enable automatic collecting of HAR data",

0 commit comments

Comments
 (0)