mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-01 22:55:23 +00:00
Merge latest green birch changeset and mozilla-central
This commit is contained in:
commit
744a030aa7
@ -577,7 +577,8 @@
|
||||
<menuitem id="menu_chromeDebugger"
|
||||
observes="devtoolsMenuBroadcaster_ChromeDebugger"/>
|
||||
<menuitem id="menu_browserConsole"
|
||||
observes="devtoolsMenuBroadcaster_BrowserConsole"/>
|
||||
observes="devtoolsMenuBroadcaster_BrowserConsole"
|
||||
accesskey="&browserConsoleCmd.accesskey;"/>
|
||||
<menuitem id="menu_responsiveUI"
|
||||
observes="devtoolsMenuBroadcaster_ResponsiveUI"
|
||||
accesskey="&responsiveDesignTool.accesskey;"/>
|
||||
|
@ -96,7 +96,7 @@
|
||||
<command id="Tools:DevToolbar" oncommand="DeveloperToolbar.toggle();" disabled="true" hidden="true"/>
|
||||
<command id="Tools:DevToolbarFocus" oncommand="DeveloperToolbar.focusToggle();" disabled="true"/>
|
||||
<command id="Tools:ChromeDebugger" oncommand="DebuggerUI.toggleChromeDebugger();" disabled="true" hidden="true"/>
|
||||
<command id="Tools:BrowserConsole" oncommand="HUDConsoleUI.toggleBrowserConsole();" disabled="true" hidden="true"/>
|
||||
<command id="Tools:BrowserConsole" oncommand="HUDConsoleUI.toggleBrowserConsole();"/>
|
||||
<command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();" disabled="true" hidden="true"/>
|
||||
<command id="Tools:ResponsiveUI" oncommand="ResponsiveUI.toggle();" disabled="true" hidden="true"/>
|
||||
<command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
|
||||
@ -187,6 +187,7 @@
|
||||
command="Tools:ChromeDebugger"/>
|
||||
<broadcaster id="devtoolsMenuBroadcaster_BrowserConsole"
|
||||
label="&browserConsoleCmd.label;"
|
||||
key="key_browserConsole"
|
||||
command="Tools:BrowserConsole"/>
|
||||
<broadcaster id="devtoolsMenuBroadcaster_Scratchpad"
|
||||
label="&scratchpad.label;"
|
||||
@ -203,7 +204,6 @@
|
||||
command="View:PageSource"/>
|
||||
<broadcaster id="devtoolsMenuBroadcaster_ErrorConsole"
|
||||
label="&errorConsoleCmd.label;"
|
||||
key="key_errorConsole"
|
||||
command="Tools:ErrorConsole"/>
|
||||
<broadcaster id="devtoolsMenuBroadcaster_GetMoreTools"
|
||||
label="&getMoreDevtoolsCmd.label;"
|
||||
@ -261,7 +261,7 @@
|
||||
<key id="key_openDownloads" key="&downloads.commandkey;" command="Tools:Downloads" modifiers="accel"/>
|
||||
#endif
|
||||
<key id="key_openAddons" key="&addons.commandkey;" command="Tools:Addons" modifiers="accel,shift"/>
|
||||
<key id="key_errorConsole" key="&errorConsoleCmd.commandkey;" command="Tools:ErrorConsole" modifiers="accel,shift"/>
|
||||
<key id="key_browserConsole" key="&browserConsoleCmd.commandkey;" command="Tools:BrowserConsole" modifiers="accel,shift"/>
|
||||
<key id="key_devToolbar" keycode="&devToolbar.keycode;" modifiers="shift"
|
||||
keytext="&devToolbar.keytext;" command="Tools:DevToolbarFocus"/>
|
||||
<key id="key_responsiveUI" key="&responsiveDesignTool.commandkey;" command="Tools:ResponsiveUI"
|
||||
|
@ -1193,17 +1193,8 @@ var gBrowserInit = {
|
||||
cmd.removeAttribute("hidden");
|
||||
}
|
||||
|
||||
// Enable the Browser Console?
|
||||
if (chromeEnabled) {
|
||||
let cmd = document.getElementById("Tools:BrowserConsole");
|
||||
cmd.removeAttribute("disabled");
|
||||
cmd.removeAttribute("hidden");
|
||||
}
|
||||
|
||||
// Enable Error Console?
|
||||
// Temporarily enabled. See bug 798925.
|
||||
let consoleEnabled = true || gPrefService.getBoolPref("devtools.errorconsole.enabled") ||
|
||||
chromeEnabled;
|
||||
let consoleEnabled = gPrefService.getBoolPref("devtools.errorconsole.enabled");
|
||||
if (consoleEnabled) {
|
||||
let cmd = document.getElementById("Tools:ErrorConsole");
|
||||
cmd.removeAttribute("disabled");
|
||||
|
@ -13,6 +13,7 @@ Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
|
||||
Cu.import("resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm");
|
||||
Cu.import("resource:///modules/devtools/ProfilerController.jsm");
|
||||
|
||||
const FORBIDDEN_IDS = new Set(["toolbox", ""]);
|
||||
const MAX_ORDINAL = 99;
|
||||
@ -621,6 +622,25 @@ let gDevToolsBrowser = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Connects to the SPS profiler when the developer tools are open.
|
||||
*/
|
||||
_connectToProfiler: function DT_connectToProfiler() {
|
||||
for (let win of gDevToolsBrowser._trackedBrowserWindows) {
|
||||
if (devtools.TargetFactory.isKnownTab(win.gBrowser.selectedTab)) {
|
||||
let target = devtools.TargetFactory.forTab(win.gBrowser.selectedTab);
|
||||
if (gDevTools._toolboxes.has(target)) {
|
||||
target.makeRemote().then(() => {
|
||||
let profiler = new ProfilerController(target);
|
||||
profiler.connect();
|
||||
}).then(null, Cu.reportError);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove the menuitem for a tool to all open browser windows.
|
||||
*
|
||||
@ -694,6 +714,7 @@ let gDevToolsBrowser = {
|
||||
* All browser windows have been closed, tidy up remaining objects.
|
||||
*/
|
||||
destroy: function() {
|
||||
gDevTools.off("toolbox-ready", gDevToolsBrowser._connectToProfiler);
|
||||
Services.obs.removeObserver(gDevToolsBrowser.destroy, "quit-application");
|
||||
},
|
||||
}
|
||||
@ -712,6 +733,7 @@ gDevTools.on("tool-unregistered", function(ev, toolId) {
|
||||
});
|
||||
|
||||
gDevTools.on("toolbox-ready", gDevToolsBrowser._updateMenuCheckbox);
|
||||
gDevTools.on("toolbox-ready", gDevToolsBrowser._connectToProfiler);
|
||||
gDevTools.on("toolbox-destroyed", gDevToolsBrowser._updateMenuCheckbox);
|
||||
|
||||
Services.obs.addObserver(gDevToolsBrowser.destroy, "quit-application", false);
|
||||
|
@ -207,6 +207,10 @@ TabTarget.prototype = {
|
||||
return this._form;
|
||||
},
|
||||
|
||||
get root() {
|
||||
return this._root;
|
||||
},
|
||||
|
||||
get client() {
|
||||
return this._client;
|
||||
},
|
||||
@ -287,6 +291,7 @@ TabTarget.prototype = {
|
||||
if (this.isLocalTab) {
|
||||
this._client.connect((aType, aTraits) => {
|
||||
this._client.listTabs(aResponse => {
|
||||
this._root = aResponse;
|
||||
this._form = aResponse.tabs[aResponse.selected];
|
||||
attachTab();
|
||||
});
|
||||
|
@ -409,6 +409,49 @@ Toolbox.prototype = {
|
||||
this._addKeysToWindow();
|
||||
},
|
||||
|
||||
/**
|
||||
* Load a tool with a given id.
|
||||
*
|
||||
* @param {string} id
|
||||
* The id of the tool to load.
|
||||
*/
|
||||
loadTool: function TBOX_loadTool(id) {
|
||||
let deferred = Promise.defer();
|
||||
let iframe = this.doc.getElementById("toolbox-panel-iframe-" + id);
|
||||
|
||||
if (iframe) {
|
||||
this.once(id + "-ready", () => { deferred.resolve() });
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
let definition = gDevTools.getToolDefinitionMap().get(id);
|
||||
iframe = this.doc.createElement("iframe");
|
||||
iframe.className = "toolbox-panel-iframe";
|
||||
iframe.id = "toolbox-panel-iframe-" + id;
|
||||
iframe.setAttribute("flex", 1);
|
||||
iframe.setAttribute("forceOwnRefreshDriver", "");
|
||||
iframe.tooltip = "aHTMLTooltip";
|
||||
|
||||
let vbox = this.doc.getElementById("toolbox-panel-" + id);
|
||||
vbox.appendChild(iframe);
|
||||
|
||||
let onLoad = () => {
|
||||
iframe.removeEventListener("DOMContentLoaded", onLoad, true);
|
||||
|
||||
let built = definition.build(iframe.contentWindow, this);
|
||||
Promise.resolve(built).then((panel) => {
|
||||
this._toolPanels.set(id, panel);
|
||||
this.emit(id + "-ready", panel);
|
||||
gDevTools.emit(id + "-ready", this, panel);
|
||||
deferred.resolve(panel);
|
||||
});
|
||||
};
|
||||
|
||||
iframe.addEventListener("DOMContentLoaded", onLoad, true);
|
||||
iframe.setAttribute("src", definition.url);
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Switch to the tool with the given id
|
||||
*
|
||||
@ -464,8 +507,6 @@ Toolbox.prototype = {
|
||||
let deck = this.doc.getElementById("toolbox-deck");
|
||||
deck.selectedIndex = index;
|
||||
|
||||
let definition = gDevTools.getToolDefinitionMap().get(id);
|
||||
|
||||
this._currentToolId = id;
|
||||
|
||||
let resolveSelected = panel => {
|
||||
@ -476,32 +517,11 @@ Toolbox.prototype = {
|
||||
|
||||
let iframe = this.doc.getElementById("toolbox-panel-iframe-" + id);
|
||||
if (!iframe) {
|
||||
iframe = this.doc.createElement("iframe");
|
||||
iframe.className = "toolbox-panel-iframe";
|
||||
iframe.id = "toolbox-panel-iframe-" + id;
|
||||
iframe.setAttribute("flex", 1);
|
||||
iframe.setAttribute("forceOwnRefreshDriver", "");
|
||||
iframe.tooltip = "aHTMLTooltip";
|
||||
|
||||
let vbox = this.doc.getElementById("toolbox-panel-" + id);
|
||||
vbox.appendChild(iframe);
|
||||
|
||||
let boundLoad = function() {
|
||||
iframe.removeEventListener("DOMContentLoaded", boundLoad, true);
|
||||
|
||||
let built = definition.build(iframe.contentWindow, this);
|
||||
Promise.resolve(built).then(function(panel) {
|
||||
this._toolPanels.set(id, panel);
|
||||
|
||||
this.emit(id + "-ready", panel);
|
||||
gDevTools.emit(id + "-ready", this, panel);
|
||||
|
||||
resolveSelected(panel);
|
||||
}.bind(this));
|
||||
}.bind(this);
|
||||
|
||||
iframe.addEventListener("DOMContentLoaded", boundLoad, true);
|
||||
iframe.setAttribute("src", definition.url);
|
||||
this.loadTool(id).then((panel) => {
|
||||
this.emit("select", id);
|
||||
this.emit(id + "-selected", panel);
|
||||
deferred.resolve(panel);
|
||||
});
|
||||
} else {
|
||||
let panel = this._toolPanels.get(id);
|
||||
// only emit 'select' event if the iframe has been loaded
|
||||
|
@ -10,13 +10,16 @@ const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/Console.jsm");
|
||||
Cu.import("resource://gre/modules/AddonManager.jsm");
|
||||
|
||||
let EXPORTED_SYMBOLS = ["ProfilerController"];
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "DebuggerServer", function () {
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
|
||||
return DebuggerServer;
|
||||
});
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "gDevTools",
|
||||
"resource:///modules/devtools/gDevTools.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
|
||||
"resource://gre/modules/devtools/dbg-server.jsm");
|
||||
|
||||
/**
|
||||
* Data structure that contains information that has
|
||||
@ -24,18 +27,24 @@ XPCOMUtils.defineLazyGetter(this, "DebuggerServer", function () {
|
||||
* instances.
|
||||
*/
|
||||
const sharedData = {
|
||||
startTime: 0,
|
||||
data: new WeakMap(),
|
||||
controllers: new WeakMap(),
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes a structure representing an individual profile.
|
||||
*/
|
||||
function makeProfile(name) {
|
||||
function makeProfile(name, def={}) {
|
||||
if (def.timeStarted == null)
|
||||
def.timeStarted = null;
|
||||
|
||||
if (def.timeEnded == null)
|
||||
def.timeEnded = null;
|
||||
|
||||
return {
|
||||
name: name,
|
||||
timeStarted: null,
|
||||
timeEnded: null
|
||||
timeStarted: def.timeStarted,
|
||||
timeEnded: def.timeEnded
|
||||
};
|
||||
}
|
||||
|
||||
@ -50,10 +59,6 @@ function getProfiles(target) {
|
||||
return sharedData.data.get(target);
|
||||
}
|
||||
|
||||
function getCurrentTime() {
|
||||
return (new Date()).getTime() - sharedData.startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Object to control the JavaScript Profiler over the remote
|
||||
* debugging protocol.
|
||||
@ -62,9 +67,14 @@ function getCurrentTime() {
|
||||
* A target object as defined in Target.jsm
|
||||
*/
|
||||
function ProfilerController(target) {
|
||||
if (sharedData.controllers.has(target)) {
|
||||
return sharedData.controllers.get(target);
|
||||
}
|
||||
|
||||
this.target = target;
|
||||
this.client = target.client;
|
||||
this.isConnected = false;
|
||||
this.consoleProfiles = [];
|
||||
|
||||
addTarget(target);
|
||||
|
||||
@ -74,6 +84,8 @@ function ProfilerController(target) {
|
||||
this.isConnected = true;
|
||||
this.actor = target.form.profilerActor;
|
||||
}
|
||||
|
||||
sharedData.controllers.set(target, this);
|
||||
};
|
||||
|
||||
ProfilerController.prototype = {
|
||||
@ -97,6 +109,76 @@ ProfilerController.prototype = {
|
||||
return profile.timeStarted !== null && profile.timeEnded === null;
|
||||
},
|
||||
|
||||
/**
|
||||
* A listener that fires whenever console.profile or console.profileEnd
|
||||
* is called.
|
||||
*
|
||||
* @param string type
|
||||
* Type of a call. Either 'profile' or 'profileEnd'.
|
||||
* @param object data
|
||||
* Event data.
|
||||
* @param object panel
|
||||
* A reference to the ProfilerPanel in the current tab.
|
||||
*/
|
||||
onConsoleEvent: function (type, data, panel) {
|
||||
let name = data.extra.name;
|
||||
|
||||
let profileStart = () => {
|
||||
if (name && this.profiles.has(name))
|
||||
return;
|
||||
|
||||
// Add profile to the UI (createProfile will return
|
||||
// an automatically generated name if 'name' is falsey).
|
||||
let profile = panel.createProfile(name);
|
||||
profile.start((name, cb) => cb());
|
||||
|
||||
// Add profile structure to shared data.
|
||||
this.profiles.set(profile.name, makeProfile(profile.name, {
|
||||
timeStarted: data.extra.currentTime
|
||||
}));
|
||||
this.consoleProfiles.push(profile.name);
|
||||
};
|
||||
|
||||
let profileEnd = () => {
|
||||
if (!name && !this.consoleProfiles.length)
|
||||
return;
|
||||
|
||||
if (!name)
|
||||
name = this.consoleProfiles.pop();
|
||||
else
|
||||
this.consoleProfiles.filter((n) => n !== name);
|
||||
|
||||
if (!this.profiles.has(name))
|
||||
return;
|
||||
|
||||
let profile = this.profiles.get(name);
|
||||
if (!this.isProfileRecording(profile))
|
||||
return;
|
||||
|
||||
let profileData = data.extra.profile;
|
||||
profile.timeEnded = data.extra.currentTime;
|
||||
|
||||
profileData.threads = profileData.threads.map((thread) => {
|
||||
let samples = thread.samples.filter((sample) => {
|
||||
return sample.time >= profile.timeStarted;
|
||||
});
|
||||
|
||||
return { samples: samples };
|
||||
});
|
||||
|
||||
let ui = panel.getProfileByName(name);
|
||||
ui.data = profileData;
|
||||
ui.parse(profileData, () => panel.emit("parsed"));
|
||||
ui.stop((name, cb) => cb());
|
||||
};
|
||||
|
||||
if (type === "profile")
|
||||
profileStart();
|
||||
|
||||
if (type === "profileEnd")
|
||||
profileEnd();
|
||||
},
|
||||
|
||||
/**
|
||||
* Connects to the client unless we're already connected.
|
||||
*
|
||||
@ -105,16 +187,75 @@ ProfilerController.prototype = {
|
||||
* the controller is already connected, this function
|
||||
* will be called immediately (synchronously).
|
||||
*/
|
||||
connect: function (cb) {
|
||||
connect: function (cb=function(){}) {
|
||||
if (this.isConnected) {
|
||||
return void cb();
|
||||
}
|
||||
|
||||
// Check if we already have a grip to the listTabs response object
|
||||
// and, if we do, use it to get to the profilerActor. Otherwise,
|
||||
// call listTabs. The problem is that if we call listTabs twice
|
||||
// webconsole tests fail (see bug 872826).
|
||||
|
||||
let register = () => {
|
||||
let data = { events: ["console-api-profiler"] };
|
||||
|
||||
// Check if Gecko Profiler Addon [1] is installed and, if it is,
|
||||
// don't register our own console event listeners. Gecko Profiler
|
||||
// Addon takes care of console.profile and console.profileEnd methods
|
||||
// and we don't want to break it.
|
||||
//
|
||||
// [1] - https://github.com/bgirard/Gecko-Profiler-Addon/
|
||||
|
||||
AddonManager.getAddonByID("jid0-edalmuivkozlouyij0lpdx548bc@jetpack", (addon) => {
|
||||
if (addon && !addon.userDisabled && !addon.softDisabled)
|
||||
return void cb();
|
||||
|
||||
this.request("registerEventNotifications", data, (resp) => {
|
||||
this.client.addListener("eventNotification", (type, resp) => {
|
||||
let toolbox = gDevTools.getToolbox(this.target);
|
||||
if (toolbox == null)
|
||||
return;
|
||||
|
||||
let panel = toolbox.getPanel("jsprofiler");
|
||||
if (panel)
|
||||
return void this.onConsoleEvent(resp.subject.action, resp.data, panel);
|
||||
|
||||
// Can't use a promise here because of a race condition when the promise
|
||||
// is resolved only after -ready event is fired when creating a new panel
|
||||
// and during the -ready event when waiting for a panel to be created:
|
||||
//
|
||||
// console.profile(); // creates a new panel, waits for the promise
|
||||
// console.profileEnd(); // panel is not created yet but loading
|
||||
//
|
||||
// -> jsprofiler-ready event is fired which triggers a promise for profileEnd
|
||||
// -> a promise for profile is triggered.
|
||||
//
|
||||
// And it should be the other way around. Hence the event.
|
||||
|
||||
toolbox.once("jsprofiler-ready", (_, panel) => {
|
||||
this.onConsoleEvent(resp.subject.action, resp.data, panel);
|
||||
});
|
||||
|
||||
toolbox.loadTool("jsprofiler");
|
||||
});
|
||||
});
|
||||
|
||||
cb();
|
||||
});
|
||||
};
|
||||
|
||||
if (this.target.root) {
|
||||
this.actor = this.target.root.profilerActor;
|
||||
this.isConnected = true;
|
||||
return void register();
|
||||
}
|
||||
|
||||
this.client.listTabs((resp) => {
|
||||
this.actor = resp.profilerActor;
|
||||
this.isConnected = true;
|
||||
cb();
|
||||
})
|
||||
register();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -144,7 +285,9 @@ ProfilerController.prototype = {
|
||||
* value indicating if the profiler is active or not.
|
||||
*/
|
||||
isActive: function (cb) {
|
||||
this.request("isActive", {}, (resp) => cb(resp.error, resp.isActive));
|
||||
this.request("isActive", {}, (resp) => {
|
||||
cb(resp.error, resp.isActive, resp.currentTime);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -163,6 +306,7 @@ ProfilerController.prototype = {
|
||||
}
|
||||
|
||||
let profile = makeProfile(name);
|
||||
this.consoleProfiles.push(name);
|
||||
this.profiles.set(name, profile);
|
||||
|
||||
// If profile is already running, no need to do anything.
|
||||
@ -170,9 +314,9 @@ ProfilerController.prototype = {
|
||||
return void cb();
|
||||
}
|
||||
|
||||
this.isActive((err, isActive) => {
|
||||
this.isActive((err, isActive, currentTime) => {
|
||||
if (isActive) {
|
||||
profile.timeStarted = getCurrentTime();
|
||||
profile.timeStarted = currentTime;
|
||||
return void cb();
|
||||
}
|
||||
|
||||
@ -187,8 +331,7 @@ ProfilerController.prototype = {
|
||||
return void cb(resp.error);
|
||||
}
|
||||
|
||||
sharedData.startTime = (new Date()).getTime();
|
||||
profile.timeStarted = getCurrentTime();
|
||||
profile.timeStarted = 0;
|
||||
cb();
|
||||
});
|
||||
});
|
||||
@ -223,7 +366,7 @@ ProfilerController.prototype = {
|
||||
}
|
||||
|
||||
let data = resp.profile;
|
||||
profile.timeEnded = getCurrentTime();
|
||||
profile.timeEnded = resp.currentTime;
|
||||
|
||||
// Filter out all samples that fall out of current
|
||||
// profile's range.
|
||||
|
@ -11,6 +11,7 @@ Cu.import("resource:///modules/devtools/ProfilerController.jsm");
|
||||
Cu.import("resource:///modules/devtools/ProfilerHelpers.jsm");
|
||||
Cu.import("resource:///modules/devtools/shared/event-emitter.js");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/Console.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["ProfilerPanel"];
|
||||
|
||||
@ -54,6 +55,7 @@ function ProfileUI(uid, name, panel) {
|
||||
this.isStarted = false;
|
||||
this.isFinished = false;
|
||||
|
||||
this.messages = [];
|
||||
this.panel = panel;
|
||||
this.uid = uid;
|
||||
this.name = name;
|
||||
@ -76,14 +78,6 @@ function ProfileUI(uid, name, panel) {
|
||||
|
||||
switch (event.data.status) {
|
||||
case "loaded":
|
||||
if (this.panel._runningUid !== null) {
|
||||
this.iframe.contentWindow.postMessage(JSON.stringify({
|
||||
uid: this._runningUid,
|
||||
isCurrent: this._runningUid === uid,
|
||||
task: "onStarted"
|
||||
}), "*");
|
||||
}
|
||||
|
||||
this.isReady = true;
|
||||
this.emit("ready");
|
||||
break;
|
||||
@ -106,6 +100,22 @@ function ProfileUI(uid, name, panel) {
|
||||
}
|
||||
|
||||
ProfileUI.prototype = {
|
||||
/**
|
||||
* Returns a contentWindow of the iframe pointing to Cleopatra
|
||||
* if it exists and can be accessed. Otherwise returns null.
|
||||
*/
|
||||
get contentWindow() {
|
||||
if (!this.iframe) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return this.iframe.contentWindow;
|
||||
} catch (err) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
show: function PUI_show() {
|
||||
this.iframe.removeAttribute("hidden");
|
||||
},
|
||||
@ -126,32 +136,27 @@ ProfileUI.prototype = {
|
||||
*/
|
||||
parse: function PUI_parse(data, onParsed) {
|
||||
if (!this.isReady) {
|
||||
return;
|
||||
return void this.on("ready", this.parse.bind(this, data, onParsed));
|
||||
}
|
||||
|
||||
let win = this.iframe.contentWindow;
|
||||
this.message({ task: "receiveProfileData", rawProfile: data }).then(() => {
|
||||
let poll = () => {
|
||||
let wait = this.panel.window.setTimeout.bind(null, poll, 100);
|
||||
let trail = this.contentWindow.gBreadcrumbTrail;
|
||||
|
||||
win.postMessage(JSON.stringify({
|
||||
task: "receiveProfileData",
|
||||
rawProfile: data
|
||||
}), "*");
|
||||
if (!trail) {
|
||||
return wait();
|
||||
}
|
||||
|
||||
let poll = function pollBreadcrumbs() {
|
||||
let wait = this.panel.window.setTimeout.bind(null, poll, 100);
|
||||
let trail = win.gBreadcrumbTrail;
|
||||
if (!trail._breadcrumbs || !trail._breadcrumbs.length) {
|
||||
return wait();
|
||||
}
|
||||
|
||||
if (!trail) {
|
||||
return wait();
|
||||
}
|
||||
onParsed();
|
||||
};
|
||||
|
||||
if (!trail._breadcrumbs || !trail._breadcrumbs.length) {
|
||||
return wait();
|
||||
}
|
||||
|
||||
onParsed();
|
||||
}.bind(this);
|
||||
|
||||
poll();
|
||||
poll();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -171,37 +176,90 @@ ProfileUI.prototype = {
|
||||
* so that it could update the UI. Also, once started, we add a
|
||||
* star to the profile name to indicate which profile is currently
|
||||
* running.
|
||||
*
|
||||
* @param function startFn
|
||||
* A function to use instead of the default
|
||||
* this.panel.startProfiling. Useful when you
|
||||
* need mark panel as started after the profiler
|
||||
* has been started elsewhere. It must take two
|
||||
* params and call the second one.
|
||||
*/
|
||||
start: function PUI_start() {
|
||||
start: function PUI_start(startFn) {
|
||||
if (this.isStarted || this.isFinished) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.panel.startProfiling(this.name, function onStart() {
|
||||
startFn = startFn || this.panel.startProfiling.bind(this.panel);
|
||||
startFn(this.name, () => {
|
||||
this.isStarted = true;
|
||||
this.updateLabel(this.name + " *");
|
||||
this.panel.broadcast(this.uid, {task: "onStarted"});
|
||||
this.panel.broadcast(this.uid, {task: "onStarted"}); // Do we really need this?
|
||||
this.emit("started");
|
||||
}.bind(this));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Stop profiling and, once stopped, notify the underlying page so
|
||||
* that it could update the UI and remove a star from the profile
|
||||
* name.
|
||||
*
|
||||
* @param function stopFn
|
||||
* A function to use instead of the default
|
||||
* this.panel.stopProfiling. Useful when you
|
||||
* need mark panel as stopped after the profiler
|
||||
* has been stopped elsewhere. It must take two
|
||||
* params and call the second one.
|
||||
*/
|
||||
stop: function PUI_stop() {
|
||||
stop: function PUI_stop(stopFn) {
|
||||
if (!this.isStarted || this.isFinished) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.panel.stopProfiling(this.name, function onStop() {
|
||||
stopFn = stopFn || this.panel.stopProfiling.bind(this.panel);
|
||||
stopFn(this.name, () => {
|
||||
this.isStarted = false;
|
||||
this.isFinished = true;
|
||||
this.updateLabel(this.name);
|
||||
this.panel.broadcast(this.uid, {task: "onStopped"});
|
||||
this.emit("stopped");
|
||||
}.bind(this));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Send a message to Cleopatra instance. If a message cannot be
|
||||
* sent, this method queues it for later.
|
||||
*
|
||||
* @param object data JSON data to send (must be serializable)
|
||||
* @return promise
|
||||
*/
|
||||
message: function PIU_message(data) {
|
||||
let deferred = Promise.defer();
|
||||
let win = this.contentWindow;
|
||||
data = JSON.stringify(data);
|
||||
|
||||
if (win) {
|
||||
win.postMessage(data, "*");
|
||||
deferred.resolve();
|
||||
} else {
|
||||
this.messages.push({ data: data, onSuccess: () => deferred.resolve() });
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
/**
|
||||
* Send all queued messages (see this.message for more info)
|
||||
*/
|
||||
flushMessages: function PIU_flushMessages() {
|
||||
if (!this.contentWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
let msg;
|
||||
while (msg = this.messages.shift()) {
|
||||
this.contentWindow.postMessage(msg.data, "*");
|
||||
msg.onSuccess();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@ -212,6 +270,7 @@ ProfileUI.prototype = {
|
||||
this.panel = null;
|
||||
this.uid = null;
|
||||
this.iframe = null;
|
||||
this.messages = null;
|
||||
}
|
||||
};
|
||||
|
||||
@ -249,6 +308,7 @@ function ProfilerPanel(frame, toolbox) {
|
||||
|
||||
this.profiles = new Map();
|
||||
this._uid = 0;
|
||||
this._msgQueue = {};
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
}
|
||||
@ -265,6 +325,7 @@ ProfilerPanel.prototype = {
|
||||
_activeUid: null,
|
||||
_runningUid: null,
|
||||
_browserWin: null,
|
||||
_msgQueue: null,
|
||||
|
||||
get activeProfile() {
|
||||
return this.profiles.get(this._activeUid);
|
||||
@ -297,6 +358,7 @@ ProfilerPanel.prototype = {
|
||||
*/
|
||||
open: function PP_open() {
|
||||
let promise;
|
||||
|
||||
// Local profiling needs to make the target remote.
|
||||
if (!this.target.isRemote) {
|
||||
promise = this.target.makeRemote();
|
||||
@ -350,7 +412,21 @@ ProfilerPanel.prototype = {
|
||||
return this.getProfileByName(name);
|
||||
}
|
||||
|
||||
let uid = ++this._uid;
|
||||
let uid = ++this._uid;
|
||||
|
||||
// If profile is anonymous, increase its UID until we get
|
||||
// to the unused name. This way if someone manually creates
|
||||
// a profile named say 'Profile 2' we won't create a dup
|
||||
// with the same name. We will just skip over uid 2.
|
||||
|
||||
if (!name) {
|
||||
name = L10N.getFormatStr("profiler.profileName", [uid]);
|
||||
while (this.getProfileByName(name)) {
|
||||
uid = ++this._uid;
|
||||
name = L10N.getFormatStr("profiler.profileName", [uid]);
|
||||
}
|
||||
}
|
||||
|
||||
let list = this.document.getElementById("profiles-list");
|
||||
let item = this.document.createElement("li");
|
||||
let wrap = this.document.createElement("h1");
|
||||
@ -403,15 +479,17 @@ ProfilerPanel.prototype = {
|
||||
this.activeProfile = profile;
|
||||
|
||||
if (profile.isReady) {
|
||||
profile.flushMessages();
|
||||
this.emit("profileSwitched", profile.uid);
|
||||
onLoad();
|
||||
return;
|
||||
}
|
||||
|
||||
profile.once("ready", function () {
|
||||
profile.once("ready", () => {
|
||||
profile.flushMessages();
|
||||
this.emit("profileSwitched", profile.uid);
|
||||
onLoad();
|
||||
}.bind(this));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -422,15 +500,14 @@ ProfilerPanel.prototype = {
|
||||
* that profiling had been successfuly started.
|
||||
*/
|
||||
startProfiling: function PP_startProfiling(name, onStart) {
|
||||
this.controller.start(name, function (err) {
|
||||
this.controller.start(name, (err) => {
|
||||
if (err) {
|
||||
Cu.reportError("ProfilerController.start: " + err.message);
|
||||
return;
|
||||
return void Cu.reportError("ProfilerController.start: " + err.message);
|
||||
}
|
||||
|
||||
onStart();
|
||||
this.emit("started");
|
||||
}.bind(this));
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
@ -503,6 +580,28 @@ ProfilerPanel.prototype = {
|
||||
return this.profiles.get(uid) || null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterates over each available profile and calls
|
||||
* a callback with it as a parameter.
|
||||
*
|
||||
* @param function cb a callback to call
|
||||
*/
|
||||
eachProfile: function PP_eachProfile(cb) {
|
||||
let uid = this._uid;
|
||||
|
||||
if (!this.profiles) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (uid >= 0) {
|
||||
if (this.profiles.has(uid)) {
|
||||
cb(this.profiles.get(uid));
|
||||
}
|
||||
|
||||
uid -= 1;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Broadcast messages to all Cleopatra instances.
|
||||
*
|
||||
@ -524,18 +623,13 @@ ProfilerPanel.prototype = {
|
||||
this._runningUid = null;
|
||||
}
|
||||
|
||||
let uid = this._uid;
|
||||
while (uid >= 0) {
|
||||
if (this.profiles.has(uid)) {
|
||||
let iframe = this.profiles.get(uid).iframe;
|
||||
iframe.contentWindow.postMessage(JSON.stringify({
|
||||
uid: target,
|
||||
isCurrent: target === uid,
|
||||
task: data.task
|
||||
}), "*");
|
||||
}
|
||||
uid -= 1;
|
||||
}
|
||||
this.eachProfile((profile) => {
|
||||
profile.message({
|
||||
uid: target,
|
||||
isCurrent: target === profile.uid,
|
||||
task: data.task
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -515,6 +515,7 @@ HistogramView.prototype = {
|
||||
cancelAnimationFrame(self._pendingAnimationFrame);
|
||||
self._pendingAnimationFrame = null;
|
||||
self._render(highlightedCallstack);
|
||||
self._busyCover.classList.remove("busy");
|
||||
});
|
||||
},
|
||||
_render: function HistogramView__render(highlightedCallstack) {
|
||||
|
@ -19,12 +19,17 @@ MOCHITEST_BROWSER_TESTS = \
|
||||
browser_profiler_controller.js \
|
||||
browser_profiler_bug_830664_multiple_profiles.js \
|
||||
browser_profiler_bug_855244_multiple_tabs.js \
|
||||
browser_profiler_console_api.js \
|
||||
browser_profiler_console_api_named.js \
|
||||
browser_profiler_console_api_mixed.js \
|
||||
browser_profiler_console_api_content.js \
|
||||
head.js \
|
||||
$(NULL)
|
||||
|
||||
MOCHITEST_BROWSER_PAGES = \
|
||||
mock_profiler_bug_834878_page.html \
|
||||
mock_profiler_bug_834878_script.js \
|
||||
mock_console_api.html \
|
||||
$(NULL)
|
||||
|
||||
MOCHITEST_BROWSER_FILES_PARTS = MOCHITEST_BROWSER_TESTS MOCHITEST_BROWSER_PAGES
|
||||
|
@ -33,11 +33,6 @@ function getCleoControls(doc) {
|
||||
];
|
||||
}
|
||||
|
||||
function sendFromProfile(uid, msg) {
|
||||
let [win, doc] = getProfileInternals(uid);
|
||||
win.parent.postMessage({ uid: uid, status: msg }, "*");
|
||||
}
|
||||
|
||||
function startProfiling() {
|
||||
gPanel.profiles.get(gPanel.activeProfile.uid).once("started", function () {
|
||||
setTimeout(function () {
|
||||
|
@ -0,0 +1,66 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
|
||||
|
||||
let gTab, gPanel;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, (tab, browser, panel) => {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
openConsole(tab, testConsoleProfile);
|
||||
});
|
||||
}
|
||||
|
||||
function testConsoleProfile(hud) {
|
||||
hud.jsterm.clearOutput(true);
|
||||
|
||||
// Here we start two named profiles and then end one of them.
|
||||
// profileEnd, when name is not provided, simply pops the latest
|
||||
// profile.
|
||||
|
||||
let profilesStarted = 0;
|
||||
|
||||
function profileEnd(_, uid) {
|
||||
let profile = gPanel.profiles.get(uid);
|
||||
|
||||
profile.once("started", () => {
|
||||
if (++profilesStarted < 2)
|
||||
return;
|
||||
|
||||
gPanel.off("profileCreated", profileEnd);
|
||||
gPanel.profiles.get(3).once("stopped", () => {
|
||||
openProfiler(gTab, checkProfiles);
|
||||
});
|
||||
|
||||
hud.jsterm.execute("console.profileEnd()");
|
||||
});
|
||||
}
|
||||
|
||||
gPanel.on("profileCreated", profileEnd);
|
||||
hud.jsterm.execute("console.profile()");
|
||||
hud.jsterm.execute("console.profile()");
|
||||
}
|
||||
|
||||
function checkProfiles(toolbox) {
|
||||
let panel = toolbox.getPanel("jsprofiler");
|
||||
let getTitle = (uid) =>
|
||||
panel.document.querySelector("li#profile-" + uid + " > h1").textContent;
|
||||
|
||||
is(getTitle(1), "Profile 1", "Profile 1 doesn't have a star next to it.");
|
||||
is(getTitle(2), "Profile 2 *", "Profile 2 doesn't have a star next to it.");
|
||||
is(getTitle(3), "Profile 3", "Profile 3 doesn't have a star next to it.");
|
||||
|
||||
// Make sure we can still stop profiles via the UI.
|
||||
|
||||
gPanel.profiles.get(2).once("stopped", () => {
|
||||
is(getTitle(2), "Profile 2", "Profile 2 doesn't have a star next to it.");
|
||||
tearDown(gTab, () => gTab = gPanel = null);
|
||||
});
|
||||
|
||||
sendFromProfile(2, "stop");
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
|
||||
const BASE = "http://example.com/browser/browser/devtools/profiler/test/";
|
||||
const PAGE = BASE + "mock_console_api.html";
|
||||
|
||||
let gTab, gPanel, gToolbox;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, (tab, browser, panel) => {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
openProfiler(tab, (toolbox) => {
|
||||
gToolbox = toolbox;
|
||||
loadUrl(PAGE, tab, () => {
|
||||
gPanel.on("profileCreated", runTests);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function runTests() {
|
||||
let getTitle = (uid) =>
|
||||
gPanel.document.querySelector("li#profile-" + uid + " > h1").textContent;
|
||||
|
||||
is(getTitle(1), "Profile 1", "Profile 1 doesn't have a star next to it.");
|
||||
is(getTitle(2), "Profile 2", "Profile 2 doesn't have a star next to it.");
|
||||
|
||||
gPanel.once("parsed", () => {
|
||||
function assertSampleAndFinish() {
|
||||
let [win,doc] = getProfileInternals();
|
||||
let sample = doc.getElementsByClassName("samplePercentage");
|
||||
|
||||
if (sample.length <= 0)
|
||||
return void setTimeout(assertSampleAndFinish, 100);
|
||||
|
||||
ok(sample.length > 0, "We have Cleopatra UI displayed");
|
||||
tearDown(gTab, () => {
|
||||
gTab = null;
|
||||
gPanel = null;
|
||||
gToolbox = null;
|
||||
});
|
||||
}
|
||||
|
||||
assertSampleAndFinish();
|
||||
});
|
||||
|
||||
gPanel.switchToProfile(gPanel.profiles.get(2));
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
|
||||
|
||||
let gTab, gPanel;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, (tab, browser, panel) => {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
openProfiler(tab, runTests);
|
||||
});
|
||||
}
|
||||
|
||||
function runTests(toolbox) {
|
||||
let panel = toolbox.getPanel("jsprofiler");
|
||||
let getTitle = (uid) =>
|
||||
panel.document.querySelector("li#profile-" + uid + " > h1").textContent;
|
||||
|
||||
panel.profiles.get(1).once("started", () => {
|
||||
is(getTitle(1), "Profile 1 *", "Profile 1 has a start next to it.");
|
||||
|
||||
openConsole(gTab, (hud) => {
|
||||
panel.profiles.get(1).once("stopped", () => {
|
||||
is(getTitle(1), "Profile 1", "Profile 1 doesn't have a star next to it.");
|
||||
tearDown(gTab, () => gTab = gPanel = null);
|
||||
});
|
||||
|
||||
hud.jsterm.execute("console.profileEnd()");
|
||||
});
|
||||
});
|
||||
|
||||
sendFromProfile(1, "start");
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const URL = "data:text/html;charset=utf8,<p>JavaScript Profiler test</p>";
|
||||
|
||||
let gTab, gPanel;
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
setUp(URL, (tab, browser, panel) => {
|
||||
gTab = tab;
|
||||
gPanel = panel;
|
||||
|
||||
openConsole(tab, testConsoleProfile);
|
||||
});
|
||||
}
|
||||
|
||||
function testConsoleProfile(hud) {
|
||||
hud.jsterm.clearOutput(true);
|
||||
|
||||
// Here we start two named profiles and then end one of them.
|
||||
|
||||
let profilesStarted = 0;
|
||||
|
||||
function profileEnd(_, uid) {
|
||||
let profile = gPanel.profiles.get(uid);
|
||||
|
||||
profile.once("started", () => {
|
||||
if (++profilesStarted < 2)
|
||||
return;
|
||||
|
||||
gPanel.off("profileCreated", profileEnd);
|
||||
gPanel.profiles.get(2).once("stopped", () => {
|
||||
openProfiler(gTab, checkProfiles);
|
||||
});
|
||||
|
||||
hud.jsterm.execute("console.profileEnd('Second')");
|
||||
});
|
||||
}
|
||||
|
||||
gPanel.on("profileCreated", profileEnd);
|
||||
hud.jsterm.execute("console.profile('Second')");
|
||||
hud.jsterm.execute("console.profile('Third')");
|
||||
}
|
||||
|
||||
function checkProfiles(toolbox) {
|
||||
let panel = toolbox.getPanel("jsprofiler");
|
||||
let getTitle = (uid) =>
|
||||
panel.document.querySelector("li#profile-" + uid + " > h1").textContent;
|
||||
|
||||
is(getTitle(1), "Profile 1", "Profile 1 doesn't have a star next to it.");
|
||||
is(getTitle(2), "Second", "Second doesn't have a star next to it.");
|
||||
is(getTitle(3), "Third *", "Third does have a star next to it.");
|
||||
|
||||
// Make sure we can still stop profiles via the queue pop.
|
||||
|
||||
gPanel.profiles.get(3).once("stopped", () => {
|
||||
openProfiler(gTab, () => {
|
||||
is(getTitle(3), "Third", "Third doesn't have a star next to it.");
|
||||
tearDown(gTab, () => gTab = gPanel = null);
|
||||
});
|
||||
});
|
||||
|
||||
openConsole(gTab, (hud) => hud.jsterm.execute("console.profileEnd()"));
|
||||
}
|
@ -14,6 +14,9 @@ let TargetFactory = temp.devtools.TargetFactory;
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm", temp);
|
||||
let DebuggerServer = temp.DebuggerServer;
|
||||
|
||||
Cu.import("resource:///modules/HUDService.jsm", temp);
|
||||
let HUDService = temp.HUDService;
|
||||
|
||||
// Import the GCLI test helper
|
||||
let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
|
||||
Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this);
|
||||
@ -33,11 +36,19 @@ function getProfileInternals(uid) {
|
||||
return [win, doc];
|
||||
}
|
||||
|
||||
function sendFromProfile(uid, msg) {
|
||||
let [win, doc] = getProfileInternals(uid);
|
||||
win.parent.postMessage({ uid: uid, status: msg }, "*");
|
||||
}
|
||||
|
||||
function loadTab(url, callback) {
|
||||
let tab = gBrowser.addTab();
|
||||
gBrowser.selectedTab = tab;
|
||||
content.location.assign(url);
|
||||
loadUrl(url, tab, callback);
|
||||
}
|
||||
|
||||
function loadUrl(url, tab, callback) {
|
||||
content.location.assign(url);
|
||||
let browser = gBrowser.getBrowserForTab(tab);
|
||||
if (browser.contentDocument.readyState === "complete") {
|
||||
callback(tab, browser);
|
||||
@ -57,6 +68,17 @@ function openProfiler(tab, callback) {
|
||||
gDevTools.showToolbox(target, "jsprofiler").then(callback);
|
||||
}
|
||||
|
||||
function openConsole(tab, cb=function(){}) {
|
||||
// This function was borrowed from webconsole/test/head.js
|
||||
let target = TargetFactory.forTab(tab);
|
||||
|
||||
gDevTools.showToolbox(target, "webconsole").then(function (toolbox) {
|
||||
let hud = toolbox.getCurrentPanel().hud;
|
||||
hud.jsterm._lazyVariablesView = false;
|
||||
cb(hud);
|
||||
});
|
||||
}
|
||||
|
||||
function closeProfiler(tab, callback) {
|
||||
let target = TargetFactory.forTab(tab);
|
||||
let toolbox = gDevTools.getToolbox(target);
|
||||
|
21
browser/devtools/profiler/test/mock_console_api.html
Normal file
21
browser/devtools/profiler/test/mock_console_api.html
Normal file
@ -0,0 +1,21 @@
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'/>
|
||||
<title>console.profile from content</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script>
|
||||
console.profile();
|
||||
var a = new Array(500);
|
||||
while (a.length) {
|
||||
a.shift();
|
||||
}
|
||||
console.profileEnd();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -25,7 +25,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "gcli",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CmdCommands",
|
||||
"resource:///modules/devtools/BuiltinCommands.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PageErrorListener",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ConsoleServiceListener",
|
||||
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
|
||||
@ -444,8 +444,8 @@ DeveloperToolbar.prototype._initErrorsCount = function DT__initErrorsCount(aTab)
|
||||
}
|
||||
|
||||
let window = aTab.linkedBrowser.contentWindow;
|
||||
let listener = new PageErrorListener(window, {
|
||||
onPageError: this._onPageError.bind(this, tabId),
|
||||
let listener = new ConsoleServiceListener(window, {
|
||||
onConsoleServiceMessage: this._onPageError.bind(this, tabId),
|
||||
});
|
||||
listener.init();
|
||||
|
||||
|
@ -131,6 +131,7 @@ MOCHITEST_BROWSER_FILES = \
|
||||
browser_console_native_getters.js \
|
||||
browser_bug_871156_ctrlw_close_tab.js \
|
||||
browser_console_private_browsing.js \
|
||||
browser_console_nsiconsolemessage.js \
|
||||
head.js \
|
||||
$(NULL)
|
||||
|
||||
|
@ -9,6 +9,15 @@ const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/te
|
||||
|
||||
function test()
|
||||
{
|
||||
let oldFunction = HUDConsoleUI.toggleBrowserConsole;
|
||||
let functionExecuted = false;
|
||||
HUDConsoleUI.toggleBrowserConsole = () => functionExecuted = true;
|
||||
EventUtils.synthesizeKey("j", { accelKey: true, shiftKey: true }, content);
|
||||
|
||||
ok(functionExecuted,
|
||||
"toggleBrowserConsole() was executed by the Ctrl-Shift-J key shortcut");
|
||||
|
||||
HUDConsoleUI.toggleBrowserConsole = oldFunction;
|
||||
HUDConsoleUI.toggleBrowserConsole().then(consoleOpened);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
// Check that nsIConsoleMessages are displayed in the Browser Console.
|
||||
// See bug 859756.
|
||||
|
||||
const TEST_URI = "data:text/html;charset=utf8,<title>bug859756</title>\n" +
|
||||
"<p>hello world\n<p>nsIConsoleMessages ftw!";
|
||||
|
||||
function test()
|
||||
{
|
||||
addTab(TEST_URI);
|
||||
browser.addEventListener("load", function onLoad() {
|
||||
browser.removeEventListener("load", onLoad, true);
|
||||
|
||||
// Test for cached nsIConsoleMessages.
|
||||
Services.console.logStringMessage("test1 for bug859756");
|
||||
|
||||
info("open web console");
|
||||
openConsole(null, consoleOpened);
|
||||
}, true);
|
||||
}
|
||||
|
||||
function consoleOpened(hud)
|
||||
{
|
||||
ok(hud, "web console opened");
|
||||
Services.console.logStringMessage("do-not-show-me");
|
||||
content.console.log("foobarz");
|
||||
|
||||
waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [
|
||||
{
|
||||
text: "foobarz",
|
||||
category: CATEGORY_WEBDEV,
|
||||
severity: SEVERITY_LOG,
|
||||
},
|
||||
],
|
||||
}).then(() => {
|
||||
let text = hud.outputNode.textContent;
|
||||
is(text.indexOf("do-not-show-me"), -1,
|
||||
"nsIConsoleMessages are not displayed");
|
||||
is(text.indexOf("test1 for bug859756"), -1,
|
||||
"nsIConsoleMessages are not displayed (confirmed)");
|
||||
closeConsole(null, onWebConsoleClose);
|
||||
});
|
||||
}
|
||||
|
||||
function onWebConsoleClose()
|
||||
{
|
||||
info("web console closed");
|
||||
HUDConsoleUI.toggleBrowserConsole().then(onBrowserConsoleOpen);
|
||||
}
|
||||
|
||||
function onBrowserConsoleOpen(hud)
|
||||
{
|
||||
ok(hud, "browser console opened");
|
||||
Services.console.logStringMessage("test2 for bug859756");
|
||||
|
||||
waitForMessages({
|
||||
webconsole: hud,
|
||||
messages: [
|
||||
{
|
||||
text: "test1 for bug859756",
|
||||
category: CATEGORY_JS,
|
||||
},
|
||||
{
|
||||
text: "test2 for bug859756",
|
||||
category: CATEGORY_JS,
|
||||
},
|
||||
{
|
||||
text: "do-not-show-me",
|
||||
category: CATEGORY_JS,
|
||||
},
|
||||
],
|
||||
}).then(finishTest);
|
||||
}
|
@ -961,6 +961,9 @@ WebConsoleFrame.prototype = {
|
||||
[category, aMessage]);
|
||||
break;
|
||||
}
|
||||
case "LogMessage":
|
||||
this.handleLogMessage(aMessage);
|
||||
break;
|
||||
case "ConsoleAPI":
|
||||
this.outputMessage(CATEGORY_WEBDEV, this.logConsoleAPIMessage,
|
||||
[aMessage]);
|
||||
@ -1198,6 +1201,21 @@ WebConsoleFrame.prototype = {
|
||||
this.outputMessage(category, this.reportPageError, [category, aPageError]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle log messages received from the server. This method outputs the given
|
||||
* message.
|
||||
*
|
||||
* @param object aPacket
|
||||
* The message packet received from the server.
|
||||
*/
|
||||
handleLogMessage: function WCF_handleLogMessage(aPacket)
|
||||
{
|
||||
this.outputMessage(CATEGORY_JS, () => {
|
||||
return this.createMessageNode(CATEGORY_JS, SEVERITY_LOG, aPacket.message,
|
||||
null, null, null, null, aPacket.timeStamp);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Log network event.
|
||||
*
|
||||
@ -4530,6 +4548,7 @@ function WebConsoleConnectionProxy(aWebConsole, aTarget)
|
||||
this.target = aTarget;
|
||||
|
||||
this._onPageError = this._onPageError.bind(this);
|
||||
this._onLogMessage = this._onLogMessage.bind(this);
|
||||
this._onConsoleAPICall = this._onConsoleAPICall.bind(this);
|
||||
this._onNetworkEvent = this._onNetworkEvent.bind(this);
|
||||
this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
|
||||
@ -4634,6 +4653,7 @@ WebConsoleConnectionProxy.prototype = {
|
||||
|
||||
let client = this.client = this.target.client;
|
||||
|
||||
client.addListener("logMessage", this._onLogMessage);
|
||||
client.addListener("pageError", this._onPageError);
|
||||
client.addListener("consoleAPICall", this._onConsoleAPICall);
|
||||
client.addListener("networkEvent", this._onNetworkEvent);
|
||||
@ -4755,6 +4775,23 @@ WebConsoleConnectionProxy.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The "logMessage" message type handler. We redirect any message to the UI
|
||||
* for displaying.
|
||||
*
|
||||
* @private
|
||||
* @param string aType
|
||||
* Message type.
|
||||
* @param object aPacket
|
||||
* The message received from the server.
|
||||
*/
|
||||
_onLogMessage: function WCCP__onLogMessage(aType, aPacket)
|
||||
{
|
||||
if (this.owner && aPacket.from == this._consoleActor) {
|
||||
this.owner.handleLogMessage(aPacket);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The "consoleAPICall" message type handler. We redirect any message to
|
||||
* the UI for displaying.
|
||||
@ -4899,6 +4936,7 @@ WebConsoleConnectionProxy.prototype = {
|
||||
return this._disconnecter.promise;
|
||||
}
|
||||
|
||||
this.client.removeListener("logMessage", this._onLogMessage);
|
||||
this.client.removeListener("pageError", this._onPageError);
|
||||
this.client.removeListener("consoleAPICall", this._onConsoleAPICall);
|
||||
this.client.removeListener("networkEvent", this._onNetworkEvent);
|
||||
|
@ -214,11 +214,12 @@ These should match what Safari and other Apple applications use on OS X Lion. --
|
||||
|
||||
<!ENTITY errorConsoleCmd.label "Error Console">
|
||||
<!ENTITY errorConsoleCmd.accesskey "C">
|
||||
<!ENTITY errorConsoleCmd.commandkey "j">
|
||||
|
||||
<!ENTITY remoteWebConsoleCmd.label "Remote Web Console">
|
||||
|
||||
<!ENTITY browserConsoleCmd.label "Browser Console">
|
||||
<!ENTITY browserConsoleCmd.commandkey "j">
|
||||
<!ENTITY browserConsoleCmd.accesskey "B">
|
||||
|
||||
<!ENTITY inspectContextMenu.label "Inspect Element">
|
||||
<!ENTITY inspectContextMenu.accesskey "Q">
|
||||
|
@ -11,7 +11,7 @@
|
||||
#include "nsISupports.idl"
|
||||
#include "nsIConsoleMessage.idl"
|
||||
|
||||
[scriptable, uuid(ec640482-be5f-49a0-a9cb-c87eacce9291)]
|
||||
[scriptable, uuid(cac9d8e8-0d53-4fa8-9903-bb367e4fa1fe)]
|
||||
interface nsIScriptError : nsIConsoleMessage
|
||||
{
|
||||
/** pseudo-flag for default case */
|
||||
@ -49,15 +49,6 @@ interface nsIScriptError : nsIConsoleMessage
|
||||
*/
|
||||
readonly attribute string category;
|
||||
|
||||
/*
|
||||
The time (in milliseconds from the Epoch) that the script error instance
|
||||
was initialised, and thus the time when the error occurred.
|
||||
Currently used to display date and time of the message in Error console.
|
||||
The timestamp is initialized as JS_now/1000 so that it can be
|
||||
compared to Date.now in Javascript.
|
||||
*/
|
||||
readonly attribute long long timeStamp;
|
||||
|
||||
/* Get the window id this was initialized with. Zero will be
|
||||
returned if init() was used instead of initWithWindowID(). */
|
||||
readonly attribute unsigned long long outerWindowID;
|
||||
@ -87,13 +78,11 @@ interface nsIScriptError : nsIConsoleMessage
|
||||
in uint32_t flags,
|
||||
in string category,
|
||||
in unsigned long long innerWindowID);
|
||||
|
||||
AUTF8String toString();
|
||||
};
|
||||
|
||||
%{ C++
|
||||
#define NS_SCRIPTERROR_CID \
|
||||
{ 0xe38e53b9, 0x5bb0, 0x456a, { 0xb5, 0x53, 0x57, 0x93, 0x70, 0xcb, 0x15, 0x67 }}
|
||||
{ 0x1950539a, 0x90f0, 0x4d22, { 0xb5, 0xaf, 0x71, 0x32, 0x9c, 0x68, 0xfa, 0x35 }}
|
||||
|
||||
#define NS_SCRIPTERROR_CONTRACTID "@mozilla.org/scripterror;1"
|
||||
%}
|
||||
|
@ -175,6 +175,7 @@ const UnsolicitedNotifications = {
|
||||
"eventNotification": "eventNotification",
|
||||
"fileActivity": "fileActivity",
|
||||
"lastPrivateContextExited": "lastPrivateContextExited",
|
||||
"logMessage": "logMessage",
|
||||
"networkEvent": "networkEvent",
|
||||
"networkEventUpdate": "networkEventUpdate",
|
||||
"newGlobal": "newGlobal",
|
||||
|
@ -5,6 +5,11 @@
|
||||
"use strict";
|
||||
|
||||
var connCount = 0;
|
||||
var startTime = 0;
|
||||
|
||||
function getCurrentTime() {
|
||||
return (new Date()).getTime() - startTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ProfilerActor. ProfilerActor provides remote access to the
|
||||
@ -44,6 +49,7 @@ ProfilerActor.prototype = {
|
||||
this._profiler.StartProfiler(aRequest.entries, aRequest.interval,
|
||||
aRequest.features, aRequest.features.length);
|
||||
this._started = true;
|
||||
startTime = (new Date()).getTime();
|
||||
return { "msg": "profiler started" }
|
||||
},
|
||||
onStopProfiler: function(aRequest) {
|
||||
@ -57,11 +63,12 @@ ProfilerActor.prototype = {
|
||||
},
|
||||
onGetProfile: function(aRequest) {
|
||||
var profile = this._profiler.getProfileData();
|
||||
return { "profile": profile }
|
||||
return { "profile": profile, "currentTime": getCurrentTime() }
|
||||
},
|
||||
onIsActive: function(aRequest) {
|
||||
var isActive = this._profiler.IsActive();
|
||||
return { "isActive": isActive }
|
||||
var currentTime = isActive ? getCurrentTime() : null;
|
||||
return { "isActive": isActive, "currentTime": currentTime }
|
||||
},
|
||||
onGetResponsivenessTimes: function(aRequest) {
|
||||
var times = this._profiler.GetResponsivenessTimes({});
|
||||
@ -128,11 +135,62 @@ ProfilerActor.prototype = {
|
||||
aSubject = (aSubject && aSubject.wrappedJSObject) || aSubject;
|
||||
aData = (aData && aData.wrappedJSObject) || aData;
|
||||
|
||||
this.conn.send({ from: this.actorID,
|
||||
type: "eventNotification",
|
||||
event: aTopic,
|
||||
subject: JSON.parse(JSON.stringify(aSubject, cycleBreaker)),
|
||||
data: JSON.parse(JSON.stringify(aData, cycleBreaker)) });
|
||||
let subj = JSON.parse(JSON.stringify(aSubject, cycleBreaker));
|
||||
let data = JSON.parse(JSON.stringify(aData, cycleBreaker));
|
||||
|
||||
let send = (extra) => {
|
||||
data = data || {};
|
||||
|
||||
if (extra)
|
||||
data.extra = extra;
|
||||
|
||||
this.conn.send({
|
||||
from: this.actorID,
|
||||
type: "eventNotification",
|
||||
event: aTopic,
|
||||
subject: subj,
|
||||
data: data
|
||||
});
|
||||
}
|
||||
|
||||
if (aTopic !== "console-api-profiler")
|
||||
return void send();
|
||||
|
||||
// If the event was generated from console.profile or
|
||||
// console.profileEnd we need to start the profiler
|
||||
// right away and only then notify our client. Otherwise,
|
||||
// we'll lose precious samples.
|
||||
|
||||
let name = subj.arguments[0];
|
||||
|
||||
if (subj.action === "profile") {
|
||||
let resp = this.onIsActive();
|
||||
|
||||
if (resp.isActive) {
|
||||
return void send({
|
||||
name: name,
|
||||
currentTime: resp.currentTime,
|
||||
action: "profile"
|
||||
});
|
||||
}
|
||||
|
||||
this.onStartProfiler({
|
||||
entries: 1000000,
|
||||
interval: 1,
|
||||
features: ["js"]
|
||||
});
|
||||
|
||||
return void send({ currentTime: 0, action: "profile", name: name });
|
||||
}
|
||||
|
||||
if (subj.action === "profileEnd") {
|
||||
let resp = this.onGetProfile();
|
||||
resp.action = "profileEnd";
|
||||
resp.name = name;
|
||||
send(resp);
|
||||
}
|
||||
|
||||
return undefined; // Otherwise xpcshell tests fail.
|
||||
}, "ProfilerActor.prototype.observe"),
|
||||
};
|
||||
|
||||
|
@ -18,7 +18,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "Services",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "WebConsoleUtils",
|
||||
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PageErrorListener",
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ConsoleServiceListener",
|
||||
"resource://gre/modules/devtools/WebConsoleUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ConsoleAPIListener",
|
||||
@ -182,10 +182,10 @@ WebConsoleActor.prototype =
|
||||
_window: null,
|
||||
|
||||
/**
|
||||
* The PageErrorListener instance.
|
||||
* The ConsoleServiceListener instance.
|
||||
* @type object
|
||||
*/
|
||||
pageErrorListener: null,
|
||||
consoleServiceListener: null,
|
||||
|
||||
/**
|
||||
* The ConsoleAPIListener instance.
|
||||
@ -228,9 +228,9 @@ WebConsoleActor.prototype =
|
||||
*/
|
||||
disconnect: function WCA_disconnect()
|
||||
{
|
||||
if (this.pageErrorListener) {
|
||||
this.pageErrorListener.destroy();
|
||||
this.pageErrorListener = null;
|
||||
if (this.consoleServiceListener) {
|
||||
this.consoleServiceListener.destroy();
|
||||
this.consoleServiceListener = null;
|
||||
}
|
||||
if (this.consoleAPIListener) {
|
||||
this.consoleAPIListener.destroy();
|
||||
@ -384,10 +384,10 @@ WebConsoleActor.prototype =
|
||||
let listener = aRequest.listeners.shift();
|
||||
switch (listener) {
|
||||
case "PageError":
|
||||
if (!this.pageErrorListener) {
|
||||
this.pageErrorListener =
|
||||
new PageErrorListener(window, this);
|
||||
this.pageErrorListener.init();
|
||||
if (!this.consoleServiceListener) {
|
||||
this.consoleServiceListener =
|
||||
new ConsoleServiceListener(window, this);
|
||||
this.consoleServiceListener.init();
|
||||
}
|
||||
startedListeners.push(listener);
|
||||
break;
|
||||
@ -447,9 +447,9 @@ WebConsoleActor.prototype =
|
||||
let listener = toDetach.shift();
|
||||
switch (listener) {
|
||||
case "PageError":
|
||||
if (this.pageErrorListener) {
|
||||
this.pageErrorListener.destroy();
|
||||
this.pageErrorListener = null;
|
||||
if (this.consoleServiceListener) {
|
||||
this.consoleServiceListener.destroy();
|
||||
this.consoleServiceListener = null;
|
||||
}
|
||||
stoppedListeners.push(listener);
|
||||
break;
|
||||
@ -505,28 +505,42 @@ WebConsoleActor.prototype =
|
||||
while (types.length > 0) {
|
||||
let type = types.shift();
|
||||
switch (type) {
|
||||
case "ConsoleAPI":
|
||||
if (this.consoleAPIListener) {
|
||||
let cache = this.consoleAPIListener
|
||||
.getCachedMessages(!this._isGlobalActor);
|
||||
cache.forEach((aMessage) => {
|
||||
let message = this.prepareConsoleMessageForRemote(aMessage);
|
||||
message._type = type;
|
||||
messages.push(message);
|
||||
});
|
||||
case "ConsoleAPI": {
|
||||
if (!this.consoleAPIListener) {
|
||||
break;
|
||||
}
|
||||
let cache = this.consoleAPIListener
|
||||
.getCachedMessages(!this._isGlobalActor);
|
||||
cache.forEach((aMessage) => {
|
||||
let message = this.prepareConsoleMessageForRemote(aMessage);
|
||||
message._type = type;
|
||||
messages.push(message);
|
||||
});
|
||||
break;
|
||||
case "PageError":
|
||||
if (this.pageErrorListener) {
|
||||
let cache = this.pageErrorListener
|
||||
.getCachedMessages(!this._isGlobalActor);
|
||||
cache.forEach((aMessage) => {
|
||||
let message = this.preparePageErrorForRemote(aMessage);
|
||||
message._type = type;
|
||||
messages.push(message);
|
||||
});
|
||||
}
|
||||
case "PageError": {
|
||||
if (!this.consoleServiceListener) {
|
||||
break;
|
||||
}
|
||||
let cache = this.consoleServiceListener
|
||||
.getCachedMessages(!this._isGlobalActor);
|
||||
cache.forEach((aMessage) => {
|
||||
let message = null;
|
||||
if (aMessage instanceof Ci.nsIScriptError) {
|
||||
message = this.preparePageErrorForRemote(aMessage);
|
||||
message._type = type;
|
||||
}
|
||||
else {
|
||||
message = {
|
||||
_type: "LogMessage",
|
||||
message: aMessage.message,
|
||||
timeStamp: aMessage.timeStamp,
|
||||
};
|
||||
}
|
||||
messages.push(message);
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -618,6 +632,10 @@ WebConsoleActor.prototype =
|
||||
let windowId = !this._isGlobalActor ?
|
||||
WebConsoleUtils.getInnerWindowId(this.window) : null;
|
||||
ConsoleAPIStorage.clearEvents(windowId);
|
||||
if (this._isGlobalActor) {
|
||||
Services.console.logStringMessage(null); // for the Error Console
|
||||
Services.console.reset();
|
||||
}
|
||||
return {};
|
||||
},
|
||||
|
||||
@ -875,19 +893,30 @@ WebConsoleActor.prototype =
|
||||
//////////////////
|
||||
|
||||
/**
|
||||
* Handler for page errors received from the PageErrorListener. This method
|
||||
* sends the nsIScriptError to the remote Web Console client.
|
||||
* Handler for messages received from the ConsoleServiceListener. This method
|
||||
* sends the nsIConsoleMessage to the remote Web Console client.
|
||||
*
|
||||
* @param nsIScriptError aPageError
|
||||
* The page error we need to send to the client.
|
||||
* @param nsIConsoleMessage aMessage
|
||||
* The message we need to send to the client.
|
||||
*/
|
||||
onPageError: function WCA_onPageError(aPageError)
|
||||
onConsoleServiceMessage: function WCA_onConsoleServiceMessage(aMessage)
|
||||
{
|
||||
let packet = {
|
||||
from: this.actorID,
|
||||
type: "pageError",
|
||||
pageError: this.preparePageErrorForRemote(aPageError),
|
||||
};
|
||||
let packet;
|
||||
if (aMessage instanceof Ci.nsIScriptError) {
|
||||
packet = {
|
||||
from: this.actorID,
|
||||
type: "pageError",
|
||||
pageError: this.preparePageErrorForRemote(aMessage),
|
||||
};
|
||||
}
|
||||
else {
|
||||
packet = {
|
||||
from: this.actorID,
|
||||
type: "logMessage",
|
||||
message: aMessage.message,
|
||||
timeStamp: aMessage.timeStamp,
|
||||
};
|
||||
}
|
||||
this.conn.send(packet);
|
||||
},
|
||||
|
||||
|
@ -42,7 +42,7 @@ XPCOMUtils.defineLazyModuleGetter(this, "VariablesView",
|
||||
"resource:///modules/devtools/VariablesView.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["WebConsoleUtils", "JSPropertyProvider", "JSTermHelpers",
|
||||
"PageErrorListener", "ConsoleAPIListener",
|
||||
"ConsoleServiceListener", "ConsoleAPIListener",
|
||||
"NetworkResponseListener", "NetworkMonitor",
|
||||
"ConsoleProgressListener"];
|
||||
|
||||
@ -876,25 +876,25 @@ return JSPropertyProvider;
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* The nsIConsoleService listener. This is used to send all the page errors
|
||||
* (JavaScript, CSS and more) to the remote Web Console instance.
|
||||
* The nsIConsoleService listener. This is used to send all of the console
|
||||
* messages (JavaScript, CSS and more) to the remote Web Console instance.
|
||||
*
|
||||
* @constructor
|
||||
* @param nsIDOMWindow [aWindow]
|
||||
* Optional - the window object for which we are created. This is used
|
||||
* for filtering out messages that belong to other windows.
|
||||
* @param object aListener
|
||||
* The listener object must have a method: onPageError. This method is
|
||||
* invoked with one argument, the nsIScriptError, whenever a relevant
|
||||
* page error is received.
|
||||
* The listener object must have one method:
|
||||
* - onConsoleServiceMessage(). This method is invoked with one argument, the
|
||||
* nsIConsoleMessage, whenever a relevant message is received.
|
||||
*/
|
||||
this.PageErrorListener = function PageErrorListener(aWindow, aListener)
|
||||
this.ConsoleServiceListener = function ConsoleServiceListener(aWindow, aListener)
|
||||
{
|
||||
this.window = aWindow;
|
||||
this.listener = aListener;
|
||||
}
|
||||
|
||||
PageErrorListener.prototype =
|
||||
ConsoleServiceListener.prototype =
|
||||
{
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]),
|
||||
|
||||
@ -905,8 +905,7 @@ PageErrorListener.prototype =
|
||||
window: null,
|
||||
|
||||
/**
|
||||
* The listener object which is notified of page errors. It must have
|
||||
* a onPageError method which is invoked with one argument: the nsIScriptError.
|
||||
* The listener object which is notified of messages from the console service.
|
||||
* @type object
|
||||
*/
|
||||
listener: null,
|
||||
@ -914,7 +913,7 @@ PageErrorListener.prototype =
|
||||
/**
|
||||
* Initialize the nsIConsoleService listener.
|
||||
*/
|
||||
init: function PEL_init()
|
||||
init: function CSL_init()
|
||||
{
|
||||
Services.console.registerListener(this);
|
||||
},
|
||||
@ -924,43 +923,48 @@ PageErrorListener.prototype =
|
||||
* messages belonging to the current window and sends them to the remote Web
|
||||
* Console instance.
|
||||
*
|
||||
* @param nsIScriptError aScriptError
|
||||
* The script error object coming from the nsIConsoleService.
|
||||
* @param nsIConsoleMessage aMessage
|
||||
* The message object coming from the nsIConsoleService.
|
||||
*/
|
||||
observe: function PEL_observe(aScriptError)
|
||||
observe: function CSL_observe(aMessage)
|
||||
{
|
||||
if (!this.listener ||
|
||||
!(aScriptError instanceof Ci.nsIScriptError)) {
|
||||
if (!this.listener) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.window) {
|
||||
if (!aScriptError.outerWindowID ||
|
||||
!this.isCategoryAllowed(aScriptError.category)) {
|
||||
if (!(aMessage instanceof Ci.nsIScriptError) ||
|
||||
!aMessage.outerWindowID ||
|
||||
!this.isCategoryAllowed(aMessage.category)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let errorWindow =
|
||||
Services.wm.getOuterWindowWithId(aScriptError.outerWindowID);
|
||||
let errorWindow = Services.wm.getOuterWindowWithId(aMessage.outerWindowID);
|
||||
if (!errorWindow || errorWindow.top != this.window) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.listener.onPageError(aScriptError);
|
||||
if (aMessage.message) {
|
||||
this.listener.onConsoleServiceMessage(aMessage);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the given script error category is allowed to be tracked or not.
|
||||
* Check if the given message category is allowed to be tracked or not.
|
||||
* We ignore chrome-originating errors as we only care about content.
|
||||
*
|
||||
* @param string aCategory
|
||||
* The nsIScriptError category you want to check.
|
||||
* The message category you want to check.
|
||||
* @return boolean
|
||||
* True if the category is allowed to be logged, false otherwise.
|
||||
*/
|
||||
isCategoryAllowed: function PEL_isCategoryAllowed(aCategory)
|
||||
isCategoryAllowed: function CSL_isCategoryAllowed(aCategory)
|
||||
{
|
||||
if (!aCategory) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (aCategory) {
|
||||
case "XPConnect JavaScript":
|
||||
case "component javascript":
|
||||
@ -983,26 +987,32 @@ PageErrorListener.prototype =
|
||||
* Tells if you want to also retrieve messages coming from private
|
||||
* windows. Defaults to false.
|
||||
* @return array
|
||||
* The array of cached messages.
|
||||
* The array of cached messages. Each element is an nsIScriptError or
|
||||
* an nsIConsoleMessage
|
||||
*/
|
||||
getCachedMessages: function PEL_getCachedMessages(aIncludePrivate = false)
|
||||
getCachedMessages: function CSL_getCachedMessages(aIncludePrivate = false)
|
||||
{
|
||||
let innerWindowId = this.window ?
|
||||
let innerWindowID = this.window ?
|
||||
WebConsoleUtils.getInnerWindowId(this.window) : null;
|
||||
let errors = Services.console.getMessageArray() || [];
|
||||
|
||||
return errors.filter((aError) => {
|
||||
if (!(aError instanceof Ci.nsIScriptError)) {
|
||||
return false;
|
||||
}
|
||||
if (!aIncludePrivate && aError.isFromPrivateWindow) {
|
||||
return false;
|
||||
}
|
||||
if (innerWindowId &&
|
||||
(aError.innerWindowID != innerWindowId ||
|
||||
!this.isCategoryAllowed(aError.category))) {
|
||||
if (aError instanceof Ci.nsIScriptError) {
|
||||
if (!aIncludePrivate && aError.isFromPrivateWindow) {
|
||||
return false;
|
||||
}
|
||||
if (innerWindowID &&
|
||||
(aError.innerWindowID != innerWindowID ||
|
||||
!this.isCategoryAllowed(aError.category))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (innerWindowID) {
|
||||
// If this is not an nsIScriptError and we need to do window-based
|
||||
// filtering we skip this message.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
},
|
||||
@ -1010,7 +1020,7 @@ PageErrorListener.prototype =
|
||||
/**
|
||||
* Remove the nsIConsoleService listener.
|
||||
*/
|
||||
destroy: function PEL_destroy()
|
||||
destroy: function CSL_destroy()
|
||||
{
|
||||
Services.console.unregisterListener(this);
|
||||
this.listener = this.window = null;
|
||||
@ -1112,12 +1122,12 @@ ConsoleAPIListener.prototype =
|
||||
{
|
||||
let innerWindowId = this.window ?
|
||||
WebConsoleUtils.getInnerWindowId(this.window) : null;
|
||||
return ConsoleAPIStorage.getEvents(innerWindowId).filter((aMessage) => {
|
||||
if (!aIncludePrivate && aMessage.private) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
let events = ConsoleAPIStorage.getEvents(innerWindowId);
|
||||
if (aIncludePrivate) {
|
||||
return events;
|
||||
}
|
||||
|
||||
return events.filter((m) => !m.private);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -24,6 +24,7 @@ MOCHITEST_CHROME_FILES = \
|
||||
test_bug819670_getter_throws.html \
|
||||
test_object_actor_native_getters.html \
|
||||
test_object_actor_native_getters_lenient_this.html \
|
||||
test_nsiconsolemessage.html \
|
||||
network_requests_iframe.html \
|
||||
data.json \
|
||||
data.json^headers^ \
|
||||
|
@ -106,7 +106,10 @@ function checkObject(aObject, aExpected)
|
||||
|
||||
function checkValue(aName, aValue, aExpected)
|
||||
{
|
||||
if (aValue === undefined) {
|
||||
if (aExpected === null) {
|
||||
ok(!aValue, "'" + aName + "' is null");
|
||||
}
|
||||
else if (aValue === undefined) {
|
||||
ok(false, "'" + aName + "' is undefined");
|
||||
}
|
||||
else if (typeof aExpected == "string" || typeof aExpected == "number" ||
|
||||
|
@ -95,7 +95,7 @@ function doConsoleCalls()
|
||||
<script class="testbody" type="text/javascript;version=1.8">
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
let consoleAPIListener, pageErrorListener;
|
||||
let consoleAPIListener, consoleServiceListener;
|
||||
let consoleAPICalls = 0;
|
||||
let pageErrors = 0;
|
||||
|
||||
@ -108,7 +108,7 @@ let handlers = {
|
||||
}
|
||||
},
|
||||
|
||||
onPageError: function onPageError()
|
||||
onConsoleServiceMessage: function onConsoleServiceMessage()
|
||||
{
|
||||
pageErrors++;
|
||||
if (pageErrors == expectedPageErrors.length) {
|
||||
@ -153,16 +153,16 @@ function onCachedConsoleAPI(aState, aResponse)
|
||||
});
|
||||
|
||||
closeDebugger(aState, function() {
|
||||
pageErrorListener = new PageErrorListener(null, handlers);
|
||||
pageErrorListener.init();
|
||||
consoleServiceListener = new ConsoleServiceListener(null, handlers);
|
||||
consoleServiceListener.init();
|
||||
doPageErrors();
|
||||
});
|
||||
}
|
||||
|
||||
function testPageErrors()
|
||||
{
|
||||
pageErrorListener.destroy();
|
||||
pageErrorListener = null;
|
||||
consoleServiceListener.destroy();
|
||||
consoleServiceListener = null;
|
||||
attachConsole(["PageError"], onAttach2);
|
||||
}
|
||||
|
||||
|
62
toolkit/devtools/webconsole/test/test_nsiconsolemessage.html
Normal file
62
toolkit/devtools/webconsole/test/test_nsiconsolemessage.html
Normal file
@ -0,0 +1,62 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf8">
|
||||
<title>Test for nsIConsoleMessages</title>
|
||||
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript;version=1.8" src="common.js"></script>
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
- http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
</head>
|
||||
<body>
|
||||
<p>Make sure that nsIConsoleMessages are logged. See bug 859756.</p>
|
||||
|
||||
<script class="testbody" type="text/javascript;version=1.8">
|
||||
"use strict";
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
let expectedMessages = [];
|
||||
|
||||
function startTest()
|
||||
{
|
||||
removeEventListener("load", startTest);
|
||||
attachConsole(["PageError"], onAttach);
|
||||
}
|
||||
|
||||
function onAttach(aState, aResponse)
|
||||
{
|
||||
onLogMessage = onLogMessage.bind(null, aState);
|
||||
aState.dbgClient.addListener("logMessage", onLogMessage);
|
||||
|
||||
expectedMessages = [{
|
||||
message: "hello world! bug859756",
|
||||
timeStamp: /^\d+$/,
|
||||
}];
|
||||
|
||||
Services.console.logStringMessage("hello world! bug859756");
|
||||
|
||||
info("waiting for messages");
|
||||
}
|
||||
|
||||
let receivedMessages = [];
|
||||
|
||||
function onLogMessage(aState, aType, aPacket)
|
||||
{
|
||||
is(aPacket.from, aState.actor, "packet actor");
|
||||
|
||||
receivedMessages.push(aPacket);
|
||||
if (receivedMessages.length != expectedMessages.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
aState.dbgClient.removeListener("logMessage", onLogMessage);
|
||||
|
||||
checkObject(receivedMessages, expectedMessages);
|
||||
|
||||
closeDebugger(aState, () => SimpleTest.finish());
|
||||
}
|
||||
|
||||
addEventListener("load", startTest);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -9,29 +9,41 @@
|
||||
|
||||
#include "nsConsoleMessage.h"
|
||||
#include "nsReadableUtils.h"
|
||||
#include "jsapi.h"
|
||||
|
||||
NS_IMPL_THREADSAFE_ISUPPORTS1(nsConsoleMessage, nsIConsoleMessage)
|
||||
|
||||
nsConsoleMessage::nsConsoleMessage()
|
||||
nsConsoleMessage::nsConsoleMessage()
|
||||
: mMessage(),
|
||||
mTimeStamp(0)
|
||||
{
|
||||
}
|
||||
|
||||
nsConsoleMessage::nsConsoleMessage(const PRUnichar *message)
|
||||
nsConsoleMessage::nsConsoleMessage(const PRUnichar *message)
|
||||
{
|
||||
mMessage.Assign(message);
|
||||
mTimeStamp = JS_Now() / 1000;
|
||||
mMessage.Assign(message);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsConsoleMessage::GetMessageMoz(PRUnichar **result) {
|
||||
*result = ToNewUnicode(mMessage);
|
||||
nsConsoleMessage::GetMessageMoz(PRUnichar **result)
|
||||
{
|
||||
*result = ToNewUnicode(mMessage);
|
||||
|
||||
return NS_OK;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// NS_IMETHODIMP
|
||||
// nsConsoleMessage::Init(const PRUnichar *message) {
|
||||
// nsAutoString newMessage(message);
|
||||
// mMessage = ToNewUnicode(newMessage);
|
||||
// return NS_OK;
|
||||
// }
|
||||
NS_IMETHODIMP
|
||||
nsConsoleMessage::GetTimeStamp(int64_t *aTimeStamp)
|
||||
{
|
||||
*aTimeStamp = mTimeStamp;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsConsoleMessage::ToString(nsACString& /*UTF8*/ aResult)
|
||||
{
|
||||
CopyUTF16toUTF8(mMessage, aResult);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ public:
|
||||
private:
|
||||
~nsConsoleMessage() {}
|
||||
|
||||
int64_t mTimeStamp;
|
||||
nsString mMessage;
|
||||
};
|
||||
|
||||
|
@ -10,13 +10,23 @@
|
||||
* provide an object that can be qi'ed to provide more specific
|
||||
* message information.
|
||||
*/
|
||||
[scriptable, uuid(41bd8784-1dd2-11b2-9553-8606958fffe1)]
|
||||
[scriptable, uuid(c14c151b-5ea4-47ed-8e85-d392cdd3e154)]
|
||||
interface nsIConsoleMessage : nsISupports
|
||||
{
|
||||
/**
|
||||
* The time (in milliseconds from the Epoch) that the message instance
|
||||
* was initialised.
|
||||
* The timestamp is initialized as JS_now/1000 so that it can be
|
||||
* compared to Date.now in Javascript.
|
||||
*/
|
||||
readonly attribute long long timeStamp;
|
||||
|
||||
[binaryname(MessageMoz)] readonly attribute wstring message;
|
||||
|
||||
AUTF8String toString();
|
||||
};
|
||||
|
||||
%{ C++
|
||||
#define NS_CONSOLEMESSAGE_CID \
|
||||
{ 0x56c9d666, 0x1dd2, 0x11b2, { 0xb4, 0x3c, 0xa8, 0x4b, 0xf3, 0xb3, 0xec, 0xbb }}
|
||||
{ 0x024efc9e, 0x54dc, 0x4844, { 0x80, 0x4b, 0x41, 0xd3, 0xf3, 0x69, 0x90, 0x73 }}
|
||||
%}
|
||||
|
Loading…
Reference in New Issue
Block a user