mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 05:37:11 +00:00
import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 1155006: Fix unified build sensitivities in js/src/jit. r=shu (6e24e1af1) - Bug 1162766 - Fix more bad implicit constructors in js. r=evilpie (39961b06d) - Bug 1151606 - Stream atoms instead of raw pointers for native functions in tracked optimizations. (r=djvj) (7641ee9d6) - pointer style (540728104) - Bug 1154997 - Deal with self-hosted builtins when stringifying tracked optimization type info. (r=djvj) (92f9a54e6) - pointer style (45742d820) - Bug 1154115 - Rewrite the JSAPI profiling API to use a FrameHandle, as to avoid multiple lookups in JitcodeGlobalTable. (r=djvj) (4d202ba9e) - Bug 1119023 - Timeline in new perf tool should filter out markers, r=jsantell (6fc1a8bbe) - Bug 1132755 - Allocations tree has a bunch of columns that don't make sense, r=jsantell (1ae9ee7e2) - Bug 1142744 - Fix tests broken by bug 1132755, r=me (cc495f72d) - Bug 1133058 - OptionsView button, when clicked, should have an 'open' attribute. r=vp (65a78d896) - Bug 1132765 - Pass through performance memory options for 'probability' and 'maxLogLength' from the front to the memory actor. r=vp (f9bbbe098) - Bug 1141817 - Fix yield statement to correctly return memory actor state so that the performance tool can poll for allocations during recording. r=vp (2ddf7d528) - Bug 1141817 - Followup to fix additional intermittents like bug 1132370, r=vp (eab962f01) - Bug 1142748 - Use a single configuration for starting/stopping recordings, r=jsantell (0181b319a) - bit of Bug 879008 - New UI for the sampling Profiler (32c4d0fe8) - Bug 1123815 - Merge gum into fx-team to enable the Performance++ tool, r=me (84aabbd61) - Bug 1143933 - Expose raw JIT optimization information in performance front end. r=vp,shu (f68a6df50) - Bug 1143915 - Allow multiple calls to memory and timeline actor's start methods, to return the local start time from the actor. r=vp (028ac4187) - Bug 978948 - Add animation generator support for setTimeout in the canvas debugger. r=vp (42d623452) - Bug 985488 - Allow canvas debugger to time out and stop recording frames. Canvas debugger 'wait' style now matches other media styles. Update labels in canvas debugger to explicitly state that it's waiting for rAF cycles, rather than appearing as if something went wrong. r=vporof (b4670d843) - Bug 1144163 - Add a rulers highlighter; added unit test. r=pbrosset (5811a67d0) - Bug 1144163 - Add a rulers highlighter; added highlighter. r=pbrosset (779f88bdd) - Bug 1144163 - Add a rulers highlighter; added gcli command and button. r=pbrosset (d0d13da51) - Bug 1110550 - Enable performance overview graphs to rerender and change on devtools theme switch. r=vp (bd91ca7cf) - Bug 1149630 - Performance graphs should inherit from a common graph and be similarly styled. r=vporof (481c841f1) - Bug 1150733 - Correctly internationalize jit samples label. r=vporof, r=flod (b5612d1a6) - Bug 1137518 - FlameGraph's destroy function should be async, r=jsantell (f103e4c15) - Bug 1137503 - Avoid potential infinite loops in `findOptimalTickInterval` functions, r=jsantell (95df6c04a) - Bug 1121194 - Support vertical panning for the flamegraph in the new performance tool, r=jsantell (06241b5b2) - Bug 1121180 - Support dark theme in flamecharts for the performance tool. r=vp (c76abe237) - Bug 1059308 - Add Target.isTabActor to tell if the remote tab actor supports attach/detach requests. r=jryans (e03dcef93) - Bug 1132370 - Wrong State: Expected 'attached', but current state is 'detached', r=jsantell (e884e8db9) - No Bug - Fix documentation for _startMemory and _stopMemory in performance/modules/front.js, r=me DONTBUILD (d79090b31) - Bug 1147656 - Remove duplicate profiler defaults from the front end and just use on the server. r=vp (35c015dd0) - Bug 1046234 - Add more DevTools Telemetry measures (display size etc) r=pbrosset, r=gijs (a235681b4) - actually package telemetry.js (e8f3a58a4) - Bug 1077464 - Wire console.profile/profileEnd to the new performance tool. Move most of the recording-model logic from the front end into the PerformanceFront and PerformanceActorConnection so it can manage recordings without the front end being viewed. r=vp,jryans,pbrosset (eef8e18c3) - Bug 1144363 - Fix this._telemetry is undefined in gDevTools. r=bgrins (ba7d02902) - init telemetry, missing parts of Bug 866642 (1e70df975) - do not use sysctl.h on Linux anymore, since it is not provided by recent glibc (b2467d7ce) - clean up some telemetry issues of histogram, parts of Bug 974171 (d30c8d0ad) - move devtools to browser - part 1 (9a856f452) - Bug 1291423: Explicitly qualify the destructor call that we invoke in Maybe::reset. r=Waldo (944904a7d) - Bug 1148075 - Dynamically add XUL commands for the debugger frontend. r=vporof (60bc91f8f) - Bug 1147945 - Let the profiler's buffer size and sample rate be configurable via prefs. r=vp (acebcbdd9) - Bug 1124326 - Improve packageDir support for Cordova. r=ochameau (4b736580a) - Bug 1124326 - Support Cordova w/o build file. r=ochameau (d4b50aeae) - Bug 1134029 - Fix 'Component returned failure code: 0x80004005 (NS_ERROR_FAILURE) [nsIURI.host]' timeouts, r=jsantell (18d16a5d0) - Bug 1147806 - Content frame filtering is confused when profiling FxOS, r=jsantell (b3c62c552) - Bug 1108843 - Generalize platform data in call tree view when platform data is hidden. r=vporof (354553ed7) - Bug 1138928 - Display only function name and file, instead of full url, in flame graphs. r=vp (4169689c1) - Bug 1152605 - Should not show host names for chrome URIs. r=vporof (c6dcf9e78) - Bug 1147604 - Inverted call trees should list (root) as leaves. r=jsantell (01768267f) - Bug 1075450 - Disable some Awesomebar actions for private windows r=mak (21d5586e7) - Bug 1120616 - Part 1: Implement filter styles in rule view r=bgrins (b66ee0282) - Bug 1120616 - Part 2: Add unit tests for filter styles in rule view r=bgrins (2892503d8) - Bug 1120616 - Part 3: Adjust the styles in the computed view's filter style search r=bgrins (41f8fae1b) - Bug 1120616 - Part 4: Add textbox context menu for rule and computed view r=bgrins (ff3f868ad) - Bug 1120616 - Part 5: Refactor style inspector tests to use synthesizeKeys r=bgrins (41db021d7) - Bug 1102219 - Part 5: Replace more `String.prototype.contains` with `String.prototype.includes` in chrome code. r=till (86ed03588) - Bug 1154018 - Check to see that nsIURI's host exists when parsing location for framenodes, and cache failures. r=vp (9494d52e7) - Bug 1160691 - Optimize FrameUtils.isContent and FrameUtils.parseLocation. (r=jsantell) (09118fd5d) - Bug 1154115 - Make the performance devtool handle the new profiler JSON format. (r=jsantell,vporof) (e3e5be7a4) - Bug 1059308 - Make frame selection button to work in browser toolbox. r=jryans,past (30fe6e61e) - Bug 1059308 - Fix tests to support chrome actor. r=jryans (01cf3926c) - Bug 1147042 - Rename attachProcess to getProcess. r=ochameau (0393ffb80) - Bug 1145824 - Profiler actor and performance tools now handle passing in a startTime to filter out SPS profiles on platform rather than client. r=vp,fitzgen (f225116ba) - Bug 1157718 - Do not use Array.prototype.includes in production code that leaves nightly in performance tool. r=fitzgen (ff06d284e) - Bug 1140728 - Rename 'Memory' to 'Allocations' in the new performance tool. r=jsantell (f584e720f) - Bug 1137500 - Always wait for the overview to be rendered in tests after a recording finishes, unless otherwise specified, r=jsantell (59825e179) - Bug 1137487 - AbstractCanvasGraph's destroy function should be async, r=jsantell (a17ae00b5) - Bug 1132758 - Performance feature visibility now based on a per recording-basis, dependent on features enabled and server support. r=vp (0d080a7c2) - Bug 1147035 - Make DeveloperToolbar.jsm use the gBrowser.contentDocumentAsCPOW shortcut. r=past. (251eff125) - Bug 1151168 - Don't flush profiled threads that are pending deletion on JS shutdown and don't delete expired markers when resetting the profile buffer. (r=djvj) (90721313a)
This commit is contained in:
@@ -178,6 +178,11 @@ let RemoteDebugger = {
|
||||
let restrictPrivileges = Services.prefs.getBoolPref("devtools.debugger.forbid-certified-apps");
|
||||
DebuggerServer.addBrowserActors("navigator:browser", restrictPrivileges);
|
||||
|
||||
// Allow debugging of chrome for any process
|
||||
if (!restrictPrivileges) {
|
||||
DebuggerServer.allowChromeProcess = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a root actor appropriate for use in a server running in B2G.
|
||||
* The returned root actor respects the factories registered with
|
||||
|
||||
@@ -1060,6 +1060,40 @@ pref("services.sync.prefs.sync.xpinstall.whitelist.required", true);
|
||||
// Enable the error console
|
||||
pref("devtools.errorconsole.enabled", true);
|
||||
|
||||
// Toolbox preferences
|
||||
pref("devtools.toolbox.footer.height", 250);
|
||||
pref("devtools.toolbox.sidebar.width", 500);
|
||||
pref("devtools.toolbox.host", "bottom");
|
||||
pref("devtools.toolbox.selectedTool", "webconsole");
|
||||
pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","tilt toggle","scratchpad","resize toggle","eyedropper","screenshot --fullpage", "rulers"]');
|
||||
pref("devtools.toolbox.sideEnabled", true);
|
||||
pref("devtools.toolbox.zoomValue", "1");
|
||||
pref("devtools.toolbox.splitconsoleEnabled", false);
|
||||
pref("devtools.toolbox.splitconsoleHeight", 100);
|
||||
|
||||
// Toolbox Button preferences
|
||||
pref("devtools.command-button-pick.enabled", true);
|
||||
pref("devtools.command-button-frames.enabled", false);
|
||||
pref("devtools.command-button-splitconsole.enabled", true);
|
||||
pref("devtools.command-button-paintflashing.enabled", false);
|
||||
pref("devtools.command-button-tilt.enabled", false);
|
||||
pref("devtools.command-button-scratchpad.enabled", false);
|
||||
pref("devtools.command-button-responsive.enabled", true);
|
||||
pref("devtools.command-button-eyedropper.enabled", false);
|
||||
pref("devtools.command-button-screenshot.enabled", false);
|
||||
pref("devtools.command-button-rulers.enabled", false);
|
||||
|
||||
// Enable the Performance tools
|
||||
pref("devtools.performance.enabled", true);
|
||||
|
||||
// The default Performance UI settings
|
||||
pref("devtools.performance.memory.sample-probability", "0.05");
|
||||
pref("devtools.performance.memory.max-log-length", 2147483647); // Math.pow(2,31) - 1
|
||||
pref("devtools.performance.timeline.hidden-markers", "[]");
|
||||
pref("devtools.performance.profiler.buffer-size", 10000000);
|
||||
pref("devtools.performance.profiler.sample-frequency-khz", 1);
|
||||
pref("devtools.performance.ui.show-jit-optimizations", false);
|
||||
|
||||
// Whether the character encoding menu is under the main Firefox button. This
|
||||
// preference is a string so that localizers can alter it.
|
||||
pref("browser.menu.showCharacterEncoding", "chrome://browser/locale/browser.properties");
|
||||
|
||||
@@ -6855,9 +6855,13 @@ let gPrivateBrowsingUI = {
|
||||
|
||||
if (gURLBar &&
|
||||
!PrivateBrowsingUtils.permanentPrivateBrowsing) {
|
||||
// Disable switch to tab autocompletion for private windows
|
||||
// (not for "Always use private browsing" mode)
|
||||
gURLBar.setAttribute("autocompletesearchparam", "");
|
||||
// Disable switch to tab autocompletion for private windows.
|
||||
// We leave it enabled for permanent private browsing mode though.
|
||||
let value = gURLBar.getAttribute("autocompletesearchparam") || "";
|
||||
if (!value.includes("disable-private-actions")) {
|
||||
gURLBar.setAttribute("autocompletesearchparam",
|
||||
value + " disable-private-actions");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -134,6 +134,7 @@ devtoolsCommandlineHandler.prototype = {
|
||||
let debuggerServer = serverLoader.DebuggerServer;
|
||||
debuggerServer.init();
|
||||
debuggerServer.addBrowserActors();
|
||||
debuggerServer.allowChromeProcess = true;
|
||||
|
||||
let listener = debuggerServer.createListener();
|
||||
listener.portOrPath = portOrPath;
|
||||
@@ -0,0 +1,5 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
browser.jar:
|
||||
@@ -30,9 +30,7 @@ loader.lazyGetter(this, "StyleEditorPanel", () => require("devtools/styleeditor/
|
||||
loader.lazyGetter(this, "ShaderEditorPanel", () => require("devtools/shadereditor/panel").ShaderEditorPanel);
|
||||
loader.lazyGetter(this, "CanvasDebuggerPanel", () => require("devtools/canvasdebugger/panel").CanvasDebuggerPanel);
|
||||
loader.lazyGetter(this, "WebAudioEditorPanel", () => require("devtools/webaudioeditor/panel").WebAudioEditorPanel);
|
||||
loader.lazyGetter(this, "ProfilerPanel", () => require("devtools/profiler/panel").ProfilerPanel);
|
||||
loader.lazyGetter(this, "PerformancePanel", () => require("devtools/performance/panel").PerformancePanel);
|
||||
loader.lazyGetter(this, "TimelinePanel", () => require("devtools/timeline/panel").TimelinePanel);
|
||||
loader.lazyGetter(this, "NetMonitorPanel", () => require("devtools/netmonitor/panel").NetMonitorPanel);
|
||||
loader.lazyGetter(this, "StoragePanel", () => require("devtools/storage/panel").StoragePanel);
|
||||
loader.lazyGetter(this, "ScratchpadPanel", () => require("devtools/scratchpad/scratchpad-panel").ScratchpadPanel);
|
||||
@@ -47,7 +45,6 @@ const shaderEditorProps = "chrome://global/locale/devtools/shadereditor.properti
|
||||
const canvasDebuggerProps = "chrome://global/locale/devtools/canvasdebugger.properties";
|
||||
const webAudioEditorProps = "chrome://global/locale/devtools/webaudioeditor.properties";
|
||||
const profilerProps = "chrome://global/locale/devtools/profiler.properties";
|
||||
const timelineProps = "chrome://global/locale/devtools/timeline.properties";
|
||||
const netMonitorProps = "chrome://global/locale/devtools/netmonitor.properties";
|
||||
const storageProps = "chrome://global/locale/devtools/storage.properties";
|
||||
const scratchpadProps = "chrome://global/locale/devtools/scratchpad.properties";
|
||||
@@ -61,7 +58,6 @@ loader.lazyGetter(this, "shaderEditorStrings", () => Services.strings.createBund
|
||||
loader.lazyGetter(this, "canvasDebuggerStrings", () => Services.strings.createBundle(canvasDebuggerProps));
|
||||
loader.lazyGetter(this, "webAudioEditorStrings", () => Services.strings.createBundle(webAudioEditorProps));
|
||||
loader.lazyGetter(this, "inspectorStrings", () => Services.strings.createBundle(inspectorProps));
|
||||
loader.lazyGetter(this, "timelineStrings", () => Services.strings.createBundle(timelineProps));
|
||||
loader.lazyGetter(this, "netMonitorStrings", () => Services.strings.createBundle(netMonitorProps));
|
||||
loader.lazyGetter(this, "storageStrings", () => Services.strings.createBundle(storageProps));
|
||||
loader.lazyGetter(this, "scratchpadStrings", () => Services.strings.createBundle(scratchpadProps));
|
||||
@@ -249,41 +245,15 @@ Tools.canvasDebugger = {
|
||||
}
|
||||
};
|
||||
|
||||
Tools.jsprofiler = {
|
||||
id: "jsprofiler",
|
||||
accesskey: l10n("profiler.accesskey", profilerStrings),
|
||||
key: l10n("profiler.commandkey2", profilerStrings),
|
||||
ordinal: 7,
|
||||
modifiers: "shift",
|
||||
visibilityswitch: "devtools.profiler.enabled",
|
||||
icon: "chrome://global/skin/devtools/tool-profiler.svg",
|
||||
invertIconForLightTheme: true,
|
||||
url: "chrome://global/content/devtools/profiler.xul",
|
||||
label: l10n("profiler.label2", profilerStrings),
|
||||
panelLabel: l10n("profiler.panelLabel2", profilerStrings),
|
||||
tooltip: l10n("profiler.tooltip2", profilerStrings),
|
||||
inMenu: true,
|
||||
|
||||
isTargetSupported: function (target) {
|
||||
// Hide the profiler when debugging devices pre bug 1046394,
|
||||
// that don't expose profiler actor in content processes.
|
||||
return target.hasActor("profiler");
|
||||
},
|
||||
|
||||
build: function (frame, target) {
|
||||
return new ProfilerPanel(frame, target);
|
||||
}
|
||||
};
|
||||
|
||||
Tools.performance = {
|
||||
id: "performance",
|
||||
ordinal: 19,
|
||||
ordinal: 7,
|
||||
icon: "chrome://global/skin/devtools/tool-profiler.svg",
|
||||
invertIconForLightTheme: true,
|
||||
url: "chrome://global/content/devtools/performance.xul",
|
||||
// TODO bug 1082695 audit the Performance tools labels
|
||||
label: "Performance++", //l10n("profiler.label2", profilerStrings),
|
||||
panelLabel: "Performance++", //l10n("profiler.panelLabel2", profilerStrings),
|
||||
visibilityswitch: "devtools.performance.enabled",
|
||||
label: l10n("profiler.label2", profilerStrings),
|
||||
panelLabel: l10n("profiler.panelLabel2", profilerStrings),
|
||||
tooltip: l10n("profiler.tooltip2", profilerStrings),
|
||||
accesskey: l10n("profiler.accesskey", profilerStrings),
|
||||
key: l10n("profiler.commandkey2", profilerStrings),
|
||||
@@ -299,27 +269,6 @@ Tools.performance = {
|
||||
}
|
||||
};
|
||||
|
||||
Tools.timeline = {
|
||||
id: "timeline",
|
||||
ordinal: 8,
|
||||
visibilityswitch: "devtools.timeline.enabled",
|
||||
icon: "chrome://global/skin/devtools/tool-network.svg",
|
||||
invertIconForLightTheme: true,
|
||||
url: "chrome://global/content/devtools/timeline/timeline.xul",
|
||||
label: l10n("timeline.label", timelineStrings),
|
||||
panelLabel: l10n("timeline.panelLabel", timelineStrings),
|
||||
tooltip: l10n("timeline.tooltip", timelineStrings),
|
||||
|
||||
isTargetSupported: function(target) {
|
||||
return target.hasActor("timeline");
|
||||
},
|
||||
|
||||
build: function (iframeWindow, toolbox) {
|
||||
let panel = new TimelinePanel(iframeWindow, toolbox);
|
||||
return panel.open();
|
||||
}
|
||||
};
|
||||
|
||||
Tools.netMonitor = {
|
||||
id: "netmonitor",
|
||||
accesskey: l10n("netmonitor.accesskey", netMonitorStrings),
|
||||
@@ -422,23 +371,12 @@ let defaultTools = [
|
||||
Tools.shaderEditor,
|
||||
Tools.canvasDebugger,
|
||||
Tools.webAudioEditor,
|
||||
Tools.jsprofiler,
|
||||
Tools.timeline,
|
||||
Tools.performance,
|
||||
Tools.netMonitor,
|
||||
Tools.storage,
|
||||
Tools.scratchpad
|
||||
];
|
||||
|
||||
// Only enable in-development performance tools if `--enable-devtools-perf`
|
||||
// used in build, turning on `devtools.performance_dev.enabled`.
|
||||
// Add to normal `defaultTools` when ready for normal release,
|
||||
// pull out MOZ_DEVTOOLS_PERFTOOLS setting in `./configure.in`, and
|
||||
// leave config on in `./browser/app/profile/firefox.js`, and always
|
||||
// build in `./browser/devtools/moz.build`.
|
||||
if (Services.prefs.getBoolPref("devtools.performance_dev.enabled")) {
|
||||
defaultTools.push(Tools.performance);
|
||||
}
|
||||
|
||||
exports.defaultTools = defaultTools;
|
||||
|
||||
for (let definition of defaultTools) {
|
||||
@@ -0,0 +1,16 @@
|
||||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'devtools-clhandler.js',
|
||||
'devtools-clhandler.manifest',
|
||||
]
|
||||
|
||||
JAR_MANIFESTS += ['jar.mn']
|
||||
|
||||
EXTRA_JS_MODULES.devtools += [
|
||||
'main.js',
|
||||
]
|
||||
@@ -419,8 +419,8 @@
|
||||
@RESPATH@/components/jsconsole-clhandler.manifest
|
||||
@RESPATH@/components/jsconsole-clhandler.js
|
||||
#ifdef MOZ_DEVTOOLS
|
||||
@RESPATH@/components/devtools-clhandler.manifest
|
||||
@RESPATH@/components/devtools-clhandler.js
|
||||
@RESPATH@/browser/components/devtools-clhandler.manifest
|
||||
@RESPATH@/browser/components/devtools-clhandler.js
|
||||
#endif
|
||||
@RESPATH@/components/webvtt.xpt
|
||||
@RESPATH@/components/WebVTT.manifest
|
||||
|
||||
+2
-1
@@ -16,6 +16,7 @@ DIRS += [
|
||||
]
|
||||
|
||||
DIRS += [
|
||||
'devtools',
|
||||
'app',
|
||||
]
|
||||
|
||||
@@ -23,4 +24,4 @@ if CONFIG['MAKENSISU']:
|
||||
DIRS += ['installer/windows']
|
||||
|
||||
DIST_SUBDIR = 'browser'
|
||||
export('DIST_SUBDIR')
|
||||
export('DIST_SUBDIR')
|
||||
|
||||
@@ -125,6 +125,9 @@ browser.jar:
|
||||
skin/classic/browser/tabbrowser/tab.png (tabbrowser/tab.png)
|
||||
skin/classic/browser/tabbrowser/tab-overflow-border.png (tabbrowser/tab-overflow-border.png)
|
||||
skin/classic/browser/tabbrowser/tabDragIndicator.png (tabbrowser/tabDragIndicator.png)
|
||||
skin/classic/browser/devtools/search-clear-failed.svg (../shared/devtools/images/search-clear-failed.svg)
|
||||
skin/classic/browser/devtools/search-clear-light.svg (../shared/devtools/images/search-clear-light.svg)
|
||||
skin/classic/browser/devtools/search-clear-dark.svg (../shared/devtools/images/search-clear-dark.svg)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
skin/classic/browser/sync-16-throbber.png
|
||||
skin/classic/browser/sync-16.png
|
||||
|
||||
@@ -160,6 +160,9 @@ browser.jar:
|
||||
skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png (tabbrowser/tab-arrow-left-inverted.png)
|
||||
skin/classic/browser/tabbrowser/tab-overflow-border.png (tabbrowser/tab-overflow-border.png)
|
||||
skin/classic/browser/tabbrowser/tabDragIndicator.png (tabbrowser/tabDragIndicator.png)
|
||||
skin/classic/browser/devtools/search-clear-failed.svg (../shared/devtools/images/search-clear-failed.svg)
|
||||
skin/classic/browser/devtools/search-clear-light.svg (../shared/devtools/images/search-clear-light.svg)
|
||||
skin/classic/browser/devtools/search-clear-dark.svg (../shared/devtools/images/search-clear-dark.svg)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
skin/classic/browser/sync-throbber.png
|
||||
skin/classic/browser/sync-16.png
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0"
|
||||
y="0"
|
||||
width="32"
|
||||
height="16"
|
||||
viewBox="0 0 32 16">
|
||||
<defs>
|
||||
<path id="glyphShape-clear" d="M8,0C3.6,0,0,3.6,0,8c0,4.4,3.6,8,8,8s8-3.6,8-8C16,3.6,12.4,0,8,0 z M11.9,10.5l-1.4,1.4L8,9.4l-2.4,2.4l-1.4-1.4L6.6,8L4.2,5.6l1.4-1.4L8,6.6l2.4-2.4l1.4,1.4L9.4,8L11.9,10.5z"/>
|
||||
<style type="text/css">
|
||||
.icon-state-default { fill: #f5f7fa; fill-opacity: .6; }
|
||||
.icon-state-pressed { fill: #7d7e80; fill-opacity: .8; }
|
||||
</style>
|
||||
</defs>
|
||||
<use xlink:href="#glyphShape-clear" class="icon-state-default" />
|
||||
<use xlink:href="#glyphShape-clear" class="icon-state-pressed" transform="translate(16)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 985 B |
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0"
|
||||
y="0"
|
||||
width="32"
|
||||
height="16"
|
||||
viewBox="0 0 32 16">
|
||||
<defs>
|
||||
<path id="glyphShape-clear" d="M8,0C3.6,0,0,3.6,0,8c0,4.4,3.6,8,8,8s8-3.6,8-8C16,3.6,12.4,0,8,0 z M11.9,10.5l-1.4,1.4L8,9.4l-2.4,2.4l-1.4-1.4L6.6,8L4.2,5.6l1.4-1.4L8,6.6l2.4-2.4l1.4,1.4L9.4,8L11.9,10.5z"/>
|
||||
<style type="text/css">
|
||||
.icon-state-default { fill: #cc3d3d; fill-opacity: 1; }
|
||||
.icon-state-pressed { fill: #802d2d; fill-opacity: 1; }
|
||||
</style>
|
||||
</defs>
|
||||
<use xlink:href="#glyphShape-clear" class="icon-state-default" />
|
||||
<use xlink:href="#glyphShape-clear" class="icon-state-pressed" transform="translate(16)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 983 B |
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<svg xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0"
|
||||
y="0"
|
||||
width="32"
|
||||
height="16"
|
||||
viewBox="0 0 32 16">
|
||||
<defs>
|
||||
<path id="glyphShape-clear" d="M8,0C3.6,0,0,3.6,0,8c0,4.4,3.6,8,8,8s8-3.6,8-8C16,3.6,12.4,0,8,0 z M11.9,10.5l-1.4,1.4L8,9.4l-2.4,2.4l-1.4-1.4L6.6,8L4.2,5.6l1.4-1.4L8,6.6l2.4-2.4l1.4,1.4L9.4,8L11.9,10.5z"/>
|
||||
<style type="text/css">
|
||||
.icon-state-default { fill: #1d2126; fill-opacity: .5; }
|
||||
.icon-state-pressed { fill: #1d2126; fill-opacity: .8; }
|
||||
</style>
|
||||
</defs>
|
||||
<use xlink:href="#glyphShape-clear" class="icon-state-default" />
|
||||
<use xlink:href="#glyphShape-clear" class="icon-state-pressed" transform="translate(16)" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 985 B |
@@ -156,6 +156,9 @@ browser.jar:
|
||||
skin/classic/browser/tabbrowser/tab-arrow-left-inverted.png (tabbrowser/tab-arrow-left-inverted.png)
|
||||
skin/classic/browser/tabbrowser/tab-overflow-border.png (tabbrowser/tab-overflow-border.png)
|
||||
skin/classic/browser/tabbrowser/tabDragIndicator.png (tabbrowser/tabDragIndicator.png)
|
||||
skin/classic/browser/devtools/search-clear-failed.svg (../shared/devtools/images/search-clear-failed.svg)
|
||||
skin/classic/browser/devtools/search-clear-light.svg (../shared/devtools/images/search-clear-light.svg)
|
||||
skin/classic/browser/devtools/search-clear-dark.svg (../shared/devtools/images/search-clear-dark.svg)
|
||||
#ifdef MOZ_SERVICES_SYNC
|
||||
skin/classic/browser/sync-throbber.png
|
||||
skin/classic/browser/sync-16.png
|
||||
|
||||
@@ -15,3 +15,6 @@ mk_add_options AUTOCLOBBER=1
|
||||
ac_add_options --enable-release
|
||||
|
||||
. "$topsrcdir/build/mozconfig.automation"
|
||||
|
||||
# Temporarily enable building the new Performance++ tool.
|
||||
ac_add_options --enable-devtools-perf
|
||||
|
||||
@@ -7369,19 +7369,6 @@ fi
|
||||
|
||||
AC_SUBST(MOZ_DEVTOOLS)
|
||||
|
||||
dnl =========================================================
|
||||
dnl Enable support for revamped devtools Performance Tools
|
||||
dnl =========================================================
|
||||
|
||||
MOZ_ARG_ENABLE_BOOL(devtools-perf,
|
||||
[ --enable-devtools-perf Set compile flags necessary for compiling devtools perftools],
|
||||
MOZ_DEVTOOLS_PERFTOOLS=1,
|
||||
MOZ_DEVTOOLS_PERFTOOLS= )
|
||||
if test -n "$MOZ_DEVTOOLS_PERFTOOLS"; then
|
||||
AC_DEFINE(MOZ_DEVTOOLS_PERFTOOLS)
|
||||
fi
|
||||
AC_SUBST(MOZ_DEVTOOLS_PERFTOOLS)
|
||||
|
||||
dnl =========================================================
|
||||
dnl Omnijar packaging (bug 552121)
|
||||
dnl =========================================================
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
// http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
function serviceWorkerTestExec(testFile) {
|
||||
var isB2G = !navigator.userAgent.contains("Android") &&
|
||||
var isB2G = !navigator.userAgent.includes("Android") &&
|
||||
/Mobile|Tablet/.test(navigator.userAgent);
|
||||
if (isB2G) {
|
||||
// TODO B2G doesn't support running service workers for now due to bug 1137683.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<script>
|
||||
var isAndroid = navigator.userAgent.contains("Android");
|
||||
var isAndroid = navigator.userAgent.includes("Android");
|
||||
var isB2G = !isAndroid && /Mobile|Tablet/.test(navigator.userAgent);
|
||||
|
||||
function ok(v, msg) {
|
||||
|
||||
@@ -7,13 +7,13 @@ onfetch = function(ev) {
|
||||
));
|
||||
}
|
||||
|
||||
else if (ev.request.url.contains("synthesized-404.txt")) {
|
||||
else if (ev.request.url.includes("synthesized-404.txt")) {
|
||||
ev.respondWith(Promise.resolve(
|
||||
new Response("synthesized response body", { status: 404 })
|
||||
));
|
||||
}
|
||||
|
||||
else if (ev.request.url.contains("synthesized-headers.txt")) {
|
||||
else if (ev.request.url.includes("synthesized-headers.txt")) {
|
||||
ev.respondWith(Promise.resolve(
|
||||
new Response("synthesized response body", {
|
||||
headers: {
|
||||
@@ -23,17 +23,17 @@ onfetch = function(ev) {
|
||||
));
|
||||
}
|
||||
|
||||
else if (ev.request.url.contains("test-respondwith-response.txt")) {
|
||||
else if (ev.request.url.includes("test-respondwith-response.txt")) {
|
||||
ev.respondWith(new Response("test-respondwith-response response body", {}));
|
||||
}
|
||||
|
||||
else if (ev.request.url.contains("synthesized-redirect-real-file.txt")) {
|
||||
else if (ev.request.url.includes("synthesized-redirect-real-file.txt")) {
|
||||
ev.respondWith(Promise.resolve(
|
||||
Response.redirect("fetch/real-file.txt")
|
||||
));
|
||||
}
|
||||
|
||||
else if (ev.request.url.contains("synthesized-redirect-synthesized.txt")) {
|
||||
else if (ev.request.url.includes("synthesized-redirect-synthesized.txt")) {
|
||||
ev.respondWith(Promise.resolve(
|
||||
Response.redirect("synthesized.txt")
|
||||
));
|
||||
@@ -112,18 +112,18 @@ onfetch = function(ev) {
|
||||
));
|
||||
}
|
||||
|
||||
else if (ev.request.url.contains("deliver-gzip")) {
|
||||
else if (ev.request.url.includes("deliver-gzip")) {
|
||||
// Don't handle the request, this will make Necko perform a network request, at
|
||||
// which point SetApplyConversion must be re-enabled, otherwise the request
|
||||
// will fail.
|
||||
return;
|
||||
}
|
||||
|
||||
else if (ev.request.url.contains("hello.gz")) {
|
||||
else if (ev.request.url.includes("hello.gz")) {
|
||||
ev.respondWith(fetch("fetch/deliver-gzip.sjs"));
|
||||
}
|
||||
|
||||
else if (ev.request.url.contains("hello-after-extracting.gz")) {
|
||||
else if (ev.request.url.includes("hello-after-extracting.gz")) {
|
||||
ev.respondWith(fetch("fetch/deliver-gzip.sjs").then(function(res) {
|
||||
return res.text().then(function(body) {
|
||||
return new Response(body, { status: res.status, statusText: res.statusText, headers: res.headers });
|
||||
@@ -131,12 +131,12 @@ onfetch = function(ev) {
|
||||
}));
|
||||
}
|
||||
|
||||
else if (ev.request.url.contains('opaque-on-same-origin')) {
|
||||
else if (ev.request.url.includes('opaque-on-same-origin')) {
|
||||
var url = 'http://example.com/tests/dom/base/test/file_CrossSiteXHR_server.sjs?status=200';
|
||||
ev.respondWith(fetch(url, { mode: 'no-cors' }));
|
||||
}
|
||||
|
||||
else if (ev.request.url.contains('opaque-no-cors')) {
|
||||
else if (ev.request.url.includes('opaque-no-cors')) {
|
||||
if (ev.request.mode != "no-cors") {
|
||||
ev.respondWith(Promise.reject());
|
||||
return;
|
||||
@@ -146,7 +146,7 @@ onfetch = function(ev) {
|
||||
ev.respondWith(fetch(url, { mode: ev.request.mode }));
|
||||
}
|
||||
|
||||
else if (ev.request.url.contains('cors-for-no-cors')) {
|
||||
else if (ev.request.url.includes('cors-for-no-cors')) {
|
||||
if (ev.request.mode != "no-cors") {
|
||||
ev.respondWith(Promise.reject());
|
||||
return;
|
||||
@@ -156,11 +156,11 @@ onfetch = function(ev) {
|
||||
ev.respondWith(fetch(url));
|
||||
}
|
||||
|
||||
else if (ev.request.url.contains('example.com')) {
|
||||
else if (ev.request.url.includes('example.com')) {
|
||||
ev.respondWith(fetch(ev.request));
|
||||
}
|
||||
|
||||
else if (ev.request.url.contains("index.html")) {
|
||||
else if (ev.request.url.includes("index.html")) {
|
||||
if (seenIndex) {
|
||||
var body = "<script>" +
|
||||
"opener.postMessage({status: 'ok', result: " + ev.isReload + "," +
|
||||
|
||||
@@ -192,9 +192,9 @@ var interfaceNamesInGlobalScope =
|
||||
|
||||
function createInterfaceMap(prefMap, permissionMap, version, userAgent, isB2G) {
|
||||
var isNightly = version.endsWith("a1");
|
||||
var isRelease = !version.contains("a");
|
||||
var isRelease = !version.includes("a");
|
||||
var isDesktop = !/Mobile|Tablet/.test(userAgent);
|
||||
var isAndroid = !!navigator.userAgent.contains("Android");
|
||||
var isAndroid = !!navigator.userAgent.includes("Android");
|
||||
|
||||
var interfaceMap = {};
|
||||
|
||||
|
||||
@@ -262,7 +262,7 @@
|
||||
/* #undef _EVENT_HAVE_STRUCT_SOCKADDR_STORAGE___SS_FAMILY */
|
||||
|
||||
/* Define to 1 if you have the `sysctl' function. */
|
||||
#define _EVENT_HAVE_SYSCTL 1
|
||||
/* #undef EVENT__HAVE_SYSCTL */
|
||||
|
||||
/* Define to 1 if you have the <sys/devpoll.h> header file. */
|
||||
/* #undef _EVENT_HAVE_SYS_DEVPOLL_H */
|
||||
@@ -301,7 +301,7 @@
|
||||
#define _EVENT_HAVE_SYS_STAT_H 1
|
||||
|
||||
/* Define to 1 if you have the <sys/sysctl.h> header file. */
|
||||
#define _EVENT_HAVE_SYS_SYSCTL_H 1
|
||||
/* #undef EVENT__HAVE_SYS_SYSCTL_H */
|
||||
|
||||
/* Define to 1 if you have the <sys/time.h> header file. */
|
||||
#define _EVENT_HAVE_SYS_TIME_H 1
|
||||
|
||||
@@ -10,11 +10,11 @@
|
||||
#include "mozilla/Alignment.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "jsbytecode.h"
|
||||
#include "js/Utility.h"
|
||||
|
||||
struct JSRuntime;
|
||||
class JSScript;
|
||||
|
||||
namespace js {
|
||||
class Activation;
|
||||
@@ -28,6 +28,9 @@ namespace js {
|
||||
|
||||
namespace JS {
|
||||
|
||||
struct ForEachTrackedOptimizationAttemptOp;
|
||||
struct ForEachTrackedOptimizationTypeInfoOp;
|
||||
|
||||
// This iterator can be used to walk the stack of a thread suspended at an
|
||||
// arbitrary pc. To provide acurate results, profiling must have been enabled
|
||||
// (via EnableRuntimeProfilingStack) before executing the callstack being
|
||||
@@ -128,9 +131,6 @@ class JS_PUBLIC_API(ProfilingFrameIterator)
|
||||
bool iteratorDone();
|
||||
};
|
||||
|
||||
extern JS_PUBLIC_API(ProfilingFrameIterator::FrameKind)
|
||||
GetProfilingFrameKindFromNativeAddr(JSRuntime* runtime, void* pc);
|
||||
|
||||
JS_FRIEND_API(bool)
|
||||
IsProfilingEnabledForRuntime(JSRuntime* runtime);
|
||||
|
||||
@@ -148,8 +148,40 @@ UpdateJSRuntimeProfilerSampleBufferGen(JSRuntime* runtime, uint32_t generation,
|
||||
|
||||
struct ForEachProfiledFrameOp
|
||||
{
|
||||
// A handle to the underlying JitcodeGlobalEntry, so as to avoid repeated
|
||||
// lookups on JitcodeGlobalTable.
|
||||
class MOZ_STACK_CLASS FrameHandle
|
||||
{
|
||||
friend JS_PUBLIC_API(void) ForEachProfiledFrame(JSRuntime* rt, void* addr,
|
||||
ForEachProfiledFrameOp& op);
|
||||
|
||||
JSRuntime* rt_;
|
||||
js::jit::JitcodeGlobalEntry& entry_;
|
||||
void* addr_;
|
||||
void* canonicalAddr_;
|
||||
const char* label_;
|
||||
uint32_t depth_;
|
||||
mozilla::Maybe<uint8_t> optsIndex_;
|
||||
|
||||
FrameHandle(JSRuntime* rt, js::jit::JitcodeGlobalEntry& entry, void* addr,
|
||||
const char* label, uint32_t depth);
|
||||
|
||||
void updateHasTrackedOptimizations();
|
||||
|
||||
public:
|
||||
const char* label() const { return label_; }
|
||||
uint32_t depth() const { return depth_; }
|
||||
bool hasTrackedOptimizations() const { return optsIndex_.isSome(); }
|
||||
void* canonicalAddress() const { return canonicalAddr_; }
|
||||
|
||||
ProfilingFrameIterator::FrameKind frameKind() const;
|
||||
void forEachOptimizationAttempt(ForEachTrackedOptimizationAttemptOp& op,
|
||||
JSScript** scriptOut, jsbytecode** pcOut) const;
|
||||
void forEachOptimizationTypeInfo(ForEachTrackedOptimizationTypeInfoOp& op) const;
|
||||
};
|
||||
|
||||
// Called once per frame.
|
||||
virtual void operator()(const char* label, bool mightHaveTrackedOptimizations) = 0;
|
||||
virtual void operator()(const FrameHandle& frame) = 0;
|
||||
};
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
|
||||
@@ -180,11 +180,6 @@ struct ForEachTrackedOptimizationAttemptOp
|
||||
virtual void operator()(TrackedStrategy strategy, TrackedOutcome outcome) = 0;
|
||||
};
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
ForEachTrackedOptimizationAttempt(JSRuntime* rt, void* addr, uint8_t index,
|
||||
ForEachTrackedOptimizationAttemptOp& op,
|
||||
JSScript** scriptOut, jsbytecode** pcOut);
|
||||
|
||||
struct ForEachTrackedOptimizationTypeInfoOp
|
||||
{
|
||||
// Called 0+ times per entry, once for each type in the type set that Ion
|
||||
@@ -215,24 +210,17 @@ struct ForEachTrackedOptimizationTypeInfoOp
|
||||
//
|
||||
// The lineno parameter is the line number if the type is keyed by
|
||||
// "constructor", "alloc site", or if the type itself refers to a scripted
|
||||
// function. Otherwise it is UINT32_MAX.
|
||||
// function. Otherwise it is Nothing().
|
||||
//
|
||||
// The location parameter is the only one that may need escaping if being
|
||||
// quoted.
|
||||
virtual void readType(const char* keyedBy, const char* name,
|
||||
const char* location, unsigned lineno) = 0;
|
||||
const char* location, mozilla::Maybe<unsigned> lineno) = 0;
|
||||
|
||||
// Called once per entry.
|
||||
virtual void operator()(TrackedTypeSite site, const char* mirType) = 0;
|
||||
};
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
ForEachTrackedOptimizationTypeInfo(JSRuntime *rt, void *addr, uint8_t index,
|
||||
ForEachTrackedOptimizationTypeInfoOp &op);
|
||||
|
||||
JS_PUBLIC_API(mozilla::Maybe<uint8_t>)
|
||||
TrackedOptimizationIndexAtAddr(JSRuntime *rt, void *addr, void **entryAddr);
|
||||
|
||||
} // namespace JS
|
||||
|
||||
#endif // js_TrackedOptimizationInfo_h
|
||||
|
||||
@@ -252,8 +252,8 @@ unum_setTextAttribute(UNumberFormat* fmt, UNumberFormatTextAttribute tag, const
|
||||
|
||||
class Locale {
|
||||
public:
|
||||
Locale(const char* language, const char* country = 0, const char* variant = 0,
|
||||
const char* keywordsAndValues = 0);
|
||||
explicit Locale(const char* language, const char* country = 0, const char* variant = 0,
|
||||
const char* keywordsAndValues = 0);
|
||||
};
|
||||
|
||||
Locale::Locale(const char* language, const char* country, const char* variant,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <stdarg.h>
|
||||
|
||||
#include "jit/BacktrackingAllocator.h"
|
||||
#include "jit/LIR.h"
|
||||
#include "jit/MIR.h"
|
||||
#include "jit/MIRGraph.h"
|
||||
|
||||
+70
-17
@@ -38,6 +38,14 @@ RegionAtAddr(const JitcodeGlobalEntry::IonEntry& entry, void* ptr,
|
||||
}
|
||||
|
||||
|
||||
void*
|
||||
JitcodeGlobalEntry::IonEntry::canonicalNativeAddrFor(JSRuntime* rt, void* ptr) const
|
||||
{
|
||||
uint32_t ptrOffset;
|
||||
JitcodeRegionEntry region = RegionAtAddr(*this, ptr, &ptrOffset);
|
||||
return (void*)(((uint8_t*) nativeStartAddr()) + region.nativeOffset());
|
||||
}
|
||||
|
||||
bool
|
||||
JitcodeGlobalEntry::IonEntry::callStackAtAddr(JSRuntime* rt, void* ptr,
|
||||
BytecodeLocationVector& results,
|
||||
@@ -148,6 +156,14 @@ JitcodeGlobalEntry::IonEntry::destroy()
|
||||
optsAllTypes_ = nullptr;
|
||||
}
|
||||
|
||||
void*
|
||||
JitcodeGlobalEntry::BaselineEntry::canonicalNativeAddrFor(JSRuntime* rt, void* ptr) const
|
||||
{
|
||||
// TODO: We can't yet normalize Baseline addresses until we unify
|
||||
// BaselineScript's PCMappingEntries with JitcodeGlobalMap.
|
||||
return ptr;
|
||||
}
|
||||
|
||||
bool
|
||||
JitcodeGlobalEntry::BaselineEntry::callStackAtAddr(JSRuntime* rt, void* ptr,
|
||||
BytecodeLocationVector& results,
|
||||
@@ -198,17 +214,25 @@ JitcodeGlobalEntry::BaselineEntry::destroy()
|
||||
}
|
||||
|
||||
static inline void
|
||||
RejoinEntry(JSRuntime *rt, const JitcodeGlobalEntry::IonCacheEntry &cache,
|
||||
void *ptr, JitcodeGlobalEntry *entry)
|
||||
RejoinEntry(JSRuntime* rt, const JitcodeGlobalEntry::IonCacheEntry& cache,
|
||||
void* ptr, JitcodeGlobalEntry* entry)
|
||||
{
|
||||
MOZ_ASSERT(cache.containsPointer(ptr));
|
||||
|
||||
// There must exist an entry for the rejoin addr if this entry exists.
|
||||
JitRuntime *jitrt = rt->jitRuntime();
|
||||
JitRuntime* jitrt = rt->jitRuntime();
|
||||
jitrt->getJitcodeGlobalTable()->lookupInfallible(cache.rejoinAddr(), entry, rt);
|
||||
MOZ_ASSERT(entry->isIon());
|
||||
}
|
||||
|
||||
void*
|
||||
JitcodeGlobalEntry::IonCacheEntry::canonicalNativeAddrFor(JSRuntime* rt, void* ptr) const
|
||||
{
|
||||
JitcodeGlobalEntry entry;
|
||||
RejoinEntry(rt, *this, ptr, &entry);
|
||||
return entry.canonicalNativeAddrFor(rt, rejoinAddr());
|
||||
}
|
||||
|
||||
bool
|
||||
JitcodeGlobalEntry::IonCacheEntry::callStackAtAddr(JSRuntime* rt, void* ptr,
|
||||
BytecodeLocationVector& results,
|
||||
@@ -419,7 +443,7 @@ JitcodeGlobalTable::lookup(void* ptr, JitcodeGlobalEntry* result, JSRuntime* rt)
|
||||
{
|
||||
MOZ_ASSERT(result);
|
||||
|
||||
JitcodeGlobalEntry *entry = lookupInternal(ptr);
|
||||
JitcodeGlobalEntry* entry = lookupInternal(ptr);
|
||||
if (!entry)
|
||||
return false;
|
||||
|
||||
@@ -495,15 +519,15 @@ JitcodeGlobalTable::addEntry(const JitcodeGlobalEntry& entry, JSRuntime* rt)
|
||||
{
|
||||
MOZ_ASSERT(entry.isIon() || entry.isBaseline() || entry.isIonCache() || entry.isDummy());
|
||||
|
||||
JitcodeGlobalEntry *searchTower[JitcodeSkiplistTower::MAX_HEIGHT];
|
||||
JitcodeGlobalEntry* searchTower[JitcodeSkiplistTower::MAX_HEIGHT];
|
||||
searchInternal(entry, searchTower);
|
||||
|
||||
// Allocate a new entry and tower.
|
||||
JitcodeSkiplistTower *newTower = allocateTower(generateTowerHeight());
|
||||
JitcodeSkiplistTower* newTower = allocateTower(generateTowerHeight());
|
||||
if (!newTower)
|
||||
return false;
|
||||
|
||||
JitcodeGlobalEntry *newEntry = allocateEntry();
|
||||
JitcodeGlobalEntry* newEntry = allocateEntry();
|
||||
if (!newEntry)
|
||||
return false;
|
||||
|
||||
@@ -1180,7 +1204,7 @@ struct JitcodeMapBufferWriteSpewer
|
||||
startPos = writer->length();
|
||||
}
|
||||
#else // !DEBUG
|
||||
JitcodeMapBufferWriteSpewer(CompactBufferWriter& w) {}
|
||||
explicit JitcodeMapBufferWriteSpewer(CompactBufferWriter& w) {}
|
||||
void spewAndAdvance(const char* name) {}
|
||||
#endif // DEBUG
|
||||
};
|
||||
@@ -1521,17 +1545,46 @@ JitcodeIonTable::WriteIonTable(CompactBufferWriter& writer,
|
||||
} // namespace jit
|
||||
} // namespace js
|
||||
|
||||
|
||||
JS_PUBLIC_API(JS::ProfilingFrameIterator::FrameKind)
|
||||
JS::GetProfilingFrameKindFromNativeAddr(JSRuntime* rt, void* addr)
|
||||
JS::ForEachProfiledFrameOp::FrameHandle::FrameHandle(JSRuntime* rt, JitcodeGlobalEntry& entry,
|
||||
void* addr, const char* label, uint32_t depth)
|
||||
: rt_(rt),
|
||||
entry_(entry),
|
||||
addr_(addr),
|
||||
canonicalAddr_(nullptr),
|
||||
label_(label),
|
||||
depth_(depth)
|
||||
{
|
||||
JitcodeGlobalTable* table = rt->jitRuntime()->getJitcodeGlobalTable();
|
||||
JitcodeGlobalEntry entry;
|
||||
table->lookupInfallible(addr, &entry, rt);
|
||||
MOZ_ASSERT(entry.isIon() || entry.isIonCache() || entry.isBaseline());
|
||||
updateHasTrackedOptimizations();
|
||||
|
||||
if (entry.isBaseline())
|
||||
if (!canonicalAddr_) {
|
||||
// If the entry has tracked optimizations, updateHasTrackedOptimizations
|
||||
// would have updated the canonical address.
|
||||
MOZ_ASSERT_IF(entry_.isIon(), !hasTrackedOptimizations());
|
||||
canonicalAddr_ = entry_.canonicalNativeAddrFor(rt_, addr_);
|
||||
}
|
||||
}
|
||||
|
||||
JS::ProfilingFrameIterator::FrameKind
|
||||
JS::ForEachProfiledFrameOp::FrameHandle::frameKind() const
|
||||
{
|
||||
if (entry_.isBaseline())
|
||||
return JS::ProfilingFrameIterator::Frame_Baseline;
|
||||
|
||||
return JS::ProfilingFrameIterator::Frame_Ion;
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS::ForEachProfiledFrame(JSRuntime* rt, void* addr, ForEachProfiledFrameOp& op)
|
||||
{
|
||||
js::jit::JitcodeGlobalTable* table = rt->jitRuntime()->getJitcodeGlobalTable();
|
||||
js::jit::JitcodeGlobalEntry entry;
|
||||
table->lookupInfallible(addr, &entry, rt);
|
||||
|
||||
// Extract the stack for the entry. Assume maximum inlining depth is <64
|
||||
const char* labels[64];
|
||||
uint32_t depth = entry.callStackAtAddr(rt, addr, labels, 64);
|
||||
MOZ_ASSERT(depth < 64);
|
||||
for (uint32_t i = depth; i != 0; i--) {
|
||||
JS::ForEachProfiledFrameOp::FrameHandle handle(rt, entry, addr, labels[i - 1], i - 1);
|
||||
op(handle);
|
||||
}
|
||||
}
|
||||
|
||||
+43
-17
@@ -318,20 +318,22 @@ class JitcodeGlobalEntry
|
||||
return -1;
|
||||
}
|
||||
|
||||
void* canonicalNativeAddrFor(JSRuntime*rt, void* ptr) const;
|
||||
|
||||
bool callStackAtAddr(JSRuntime* rt, void* ptr, BytecodeLocationVector& results,
|
||||
uint32_t* depth) const;
|
||||
|
||||
uint32_t callStackAtAddr(JSRuntime* rt, void* ptr, const char** results,
|
||||
uint32_t maxResults) const;
|
||||
|
||||
void youngestFrameLocationAtAddr(JSRuntime *rt, void *ptr,
|
||||
JSScript **script, jsbytecode **pc) const;
|
||||
void youngestFrameLocationAtAddr(JSRuntime* rt, void* ptr,
|
||||
JSScript** script, jsbytecode** pc) const;
|
||||
|
||||
bool hasTrackedOptimizations() const {
|
||||
return !!optsRegionTable_;
|
||||
}
|
||||
|
||||
const IonTrackedOptimizationsRegionTable *trackedOptimizationsRegionTable() const {
|
||||
const IonTrackedOptimizationsRegionTable* trackedOptimizationsRegionTable() const {
|
||||
MOZ_ASSERT(hasTrackedOptimizations());
|
||||
return optsRegionTable_;
|
||||
}
|
||||
@@ -356,9 +358,9 @@ class JitcodeGlobalEntry
|
||||
return optsAllTypes_;
|
||||
}
|
||||
|
||||
mozilla::Maybe<uint8_t> trackedOptimizationIndexAtAddr(void *ptr, uint32_t *entryOffsetOut);
|
||||
mozilla::Maybe<uint8_t> trackedOptimizationIndexAtAddr(void* ptr, uint32_t* entryOffsetOut);
|
||||
|
||||
bool markIfUnmarked(JSTracer *trc);
|
||||
bool markIfUnmarked(JSTracer* trc);
|
||||
void sweep();
|
||||
bool isMarkedFromAnyThread();
|
||||
};
|
||||
@@ -374,8 +376,8 @@ class JitcodeGlobalEntry
|
||||
jsbytecode* ionAbortPc_;
|
||||
const char* ionAbortMessage_;
|
||||
|
||||
void init(JitCode *code, void *nativeStartAddr, void *nativeEndAddr,
|
||||
JSScript *script, const char *str)
|
||||
void init(JitCode* code, void* nativeStartAddr, void* nativeEndAddr,
|
||||
JSScript* script, const char* str)
|
||||
{
|
||||
MOZ_ASSERT(script != nullptr);
|
||||
BaseEntry::init(Baseline, code, nativeStartAddr, nativeEndAddr);
|
||||
@@ -405,16 +407,18 @@ class JitcodeGlobalEntry
|
||||
|
||||
void destroy();
|
||||
|
||||
void* canonicalNativeAddrFor(JSRuntime* rt, void* ptr) const;
|
||||
|
||||
bool callStackAtAddr(JSRuntime* rt, void* ptr, BytecodeLocationVector& results,
|
||||
uint32_t* depth) const;
|
||||
|
||||
uint32_t callStackAtAddr(JSRuntime* rt, void* ptr, const char** results,
|
||||
uint32_t maxResults) const;
|
||||
|
||||
void youngestFrameLocationAtAddr(JSRuntime *rt, void *ptr,
|
||||
JSScript **script, jsbytecode **pc) const;
|
||||
void youngestFrameLocationAtAddr(JSRuntime* rt, void* ptr,
|
||||
JSScript** script, jsbytecode** pc) const;
|
||||
|
||||
bool markIfUnmarked(JSTracer *trc);
|
||||
bool markIfUnmarked(JSTracer* trc);
|
||||
void sweep();
|
||||
bool isMarkedFromAnyThread();
|
||||
};
|
||||
@@ -423,8 +427,8 @@ class JitcodeGlobalEntry
|
||||
{
|
||||
void* rejoinAddr_;
|
||||
|
||||
void init(JitCode *code, void *nativeStartAddr, void *nativeEndAddr,
|
||||
void *rejoinAddr)
|
||||
void init(JitCode* code, void* nativeStartAddr, void* nativeEndAddr,
|
||||
void* rejoinAddr)
|
||||
{
|
||||
MOZ_ASSERT(rejoinAddr != nullptr);
|
||||
BaseEntry::init(IonCache, code, nativeStartAddr, nativeEndAddr);
|
||||
@@ -437,6 +441,8 @@ class JitcodeGlobalEntry
|
||||
|
||||
void destroy() {}
|
||||
|
||||
void* canonicalNativeAddrFor(JSRuntime* rt, void* ptr) const;
|
||||
|
||||
bool callStackAtAddr(JSRuntime* rt, void* ptr, BytecodeLocationVector& results,
|
||||
uint32_t* depth) const;
|
||||
|
||||
@@ -462,6 +468,10 @@ class JitcodeGlobalEntry
|
||||
|
||||
void destroy() {}
|
||||
|
||||
void* canonicalNativeAddrFor(JSRuntime* rt, void* ptr) const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool callStackAtAddr(JSRuntime* rt, void* ptr, BytecodeLocationVector& results,
|
||||
uint32_t* depth) const
|
||||
{
|
||||
@@ -529,31 +539,31 @@ class JitcodeGlobalEntry
|
||||
base_.init();
|
||||
}
|
||||
|
||||
explicit JitcodeGlobalEntry(const IonEntry &ion)
|
||||
explicit JitcodeGlobalEntry(const IonEntry& ion)
|
||||
: tower_(nullptr)
|
||||
{
|
||||
ion_ = ion;
|
||||
}
|
||||
|
||||
explicit JitcodeGlobalEntry(const BaselineEntry &baseline)
|
||||
explicit JitcodeGlobalEntry(const BaselineEntry& baseline)
|
||||
: tower_(nullptr)
|
||||
{
|
||||
baseline_ = baseline;
|
||||
}
|
||||
|
||||
explicit JitcodeGlobalEntry(const IonCacheEntry &ionCache)
|
||||
explicit JitcodeGlobalEntry(const IonCacheEntry& ionCache)
|
||||
: tower_(nullptr)
|
||||
{
|
||||
ionCache_ = ionCache;
|
||||
}
|
||||
|
||||
explicit JitcodeGlobalEntry(const DummyEntry &dummy)
|
||||
explicit JitcodeGlobalEntry(const DummyEntry& dummy)
|
||||
: tower_(nullptr)
|
||||
{
|
||||
dummy_ = dummy;
|
||||
}
|
||||
|
||||
explicit JitcodeGlobalEntry(const QueryEntry &query)
|
||||
explicit JitcodeGlobalEntry(const QueryEntry& query)
|
||||
: tower_(nullptr)
|
||||
{
|
||||
query_ = query;
|
||||
@@ -702,6 +712,22 @@ class JitcodeGlobalEntry
|
||||
return query_;
|
||||
}
|
||||
|
||||
void* canonicalNativeAddrFor(JSRuntime* rt, void* ptr) const {
|
||||
switch (kind()) {
|
||||
case Ion:
|
||||
return ionEntry().canonicalNativeAddrFor(rt, ptr);
|
||||
case Baseline:
|
||||
return baselineEntry().canonicalNativeAddrFor(rt, ptr);
|
||||
case IonCache:
|
||||
return ionCacheEntry().canonicalNativeAddrFor(rt, ptr);
|
||||
case Dummy:
|
||||
return dummyEntry().canonicalNativeAddrFor(rt, ptr);
|
||||
default:
|
||||
MOZ_CRASH("Invalid JitcodeGlobalEntry kind.");
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Read the inline call stack at a given point in the native code and append into
|
||||
// the given vector. Innermost (script,pc) pair will be appended first, and
|
||||
// outermost appended last.
|
||||
|
||||
@@ -1132,30 +1132,20 @@ IonBuilder::trackInlineSuccessUnchecked(InliningStatus status)
|
||||
trackOptimizationOutcome(TrackedOutcome::Inlined);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS::ForEachTrackedOptimizationAttempt(JSRuntime* rt, void* addr, uint8_t index,
|
||||
ForEachTrackedOptimizationAttemptOp& op,
|
||||
JSScript** scriptOut, jsbytecode** pcOut)
|
||||
{
|
||||
JitcodeGlobalTable* table = rt->jitRuntime()->getJitcodeGlobalTable();
|
||||
JitcodeGlobalEntry entry;
|
||||
table->lookupInfallible(addr, &entry, rt);
|
||||
entry.youngestFrameLocationAtAddr(rt, addr, scriptOut, pcOut);
|
||||
entry.trackedOptimizationAttempts(index).forEach(op);
|
||||
}
|
||||
|
||||
static void
|
||||
InterpretedFunctionFilenameAndLineNumber(JSFunction* fun, const char** filename, unsigned* lineno)
|
||||
InterpretedFunctionFilenameAndLineNumber(JSFunction* fun, const char** filename,
|
||||
Maybe<unsigned>* lineno)
|
||||
{
|
||||
ScriptSource* source;
|
||||
if (fun->hasScript()) {
|
||||
source = fun->nonLazyScript()->maybeForwardedScriptSource();
|
||||
*lineno = fun->nonLazyScript()->lineno();
|
||||
*filename = fun->nonLazyScript()->maybeForwardedScriptSource()->filename();
|
||||
*lineno = Some((unsigned) fun->nonLazyScript()->lineno());
|
||||
} else if (fun->lazyScriptOrNull()) {
|
||||
*filename = fun->lazyScript()->maybeForwardedScriptSource()->filename();
|
||||
*lineno = Some((unsigned) fun->lazyScript()->lineno());
|
||||
} else {
|
||||
source = fun->lazyScript()->maybeForwardedScriptSource();
|
||||
*lineno = fun->lazyScript()->lineno();
|
||||
*filename = "(self-hosted builtin)";
|
||||
*lineno = Nothing();
|
||||
}
|
||||
*filename = source->filename();
|
||||
}
|
||||
|
||||
static JSFunction*
|
||||
@@ -1180,7 +1170,7 @@ IonTrackedOptimizationsTypeInfo::ForEachOpAdapter::readType(const IonTrackedType
|
||||
TypeSet::Type ty = tracked.type;
|
||||
|
||||
if (ty.isPrimitive() || ty.isUnknown() || ty.isAnyObject()) {
|
||||
op_.readType("primitive", TypeSet::NonObjectTypeString(ty), nullptr, 0);
|
||||
op_.readType("primitive", TypeSet::NonObjectTypeString(ty), nullptr, Nothing());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1188,9 +1178,18 @@ IonTrackedOptimizationsTypeInfo::ForEachOpAdapter::readType(const IonTrackedType
|
||||
const uint32_t bufsize = mozilla::ArrayLength(buf);
|
||||
|
||||
if (JSFunction* fun = FunctionFromTrackedType(tracked)) {
|
||||
// The displayAtom is useful for identifying both native and
|
||||
// interpreted functions.
|
||||
char* name = nullptr;
|
||||
if (fun->displayAtom()) {
|
||||
PutEscapedString(buf, bufsize, fun->displayAtom(), 0);
|
||||
name = buf;
|
||||
}
|
||||
|
||||
if (fun->isNative()) {
|
||||
//
|
||||
// Print out the absolute address of the function pointer.
|
||||
// Try printing out the displayAtom of the native function and the
|
||||
// absolute address of the native function pointer.
|
||||
//
|
||||
// Note that this address is not usable without knowing the
|
||||
// starting address at which our shared library is loaded. Shared
|
||||
@@ -1207,20 +1206,20 @@ IonTrackedOptimizationsTypeInfo::ForEachOpAdapter::readType(const IonTrackedType
|
||||
// if (dladdr(addr, &info) != 0)
|
||||
// offset = uintptr_t(addr) - uintptr_t(info.dli_fbase);
|
||||
//
|
||||
uintptr_t addr = JS_FUNC_TO_DATA_PTR(uintptr_t, fun->native());
|
||||
JS_snprintf(buf, bufsize, "%llx", addr);
|
||||
op_.readType("native", nullptr, buf, UINT32_MAX);
|
||||
char locationBuf[20];
|
||||
if (!name) {
|
||||
uintptr_t addr = JS_FUNC_TO_DATA_PTR(uintptr_t, fun->native());
|
||||
JS_snprintf(locationBuf, mozilla::ArrayLength(locationBuf), "%llx", addr);
|
||||
}
|
||||
op_.readType("native", name, name ? nullptr : locationBuf, Nothing());
|
||||
return;
|
||||
}
|
||||
|
||||
if (fun->displayAtom())
|
||||
PutEscapedString(buf, bufsize, fun->displayAtom(), 0);
|
||||
const char* filename;
|
||||
unsigned lineno;
|
||||
Maybe<unsigned> lineno;
|
||||
InterpretedFunctionFilenameAndLineNumber(fun, &filename, &lineno);
|
||||
op_.readType(tracked.constructor ? "constructor" : "function",
|
||||
fun->displayAtom() ? buf : nullptr,
|
||||
filename, lineno);
|
||||
name, filename, lineno);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1231,16 +1230,16 @@ IonTrackedOptimizationsTypeInfo::ForEachOpAdapter::readType(const IonTrackedType
|
||||
JSScript* script = tracked.script;
|
||||
op_.readType("alloc site", buf,
|
||||
script->maybeForwardedScriptSource()->filename(),
|
||||
PCToLineNumber(script, script->offsetToPC(tracked.offset)));
|
||||
Some(PCToLineNumber(script, script->offsetToPC(tracked.offset))));
|
||||
return;
|
||||
}
|
||||
|
||||
if (ty.isGroup()) {
|
||||
op_.readType("prototype", buf, nullptr, UINT32_MAX);
|
||||
op_.readType("prototype", buf, nullptr, Nothing());
|
||||
return;
|
||||
}
|
||||
|
||||
op_.readType("singleton", buf, nullptr, UINT32_MAX);
|
||||
op_.readType("singleton", buf, nullptr, Nothing());
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1250,28 +1249,35 @@ IonTrackedOptimizationsTypeInfo::ForEachOpAdapter::operator()(JS::TrackedTypeSit
|
||||
op_(site, StringFromMIRType(mirType));
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS::ForEachTrackedOptimizationTypeInfo(JSRuntime* rt, void* addr, uint8_t index,
|
||||
ForEachTrackedOptimizationTypeInfoOp& op)
|
||||
typedef JS::ForEachProfiledFrameOp::FrameHandle FrameHandle;
|
||||
|
||||
void
|
||||
FrameHandle::updateHasTrackedOptimizations()
|
||||
{
|
||||
JitcodeGlobalTable* table = rt->jitRuntime()->getJitcodeGlobalTable();
|
||||
JitcodeGlobalEntry entry;
|
||||
table->lookupInfallible(addr, &entry, rt);
|
||||
IonTrackedOptimizationsTypeInfo::ForEachOpAdapter adapter(op);
|
||||
entry.trackedOptimizationTypeInfo(index).forEach(adapter, entry.allTrackedTypes());
|
||||
// All inlined frames will have the same optimization information by
|
||||
// virtue of sharing the JitcodeGlobalEntry, but such information is
|
||||
// only interpretable on the youngest frame.
|
||||
if (depth() != 0)
|
||||
return;
|
||||
if (!entry_.hasTrackedOptimizations())
|
||||
return;
|
||||
uint32_t entryOffset;
|
||||
optsIndex_ = entry_.trackedOptimizationIndexAtAddr(addr_, &entryOffset);
|
||||
if (optsIndex_.isSome())
|
||||
canonicalAddr_ = (void*)(((uint8_t*) entry_.nativeStartAddr()) + entryOffset);
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(Maybe<uint8_t>)
|
||||
JS::TrackedOptimizationIndexAtAddr(JSRuntime* rt, void* addr, void** entryAddr)
|
||||
void
|
||||
FrameHandle::forEachOptimizationAttempt(ForEachTrackedOptimizationAttemptOp& op,
|
||||
JSScript** scriptOut, jsbytecode** pcOut) const
|
||||
{
|
||||
JitcodeGlobalTable* table = rt->jitRuntime()->getJitcodeGlobalTable();
|
||||
JitcodeGlobalEntry entry;
|
||||
table->lookupInfallible(addr, &entry, rt);
|
||||
if (!entry.hasTrackedOptimizations())
|
||||
return Nothing();
|
||||
uint32_t entryOffset = 0;
|
||||
Maybe<uint8_t> index = entry.trackedOptimizationIndexAtAddr(addr, &entryOffset);
|
||||
if (index.isSome())
|
||||
*entryAddr = (void*)(((uint8_t*) entry.nativeStartAddr()) + entryOffset);
|
||||
return index;
|
||||
entry_.trackedOptimizationAttempts(*optsIndex_).forEach(op);
|
||||
entry_.youngestFrameLocationAtAddr(rt_, addr_, scriptOut, pcOut);
|
||||
}
|
||||
|
||||
void
|
||||
FrameHandle::forEachOptimizationTypeInfo(ForEachTrackedOptimizationTypeInfoOp& op) const
|
||||
{
|
||||
IonTrackedOptimizationsTypeInfo::ForEachOpAdapter adapter(op);
|
||||
entry_.trackedOptimizationTypeInfo(*optsIndex_).forEach(adapter, entry_.allTrackedTypes());
|
||||
}
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
# error "Wrong architecture. Only x86 and x64 should build this file!"
|
||||
#endif
|
||||
|
||||
#include "jit/RegisterSets.h"
|
||||
|
||||
const char*
|
||||
FloatRegister::name() const {
|
||||
js::jit::FloatRegister::name() const {
|
||||
static const char* const names[] = {
|
||||
|
||||
#ifdef JS_CODEGEN_X64
|
||||
@@ -38,8 +40,8 @@ FloatRegister::name() const {
|
||||
return names[size_t(code())];
|
||||
}
|
||||
|
||||
FloatRegisterSet
|
||||
FloatRegister::ReduceSetForPush(const FloatRegisterSet& s)
|
||||
js::jit::FloatRegisterSet
|
||||
js::jit::FloatRegister::ReduceSetForPush(const FloatRegisterSet& s)
|
||||
{
|
||||
SetType bits = s.bits();
|
||||
|
||||
@@ -58,7 +60,7 @@ FloatRegister::ReduceSetForPush(const FloatRegisterSet& s)
|
||||
}
|
||||
|
||||
uint32_t
|
||||
FloatRegister::GetPushSizeInBytes(const FloatRegisterSet& s)
|
||||
js::jit::FloatRegister::GetPushSizeInBytes(const FloatRegisterSet& s)
|
||||
{
|
||||
SetType all = s.bits();
|
||||
SetType float32x4Set =
|
||||
@@ -92,7 +94,7 @@ FloatRegister::GetPushSizeInBytes(const FloatRegisterSet& s)
|
||||
+ count32b * sizeof(float);
|
||||
}
|
||||
uint32_t
|
||||
FloatRegister::getRegisterDumpOffsetInBytes()
|
||||
js::jit::FloatRegister::getRegisterDumpOffsetInBytes()
|
||||
{
|
||||
return uint32_t(encoding()) * sizeof(FloatRegisters::RegisterContent);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
# error "Unsupported architecture!"
|
||||
#endif
|
||||
|
||||
#include "mozilla/MathAlgorithms.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
#include "jit/x86-shared/Constants-x86-shared.h"
|
||||
|
||||
namespace js {
|
||||
|
||||
@@ -7,6 +7,11 @@
|
||||
#ifndef jit_x86_shared_Constants_x86_shared_h
|
||||
#define jit_x86_shared_Constants_x86_shared_h
|
||||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
#include "mozilla/Assertions.h"
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace js {
|
||||
namespace jit {
|
||||
|
||||
|
||||
+32
-32
@@ -278,7 +278,7 @@ extern JS_EXPORT_API(void) add_history(char* line);
|
||||
#endif
|
||||
|
||||
static char*
|
||||
GetLine(FILE* file, const char * prompt)
|
||||
GetLine(FILE* file, const char* prompt)
|
||||
{
|
||||
size_t size;
|
||||
char* buffer;
|
||||
@@ -428,7 +428,7 @@ SkipUTF8BOM(FILE* file)
|
||||
}
|
||||
|
||||
static void
|
||||
RunFile(JSContext *cx, const char *filename, FILE *file, bool compileOnly)
|
||||
RunFile(JSContext* cx, const char* filename, FILE* file, bool compileOnly)
|
||||
{
|
||||
SkipUTF8BOM(file);
|
||||
|
||||
@@ -475,8 +475,8 @@ RunFile(JSContext *cx, const char *filename, FILE *file, bool compileOnly)
|
||||
}
|
||||
|
||||
static bool
|
||||
EvalAndPrint(JSContext *cx, const char *bytes, size_t length,
|
||||
int lineno, bool compileOnly, FILE *out)
|
||||
EvalAndPrint(JSContext* cx, const char* bytes, size_t length,
|
||||
int lineno, bool compileOnly, FILE* out)
|
||||
{
|
||||
// Eval.
|
||||
JS::CompileOptions options(cx);
|
||||
@@ -510,7 +510,7 @@ EvalAndPrint(JSContext *cx, const char *bytes, size_t length,
|
||||
}
|
||||
|
||||
static void
|
||||
ReadEvalPrintLoop(JSContext *cx, FILE *in, FILE *out, bool compileOnly)
|
||||
ReadEvalPrintLoop(JSContext* cx, FILE* in, FILE* out, bool compileOnly)
|
||||
{
|
||||
int lineno = 1;
|
||||
bool hitEOF = false;
|
||||
@@ -577,7 +577,7 @@ class AutoCloseInputFile
|
||||
};
|
||||
|
||||
static void
|
||||
Process(JSContext *cx, const char *filename, bool forceTTY)
|
||||
Process(JSContext* cx, const char* filename, bool forceTTY)
|
||||
{
|
||||
FILE* file;
|
||||
if (forceTTY || !filename || strcmp(filename, "-") == 0) {
|
||||
@@ -2757,7 +2757,7 @@ struct WorkerInput
|
||||
}
|
||||
};
|
||||
|
||||
static void SetWorkerRuntimeOptions(JSRuntime *rt);
|
||||
static void SetWorkerRuntimeOptions(JSRuntime* rt);
|
||||
|
||||
static void
|
||||
WorkerMain(void* arg)
|
||||
@@ -2850,14 +2850,14 @@ EvalInWorker(JSContext* cx, unsigned argc, jsval* vp)
|
||||
}
|
||||
|
||||
static bool
|
||||
ShapeOf(JSContext *cx, unsigned argc, JS::Value *vp)
|
||||
ShapeOf(JSContext* cx, unsigned argc, JS::Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
if (!args.get(0).isObject()) {
|
||||
JS_ReportError(cx, "shapeOf: object expected");
|
||||
return false;
|
||||
}
|
||||
JSObject *obj = &args[0].toObject();
|
||||
JSObject* obj = &args[0].toObject();
|
||||
args.rval().set(JS_NumberValue(double(uintptr_t(obj->maybeShape()) >> 3)));
|
||||
return true;
|
||||
}
|
||||
@@ -2874,7 +2874,7 @@ IsBefore(int64_t t1, int64_t t2)
|
||||
}
|
||||
|
||||
static bool
|
||||
Sleep_fn(JSContext *cx, unsigned argc, Value *vp)
|
||||
Sleep_fn(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
int64_t t_ticks;
|
||||
@@ -4305,17 +4305,17 @@ SetSharedArrayBuffer(JSContext* cx, unsigned argc, Value* vp)
|
||||
|
||||
class SprintOptimizationTypeInfoOp : public ForEachTrackedOptimizationTypeInfoOp
|
||||
{
|
||||
Sprinter *sp;
|
||||
Sprinter* sp;
|
||||
bool startedTypes_;
|
||||
|
||||
public:
|
||||
explicit SprintOptimizationTypeInfoOp(Sprinter *sp)
|
||||
explicit SprintOptimizationTypeInfoOp(Sprinter* sp)
|
||||
: sp(sp),
|
||||
startedTypes_(false)
|
||||
{ }
|
||||
|
||||
void readType(const char *keyedBy, const char *name,
|
||||
const char *location, unsigned lineno) override
|
||||
void readType(const char* keyedBy, const char* name,
|
||||
const char* location, Maybe<unsigned> lineno) override
|
||||
{
|
||||
if (!startedTypes_) {
|
||||
startedTypes_ = true;
|
||||
@@ -4329,12 +4329,12 @@ class SprintOptimizationTypeInfoOp : public ForEachTrackedOptimizationTypeInfoOp
|
||||
PutEscapedString(buf, mozilla::ArrayLength(buf), location, strlen(location), '"');
|
||||
Sprint(sp, ",\"location\":%s", buf);
|
||||
}
|
||||
if (lineno != UINT32_MAX)
|
||||
Sprint(sp, ",\"line\":%u", lineno);
|
||||
if (lineno.isSome())
|
||||
Sprint(sp, ",\"line\":%u", *lineno);
|
||||
Sprint(sp, "},");
|
||||
}
|
||||
|
||||
void operator()(TrackedTypeSite site, const char *mirType) override {
|
||||
void operator()(TrackedTypeSite site, const char* mirType) override {
|
||||
if (startedTypes_) {
|
||||
// Clear trailing ,
|
||||
if ((*sp)[sp->getOffset() - 1] == ',')
|
||||
@@ -4352,10 +4352,10 @@ class SprintOptimizationTypeInfoOp : public ForEachTrackedOptimizationTypeInfoOp
|
||||
|
||||
class SprintOptimizationAttemptsOp : public ForEachTrackedOptimizationAttemptOp
|
||||
{
|
||||
Sprinter *sp;
|
||||
Sprinter* sp;
|
||||
|
||||
public:
|
||||
explicit SprintOptimizationAttemptsOp(Sprinter *sp)
|
||||
explicit SprintOptimizationAttemptsOp(Sprinter* sp)
|
||||
: sp(sp)
|
||||
{ }
|
||||
|
||||
@@ -4366,11 +4366,11 @@ class SprintOptimizationAttemptsOp : public ForEachTrackedOptimizationAttemptOp
|
||||
};
|
||||
|
||||
static bool
|
||||
ReflectTrackedOptimizations(JSContext *cx, unsigned argc, Value *vp)
|
||||
ReflectTrackedOptimizations(JSContext* cx, unsigned argc, Value* vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
RootedObject callee(cx, &args.callee());
|
||||
JSRuntime *rt = cx->runtime();
|
||||
JSRuntime* rt = cx->runtime();
|
||||
|
||||
if (!rt->hasJitRuntime() || !rt->jitRuntime()->isOptimizationTrackingEnabled(rt)) {
|
||||
JS_ReportError(cx, "Optimization tracking is off.");
|
||||
@@ -4393,13 +4393,13 @@ ReflectTrackedOptimizations(JSContext *cx, unsigned argc, Value *vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
jit::JitcodeGlobalTable *table = rt->jitRuntime()->getJitcodeGlobalTable();
|
||||
jit::JitcodeGlobalTable* table = rt->jitRuntime()->getJitcodeGlobalTable();
|
||||
jit::JitcodeGlobalEntry entry;
|
||||
jit::IonScript *ion = fun->nonLazyScript()->ionScript();
|
||||
jit::IonScript* ion = fun->nonLazyScript()->ionScript();
|
||||
table->lookupInfallible(ion->method()->raw(), &entry, rt);
|
||||
|
||||
if (!entry.hasTrackedOptimizations()) {
|
||||
JSObject *obj = JS_NewPlainObject(cx);
|
||||
JSObject* obj = JS_NewPlainObject(cx);
|
||||
if (!obj)
|
||||
return false;
|
||||
args.rval().setObject(*obj);
|
||||
@@ -4421,14 +4421,14 @@ ReflectTrackedOptimizations(JSContext *cx, unsigned argc, Value *vp)
|
||||
uint32_t startOffset, endOffset;
|
||||
uint8_t index;
|
||||
iter.readNext(&startOffset, &endOffset, &index);
|
||||
JSScript *script;
|
||||
jsbytecode *pc;
|
||||
JSScript* script;
|
||||
jsbytecode* pc;
|
||||
// Use endOffset, as startOffset may be associated with a
|
||||
// previous, adjacent region ending exactly at startOffset. That
|
||||
// is, suppose we have two regions [0, startOffset], [startOffset,
|
||||
// endOffset]. Since we are not querying a return address, we want
|
||||
// the second region and not the first.
|
||||
uint8_t *addr = ion->method()->raw() + endOffset;
|
||||
uint8_t* addr = ion->method()->raw() + endOffset;
|
||||
entry.youngestFrameLocationAtAddr(rt, addr, &script, &pc);
|
||||
Sprint(&sp, "{\"location\":\"%s:%u\",\"offset\":%u,\"index\":%u}%s",
|
||||
script->filename(), script->lineno(), script->pcToOffset(pc), index,
|
||||
@@ -5662,7 +5662,7 @@ NewGlobalObject(JSContext* cx, JS::CompartmentOptions& options,
|
||||
}
|
||||
|
||||
static bool
|
||||
BindScriptArgs(JSContext *cx, OptionParser *op)
|
||||
BindScriptArgs(JSContext* cx, OptionParser* op)
|
||||
{
|
||||
MultiStringRange msr = op->getMultiStringArg("scriptArgs");
|
||||
RootedObject scriptArgs(cx);
|
||||
@@ -5694,7 +5694,7 @@ OptionFailure(const char* option, const char* str)
|
||||
}
|
||||
|
||||
static int
|
||||
ProcessArgs(JSContext *cx, OptionParser *op)
|
||||
ProcessArgs(JSContext* cx, OptionParser* op)
|
||||
{
|
||||
if (op->getBoolOption('s'))
|
||||
JS::RuntimeOptionsRef(cx).toggleExtraWarnings();
|
||||
@@ -5834,7 +5834,7 @@ SetRuntimeOptions(JSRuntime* rt, const OptionParser& op)
|
||||
if (op.getBoolOption("ion-extra-checks"))
|
||||
jit::js_JitOptions.runExtraChecks = true;
|
||||
|
||||
if (const char *str = op.getStringOption("ion-inlining")) {
|
||||
if (const char* str = op.getStringOption("ion-inlining")) {
|
||||
if (strcmp(str, "on") == 0)
|
||||
jit::js_JitOptions.disableInlining = false;
|
||||
else if (strcmp(str, "off") == 0)
|
||||
@@ -5943,7 +5943,7 @@ SetRuntimeOptions(JSRuntime* rt, const OptionParser& op)
|
||||
#endif
|
||||
|
||||
#ifdef JS_GC_ZEAL
|
||||
const char *zealStr = op.getStringOption("gc-zeal");
|
||||
const char* zealStr = op.getStringOption("gc-zeal");
|
||||
gZealStr[0] = 0;
|
||||
if (zealStr) {
|
||||
if (!rt->gc.parseAndSetZeal(zealStr))
|
||||
@@ -5957,7 +5957,7 @@ SetRuntimeOptions(JSRuntime* rt, const OptionParser& op)
|
||||
}
|
||||
|
||||
static void
|
||||
SetWorkerRuntimeOptions(JSRuntime *rt)
|
||||
SetWorkerRuntimeOptions(JSRuntime* rt)
|
||||
{
|
||||
// Copy option values from the main thread.
|
||||
JS::RuntimeOptionsRef(rt).setBaseline(enableBaseline)
|
||||
|
||||
@@ -1959,22 +1959,3 @@ JS::ProfilingFrameIterator::isJit() const
|
||||
{
|
||||
return activation_->isJit();
|
||||
}
|
||||
|
||||
JS_PUBLIC_API(void)
|
||||
JS::ForEachProfiledFrame(JSRuntime* rt, void* addr, ForEachProfiledFrameOp& op)
|
||||
{
|
||||
jit::JitcodeGlobalTable* table = rt->jitRuntime()->getJitcodeGlobalTable();
|
||||
jit::JitcodeGlobalEntry entry;
|
||||
table->lookupInfallible(addr, &entry, rt);
|
||||
|
||||
// Extract the stack for the entry. Assume maximum inlining depth is <64
|
||||
const char* labels[64];
|
||||
uint32_t depth = entry.callStackAtAddr(rt, addr, labels, 64);
|
||||
MOZ_ASSERT(depth < 64);
|
||||
for (uint32_t i = depth; i != 0; i--) {
|
||||
// All inlined frames will have the same optimization information by
|
||||
// virtue of sharing the JitcodeGlobalEntry, but such information is
|
||||
// only interpretable on the youngest frame.
|
||||
op(labels[i - 1], i == 1 && entry.hasTrackedOptimizations());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,7 +331,7 @@ __FBSDID("$FreeBSD: head/lib/libc/stdlib/malloc.c 180599 2008-07-18 19:35:44Z ja
|
||||
#endif
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#if !defined(MOZ_MEMORY_SOLARIS) && !defined(MOZ_MEMORY_ANDROID)
|
||||
#if !defined(MOZ_MEMORY_SOLARIS) && !defined(MOZ_MEMORY_ANDROID) && !defined(MOZ_MEMORY_LINUX)
|
||||
#include <sys/sysctl.h>
|
||||
#endif
|
||||
#include <sys/uio.h>
|
||||
|
||||
+1
-1
@@ -370,7 +370,7 @@ public:
|
||||
void reset()
|
||||
{
|
||||
if (isSome()) {
|
||||
ref().~T();
|
||||
ref().T::~T();
|
||||
mIsSome = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -803,6 +803,31 @@ TestComparisonOperators()
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check that Maybe<> can wrap a superclass that happens to also be a concrete
|
||||
// class (i.e. that the compiler doesn't warn when we invoke the superclass's
|
||||
// destructor explicitly in |reset()|.
|
||||
class MySuperClass {
|
||||
virtual void VirtualMethod() { /* do nothing */ }
|
||||
};
|
||||
|
||||
class MyDerivedClass : public MySuperClass {
|
||||
void VirtualMethod() override { /* do nothing */ }
|
||||
};
|
||||
|
||||
static bool
|
||||
TestVirtualFunction() {
|
||||
Maybe<MySuperClass> super;
|
||||
super.emplace();
|
||||
super.reset();
|
||||
|
||||
Maybe<MyDerivedClass> derived;
|
||||
derived.emplace();
|
||||
derived.reset();
|
||||
|
||||
// If this compiles successfully, we've passed.
|
||||
return true;
|
||||
}
|
||||
|
||||
int
|
||||
main()
|
||||
{
|
||||
@@ -813,6 +838,7 @@ main()
|
||||
RUN_TEST(TestMap);
|
||||
RUN_TEST(TestToMaybe);
|
||||
RUN_TEST(TestComparisonOperators);
|
||||
RUN_TEST(TestVirtualFunction);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -365,6 +365,7 @@ function _setupDebuggerServer(breakpointFiles, callback) {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
DebuggerServer.addActors("resource://testing-common/dbg-actors.js");
|
||||
DebuggerServer.allowChromeProcess = true;
|
||||
|
||||
// An observer notification that tells us when we can "resume" script
|
||||
// execution.
|
||||
|
||||
@@ -5434,6 +5434,76 @@
|
||||
"n_buckets": "50",
|
||||
"description": "The total number of distinct attempts by third-party sites to place cookies which have been rejected. Measures are normalized per 24h."
|
||||
},
|
||||
"DEVTOOLS_OS_ENUMERATED_PER_USER": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 13,
|
||||
"description": "OS of DevTools user (0:Windows XP, 1:Windows Vista, 2:Windows 7, 3:Windows 8, 4:Windows 8.1, 5:OSX, 6:Linux 7:reserved, 8:reserved, 9:reserved, 10:reserved, 11:reserved, 12:other)"
|
||||
},
|
||||
"DEVTOOLS_OS_IS_64_BITS_PER_USER": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 3,
|
||||
"description": "OS bit size of DevTools user (0:32bit, 1:64bit, 2:128bit)"
|
||||
},
|
||||
"DEVTOOLS_SCREEN_RESOLUTION_ENUMERATED_PER_USER": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "enumerated",
|
||||
"n_values": 13,
|
||||
"description": "Screen resolution of DevTools user (0:lower, 1:800x600, 2:1024x768, 3:1280x800, 4:1280x1024, 5:1366x768, 6:1440x900, 7:1920x1080, 8:2560×1440, 9:2560×1600, 10:2880x1800, 11:other, 12:higher)"
|
||||
},
|
||||
"DEVTOOLS_TOOLBOX_OPENED_BOOLEAN": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "boolean",
|
||||
"description": "How many times has the devtool's toolbox been opened?"
|
||||
},
|
||||
"DEVTOOLS_TOOLBOX_OPENED_PER_USER_FLAG": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "flag",
|
||||
"description": "How many times has the devtool's toolbox been opened?"
|
||||
},
|
||||
"DEVTOOLS_TOOLBOX_TIME_ACTIVE_SECONDS": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": "10000000",
|
||||
"n_buckets": 100,
|
||||
"description": "How long has the toolbox been active (seconds)"
|
||||
},
|
||||
"DEVTOOLS_OPTIONS_TIME_ACTIVE_SECONDS": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "exponential",
|
||||
"high": "10000000",
|
||||
"n_buckets": 100,
|
||||
"description": "How long has the options panel been active (seconds)"
|
||||
},
|
||||
"DEVTOOLS_TABS_OPEN_PEAK_LINEAR": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "linear",
|
||||
"high": "101",
|
||||
"n_buckets": 100,
|
||||
"description": "The peak number of open tabs in all windows for a session for devtools users."
|
||||
},
|
||||
"DEVTOOLS_TABS_OPEN_AVERAGE_LINEAR": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "linear",
|
||||
"high": "101",
|
||||
"n_buckets": "100",
|
||||
"description": "The mean number of open tabs in all windows for a session for devtools users."
|
||||
},
|
||||
"DEVTOOLS_TABS_PINNED_PEAK_LINEAR": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "linear",
|
||||
"high": "101",
|
||||
"n_buckets": "100",
|
||||
"description": "The peak number of pinned tabs (app tabs) in all windows for a session for devtools users."
|
||||
},
|
||||
"DEVTOOLS_TABS_PINNED_AVERAGE_LINEAR": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "linear",
|
||||
"high": "101",
|
||||
"n_buckets": "100",
|
||||
"description": "The mean number of pinned tabs (app tabs) in all windows for a session for devtools users."
|
||||
},
|
||||
"BROWSER_IS_USER_DEFAULT": {
|
||||
"expires_in_version": "never",
|
||||
"kind": "boolean",
|
||||
|
||||
@@ -78,7 +78,7 @@ BuiltinProvider.prototype = {
|
||||
// When you add a line to this mapping, don't forget to make a
|
||||
// corresponding addition to the SrcdirProvider mapping below as well.
|
||||
"": "resource://gre/modules/commonjs/",
|
||||
"main": "resource://gre/modules/devtools/main.js",
|
||||
"main": "resource:///modules/devtools/main.js",
|
||||
"devtools": "resource://gre/modules/devtools",
|
||||
"devtools/toolkit": "resource://gre/modules/devtools",
|
||||
"devtools/server": "resource://gre/modules/devtools/server",
|
||||
|
||||
@@ -119,6 +119,29 @@ let selectNode = Task.async(function*(data, inspector, reason="test") {
|
||||
yield updated;
|
||||
});
|
||||
|
||||
/**
|
||||
* Takes an Inspector panel that was just created, and waits
|
||||
* for a "inspector-updated" event as well as the animation inspector
|
||||
* sidebar to be ready. Returns a promise once these are completed.
|
||||
*
|
||||
* @param {InspectorPanel} inspector
|
||||
* @return {Promise}
|
||||
*/
|
||||
let waitForAnimationInspectorReady = Task.async(function*(inspector) {
|
||||
let win = inspector.sidebar.getWindowForTab("animationinspector");
|
||||
let updated = inspector.once("inspector-updated");
|
||||
|
||||
// In e10s, if we wait for underlying toolbox actors to
|
||||
// load (by setting gDevTools.testing to true), we miss the "animationinspector-ready"
|
||||
// event on the sidebar, so check to see if the iframe
|
||||
// is already loaded.
|
||||
let tabReady = win.document.readyState === "complete" ?
|
||||
promise.resolve() :
|
||||
inspector.sidebar.once("animationinspector-ready");
|
||||
|
||||
return promise.all([updated, tabReady]);
|
||||
});
|
||||
|
||||
/**
|
||||
* Open the toolbox, with the inspector tool visible and the animationinspector
|
||||
* sidebar selected.
|
||||
@@ -129,18 +152,19 @@ let openAnimationInspector = Task.async(function*() {
|
||||
|
||||
info("Opening the toolbox with the inspector selected");
|
||||
let toolbox = yield gDevTools.showToolbox(target, "inspector");
|
||||
yield waitForToolboxFrameFocus(toolbox);
|
||||
|
||||
info("Switching to the animationinspector");
|
||||
let inspector = toolbox.getPanel("inspector");
|
||||
let initPromises = [
|
||||
inspector.once("inspector-updated"),
|
||||
inspector.sidebar.once("animationinspector-ready")
|
||||
];
|
||||
|
||||
let panelReady = waitForAnimationInspectorReady(inspector);
|
||||
|
||||
info("Waiting for toolbox focus");
|
||||
yield waitForToolboxFrameFocus(toolbox);
|
||||
|
||||
inspector.sidebar.select("animationinspector");
|
||||
|
||||
info("Waiting for the inspector and sidebar to be ready");
|
||||
yield promise.all(initPromises);
|
||||
yield panelReady;
|
||||
|
||||
let win = inspector.sidebar.getWindowForTab("animationinspector");
|
||||
let {AnimationsController, AnimationsPanel} = win;
|
||||
|
||||
@@ -12,8 +12,9 @@ const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
|
||||
let XMLHttpRequest = CC("@mozilla.org/xmlextras/xmlhttprequest;1");
|
||||
let strings = Services.strings.createBundle("chrome://global/locale/devtools/app-manager.properties");
|
||||
|
||||
function AppValidator(project) {
|
||||
this.project = project;
|
||||
function AppValidator({ type, location }) {
|
||||
this.type = type;
|
||||
this.location = location;
|
||||
this.errors = [];
|
||||
this.warnings = [];
|
||||
}
|
||||
@@ -27,7 +28,7 @@ AppValidator.prototype.warning = function (message) {
|
||||
};
|
||||
|
||||
AppValidator.prototype._getPackagedManifestFile = function () {
|
||||
let manifestFile = FileUtils.File(this.project.location);
|
||||
let manifestFile = FileUtils.File(this.location);
|
||||
if (!manifestFile.exists()) {
|
||||
this.error(strings.GetStringFromName("validator.nonExistingFolder"));
|
||||
return null;
|
||||
@@ -149,12 +150,12 @@ AppValidator.prototype._fetchManifest = function (manifestURL) {
|
||||
|
||||
AppValidator.prototype._getManifest = function () {
|
||||
let manifestURL;
|
||||
if (this.project.type == "packaged") {
|
||||
if (this.type == "packaged") {
|
||||
manifestURL = this._getPackagedManifestURL();
|
||||
if (!manifestURL)
|
||||
return promise.resolve(null);
|
||||
} else if (this.project.type == "hosted") {
|
||||
manifestURL = this.project.location;
|
||||
} else if (this.type == "hosted") {
|
||||
manifestURL = this.location;
|
||||
try {
|
||||
Services.io.newURI(manifestURL, null, null);
|
||||
} catch(e) {
|
||||
@@ -162,7 +163,7 @@ AppValidator.prototype._getManifest = function () {
|
||||
return promise.resolve(null);
|
||||
}
|
||||
} else {
|
||||
this.error(strings.formatStringFromName("validator.invalidProjectType", [this.project.type], 1));
|
||||
this.error(strings.formatStringFromName("validator.invalidProjectType", [this.type], 1));
|
||||
return promise.resolve(null);
|
||||
}
|
||||
return this._fetchManifest(manifestURL);
|
||||
@@ -181,11 +182,11 @@ AppValidator.prototype.validateManifest = function (manifest) {
|
||||
};
|
||||
|
||||
AppValidator.prototype._getOriginURL = function () {
|
||||
if (this.project.type == "packaged") {
|
||||
if (this.type == "packaged") {
|
||||
let manifestURL = Services.io.newURI(this.manifestURL, null, null);
|
||||
return Services.io.newURI(".", null, manifestURL).spec;
|
||||
} else if (this.project.type == "hosted") {
|
||||
return Services.io.newURI(this.project.location, null, null).prePath;
|
||||
} else if (this.type == "hosted") {
|
||||
return Services.io.newURI(this.location, null, null).prePath;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -203,9 +204,9 @@ AppValidator.prototype.validateLaunchPath = function (manifest) {
|
||||
}
|
||||
let origin = this._getOriginURL();
|
||||
let path;
|
||||
if (this.project.type == "packaged") {
|
||||
if (this.type == "packaged") {
|
||||
path = "." + ( manifest.launch_path || "/index.html" );
|
||||
} else if (this.project.type == "hosted") {
|
||||
} else if (this.type == "hosted") {
|
||||
path = manifest.launch_path || "/";
|
||||
}
|
||||
let indexURL;
|
||||
@@ -251,7 +252,7 @@ AppValidator.prototype.validateType = function (manifest) {
|
||||
let appType = manifest.type || "web";
|
||||
if (["web", "trusted", "privileged", "certified"].indexOf(appType) === -1) {
|
||||
this.error(strings.formatStringFromName("validator.invalidAppType", [appType], 1));
|
||||
} else if (this.project.type == "hosted" &&
|
||||
} else if (this.type == "hosted" &&
|
||||
["certified", "privileged"].indexOf(appType) !== -1) {
|
||||
this.error(strings.formatStringFromName("validator.invalidHostedPriviledges", [appType], 1));
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
let validator = createPackaged("wrong-launch-path");
|
||||
validator.validate().then(() => {
|
||||
is(validator.errors.length, 1, "app with wrong path got an error");
|
||||
let file = nsFile(validator.project.location);
|
||||
let file = nsFile(validator.location);
|
||||
file.append("wrong-path.html");
|
||||
let url = Services.io.newFileURI(file);
|
||||
is(validator.errors[0], strings.formatStringFromName("validator.accessFailedLaunchPath", [url.spec], 1),
|
||||
|
||||
@@ -0,0 +1,513 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Functions handling details about a single recorded animation frame snapshot
|
||||
* (the calls list, rendering preview, thumbnails filmstrip etc.).
|
||||
*/
|
||||
let CallsListView = Heritage.extend(WidgetMethods, {
|
||||
/**
|
||||
* Initialization function, called when the tool is started.
|
||||
*/
|
||||
initialize: function() {
|
||||
this.widget = new SideMenuWidget($("#calls-list"));
|
||||
this._slider = $("#calls-slider");
|
||||
this._searchbox = $("#calls-searchbox");
|
||||
this._filmstrip = $("#snapshot-filmstrip");
|
||||
|
||||
this._onSelect = this._onSelect.bind(this);
|
||||
this._onSlideMouseDown = this._onSlideMouseDown.bind(this);
|
||||
this._onSlideMouseUp = this._onSlideMouseUp.bind(this);
|
||||
this._onSlide = this._onSlide.bind(this);
|
||||
this._onSearch = this._onSearch.bind(this);
|
||||
this._onScroll = this._onScroll.bind(this);
|
||||
this._onExpand = this._onExpand.bind(this);
|
||||
this._onStackFileClick = this._onStackFileClick.bind(this);
|
||||
this._onThumbnailClick = this._onThumbnailClick.bind(this);
|
||||
|
||||
this.widget.addEventListener("select", this._onSelect, false);
|
||||
this._slider.addEventListener("mousedown", this._onSlideMouseDown, false);
|
||||
this._slider.addEventListener("mouseup", this._onSlideMouseUp, false);
|
||||
this._slider.addEventListener("change", this._onSlide, false);
|
||||
this._searchbox.addEventListener("input", this._onSearch, false);
|
||||
this._filmstrip.addEventListener("wheel", this._onScroll, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function, called when the tool is closed.
|
||||
*/
|
||||
destroy: function() {
|
||||
this.widget.removeEventListener("select", this._onSelect, false);
|
||||
this._slider.removeEventListener("mousedown", this._onSlideMouseDown, false);
|
||||
this._slider.removeEventListener("mouseup", this._onSlideMouseUp, false);
|
||||
this._slider.removeEventListener("change", this._onSlide, false);
|
||||
this._searchbox.removeEventListener("input", this._onSearch, false);
|
||||
this._filmstrip.removeEventListener("wheel", this._onScroll, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates this container with a list of function calls.
|
||||
*
|
||||
* @param array functionCalls
|
||||
* A list of function call actors received from the backend.
|
||||
*/
|
||||
showCalls: function(functionCalls) {
|
||||
this.empty();
|
||||
|
||||
for (let i = 0, len = functionCalls.length; i < len; i++) {
|
||||
let call = functionCalls[i];
|
||||
|
||||
let view = document.createElement("vbox");
|
||||
view.className = "call-item-view devtools-monospace";
|
||||
view.setAttribute("flex", "1");
|
||||
|
||||
let contents = document.createElement("hbox");
|
||||
contents.className = "call-item-contents";
|
||||
contents.setAttribute("align", "center");
|
||||
contents.addEventListener("dblclick", this._onExpand);
|
||||
view.appendChild(contents);
|
||||
|
||||
let index = document.createElement("label");
|
||||
index.className = "plain call-item-index";
|
||||
index.setAttribute("flex", "1");
|
||||
index.setAttribute("value", i + 1);
|
||||
|
||||
let gutter = document.createElement("hbox");
|
||||
gutter.className = "call-item-gutter";
|
||||
gutter.appendChild(index);
|
||||
contents.appendChild(gutter);
|
||||
|
||||
// Not all function calls have a caller that was stringified (e.g.
|
||||
// context calls have a "gl" or "ctx" caller preview).
|
||||
if (call.callerPreview) {
|
||||
let context = document.createElement("label");
|
||||
context.className = "plain call-item-context";
|
||||
context.setAttribute("value", call.callerPreview);
|
||||
contents.appendChild(context);
|
||||
|
||||
let separator = document.createElement("label");
|
||||
separator.className = "plain call-item-separator";
|
||||
separator.setAttribute("value", ".");
|
||||
contents.appendChild(separator);
|
||||
}
|
||||
|
||||
let name = document.createElement("label");
|
||||
name.className = "plain call-item-name";
|
||||
name.setAttribute("value", call.name);
|
||||
contents.appendChild(name);
|
||||
|
||||
let argsPreview = document.createElement("label");
|
||||
argsPreview.className = "plain call-item-args";
|
||||
argsPreview.setAttribute("crop", "end");
|
||||
argsPreview.setAttribute("flex", "100");
|
||||
// Getters and setters are displayed differently from regular methods.
|
||||
if (call.type == CallWatcherFront.METHOD_FUNCTION) {
|
||||
argsPreview.setAttribute("value", "(" + call.argsPreview + ")");
|
||||
} else {
|
||||
argsPreview.setAttribute("value", " = " + call.argsPreview);
|
||||
}
|
||||
contents.appendChild(argsPreview);
|
||||
|
||||
let location = document.createElement("label");
|
||||
location.className = "plain call-item-location";
|
||||
location.setAttribute("value", getFileName(call.file) + ":" + call.line);
|
||||
location.setAttribute("crop", "start");
|
||||
location.setAttribute("flex", "1");
|
||||
location.addEventListener("mousedown", this._onExpand);
|
||||
contents.appendChild(location);
|
||||
|
||||
// Append a function call item to this container.
|
||||
this.push([view], {
|
||||
staged: true,
|
||||
attachment: {
|
||||
actor: call
|
||||
}
|
||||
});
|
||||
|
||||
// Highlight certain calls that are probably more interesting than
|
||||
// everything else, making it easier to quickly glance over them.
|
||||
if (CanvasFront.DRAW_CALLS.has(call.name)) {
|
||||
view.setAttribute("draw-call", "");
|
||||
}
|
||||
if (CanvasFront.INTERESTING_CALLS.has(call.name)) {
|
||||
view.setAttribute("interesting-call", "");
|
||||
}
|
||||
}
|
||||
|
||||
// Flushes all the prepared function call items into this container.
|
||||
this.commit();
|
||||
window.emit(EVENTS.CALL_LIST_POPULATED);
|
||||
|
||||
// Resetting the function selection slider's value (shown in this
|
||||
// container's toolbar) would trigger a selection event, which should be
|
||||
// ignored in this case.
|
||||
this._ignoreSliderChanges = true;
|
||||
this._slider.value = 0;
|
||||
this._slider.max = functionCalls.length - 1;
|
||||
this._ignoreSliderChanges = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Displays an image in the rendering preview of this container, generated
|
||||
* for the specified draw call in the recorded animation frame snapshot.
|
||||
*
|
||||
* @param array screenshot
|
||||
* A single "snapshot-image" instance received from the backend.
|
||||
*/
|
||||
showScreenshot: function(screenshot) {
|
||||
let { index, width, height, scaling, flipped, pixels } = screenshot;
|
||||
|
||||
let screenshotNode = $("#screenshot-image");
|
||||
screenshotNode.setAttribute("flipped", flipped);
|
||||
drawBackground("screenshot-rendering", width, height, pixels);
|
||||
|
||||
let dimensionsNode = $("#screenshot-dimensions");
|
||||
let actualWidth = (width / scaling) | 0;
|
||||
let actualHeight = (height / scaling) | 0;
|
||||
dimensionsNode.setAttribute("value",
|
||||
SHARED_L10N.getFormatStr("dimensions", actualWidth, actualHeight));
|
||||
|
||||
window.emit(EVENTS.CALL_SCREENSHOT_DISPLAYED);
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates this container's footer with a list of thumbnails, one generated
|
||||
* for each draw call in the recorded animation frame snapshot.
|
||||
*
|
||||
* @param array thumbnails
|
||||
* An array of "snapshot-image" instances received from the backend.
|
||||
*/
|
||||
showThumbnails: function(thumbnails) {
|
||||
while (this._filmstrip.hasChildNodes()) {
|
||||
this._filmstrip.firstChild.remove();
|
||||
}
|
||||
for (let thumbnail of thumbnails) {
|
||||
this.appendThumbnail(thumbnail);
|
||||
}
|
||||
|
||||
window.emit(EVENTS.THUMBNAILS_DISPLAYED);
|
||||
},
|
||||
|
||||
/**
|
||||
* Displays an image in the thumbnails list of this container, generated
|
||||
* for the specified draw call in the recorded animation frame snapshot.
|
||||
*
|
||||
* @param array thumbnail
|
||||
* A single "snapshot-image" instance received from the backend.
|
||||
*/
|
||||
appendThumbnail: function(thumbnail) {
|
||||
let { index, width, height, flipped, pixels } = thumbnail;
|
||||
|
||||
let thumbnailNode = document.createElementNS(HTML_NS, "canvas");
|
||||
thumbnailNode.setAttribute("flipped", flipped);
|
||||
thumbnailNode.width = Math.max(CanvasFront.THUMBNAIL_SIZE, width);
|
||||
thumbnailNode.height = Math.max(CanvasFront.THUMBNAIL_SIZE, height);
|
||||
drawImage(thumbnailNode, width, height, pixels, { centered: true });
|
||||
|
||||
thumbnailNode.className = "filmstrip-thumbnail";
|
||||
thumbnailNode.onmousedown = e => this._onThumbnailClick(e, index);
|
||||
thumbnailNode.setAttribute("index", index);
|
||||
this._filmstrip.appendChild(thumbnailNode);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the currently highlighted thumbnail in this container.
|
||||
* A screenshot will always correlate to a thumbnail in the filmstrip,
|
||||
* both being identified by the same 'index' of the context function call.
|
||||
*
|
||||
* @param number index
|
||||
* The context function call's index.
|
||||
*/
|
||||
set highlightedThumbnail(index) {
|
||||
let currHighlightedThumbnail = $(".filmstrip-thumbnail[index='" + index + "']");
|
||||
if (currHighlightedThumbnail == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let prevIndex = this._highlightedThumbnailIndex
|
||||
let prevHighlightedThumbnail = $(".filmstrip-thumbnail[index='" + prevIndex + "']");
|
||||
if (prevHighlightedThumbnail) {
|
||||
prevHighlightedThumbnail.removeAttribute("highlighted");
|
||||
}
|
||||
|
||||
currHighlightedThumbnail.setAttribute("highlighted", "");
|
||||
currHighlightedThumbnail.scrollIntoView();
|
||||
this._highlightedThumbnailIndex = index;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the currently highlighted thumbnail in this container.
|
||||
* @return number
|
||||
*/
|
||||
get highlightedThumbnail() {
|
||||
return this._highlightedThumbnailIndex;
|
||||
},
|
||||
|
||||
/**
|
||||
* The select listener for this container.
|
||||
*/
|
||||
_onSelect: function({ detail: callItem }) {
|
||||
if (!callItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Some of the stepping buttons don't make sense specifically while the
|
||||
// last function call is selected.
|
||||
if (this.selectedIndex == this.itemCount - 1) {
|
||||
$("#resume").setAttribute("disabled", "true");
|
||||
$("#step-over").setAttribute("disabled", "true");
|
||||
$("#step-out").setAttribute("disabled", "true");
|
||||
} else {
|
||||
$("#resume").removeAttribute("disabled");
|
||||
$("#step-over").removeAttribute("disabled");
|
||||
$("#step-out").removeAttribute("disabled");
|
||||
}
|
||||
|
||||
// Correlate the currently selected item with the function selection
|
||||
// slider's value. Avoid triggering a redundant selection event.
|
||||
this._ignoreSliderChanges = true;
|
||||
this._slider.value = this.selectedIndex;
|
||||
this._ignoreSliderChanges = false;
|
||||
|
||||
// Can't generate screenshots for function call actors loaded from disk.
|
||||
// XXX: Bug 984844.
|
||||
if (callItem.attachment.actor.isLoadedFromDisk) {
|
||||
return;
|
||||
}
|
||||
|
||||
// To keep continuous selection buttery smooth (for example, while pressing
|
||||
// the DOWN key or moving the slider), only display the screenshot after
|
||||
// any kind of user input stops.
|
||||
setConditionalTimeout("screenshot-display", SCREENSHOT_DISPLAY_DELAY, () => {
|
||||
return !this._isSliding;
|
||||
}, () => {
|
||||
let frameSnapshot = SnapshotsListView.selectedItem.attachment.actor
|
||||
let functionCall = callItem.attachment.actor;
|
||||
frameSnapshot.generateScreenshotFor(functionCall).then(screenshot => {
|
||||
this.showScreenshot(screenshot);
|
||||
this.highlightedThumbnail = screenshot.index;
|
||||
}).catch(Cu.reportError);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The mousedown listener for the call selection slider.
|
||||
*/
|
||||
_onSlideMouseDown: function() {
|
||||
this._isSliding = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* The mouseup listener for the call selection slider.
|
||||
*/
|
||||
_onSlideMouseUp: function() {
|
||||
this._isSliding = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* The change listener for the call selection slider.
|
||||
*/
|
||||
_onSlide: function() {
|
||||
// Avoid performing any operations when programatically changing the value.
|
||||
if (this._ignoreSliderChanges) {
|
||||
return;
|
||||
}
|
||||
let selectedFunctionCallIndex = this.selectedIndex = this._slider.value;
|
||||
|
||||
// While sliding, immediately show the most relevant thumbnail for a
|
||||
// function call, for a nice diff-like animation effect between draws.
|
||||
let thumbnails = SnapshotsListView.selectedItem.attachment.thumbnails;
|
||||
let thumbnail = getThumbnailForCall(thumbnails, selectedFunctionCallIndex);
|
||||
|
||||
// Avoid drawing and highlighting if the selected function call has the
|
||||
// same thumbnail as the last one.
|
||||
if (thumbnail.index == this.highlightedThumbnail) {
|
||||
return;
|
||||
}
|
||||
// If a thumbnail wasn't found (e.g. the backend avoids creating thumbnails
|
||||
// when rendering offscreen), simply defer to the first available one.
|
||||
if (thumbnail.index == -1) {
|
||||
thumbnail = thumbnails[0];
|
||||
}
|
||||
|
||||
let { index, width, height, flipped, pixels } = thumbnail;
|
||||
this.highlightedThumbnail = index;
|
||||
|
||||
let screenshotNode = $("#screenshot-image");
|
||||
screenshotNode.setAttribute("flipped", flipped);
|
||||
drawBackground("screenshot-rendering", width, height, pixels);
|
||||
},
|
||||
|
||||
/**
|
||||
* The input listener for the calls searchbox.
|
||||
*/
|
||||
_onSearch: function(e) {
|
||||
let lowerCaseSearchToken = this._searchbox.value.toLowerCase();
|
||||
|
||||
this.filterContents(e => {
|
||||
let call = e.attachment.actor;
|
||||
let name = call.name.toLowerCase();
|
||||
let file = call.file.toLowerCase();
|
||||
let line = call.line.toString().toLowerCase();
|
||||
let args = call.argsPreview.toLowerCase();
|
||||
|
||||
return name.includes(lowerCaseSearchToken) ||
|
||||
file.includes(lowerCaseSearchToken) ||
|
||||
line.includes(lowerCaseSearchToken) ||
|
||||
args.includes(lowerCaseSearchToken);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The wheel listener for the filmstrip that contains all the thumbnails.
|
||||
*/
|
||||
_onScroll: function(e) {
|
||||
this._filmstrip.scrollLeft += e.deltaX;
|
||||
},
|
||||
|
||||
/**
|
||||
* The click/dblclick listener for an item or location url in this container.
|
||||
* When expanding an item, it's corresponding call stack will be displayed.
|
||||
*/
|
||||
_onExpand: function(e) {
|
||||
let callItem = this.getItemForElement(e.target);
|
||||
let view = $(".call-item-view", callItem.target);
|
||||
|
||||
// If the call stack nodes were already created, simply re-show them
|
||||
// or jump to the corresponding file and line in the Debugger if a
|
||||
// location link was clicked.
|
||||
if (view.hasAttribute("call-stack-populated")) {
|
||||
let isExpanded = view.getAttribute("call-stack-expanded") == "true";
|
||||
|
||||
// If clicking on the location, jump to the Debugger.
|
||||
if (e.target.classList.contains("call-item-location")) {
|
||||
let { file, line } = callItem.attachment.actor;
|
||||
viewSourceInDebugger(file, line);
|
||||
return;
|
||||
}
|
||||
// Otherwise hide the call stack.
|
||||
else {
|
||||
view.setAttribute("call-stack-expanded", !isExpanded);
|
||||
$(".call-item-stack", view).hidden = isExpanded;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let list = document.createElement("vbox");
|
||||
list.className = "call-item-stack";
|
||||
view.setAttribute("call-stack-populated", "");
|
||||
view.setAttribute("call-stack-expanded", "true");
|
||||
view.appendChild(list);
|
||||
|
||||
/**
|
||||
* Creates a function call nodes in this container for a stack.
|
||||
*/
|
||||
let display = stack => {
|
||||
for (let i = 1; i < stack.length; i++) {
|
||||
let call = stack[i];
|
||||
|
||||
let contents = document.createElement("hbox");
|
||||
contents.className = "call-item-stack-fn";
|
||||
contents.style.MozPaddingStart = (i * STACK_FUNC_INDENTATION) + "px";
|
||||
|
||||
let name = document.createElement("label");
|
||||
name.className = "plain call-item-stack-fn-name";
|
||||
name.setAttribute("value", "??" + call.name + "()");
|
||||
contents.appendChild(name);
|
||||
|
||||
let spacer = document.createElement("spacer");
|
||||
spacer.setAttribute("flex", "100");
|
||||
contents.appendChild(spacer);
|
||||
|
||||
let location = document.createElement("label");
|
||||
location.className = "plain call-item-stack-fn-location";
|
||||
location.setAttribute("value", getFileName(call.file) + ":" + call.line);
|
||||
location.setAttribute("crop", "start");
|
||||
location.setAttribute("flex", "1");
|
||||
location.addEventListener("mousedown", e => this._onStackFileClick(e, call));
|
||||
contents.appendChild(location);
|
||||
|
||||
list.appendChild(contents);
|
||||
}
|
||||
|
||||
window.emit(EVENTS.CALL_STACK_DISPLAYED);
|
||||
};
|
||||
|
||||
// If this animation snapshot is loaded from disk, there are no corresponding
|
||||
// backend actors available and the data is immediately available.
|
||||
let functionCall = callItem.attachment.actor;
|
||||
if (functionCall.isLoadedFromDisk) {
|
||||
display(functionCall.stack);
|
||||
}
|
||||
// ..otherwise we need to request the function call stack from the backend.
|
||||
else {
|
||||
callItem.attachment.actor.getDetails().then(fn => display(fn.stack));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for a location link in the call stack.
|
||||
*
|
||||
* @param string file
|
||||
* The url of the source owning the function.
|
||||
* @param number line
|
||||
* The line of the respective function.
|
||||
*/
|
||||
_onStackFileClick: function(e, { file, line }) {
|
||||
viewSourceInDebugger(file, line);
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for a thumbnail in the filmstrip.
|
||||
*
|
||||
* @param number index
|
||||
* The function index in the recorded animation frame snapshot.
|
||||
*/
|
||||
_onThumbnailClick: function(e, index) {
|
||||
this.selectedIndex = index;
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for the "resume" button in this container's toolbar.
|
||||
*/
|
||||
_onResume: function() {
|
||||
// Jump to the next draw call in the recorded animation frame snapshot.
|
||||
let drawCall = getNextDrawCall(this.items, this.selectedItem);
|
||||
if (drawCall) {
|
||||
this.selectedItem = drawCall;
|
||||
return;
|
||||
}
|
||||
|
||||
// If there are no more draw calls, just jump to the last context call.
|
||||
this._onStepOut();
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for the "step over" button in this container's toolbar.
|
||||
*/
|
||||
_onStepOver: function() {
|
||||
this.selectedIndex++;
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for the "step in" button in this container's toolbar.
|
||||
*/
|
||||
_onStepIn: function() {
|
||||
if (this.selectedIndex == -1) {
|
||||
this._onResume();
|
||||
return;
|
||||
}
|
||||
let callItem = this.selectedItem;
|
||||
let { file, line } = callItem.attachment.actor;
|
||||
viewSourceInDebugger(file, line);
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for the "step out" button in this container's toolbar.
|
||||
*/
|
||||
_onStepOut: function() {
|
||||
this.selectedIndex = this.itemCount - 1;
|
||||
}
|
||||
});
|
||||
@@ -9,6 +9,8 @@ Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/SideMenuWidget.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/ViewHelpers.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/Console.jsm");
|
||||
Cu.import("resource://gre/modules/devtools/gDevTools.jsm");
|
||||
|
||||
const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require;
|
||||
const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
|
||||
@@ -16,6 +18,8 @@ const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
const { CallWatcherFront } = require("devtools/server/actors/call-watcher");
|
||||
const { CanvasFront } = require("devtools/server/actors/canvas");
|
||||
|
||||
const CANVAS_ACTOR_RECORDING_ATTEMPT = gDevTools.testing ? 500 : 5000;
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
|
||||
@@ -39,9 +43,12 @@ const EVENTS = {
|
||||
// When all the animation frame snapshots are removed by the user.
|
||||
SNAPSHOTS_LIST_CLEARED: "CanvasDebugger:SnapshotsListCleared",
|
||||
|
||||
// When an animation frame snapshot starts/finishes being recorded.
|
||||
// When an animation frame snapshot starts/finishes being recorded, and
|
||||
// whether it was completed succesfully or cancelled.
|
||||
SNAPSHOT_RECORDING_STARTED: "CanvasDebugger:SnapshotRecordingStarted",
|
||||
SNAPSHOT_RECORDING_FINISHED: "CanvasDebugger:SnapshotRecordingFinished",
|
||||
SNAPSHOT_RECORDING_COMPLETED: "CanvasDebugger:SnapshotRecordingCompleted",
|
||||
SNAPSHOT_RECORDING_CANCELLED: "CanvasDebugger:SnapshotRecordingCancelled",
|
||||
|
||||
// When an animation frame snapshot was selected and all its data displayed.
|
||||
SNAPSHOT_RECORDING_SELECTED: "CanvasDebugger:SnapshotRecordingSelected",
|
||||
@@ -153,7 +160,7 @@ let EventsHandler = {
|
||||
|
||||
$("#reload-notice").hidden = true;
|
||||
$("#empty-notice").hidden = false;
|
||||
$("#import-notice").hidden = true;
|
||||
$("#waiting-notice").hidden = true;
|
||||
|
||||
$("#debugging-pane-contents").hidden = true;
|
||||
$("#screenshot-container").hidden = true;
|
||||
@@ -163,896 +170,6 @@ let EventsHandler = {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Functions handling the recorded animation frame snapshots UI.
|
||||
*/
|
||||
let SnapshotsListView = Heritage.extend(WidgetMethods, {
|
||||
/**
|
||||
* Initialization function, called when the tool is started.
|
||||
*/
|
||||
initialize: function() {
|
||||
this.widget = new SideMenuWidget($("#snapshots-list"), {
|
||||
showArrows: true
|
||||
});
|
||||
|
||||
this._onSelect = this._onSelect.bind(this);
|
||||
this._onClearButtonClick = this._onClearButtonClick.bind(this);
|
||||
this._onRecordButtonClick = this._onRecordButtonClick.bind(this);
|
||||
this._onImportButtonClick = this._onImportButtonClick.bind(this);
|
||||
this._onSaveButtonClick = this._onSaveButtonClick.bind(this);
|
||||
|
||||
this.emptyText = L10N.getStr("noSnapshotsText");
|
||||
this.widget.addEventListener("select", this._onSelect, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function, called when the tool is closed.
|
||||
*/
|
||||
destroy: function() {
|
||||
this.widget.removeEventListener("select", this._onSelect, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a snapshot entry to this container.
|
||||
*
|
||||
* @return object
|
||||
* The newly inserted item.
|
||||
*/
|
||||
addSnapshot: function() {
|
||||
let contents = document.createElement("hbox");
|
||||
contents.className = "snapshot-item";
|
||||
|
||||
let thumbnail = document.createElementNS(HTML_NS, "canvas");
|
||||
thumbnail.className = "snapshot-item-thumbnail";
|
||||
thumbnail.width = CanvasFront.THUMBNAIL_SIZE;
|
||||
thumbnail.height = CanvasFront.THUMBNAIL_SIZE;
|
||||
|
||||
let title = document.createElement("label");
|
||||
title.className = "plain snapshot-item-title";
|
||||
title.setAttribute("value",
|
||||
L10N.getFormatStr("snapshotsList.itemLabel", this.itemCount + 1));
|
||||
|
||||
let calls = document.createElement("label");
|
||||
calls.className = "plain snapshot-item-calls";
|
||||
calls.setAttribute("value",
|
||||
L10N.getStr("snapshotsList.loadingLabel"));
|
||||
|
||||
let save = document.createElement("label");
|
||||
save.className = "plain snapshot-item-save";
|
||||
save.addEventListener("click", this._onSaveButtonClick, false);
|
||||
|
||||
let spacer = document.createElement("spacer");
|
||||
spacer.setAttribute("flex", "1");
|
||||
|
||||
let footer = document.createElement("hbox");
|
||||
footer.className = "snapshot-item-footer";
|
||||
footer.appendChild(save);
|
||||
|
||||
let details = document.createElement("vbox");
|
||||
details.className = "snapshot-item-details";
|
||||
details.appendChild(title);
|
||||
details.appendChild(calls);
|
||||
details.appendChild(spacer);
|
||||
details.appendChild(footer);
|
||||
|
||||
contents.appendChild(thumbnail);
|
||||
contents.appendChild(details);
|
||||
|
||||
// Append a recorded snapshot item to this container.
|
||||
return this.push([contents], {
|
||||
attachment: {
|
||||
// The snapshot and function call actors, along with the thumbnails
|
||||
// will be available as soon as recording finishes.
|
||||
actor: null,
|
||||
calls: null,
|
||||
thumbnails: null,
|
||||
screenshot: null
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Customizes a shapshot in this container.
|
||||
*
|
||||
* @param Item snapshotItem
|
||||
* An item inserted via `SnapshotsListView.addSnapshot`.
|
||||
* @param object snapshotActor
|
||||
* The frame snapshot actor received from the backend.
|
||||
* @param object snapshotOverview
|
||||
* Additional data about the snapshot received from the backend.
|
||||
*/
|
||||
customizeSnapshot: function(snapshotItem, snapshotActor, snapshotOverview) {
|
||||
// Make sure the function call actors are stored on the item,
|
||||
// to be used when populating the CallsListView.
|
||||
snapshotItem.attachment.actor = snapshotActor;
|
||||
let functionCalls = snapshotItem.attachment.calls = snapshotOverview.calls;
|
||||
let thumbnails = snapshotItem.attachment.thumbnails = snapshotOverview.thumbnails;
|
||||
let screenshot = snapshotItem.attachment.screenshot = snapshotOverview.screenshot;
|
||||
|
||||
let lastThumbnail = thumbnails[thumbnails.length - 1];
|
||||
let { width, height, flipped, pixels } = lastThumbnail;
|
||||
|
||||
let thumbnailNode = $(".snapshot-item-thumbnail", snapshotItem.target);
|
||||
thumbnailNode.setAttribute("flipped", flipped);
|
||||
drawImage(thumbnailNode, width, height, pixels, { centered: true });
|
||||
|
||||
let callsNode = $(".snapshot-item-calls", snapshotItem.target);
|
||||
let drawCalls = functionCalls.filter(e => CanvasFront.DRAW_CALLS.has(e.name));
|
||||
|
||||
let drawCallsStr = PluralForm.get(drawCalls.length,
|
||||
L10N.getStr("snapshotsList.drawCallsLabel"));
|
||||
let funcCallsStr = PluralForm.get(functionCalls.length,
|
||||
L10N.getStr("snapshotsList.functionCallsLabel"));
|
||||
|
||||
callsNode.setAttribute("value",
|
||||
drawCallsStr.replace("#1", drawCalls.length) + ", " +
|
||||
funcCallsStr.replace("#1", functionCalls.length));
|
||||
|
||||
let saveNode = $(".snapshot-item-save", snapshotItem.target);
|
||||
saveNode.setAttribute("disabled", !!snapshotItem.isLoadedFromDisk);
|
||||
saveNode.setAttribute("value", snapshotItem.isLoadedFromDisk
|
||||
? L10N.getStr("snapshotsList.loadedLabel")
|
||||
: L10N.getStr("snapshotsList.saveLabel"));
|
||||
|
||||
// Make sure there's always a selected item available.
|
||||
if (!this.selectedItem) {
|
||||
this.selectedIndex = 0;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The select listener for this container.
|
||||
*/
|
||||
_onSelect: function({ detail: snapshotItem }) {
|
||||
// Check to ensure the attachment has an actor, like
|
||||
// an in-progress recording.
|
||||
if (!snapshotItem || !snapshotItem.attachment.actor) {
|
||||
return;
|
||||
}
|
||||
let { calls, thumbnails, screenshot } = snapshotItem.attachment;
|
||||
|
||||
$("#reload-notice").hidden = true;
|
||||
$("#empty-notice").hidden = true;
|
||||
$("#import-notice").hidden = false;
|
||||
|
||||
$("#debugging-pane-contents").hidden = true;
|
||||
$("#screenshot-container").hidden = true;
|
||||
$("#snapshot-filmstrip").hidden = true;
|
||||
|
||||
Task.spawn(function*() {
|
||||
// Wait for a few milliseconds between presenting the function calls,
|
||||
// screenshot and thumbnails, to allow each component being
|
||||
// sequentially drawn. This gives the illusion of snappiness.
|
||||
|
||||
yield DevToolsUtils.waitForTime(SNAPSHOT_DATA_DISPLAY_DELAY);
|
||||
CallsListView.showCalls(calls);
|
||||
$("#debugging-pane-contents").hidden = false;
|
||||
$("#import-notice").hidden = true;
|
||||
|
||||
yield DevToolsUtils.waitForTime(SNAPSHOT_DATA_DISPLAY_DELAY);
|
||||
CallsListView.showThumbnails(thumbnails);
|
||||
$("#snapshot-filmstrip").hidden = false;
|
||||
|
||||
yield DevToolsUtils.waitForTime(SNAPSHOT_DATA_DISPLAY_DELAY);
|
||||
CallsListView.showScreenshot(screenshot);
|
||||
$("#screenshot-container").hidden = false;
|
||||
|
||||
window.emit(EVENTS.SNAPSHOT_RECORDING_SELECTED);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for the "clear" button in this container.
|
||||
*/
|
||||
_onClearButtonClick: function() {
|
||||
Task.spawn(function*() {
|
||||
SnapshotsListView.empty();
|
||||
CallsListView.empty();
|
||||
|
||||
$("#reload-notice").hidden = true;
|
||||
$("#empty-notice").hidden = true;
|
||||
$("#import-notice").hidden = true;
|
||||
|
||||
if (yield gFront.isInitialized()) {
|
||||
$("#empty-notice").hidden = false;
|
||||
} else {
|
||||
$("#reload-notice").hidden = false;
|
||||
}
|
||||
|
||||
$("#debugging-pane-contents").hidden = true;
|
||||
$("#screenshot-container").hidden = true;
|
||||
$("#snapshot-filmstrip").hidden = true;
|
||||
|
||||
window.emit(EVENTS.SNAPSHOTS_LIST_CLEARED);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for the "record" button in this container.
|
||||
*/
|
||||
_onRecordButtonClick: function() {
|
||||
Task.spawn(function*() {
|
||||
$("#record-snapshot").setAttribute("checked", "true");
|
||||
$("#record-snapshot").setAttribute("disabled", "true");
|
||||
|
||||
// Insert a "dummy" snapshot item in the view, to hint that recording
|
||||
// has now started. However, wait for a few milliseconds before actually
|
||||
// starting the recording, since that might block rendering and prevent
|
||||
// the dummy snapshot item from being drawn.
|
||||
let snapshotItem = this.addSnapshot();
|
||||
|
||||
// If this is the first item, immediately show the "Loading…" notice.
|
||||
if (this.itemCount == 1) {
|
||||
$("#empty-notice").hidden = true;
|
||||
$("#import-notice").hidden = false;
|
||||
}
|
||||
|
||||
yield DevToolsUtils.waitForTime(SNAPSHOT_START_RECORDING_DELAY);
|
||||
window.emit(EVENTS.SNAPSHOT_RECORDING_STARTED);
|
||||
|
||||
let snapshotActor = yield gFront.recordAnimationFrame();
|
||||
let snapshotOverview = yield snapshotActor.getOverview();
|
||||
this.customizeSnapshot(snapshotItem, snapshotActor, snapshotOverview);
|
||||
|
||||
$("#record-snapshot").removeAttribute("checked");
|
||||
$("#record-snapshot").removeAttribute("disabled");
|
||||
|
||||
window.emit(EVENTS.SNAPSHOT_RECORDING_FINISHED);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for the "import" button in this container.
|
||||
*/
|
||||
_onImportButtonClick: function() {
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
fp.init(window, L10N.getStr("snapshotsList.saveDialogTitle"), Ci.nsIFilePicker.modeOpen);
|
||||
fp.appendFilter(L10N.getStr("snapshotsList.saveDialogJSONFilter"), "*.json");
|
||||
fp.appendFilter(L10N.getStr("snapshotsList.saveDialogAllFilter"), "*.*");
|
||||
|
||||
if (fp.show() != Ci.nsIFilePicker.returnOK) {
|
||||
return;
|
||||
}
|
||||
|
||||
let channel = NetUtil.newChannel2(fp.file,
|
||||
null,
|
||||
null,
|
||||
window.document,
|
||||
null, // aLoadingPrincipal
|
||||
null, // aTriggeringPrincipal
|
||||
Ci.nsILoadInfo.SEC_NORMAL,
|
||||
Ci.nsIContentPolicy.TYPE_OTHER);
|
||||
channel.contentType = "text/plain";
|
||||
|
||||
NetUtil.asyncFetch2(channel, (inputStream, status) => {
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
console.error("Could not import recorded animation frame snapshot file.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let string = NetUtil.readInputStreamToString(inputStream, inputStream.available());
|
||||
var data = JSON.parse(string);
|
||||
} catch (e) {
|
||||
console.error("Could not read animation frame snapshot file.");
|
||||
return;
|
||||
}
|
||||
if (data.fileType != CALLS_LIST_SERIALIZER_IDENTIFIER) {
|
||||
console.error("Unrecognized animation frame snapshot file.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Add a `isLoadedFromDisk` flag on everything to avoid sending invalid
|
||||
// requests to the backend, since we're not dealing with actors anymore.
|
||||
let snapshotItem = this.addSnapshot();
|
||||
snapshotItem.isLoadedFromDisk = true;
|
||||
data.calls.forEach(e => e.isLoadedFromDisk = true);
|
||||
|
||||
this.customizeSnapshot(snapshotItem, data.calls, data);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for the "save" button of each item in this container.
|
||||
*/
|
||||
_onSaveButtonClick: function(e) {
|
||||
let snapshotItem = this.getItemForElement(e.target);
|
||||
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
fp.init(window, L10N.getStr("snapshotsList.saveDialogTitle"), Ci.nsIFilePicker.modeSave);
|
||||
fp.appendFilter(L10N.getStr("snapshotsList.saveDialogJSONFilter"), "*.json");
|
||||
fp.appendFilter(L10N.getStr("snapshotsList.saveDialogAllFilter"), "*.*");
|
||||
fp.defaultString = "snapshot.json";
|
||||
|
||||
// Start serializing all the function call actors for the specified snapshot,
|
||||
// while the nsIFilePicker dialog is being opened. Snappy.
|
||||
let serialized = Task.spawn(function*() {
|
||||
let data = {
|
||||
fileType: CALLS_LIST_SERIALIZER_IDENTIFIER,
|
||||
version: CALLS_LIST_SERIALIZER_VERSION,
|
||||
calls: [],
|
||||
thumbnails: [],
|
||||
screenshot: null
|
||||
};
|
||||
let functionCalls = snapshotItem.attachment.calls;
|
||||
let thumbnails = snapshotItem.attachment.thumbnails;
|
||||
let screenshot = snapshotItem.attachment.screenshot;
|
||||
|
||||
// Prepare all the function calls for serialization.
|
||||
yield DevToolsUtils.yieldingEach(functionCalls, (call, i) => {
|
||||
let { type, name, file, line, argsPreview, callerPreview } = call;
|
||||
return call.getDetails().then(({ stack }) => {
|
||||
data.calls[i] = {
|
||||
type: type,
|
||||
name: name,
|
||||
file: file,
|
||||
line: line,
|
||||
stack: stack,
|
||||
argsPreview: argsPreview,
|
||||
callerPreview: callerPreview
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// Prepare all the thumbnails for serialization.
|
||||
yield DevToolsUtils.yieldingEach(thumbnails, (thumbnail, i) => {
|
||||
let { index, width, height, flipped, pixels } = thumbnail;
|
||||
data.thumbnails.push({ index, width, height, flipped, pixels });
|
||||
});
|
||||
|
||||
// Prepare the screenshot for serialization.
|
||||
let { index, width, height, flipped, pixels } = screenshot;
|
||||
data.screenshot = { index, width, height, flipped, pixels };
|
||||
|
||||
let string = JSON.stringify(data);
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
|
||||
createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
|
||||
converter.charset = "UTF-8";
|
||||
return converter.convertToInputStream(string);
|
||||
});
|
||||
|
||||
// Open the nsIFilePicker and wait for the function call actors to finish
|
||||
// being serialized, in order to save the generated JSON data to disk.
|
||||
fp.open({ done: result => {
|
||||
if (result == Ci.nsIFilePicker.returnCancel) {
|
||||
return;
|
||||
}
|
||||
let footer = $(".snapshot-item-footer", snapshotItem.target);
|
||||
let save = $(".snapshot-item-save", snapshotItem.target);
|
||||
|
||||
// Show a throbber and a "Saving…" label if serializing isn't immediate.
|
||||
setNamedTimeout("call-list-save", CALLS_LIST_SLOW_SAVE_DELAY, () => {
|
||||
footer.classList.add("devtools-throbber");
|
||||
save.setAttribute("disabled", "true");
|
||||
save.setAttribute("value", L10N.getStr("snapshotsList.savingLabel"));
|
||||
});
|
||||
|
||||
serialized.then(inputStream => {
|
||||
let outputStream = FileUtils.openSafeFileOutputStream(fp.file);
|
||||
|
||||
NetUtil.asyncCopy(inputStream, outputStream, status => {
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
console.error("Could not save recorded animation frame snapshot file.");
|
||||
}
|
||||
clearNamedTimeout("call-list-save");
|
||||
footer.classList.remove("devtools-throbber");
|
||||
save.removeAttribute("disabled");
|
||||
save.setAttribute("value", L10N.getStr("snapshotsList.saveLabel"));
|
||||
});
|
||||
});
|
||||
}});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Functions handling details about a single recorded animation frame snapshot
|
||||
* (the calls list, rendering preview, thumbnails filmstrip etc.).
|
||||
*/
|
||||
let CallsListView = Heritage.extend(WidgetMethods, {
|
||||
/**
|
||||
* Initialization function, called when the tool is started.
|
||||
*/
|
||||
initialize: function() {
|
||||
this.widget = new SideMenuWidget($("#calls-list"));
|
||||
this._slider = $("#calls-slider");
|
||||
this._searchbox = $("#calls-searchbox");
|
||||
this._filmstrip = $("#snapshot-filmstrip");
|
||||
|
||||
this._onSelect = this._onSelect.bind(this);
|
||||
this._onSlideMouseDown = this._onSlideMouseDown.bind(this);
|
||||
this._onSlideMouseUp = this._onSlideMouseUp.bind(this);
|
||||
this._onSlide = this._onSlide.bind(this);
|
||||
this._onSearch = this._onSearch.bind(this);
|
||||
this._onScroll = this._onScroll.bind(this);
|
||||
this._onExpand = this._onExpand.bind(this);
|
||||
this._onStackFileClick = this._onStackFileClick.bind(this);
|
||||
this._onThumbnailClick = this._onThumbnailClick.bind(this);
|
||||
|
||||
this.widget.addEventListener("select", this._onSelect, false);
|
||||
this._slider.addEventListener("mousedown", this._onSlideMouseDown, false);
|
||||
this._slider.addEventListener("mouseup", this._onSlideMouseUp, false);
|
||||
this._slider.addEventListener("change", this._onSlide, false);
|
||||
this._searchbox.addEventListener("input", this._onSearch, false);
|
||||
this._filmstrip.addEventListener("wheel", this._onScroll, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function, called when the tool is closed.
|
||||
*/
|
||||
destroy: function() {
|
||||
this.widget.removeEventListener("select", this._onSelect, false);
|
||||
this._slider.removeEventListener("mousedown", this._onSlideMouseDown, false);
|
||||
this._slider.removeEventListener("mouseup", this._onSlideMouseUp, false);
|
||||
this._slider.removeEventListener("change", this._onSlide, false);
|
||||
this._searchbox.removeEventListener("input", this._onSearch, false);
|
||||
this._filmstrip.removeEventListener("wheel", this._onScroll, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates this container with a list of function calls.
|
||||
*
|
||||
* @param array functionCalls
|
||||
* A list of function call actors received from the backend.
|
||||
*/
|
||||
showCalls: function(functionCalls) {
|
||||
this.empty();
|
||||
|
||||
for (let i = 0, len = functionCalls.length; i < len; i++) {
|
||||
let call = functionCalls[i];
|
||||
|
||||
let view = document.createElement("vbox");
|
||||
view.className = "call-item-view devtools-monospace";
|
||||
view.setAttribute("flex", "1");
|
||||
|
||||
let contents = document.createElement("hbox");
|
||||
contents.className = "call-item-contents";
|
||||
contents.setAttribute("align", "center");
|
||||
contents.addEventListener("dblclick", this._onExpand);
|
||||
view.appendChild(contents);
|
||||
|
||||
let index = document.createElement("label");
|
||||
index.className = "plain call-item-index";
|
||||
index.setAttribute("flex", "1");
|
||||
index.setAttribute("value", i + 1);
|
||||
|
||||
let gutter = document.createElement("hbox");
|
||||
gutter.className = "call-item-gutter";
|
||||
gutter.appendChild(index);
|
||||
contents.appendChild(gutter);
|
||||
|
||||
// Not all function calls have a caller that was stringified (e.g.
|
||||
// context calls have a "gl" or "ctx" caller preview).
|
||||
if (call.callerPreview) {
|
||||
let context = document.createElement("label");
|
||||
context.className = "plain call-item-context";
|
||||
context.setAttribute("value", call.callerPreview);
|
||||
contents.appendChild(context);
|
||||
|
||||
let separator = document.createElement("label");
|
||||
separator.className = "plain call-item-separator";
|
||||
separator.setAttribute("value", ".");
|
||||
contents.appendChild(separator);
|
||||
}
|
||||
|
||||
let name = document.createElement("label");
|
||||
name.className = "plain call-item-name";
|
||||
name.setAttribute("value", call.name);
|
||||
contents.appendChild(name);
|
||||
|
||||
let argsPreview = document.createElement("label");
|
||||
argsPreview.className = "plain call-item-args";
|
||||
argsPreview.setAttribute("crop", "end");
|
||||
argsPreview.setAttribute("flex", "100");
|
||||
// Getters and setters are displayed differently from regular methods.
|
||||
if (call.type == CallWatcherFront.METHOD_FUNCTION) {
|
||||
argsPreview.setAttribute("value", "(" + call.argsPreview + ")");
|
||||
} else {
|
||||
argsPreview.setAttribute("value", " = " + call.argsPreview);
|
||||
}
|
||||
contents.appendChild(argsPreview);
|
||||
|
||||
let location = document.createElement("label");
|
||||
location.className = "plain call-item-location";
|
||||
location.setAttribute("value", getFileName(call.file) + ":" + call.line);
|
||||
location.setAttribute("crop", "start");
|
||||
location.setAttribute("flex", "1");
|
||||
location.addEventListener("mousedown", this._onExpand);
|
||||
contents.appendChild(location);
|
||||
|
||||
// Append a function call item to this container.
|
||||
this.push([view], {
|
||||
staged: true,
|
||||
attachment: {
|
||||
actor: call
|
||||
}
|
||||
});
|
||||
|
||||
// Highlight certain calls that are probably more interesting than
|
||||
// everything else, making it easier to quickly glance over them.
|
||||
if (CanvasFront.DRAW_CALLS.has(call.name)) {
|
||||
view.setAttribute("draw-call", "");
|
||||
}
|
||||
if (CanvasFront.INTERESTING_CALLS.has(call.name)) {
|
||||
view.setAttribute("interesting-call", "");
|
||||
}
|
||||
}
|
||||
|
||||
// Flushes all the prepared function call items into this container.
|
||||
this.commit();
|
||||
window.emit(EVENTS.CALL_LIST_POPULATED);
|
||||
|
||||
// Resetting the function selection slider's value (shown in this
|
||||
// container's toolbar) would trigger a selection event, which should be
|
||||
// ignored in this case.
|
||||
this._ignoreSliderChanges = true;
|
||||
this._slider.value = 0;
|
||||
this._slider.max = functionCalls.length - 1;
|
||||
this._ignoreSliderChanges = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* Displays an image in the rendering preview of this container, generated
|
||||
* for the specified draw call in the recorded animation frame snapshot.
|
||||
*
|
||||
* @param array screenshot
|
||||
* A single "snapshot-image" instance received from the backend.
|
||||
*/
|
||||
showScreenshot: function(screenshot) {
|
||||
let { index, width, height, scaling, flipped, pixels } = screenshot;
|
||||
|
||||
let screenshotNode = $("#screenshot-image");
|
||||
screenshotNode.setAttribute("flipped", flipped);
|
||||
drawBackground("screenshot-rendering", width, height, pixels);
|
||||
|
||||
let dimensionsNode = $("#screenshot-dimensions");
|
||||
let actualWidth = (width / scaling) | 0;
|
||||
let actualHeight = (height / scaling) | 0;
|
||||
dimensionsNode.setAttribute("value",
|
||||
SHARED_L10N.getFormatStr("dimensions", actualWidth, actualHeight));
|
||||
|
||||
window.emit(EVENTS.CALL_SCREENSHOT_DISPLAYED);
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates this container's footer with a list of thumbnails, one generated
|
||||
* for each draw call in the recorded animation frame snapshot.
|
||||
*
|
||||
* @param array thumbnails
|
||||
* An array of "snapshot-image" instances received from the backend.
|
||||
*/
|
||||
showThumbnails: function(thumbnails) {
|
||||
while (this._filmstrip.hasChildNodes()) {
|
||||
this._filmstrip.firstChild.remove();
|
||||
}
|
||||
for (let thumbnail of thumbnails) {
|
||||
this.appendThumbnail(thumbnail);
|
||||
}
|
||||
|
||||
window.emit(EVENTS.THUMBNAILS_DISPLAYED);
|
||||
},
|
||||
|
||||
/**
|
||||
* Displays an image in the thumbnails list of this container, generated
|
||||
* for the specified draw call in the recorded animation frame snapshot.
|
||||
*
|
||||
* @param array thumbnail
|
||||
* A single "snapshot-image" instance received from the backend.
|
||||
*/
|
||||
appendThumbnail: function(thumbnail) {
|
||||
let { index, width, height, flipped, pixels } = thumbnail;
|
||||
|
||||
let thumbnailNode = document.createElementNS(HTML_NS, "canvas");
|
||||
thumbnailNode.setAttribute("flipped", flipped);
|
||||
thumbnailNode.width = Math.max(CanvasFront.THUMBNAIL_SIZE, width);
|
||||
thumbnailNode.height = Math.max(CanvasFront.THUMBNAIL_SIZE, height);
|
||||
drawImage(thumbnailNode, width, height, pixels, { centered: true });
|
||||
|
||||
thumbnailNode.className = "filmstrip-thumbnail";
|
||||
thumbnailNode.onmousedown = e => this._onThumbnailClick(e, index);
|
||||
thumbnailNode.setAttribute("index", index);
|
||||
this._filmstrip.appendChild(thumbnailNode);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the currently highlighted thumbnail in this container.
|
||||
* A screenshot will always correlate to a thumbnail in the filmstrip,
|
||||
* both being identified by the same 'index' of the context function call.
|
||||
*
|
||||
* @param number index
|
||||
* The context function call's index.
|
||||
*/
|
||||
set highlightedThumbnail(index) {
|
||||
let currHighlightedThumbnail = $(".filmstrip-thumbnail[index='" + index + "']");
|
||||
if (currHighlightedThumbnail == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let prevIndex = this._highlightedThumbnailIndex
|
||||
let prevHighlightedThumbnail = $(".filmstrip-thumbnail[index='" + prevIndex + "']");
|
||||
if (prevHighlightedThumbnail) {
|
||||
prevHighlightedThumbnail.removeAttribute("highlighted");
|
||||
}
|
||||
|
||||
currHighlightedThumbnail.setAttribute("highlighted", "");
|
||||
currHighlightedThumbnail.scrollIntoView();
|
||||
this._highlightedThumbnailIndex = index;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the currently highlighted thumbnail in this container.
|
||||
* @return number
|
||||
*/
|
||||
get highlightedThumbnail() {
|
||||
return this._highlightedThumbnailIndex;
|
||||
},
|
||||
|
||||
/**
|
||||
* The select listener for this container.
|
||||
*/
|
||||
_onSelect: function({ detail: callItem }) {
|
||||
if (!callItem) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Some of the stepping buttons don't make sense specifically while the
|
||||
// last function call is selected.
|
||||
if (this.selectedIndex == this.itemCount - 1) {
|
||||
$("#resume").setAttribute("disabled", "true");
|
||||
$("#step-over").setAttribute("disabled", "true");
|
||||
$("#step-out").setAttribute("disabled", "true");
|
||||
} else {
|
||||
$("#resume").removeAttribute("disabled");
|
||||
$("#step-over").removeAttribute("disabled");
|
||||
$("#step-out").removeAttribute("disabled");
|
||||
}
|
||||
|
||||
// Correlate the currently selected item with the function selection
|
||||
// slider's value. Avoid triggering a redundant selection event.
|
||||
this._ignoreSliderChanges = true;
|
||||
this._slider.value = this.selectedIndex;
|
||||
this._ignoreSliderChanges = false;
|
||||
|
||||
// Can't generate screenshots for function call actors loaded from disk.
|
||||
// XXX: Bug 984844.
|
||||
if (callItem.attachment.actor.isLoadedFromDisk) {
|
||||
return;
|
||||
}
|
||||
|
||||
// To keep continuous selection buttery smooth (for example, while pressing
|
||||
// the DOWN key or moving the slider), only display the screenshot after
|
||||
// any kind of user input stops.
|
||||
setConditionalTimeout("screenshot-display", SCREENSHOT_DISPLAY_DELAY, () => {
|
||||
return !this._isSliding;
|
||||
}, () => {
|
||||
let frameSnapshot = SnapshotsListView.selectedItem.attachment.actor
|
||||
let functionCall = callItem.attachment.actor;
|
||||
frameSnapshot.generateScreenshotFor(functionCall).then(screenshot => {
|
||||
this.showScreenshot(screenshot);
|
||||
this.highlightedThumbnail = screenshot.index;
|
||||
}).catch(Cu.reportError);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The mousedown listener for the call selection slider.
|
||||
*/
|
||||
_onSlideMouseDown: function() {
|
||||
this._isSliding = true;
|
||||
},
|
||||
|
||||
/**
|
||||
* The mouseup listener for the call selection slider.
|
||||
*/
|
||||
_onSlideMouseUp: function() {
|
||||
this._isSliding = false;
|
||||
},
|
||||
|
||||
/**
|
||||
* The change listener for the call selection slider.
|
||||
*/
|
||||
_onSlide: function() {
|
||||
// Avoid performing any operations when programatically changing the value.
|
||||
if (this._ignoreSliderChanges) {
|
||||
return;
|
||||
}
|
||||
let selectedFunctionCallIndex = this.selectedIndex = this._slider.value;
|
||||
|
||||
// While sliding, immediately show the most relevant thumbnail for a
|
||||
// function call, for a nice diff-like animation effect between draws.
|
||||
let thumbnails = SnapshotsListView.selectedItem.attachment.thumbnails;
|
||||
let thumbnail = getThumbnailForCall(thumbnails, selectedFunctionCallIndex);
|
||||
|
||||
// Avoid drawing and highlighting if the selected function call has the
|
||||
// same thumbnail as the last one.
|
||||
if (thumbnail.index == this.highlightedThumbnail) {
|
||||
return;
|
||||
}
|
||||
// If a thumbnail wasn't found (e.g. the backend avoids creating thumbnails
|
||||
// when rendering offscreen), simply defer to the first available one.
|
||||
if (thumbnail.index == -1) {
|
||||
thumbnail = thumbnails[0];
|
||||
}
|
||||
|
||||
let { index, width, height, flipped, pixels } = thumbnail;
|
||||
this.highlightedThumbnail = index;
|
||||
|
||||
let screenshotNode = $("#screenshot-image");
|
||||
screenshotNode.setAttribute("flipped", flipped);
|
||||
drawBackground("screenshot-rendering", width, height, pixels);
|
||||
},
|
||||
|
||||
/**
|
||||
* The input listener for the calls searchbox.
|
||||
*/
|
||||
_onSearch: function(e) {
|
||||
let lowerCaseSearchToken = this._searchbox.value.toLowerCase();
|
||||
|
||||
this.filterContents(e => {
|
||||
let call = e.attachment.actor;
|
||||
let name = call.name.toLowerCase();
|
||||
let file = call.file.toLowerCase();
|
||||
let line = call.line.toString().toLowerCase();
|
||||
let args = call.argsPreview.toLowerCase();
|
||||
|
||||
return name.contains(lowerCaseSearchToken) ||
|
||||
file.contains(lowerCaseSearchToken) ||
|
||||
line.contains(lowerCaseSearchToken) ||
|
||||
args.contains(lowerCaseSearchToken);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The wheel listener for the filmstrip that contains all the thumbnails.
|
||||
*/
|
||||
_onScroll: function(e) {
|
||||
this._filmstrip.scrollLeft += e.deltaX;
|
||||
},
|
||||
|
||||
/**
|
||||
* The click/dblclick listener for an item or location url in this container.
|
||||
* When expanding an item, it's corresponding call stack will be displayed.
|
||||
*/
|
||||
_onExpand: function(e) {
|
||||
let callItem = this.getItemForElement(e.target);
|
||||
let view = $(".call-item-view", callItem.target);
|
||||
|
||||
// If the call stack nodes were already created, simply re-show them
|
||||
// or jump to the corresponding file and line in the Debugger if a
|
||||
// location link was clicked.
|
||||
if (view.hasAttribute("call-stack-populated")) {
|
||||
let isExpanded = view.getAttribute("call-stack-expanded") == "true";
|
||||
|
||||
// If clicking on the location, jump to the Debugger.
|
||||
if (e.target.classList.contains("call-item-location")) {
|
||||
let { file, line } = callItem.attachment.actor;
|
||||
viewSourceInDebugger(file, line);
|
||||
return;
|
||||
}
|
||||
// Otherwise hide the call stack.
|
||||
else {
|
||||
view.setAttribute("call-stack-expanded", !isExpanded);
|
||||
$(".call-item-stack", view).hidden = isExpanded;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let list = document.createElement("vbox");
|
||||
list.className = "call-item-stack";
|
||||
view.setAttribute("call-stack-populated", "");
|
||||
view.setAttribute("call-stack-expanded", "true");
|
||||
view.appendChild(list);
|
||||
|
||||
/**
|
||||
* Creates a function call nodes in this container for a stack.
|
||||
*/
|
||||
let display = stack => {
|
||||
for (let i = 1; i < stack.length; i++) {
|
||||
let call = stack[i];
|
||||
|
||||
let contents = document.createElement("hbox");
|
||||
contents.className = "call-item-stack-fn";
|
||||
contents.style.MozPaddingStart = (i * STACK_FUNC_INDENTATION) + "px";
|
||||
|
||||
let name = document.createElement("label");
|
||||
name.className = "plain call-item-stack-fn-name";
|
||||
name.setAttribute("value", "↳ " + call.name + "()");
|
||||
contents.appendChild(name);
|
||||
|
||||
let spacer = document.createElement("spacer");
|
||||
spacer.setAttribute("flex", "100");
|
||||
contents.appendChild(spacer);
|
||||
|
||||
let location = document.createElement("label");
|
||||
location.className = "plain call-item-stack-fn-location";
|
||||
location.setAttribute("value", getFileName(call.file) + ":" + call.line);
|
||||
location.setAttribute("crop", "start");
|
||||
location.setAttribute("flex", "1");
|
||||
location.addEventListener("mousedown", e => this._onStackFileClick(e, call));
|
||||
contents.appendChild(location);
|
||||
|
||||
list.appendChild(contents);
|
||||
}
|
||||
|
||||
window.emit(EVENTS.CALL_STACK_DISPLAYED);
|
||||
};
|
||||
|
||||
// If this animation snapshot is loaded from disk, there are no corresponding
|
||||
// backend actors available and the data is immediately available.
|
||||
let functionCall = callItem.attachment.actor;
|
||||
if (functionCall.isLoadedFromDisk) {
|
||||
display(functionCall.stack);
|
||||
}
|
||||
// ..otherwise we need to request the function call stack from the backend.
|
||||
else {
|
||||
callItem.attachment.actor.getDetails().then(fn => display(fn.stack));
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for a location link in the call stack.
|
||||
*
|
||||
* @param string file
|
||||
* The url of the source owning the function.
|
||||
* @param number line
|
||||
* The line of the respective function.
|
||||
*/
|
||||
_onStackFileClick: function(e, { file, line }) {
|
||||
viewSourceInDebugger(file, line);
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for a thumbnail in the filmstrip.
|
||||
*
|
||||
* @param number index
|
||||
* The function index in the recorded animation frame snapshot.
|
||||
*/
|
||||
_onThumbnailClick: function(e, index) {
|
||||
this.selectedIndex = index;
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for the "resume" button in this container's toolbar.
|
||||
*/
|
||||
_onResume: function() {
|
||||
// Jump to the next draw call in the recorded animation frame snapshot.
|
||||
let drawCall = getNextDrawCall(this.items, this.selectedItem);
|
||||
if (drawCall) {
|
||||
this.selectedItem = drawCall;
|
||||
return;
|
||||
}
|
||||
|
||||
// If there are no more draw calls, just jump to the last context call.
|
||||
this._onStepOut();
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for the "step over" button in this container's toolbar.
|
||||
*/
|
||||
_onStepOver: function() {
|
||||
this.selectedIndex++;
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for the "step in" button in this container's toolbar.
|
||||
*/
|
||||
_onStepIn: function() {
|
||||
if (this.selectedIndex == -1) {
|
||||
this._onResume();
|
||||
return;
|
||||
}
|
||||
let callItem = this.selectedItem;
|
||||
let { file, line } = callItem.attachment.actor;
|
||||
viewSourceInDebugger(file, line);
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for the "step out" button in this container's toolbar.
|
||||
*/
|
||||
_onStepOut: function() {
|
||||
this.selectedIndex = this.itemCount - 1;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Localization convenience methods.
|
||||
*/
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
<script src="chrome://global/content/devtools/theme-switching.js"/>
|
||||
<script type="application/javascript" src="canvasdebugger.js"/>
|
||||
<script type="application/javascript" src="canvasdebugger/callslist.js"/>
|
||||
<script type="application/javascript" src="canvasdebugger/snapshotslist.js"/>
|
||||
|
||||
<hbox class="theme-body" flex="1">
|
||||
<vbox id="snapshots-pane">
|
||||
@@ -71,13 +73,15 @@
|
||||
<label value="&canvasDebuggerUI.emptyNotice2;"/>
|
||||
</hbox>
|
||||
|
||||
<hbox id="import-notice"
|
||||
class="notice-container"
|
||||
<hbox id="waiting-notice"
|
||||
class="notice-container devtools-throbber"
|
||||
align="center"
|
||||
pack="center"
|
||||
flex="1"
|
||||
hidden="true">
|
||||
<label value="&canvasDebuggerUI.importNotice;"/>
|
||||
<label id="requests-menu-waiting-notice-label"
|
||||
class="plain"
|
||||
value="&canvasDebuggerUI.waitingNotice;"/>
|
||||
</hbox>
|
||||
|
||||
<box id="debugging-pane-contents"
|
||||
|
||||
@@ -0,0 +1,496 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Functions handling the recorded animation frame snapshots UI.
|
||||
*/
|
||||
let SnapshotsListView = Heritage.extend(WidgetMethods, {
|
||||
/**
|
||||
* Initialization function, called when the tool is started.
|
||||
*/
|
||||
initialize: function() {
|
||||
this.widget = new SideMenuWidget($("#snapshots-list"), {
|
||||
showArrows: true
|
||||
});
|
||||
|
||||
this._onSelect = this._onSelect.bind(this);
|
||||
this._onClearButtonClick = this._onClearButtonClick.bind(this);
|
||||
this._onRecordButtonClick = this._onRecordButtonClick.bind(this);
|
||||
this._onImportButtonClick = this._onImportButtonClick.bind(this);
|
||||
this._onSaveButtonClick = this._onSaveButtonClick.bind(this);
|
||||
this._onRecordSuccess = this._onRecordSuccess.bind(this);
|
||||
this._onRecordFailure = this._onRecordFailure.bind(this);
|
||||
this._stopRecordingAnimation = this._stopRecordingAnimation.bind(this);
|
||||
|
||||
window.on(EVENTS.SNAPSHOT_RECORDING_FINISHED, this._enableRecordButton);
|
||||
this.emptyText = L10N.getStr("noSnapshotsText");
|
||||
this.widget.addEventListener("select", this._onSelect, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function, called when the tool is closed.
|
||||
*/
|
||||
destroy: function() {
|
||||
clearNamedTimeout("canvas-actor-recording");
|
||||
window.off(EVENTS.SNAPSHOT_RECORDING_FINISHED, this._enableRecordButton);
|
||||
this.widget.removeEventListener("select", this._onSelect, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a snapshot entry to this container.
|
||||
*
|
||||
* @return object
|
||||
* The newly inserted item.
|
||||
*/
|
||||
addSnapshot: function() {
|
||||
let contents = document.createElement("hbox");
|
||||
contents.className = "snapshot-item";
|
||||
|
||||
let thumbnail = document.createElementNS(HTML_NS, "canvas");
|
||||
thumbnail.className = "snapshot-item-thumbnail";
|
||||
thumbnail.width = CanvasFront.THUMBNAIL_SIZE;
|
||||
thumbnail.height = CanvasFront.THUMBNAIL_SIZE;
|
||||
|
||||
let title = document.createElement("label");
|
||||
title.className = "plain snapshot-item-title";
|
||||
title.setAttribute("value",
|
||||
L10N.getFormatStr("snapshotsList.itemLabel", this.itemCount + 1));
|
||||
|
||||
let calls = document.createElement("label");
|
||||
calls.className = "plain snapshot-item-calls";
|
||||
calls.setAttribute("value",
|
||||
L10N.getStr("snapshotsList.loadingLabel"));
|
||||
|
||||
let save = document.createElement("label");
|
||||
save.className = "plain snapshot-item-save";
|
||||
save.addEventListener("click", this._onSaveButtonClick, false);
|
||||
|
||||
let spacer = document.createElement("spacer");
|
||||
spacer.setAttribute("flex", "1");
|
||||
|
||||
let footer = document.createElement("hbox");
|
||||
footer.className = "snapshot-item-footer";
|
||||
footer.appendChild(save);
|
||||
|
||||
let details = document.createElement("vbox");
|
||||
details.className = "snapshot-item-details";
|
||||
details.appendChild(title);
|
||||
details.appendChild(calls);
|
||||
details.appendChild(spacer);
|
||||
details.appendChild(footer);
|
||||
|
||||
contents.appendChild(thumbnail);
|
||||
contents.appendChild(details);
|
||||
|
||||
// Append a recorded snapshot item to this container.
|
||||
return this.push([contents], {
|
||||
attachment: {
|
||||
// The snapshot and function call actors, along with the thumbnails
|
||||
// will be available as soon as recording finishes.
|
||||
actor: null,
|
||||
calls: null,
|
||||
thumbnails: null,
|
||||
screenshot: null
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes the last snapshot added, in the event no requestAnimationFrame loop was found.
|
||||
*/
|
||||
removeLastSnapshot: function () {
|
||||
this.removeAt(this.itemCount - 1);
|
||||
// If this is the only item, revert back to the empty notice
|
||||
if (this.itemCount === 0) {
|
||||
$("#empty-notice").hidden = false;
|
||||
$("#waiting-notice").hidden = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Customizes a shapshot in this container.
|
||||
*
|
||||
* @param Item snapshotItem
|
||||
* An item inserted via `SnapshotsListView.addSnapshot`.
|
||||
* @param object snapshotActor
|
||||
* The frame snapshot actor received from the backend.
|
||||
* @param object snapshotOverview
|
||||
* Additional data about the snapshot received from the backend.
|
||||
*/
|
||||
customizeSnapshot: function(snapshotItem, snapshotActor, snapshotOverview) {
|
||||
// Make sure the function call actors are stored on the item,
|
||||
// to be used when populating the CallsListView.
|
||||
snapshotItem.attachment.actor = snapshotActor;
|
||||
let functionCalls = snapshotItem.attachment.calls = snapshotOverview.calls;
|
||||
let thumbnails = snapshotItem.attachment.thumbnails = snapshotOverview.thumbnails;
|
||||
let screenshot = snapshotItem.attachment.screenshot = snapshotOverview.screenshot;
|
||||
|
||||
let lastThumbnail = thumbnails[thumbnails.length - 1];
|
||||
let { width, height, flipped, pixels } = lastThumbnail;
|
||||
|
||||
let thumbnailNode = $(".snapshot-item-thumbnail", snapshotItem.target);
|
||||
thumbnailNode.setAttribute("flipped", flipped);
|
||||
drawImage(thumbnailNode, width, height, pixels, { centered: true });
|
||||
|
||||
let callsNode = $(".snapshot-item-calls", snapshotItem.target);
|
||||
let drawCalls = functionCalls.filter(e => CanvasFront.DRAW_CALLS.has(e.name));
|
||||
|
||||
let drawCallsStr = PluralForm.get(drawCalls.length,
|
||||
L10N.getStr("snapshotsList.drawCallsLabel"));
|
||||
let funcCallsStr = PluralForm.get(functionCalls.length,
|
||||
L10N.getStr("snapshotsList.functionCallsLabel"));
|
||||
|
||||
callsNode.setAttribute("value",
|
||||
drawCallsStr.replace("#1", drawCalls.length) + ", " +
|
||||
funcCallsStr.replace("#1", functionCalls.length));
|
||||
|
||||
let saveNode = $(".snapshot-item-save", snapshotItem.target);
|
||||
saveNode.setAttribute("disabled", !!snapshotItem.isLoadedFromDisk);
|
||||
saveNode.setAttribute("value", snapshotItem.isLoadedFromDisk
|
||||
? L10N.getStr("snapshotsList.loadedLabel")
|
||||
: L10N.getStr("snapshotsList.saveLabel"));
|
||||
|
||||
// Make sure there's always a selected item available.
|
||||
if (!this.selectedItem) {
|
||||
this.selectedIndex = 0;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* The select listener for this container.
|
||||
*/
|
||||
_onSelect: function({ detail: snapshotItem }) {
|
||||
if (!snapshotItem) {
|
||||
return;
|
||||
}
|
||||
let { calls, thumbnails, screenshot } = snapshotItem.attachment;
|
||||
|
||||
$("#reload-notice").hidden = true;
|
||||
$("#empty-notice").hidden = true;
|
||||
$("#waiting-notice").hidden = false;
|
||||
|
||||
$("#debugging-pane-contents").hidden = true;
|
||||
$("#screenshot-container").hidden = true;
|
||||
$("#snapshot-filmstrip").hidden = true;
|
||||
|
||||
Task.spawn(function*() {
|
||||
// Wait for a few milliseconds between presenting the function calls,
|
||||
// screenshot and thumbnails, to allow each component being
|
||||
// sequentially drawn. This gives the illusion of snappiness.
|
||||
|
||||
yield DevToolsUtils.waitForTime(SNAPSHOT_DATA_DISPLAY_DELAY);
|
||||
CallsListView.showCalls(calls);
|
||||
$("#debugging-pane-contents").hidden = false;
|
||||
$("#waiting-notice").hidden = true;
|
||||
|
||||
yield DevToolsUtils.waitForTime(SNAPSHOT_DATA_DISPLAY_DELAY);
|
||||
CallsListView.showThumbnails(thumbnails);
|
||||
$("#snapshot-filmstrip").hidden = false;
|
||||
|
||||
yield DevToolsUtils.waitForTime(SNAPSHOT_DATA_DISPLAY_DELAY);
|
||||
CallsListView.showScreenshot(screenshot);
|
||||
$("#screenshot-container").hidden = false;
|
||||
|
||||
window.emit(EVENTS.SNAPSHOT_RECORDING_SELECTED);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for the "clear" button in this container.
|
||||
*/
|
||||
_onClearButtonClick: function() {
|
||||
Task.spawn(function*() {
|
||||
SnapshotsListView.empty();
|
||||
CallsListView.empty();
|
||||
|
||||
$("#reload-notice").hidden = true;
|
||||
$("#empty-notice").hidden = true;
|
||||
$("#waiting-notice").hidden = true;
|
||||
|
||||
if (yield gFront.isInitialized()) {
|
||||
$("#empty-notice").hidden = false;
|
||||
} else {
|
||||
$("#reload-notice").hidden = false;
|
||||
}
|
||||
|
||||
$("#debugging-pane-contents").hidden = true;
|
||||
$("#screenshot-container").hidden = true;
|
||||
$("#snapshot-filmstrip").hidden = true;
|
||||
|
||||
window.emit(EVENTS.SNAPSHOTS_LIST_CLEARED);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for the "record" button in this container.
|
||||
*/
|
||||
_onRecordButtonClick: function () {
|
||||
this._disableRecordButton();
|
||||
|
||||
if (this._recording) {
|
||||
this._stopRecordingAnimation();
|
||||
return;
|
||||
}
|
||||
|
||||
// Insert a "dummy" snapshot item in the view, to hint that recording
|
||||
// has now started. However, wait for a few milliseconds before actually
|
||||
// starting the recording, since that might block rendering and prevent
|
||||
// the dummy snapshot item from being drawn.
|
||||
this.addSnapshot();
|
||||
|
||||
// If this is the first item, immediately show the "Loading?? notice.
|
||||
if (this.itemCount == 1) {
|
||||
$("#empty-notice").hidden = true;
|
||||
$("#waiting-notice").hidden = false;
|
||||
}
|
||||
|
||||
this._recordAnimation();
|
||||
},
|
||||
|
||||
/**
|
||||
* Makes the record button able to be clicked again.
|
||||
*/
|
||||
_enableRecordButton: function () {
|
||||
$("#record-snapshot").removeAttribute("disabled");
|
||||
},
|
||||
|
||||
/**
|
||||
* Makes the record button unable to be clicked.
|
||||
*/
|
||||
_disableRecordButton: function () {
|
||||
$("#record-snapshot").setAttribute("disabled", true);
|
||||
},
|
||||
|
||||
/**
|
||||
* Begins recording an animation.
|
||||
*/
|
||||
_recordAnimation: Task.async(function *() {
|
||||
if (this._recording) {
|
||||
return;
|
||||
}
|
||||
this._recording = true;
|
||||
$("#record-snapshot").setAttribute("checked", "true");
|
||||
|
||||
setNamedTimeout("canvas-actor-recording", CANVAS_ACTOR_RECORDING_ATTEMPT, this._stopRecordingAnimation);
|
||||
|
||||
yield DevToolsUtils.waitForTime(SNAPSHOT_START_RECORDING_DELAY);
|
||||
window.emit(EVENTS.SNAPSHOT_RECORDING_STARTED);
|
||||
|
||||
gFront.recordAnimationFrame().then(snapshot => {
|
||||
if (snapshot) {
|
||||
this._onRecordSuccess(snapshot);
|
||||
} else {
|
||||
this._onRecordFailure();
|
||||
}
|
||||
});
|
||||
|
||||
// Wait another delay before reenabling the button to stop the recording
|
||||
// if a recording is not found.
|
||||
yield DevToolsUtils.waitForTime(SNAPSHOT_START_RECORDING_DELAY);
|
||||
this._enableRecordButton();
|
||||
}),
|
||||
|
||||
/**
|
||||
* Stops recording animation. Called when a click on the stopwatch occurs during a recording,
|
||||
* or if a recording times out.
|
||||
*/
|
||||
_stopRecordingAnimation: Task.async(function *() {
|
||||
clearNamedTimeout("canvas-actor-recording");
|
||||
let actorCanStop = yield gTarget.actorHasMethod("canvas", "stopRecordingAnimationFrame");
|
||||
|
||||
if (actorCanStop) {
|
||||
yield gFront.stopRecordingAnimationFrame();
|
||||
}
|
||||
// If actor does not have the method to stop recording (Fx39+),
|
||||
// manually call the record failure method. This will call a connection failure
|
||||
// on disconnect as a result of `gFront.recordAnimationFrame()` never resolving,
|
||||
// but this is better than it hanging when there is no requestAnimationFrame anyway.
|
||||
else {
|
||||
this._onRecordFailure();
|
||||
}
|
||||
|
||||
this._recording = false;
|
||||
$("#record-snapshot").removeAttribute("checked");
|
||||
this._enableRecordButton();
|
||||
}),
|
||||
|
||||
/**
|
||||
* Resolves from the front's recordAnimationFrame to setup the interface with the screenshots.
|
||||
*/
|
||||
_onRecordSuccess: Task.async(function *(snapshotActor) {
|
||||
// Clear bail-out case if frame found in CANVAS_ACTOR_RECORDING_ATTEMPT milliseconds
|
||||
clearNamedTimeout("canvas-actor-recording");
|
||||
let snapshotItem = this.getItemAtIndex(this.itemCount - 1);
|
||||
let snapshotOverview = yield snapshotActor.getOverview();
|
||||
this.customizeSnapshot(snapshotItem, snapshotActor, snapshotOverview);
|
||||
|
||||
this._recording = false;
|
||||
$("#record-snapshot").removeAttribute("checked");
|
||||
|
||||
window.emit(EVENTS.SNAPSHOT_RECORDING_COMPLETED);
|
||||
window.emit(EVENTS.SNAPSHOT_RECORDING_FINISHED);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Called as a reject from the front's recordAnimationFrame.
|
||||
*/
|
||||
_onRecordFailure: function () {
|
||||
clearNamedTimeout("canvas-actor-recording");
|
||||
showNotification(gToolbox, "canvas-debugger-timeout", L10N.getStr("recordingTimeoutFailure"));
|
||||
window.emit(EVENTS.SNAPSHOT_RECORDING_CANCELLED);
|
||||
window.emit(EVENTS.SNAPSHOT_RECORDING_FINISHED);
|
||||
this.removeLastSnapshot();
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for the "import" button in this container.
|
||||
*/
|
||||
_onImportButtonClick: function() {
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
fp.init(window, L10N.getStr("snapshotsList.saveDialogTitle"), Ci.nsIFilePicker.modeOpen);
|
||||
fp.appendFilter(L10N.getStr("snapshotsList.saveDialogJSONFilter"), "*.json");
|
||||
fp.appendFilter(L10N.getStr("snapshotsList.saveDialogAllFilter"), "*.*");
|
||||
|
||||
if (fp.show() != Ci.nsIFilePicker.returnOK) {
|
||||
return;
|
||||
}
|
||||
|
||||
let channel = NetUtil.newChannel2(fp.file,
|
||||
null,
|
||||
null,
|
||||
window.document,
|
||||
null, // aLoadingPrincipal
|
||||
null, // aTriggeringPrincipal
|
||||
Ci.nsILoadInfo.SEC_NORMAL,
|
||||
Ci.nsIContentPolicy.TYPE_OTHER);
|
||||
channel.contentType = "text/plain";
|
||||
|
||||
NetUtil.asyncFetch2(channel, (inputStream, status) => {
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
console.error("Could not import recorded animation frame snapshot file.");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
let string = NetUtil.readInputStreamToString(inputStream, inputStream.available());
|
||||
var data = JSON.parse(string);
|
||||
} catch (e) {
|
||||
console.error("Could not read animation frame snapshot file.");
|
||||
return;
|
||||
}
|
||||
if (data.fileType != CALLS_LIST_SERIALIZER_IDENTIFIER) {
|
||||
console.error("Unrecognized animation frame snapshot file.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Add a `isLoadedFromDisk` flag on everything to avoid sending invalid
|
||||
// requests to the backend, since we're not dealing with actors anymore.
|
||||
let snapshotItem = this.addSnapshot();
|
||||
snapshotItem.isLoadedFromDisk = true;
|
||||
data.calls.forEach(e => e.isLoadedFromDisk = true);
|
||||
|
||||
this.customizeSnapshot(snapshotItem, data.calls, data);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* The click listener for the "save" button of each item in this container.
|
||||
*/
|
||||
_onSaveButtonClick: function(e) {
|
||||
let snapshotItem = this.getItemForElement(e.target);
|
||||
|
||||
let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
|
||||
fp.init(window, L10N.getStr("snapshotsList.saveDialogTitle"), Ci.nsIFilePicker.modeSave);
|
||||
fp.appendFilter(L10N.getStr("snapshotsList.saveDialogJSONFilter"), "*.json");
|
||||
fp.appendFilter(L10N.getStr("snapshotsList.saveDialogAllFilter"), "*.*");
|
||||
fp.defaultString = "snapshot.json";
|
||||
|
||||
// Start serializing all the function call actors for the specified snapshot,
|
||||
// while the nsIFilePicker dialog is being opened. Snappy.
|
||||
let serialized = Task.spawn(function*() {
|
||||
let data = {
|
||||
fileType: CALLS_LIST_SERIALIZER_IDENTIFIER,
|
||||
version: CALLS_LIST_SERIALIZER_VERSION,
|
||||
calls: [],
|
||||
thumbnails: [],
|
||||
screenshot: null
|
||||
};
|
||||
let functionCalls = snapshotItem.attachment.calls;
|
||||
let thumbnails = snapshotItem.attachment.thumbnails;
|
||||
let screenshot = snapshotItem.attachment.screenshot;
|
||||
|
||||
// Prepare all the function calls for serialization.
|
||||
yield DevToolsUtils.yieldingEach(functionCalls, (call, i) => {
|
||||
let { type, name, file, line, argsPreview, callerPreview } = call;
|
||||
return call.getDetails().then(({ stack }) => {
|
||||
data.calls[i] = {
|
||||
type: type,
|
||||
name: name,
|
||||
file: file,
|
||||
line: line,
|
||||
stack: stack,
|
||||
argsPreview: argsPreview,
|
||||
callerPreview: callerPreview
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
// Prepare all the thumbnails for serialization.
|
||||
yield DevToolsUtils.yieldingEach(thumbnails, (thumbnail, i) => {
|
||||
let { index, width, height, flipped, pixels } = thumbnail;
|
||||
data.thumbnails.push({ index, width, height, flipped, pixels });
|
||||
});
|
||||
|
||||
// Prepare the screenshot for serialization.
|
||||
let { index, width, height, flipped, pixels } = screenshot;
|
||||
data.screenshot = { index, width, height, flipped, pixels };
|
||||
|
||||
let string = JSON.stringify(data);
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
|
||||
createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
|
||||
converter.charset = "UTF-8";
|
||||
return converter.convertToInputStream(string);
|
||||
});
|
||||
|
||||
// Open the nsIFilePicker and wait for the function call actors to finish
|
||||
// being serialized, in order to save the generated JSON data to disk.
|
||||
fp.open({ done: result => {
|
||||
if (result == Ci.nsIFilePicker.returnCancel) {
|
||||
return;
|
||||
}
|
||||
let footer = $(".snapshot-item-footer", snapshotItem.target);
|
||||
let save = $(".snapshot-item-save", snapshotItem.target);
|
||||
|
||||
// Show a throbber and a "Saving?? label if serializing isn't immediate.
|
||||
setNamedTimeout("call-list-save", CALLS_LIST_SLOW_SAVE_DELAY, () => {
|
||||
footer.classList.add("devtools-throbber");
|
||||
save.setAttribute("disabled", "true");
|
||||
save.setAttribute("value", L10N.getStr("snapshotsList.savingLabel"));
|
||||
});
|
||||
|
||||
serialized.then(inputStream => {
|
||||
let outputStream = FileUtils.openSafeFileOutputStream(fp.file);
|
||||
|
||||
NetUtil.asyncCopy(inputStream, outputStream, status => {
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
console.error("Could not save recorded animation frame snapshot file.");
|
||||
}
|
||||
clearNamedTimeout("call-list-save");
|
||||
footer.classList.remove("devtools-throbber");
|
||||
save.removeAttribute("disabled");
|
||||
save.setAttribute("value", L10N.getStr("snapshotsList.saveLabel"));
|
||||
});
|
||||
});
|
||||
}});
|
||||
}
|
||||
});
|
||||
|
||||
function showNotification (toolbox, name, message) {
|
||||
let notificationBox = toolbox.getNotificationBox();
|
||||
let notification = notificationBox.getNotificationWithValue(name);
|
||||
if (!notification) {
|
||||
notificationBox.appendNotification(message, name, "", notificationBox.PRIORITY_WARNING_HIGH);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
subsuite = devtools
|
||||
support-files =
|
||||
doc_raf-begin.html
|
||||
doc_settimeout.html
|
||||
doc_no-canvas.html
|
||||
doc_simple-canvas.html
|
||||
doc_simple-canvas-bitmasks.html
|
||||
doc_simple-canvas-deep-stack.html
|
||||
@@ -20,6 +22,8 @@ support-files =
|
||||
[browser_canvas-actor-test-08.js]
|
||||
[browser_canvas-actor-test-09.js]
|
||||
[browser_canvas-actor-test-10.js]
|
||||
[browser_canvas-actor-test-11.js]
|
||||
[browser_canvas-actor-test-12.js]
|
||||
[browser_canvas-frontend-call-highlight.js]
|
||||
[browser_canvas-frontend-call-list.js]
|
||||
[browser_canvas-frontend-call-search.js]
|
||||
@@ -43,3 +47,5 @@ skip-if = e10s # bug 1102301 - leaks while running as a standalone directory in
|
||||
[browser_canvas-frontend-snapshot-select-01.js]
|
||||
[browser_canvas-frontend-snapshot-select-02.js]
|
||||
[browser_canvas-frontend-stepping.js]
|
||||
[browser_canvas-frontend-stop-01.js]
|
||||
[browser_canvas-frontend-stop-02.js]
|
||||
|
||||
@@ -0,0 +1,138 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that loops using setTimeout are recorded and stored
|
||||
* for a canvas context, and that the generated screenshots are correct.
|
||||
*/
|
||||
|
||||
function ifTestingSupported() {
|
||||
let { target, front } = yield initCanvasDebuggerBackend(SET_TIMEOUT_URL);
|
||||
|
||||
let navigated = once(target, "navigate");
|
||||
|
||||
yield front.setup({ reload: true });
|
||||
ok(true, "The front was setup up successfully.");
|
||||
|
||||
yield navigated;
|
||||
ok(true, "Target automatically navigated when the front was set up.");
|
||||
|
||||
let snapshotActor = yield front.recordAnimationFrame();
|
||||
ok(snapshotActor,
|
||||
"A snapshot actor was sent after recording.");
|
||||
|
||||
let animationOverview = yield snapshotActor.getOverview();
|
||||
ok(snapshotActor,
|
||||
"An animation overview could be retrieved after recording.");
|
||||
|
||||
let functionCalls = animationOverview.calls;
|
||||
ok(functionCalls,
|
||||
"An array of function call actors was sent after recording.");
|
||||
is(functionCalls.length, 8,
|
||||
"The number of function call actors is correct.");
|
||||
|
||||
is(functionCalls[0].type, CallWatcherFront.METHOD_FUNCTION,
|
||||
"The first called function is correctly identified as a method.");
|
||||
is(functionCalls[0].name, "clearRect",
|
||||
"The first called function's name is correct.");
|
||||
is(functionCalls[0].file, SET_TIMEOUT_URL,
|
||||
"The first called function's file is correct.");
|
||||
is(functionCalls[0].line, 25,
|
||||
"The first called function's line is correct.");
|
||||
is(functionCalls[0].argsPreview, "0, 0, 128, 128",
|
||||
"The first called function's args preview is correct.");
|
||||
is(functionCalls[0].callerPreview, "ctx",
|
||||
"The first called function's caller preview is correct.");
|
||||
|
||||
is(functionCalls[6].type, CallWatcherFront.METHOD_FUNCTION,
|
||||
"The penultimate called function is correctly identified as a method.");
|
||||
is(functionCalls[6].name, "fillRect",
|
||||
"The penultimate called function's name is correct.");
|
||||
is(functionCalls[6].file, SET_TIMEOUT_URL,
|
||||
"The penultimate called function's file is correct.");
|
||||
is(functionCalls[6].line, 21,
|
||||
"The penultimate called function's line is correct.");
|
||||
is(functionCalls[6].argsPreview, "10, 10, 55, 50",
|
||||
"The penultimate called function's args preview is correct.");
|
||||
is(functionCalls[6].callerPreview, "ctx",
|
||||
"The penultimate called function's caller preview is correct.");
|
||||
|
||||
is(functionCalls[7].type, CallWatcherFront.METHOD_FUNCTION,
|
||||
"The last called function is correctly identified as a method.");
|
||||
is(functionCalls[7].name, "setTimeout",
|
||||
"The last called function's name is correct.");
|
||||
is(functionCalls[7].file, SET_TIMEOUT_URL,
|
||||
"The last called function's file is correct.");
|
||||
is(functionCalls[7].line, 30,
|
||||
"The last called function's line is correct.");
|
||||
ok(functionCalls[7].argsPreview.includes("Function"),
|
||||
"The last called function's args preview is correct.");
|
||||
is(functionCalls[7].callerPreview, "",
|
||||
"The last called function's caller preview is correct.");
|
||||
|
||||
let firstNonDrawCall = yield functionCalls[1].getDetails();
|
||||
let secondNonDrawCall = yield functionCalls[3].getDetails();
|
||||
let lastNonDrawCall = yield functionCalls[7].getDetails();
|
||||
|
||||
is(firstNonDrawCall.name, "fillStyle",
|
||||
"The first non-draw function's name is correct.");
|
||||
is(secondNonDrawCall.name, "fillStyle",
|
||||
"The second non-draw function's name is correct.");
|
||||
is(lastNonDrawCall.name, "setTimeout",
|
||||
"The last non-draw function's name is correct.");
|
||||
|
||||
let firstScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[1]);
|
||||
let secondScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[3]);
|
||||
let lastScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[7]);
|
||||
|
||||
ok(firstScreenshot,
|
||||
"A screenshot was successfully retrieved for the first non-draw function.");
|
||||
ok(secondScreenshot,
|
||||
"A screenshot was successfully retrieved for the second non-draw function.");
|
||||
ok(lastScreenshot,
|
||||
"A screenshot was successfully retrieved for the last non-draw function.");
|
||||
|
||||
let firstActualScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[0]);
|
||||
ok(sameArray(firstScreenshot.pixels, firstActualScreenshot.pixels),
|
||||
"The screenshot for the first non-draw function is correct.");
|
||||
is(firstScreenshot.width, 128,
|
||||
"The screenshot for the first non-draw function has the correct width.");
|
||||
is(firstScreenshot.height, 128,
|
||||
"The screenshot for the first non-draw function has the correct height.");
|
||||
|
||||
let secondActualScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[2]);
|
||||
ok(sameArray(secondScreenshot.pixels, secondActualScreenshot.pixels),
|
||||
"The screenshot for the second non-draw function is correct.");
|
||||
is(secondScreenshot.width, 128,
|
||||
"The screenshot for the second non-draw function has the correct width.");
|
||||
is(secondScreenshot.height, 128,
|
||||
"The screenshot for the second non-draw function has the correct height.");
|
||||
|
||||
let lastActualScreenshot = yield snapshotActor.generateScreenshotFor(functionCalls[6]);
|
||||
ok(sameArray(lastScreenshot.pixels, lastActualScreenshot.pixels),
|
||||
"The screenshot for the last non-draw function is correct.");
|
||||
is(lastScreenshot.width, 128,
|
||||
"The screenshot for the last non-draw function has the correct width.");
|
||||
is(lastScreenshot.height, 128,
|
||||
"The screenshot for the last non-draw function has the correct height.");
|
||||
|
||||
ok(!sameArray(firstScreenshot.pixels, secondScreenshot.pixels),
|
||||
"The screenshots taken on consecutive draw calls are different (1).");
|
||||
ok(!sameArray(secondScreenshot.pixels, lastScreenshot.pixels),
|
||||
"The screenshots taken on consecutive draw calls are different (2).");
|
||||
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
||||
|
||||
function sameArray(a, b) {
|
||||
if (a.length != b.length) {
|
||||
return false;
|
||||
}
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (a[i] !== b[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that the recording can be disabled via stopRecordingAnimationFrame
|
||||
* in the event no rAF loop is found.
|
||||
*/
|
||||
|
||||
function ifTestingSupported() {
|
||||
let { target, front } = yield initCanvasDebuggerBackend(NO_CANVAS_URL);
|
||||
loadFrameScripts();
|
||||
|
||||
let navigated = once(target, "navigate");
|
||||
|
||||
yield front.setup({ reload: true });
|
||||
ok(true, "The front was setup up successfully.");
|
||||
|
||||
yield navigated;
|
||||
ok(true, "Target automatically navigated when the front was set up.");
|
||||
|
||||
let startRecording = front.recordAnimationFrame();
|
||||
yield front.stopRecordingAnimationFrame();
|
||||
|
||||
ok(!(yield startRecording),
|
||||
"recordAnimationFrame() does not return a SnapshotActor when cancelled.");
|
||||
|
||||
yield removeTab(target.tab);
|
||||
finish();
|
||||
}
|
||||
@@ -25,8 +25,8 @@ function ifTestingSupported() {
|
||||
"The reload notice should initially be visible.");
|
||||
is($("#empty-notice").getAttribute("hidden"), "true",
|
||||
"The empty notice should initially be hidden.");
|
||||
is($("#import-notice").getAttribute("hidden"), "true",
|
||||
"The import notice should initially be hidden.");
|
||||
is($("#waiting-notice").getAttribute("hidden"), "true",
|
||||
"The waiting notice should initially be hidden.");
|
||||
|
||||
is($("#screenshot-container").getAttribute("hidden"), "true",
|
||||
"The screenshot container should initially be hidden.");
|
||||
|
||||
@@ -32,8 +32,6 @@ function ifTestingSupported() {
|
||||
|
||||
is($("#record-snapshot").getAttribute("checked"), "true",
|
||||
"The 'record snapshot' button should now be checked.");
|
||||
is($("#record-snapshot").getAttribute("disabled"), "true",
|
||||
"The 'record snapshot' button should now be disabled.");
|
||||
is($("#record-snapshot").hasAttribute("hidden"), false,
|
||||
"The 'record snapshot' button should still be visible.");
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ function ifTestingSupported() {
|
||||
"The reload notice should now be hidden.");
|
||||
is($("#empty-notice").getAttribute("hidden"), "true",
|
||||
"The empty notice should now be hidden.");
|
||||
is($("#import-notice").hasAttribute("hidden"), false,
|
||||
"The import notice should now be visible.");
|
||||
is($("#waiting-notice").hasAttribute("hidden"), false,
|
||||
"The waiting notice should now be visible.");
|
||||
|
||||
is($("#screenshot-container").getAttribute("hidden"), "true",
|
||||
"The screenshot container should still be hidden.");
|
||||
@@ -57,8 +57,8 @@ function ifTestingSupported() {
|
||||
"The reload notice should now be hidden.");
|
||||
is($("#empty-notice").getAttribute("hidden"), "true",
|
||||
"The empty notice should now be hidden.");
|
||||
is($("#import-notice").getAttribute("hidden"), "true",
|
||||
"The import notice should now be hidden.");
|
||||
is($("#waiting-notice").getAttribute("hidden"), "true",
|
||||
"The waiting notice should now be hidden.");
|
||||
|
||||
is($("#screenshot-container").hasAttribute("hidden"), false,
|
||||
"The screenshot container should now be visible.");
|
||||
|
||||
@@ -39,8 +39,8 @@ function ifTestingSupported() {
|
||||
"The reload notice should now be hidden.");
|
||||
is($("#empty-notice").hasAttribute("hidden"), false,
|
||||
"The empty notice should now be visible.");
|
||||
is($("#import-notice").getAttribute("hidden"), "true",
|
||||
"The import notice should now be hidden.");
|
||||
is($("#waiting-notice").getAttribute("hidden"), "true",
|
||||
"The waiting notice should now be hidden.");
|
||||
|
||||
is($("#snapshot-filmstrip").getAttribute("hidden"), "true",
|
||||
"The snapshot filmstrip should still be hidden.");
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that you can stop a recording that does not have a rAF cycle.
|
||||
*/
|
||||
|
||||
function ifTestingSupported() {
|
||||
let { target, panel } = yield initCanvasDebuggerFrontend(NO_CANVAS_URL);
|
||||
let { window, EVENTS, $, SnapshotsListView } = panel.panelWin;
|
||||
|
||||
yield reload(target);
|
||||
|
||||
let recordingStarted = once(window, EVENTS.SNAPSHOT_RECORDING_STARTED);
|
||||
SnapshotsListView._onRecordButtonClick();
|
||||
|
||||
yield recordingStarted;
|
||||
|
||||
is($("#empty-notice").hidden, true, "Empty notice not shown");
|
||||
is($("#waiting-notice").hidden, false, "Waiting notice shown");
|
||||
|
||||
let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
|
||||
let recordingCancelled = once(window, EVENTS.SNAPSHOT_RECORDING_CANCELLED);
|
||||
SnapshotsListView._onRecordButtonClick();
|
||||
|
||||
yield promise.all([recordingFinished, recordingCancelled]);
|
||||
|
||||
ok(true, "Recording stopped and was considered failed.");
|
||||
|
||||
is(SnapshotsListView.itemCount, 0, "No snapshots in the list.");
|
||||
is($("#empty-notice").hidden, false, "Empty notice shown");
|
||||
is($("#waiting-notice").hidden, true, "Waiting notice not shown");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
/**
|
||||
* Tests that a recording that does not have a rAF cycle fails after timeout.
|
||||
*/
|
||||
|
||||
function ifTestingSupported() {
|
||||
let { target, panel } = yield initCanvasDebuggerFrontend(NO_CANVAS_URL);
|
||||
let { window, EVENTS, $, SnapshotsListView } = panel.panelWin;
|
||||
|
||||
yield reload(target);
|
||||
|
||||
let recordingStarted = once(window, EVENTS.SNAPSHOT_RECORDING_STARTED);
|
||||
SnapshotsListView._onRecordButtonClick();
|
||||
|
||||
yield recordingStarted;
|
||||
|
||||
is($("#empty-notice").hidden, true, "Empty notice not shown");
|
||||
is($("#waiting-notice").hidden, false, "Waiting notice shown");
|
||||
|
||||
let recordingFinished = once(window, EVENTS.SNAPSHOT_RECORDING_FINISHED);
|
||||
let recordingCancelled = once(window, EVENTS.SNAPSHOT_RECORDING_CANCELLED);
|
||||
|
||||
yield promise.all([recordingFinished, recordingCancelled]);
|
||||
|
||||
ok(true, "Recording stopped and was considered failed.");
|
||||
|
||||
is(SnapshotsListView.itemCount, 0, "No snapshots in the list.");
|
||||
is($("#empty-notice").hidden, false, "Empty notice shown");
|
||||
is($("#waiting-notice").hidden, true, "Waiting notice not shown");
|
||||
|
||||
yield teardown(panel);
|
||||
finish();
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Canvas inspector test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,37 @@
|
||||
<!-- Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ -->
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8"/>
|
||||
<title>Canvas inspector test page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<canvas width="128" height="128"></canvas>
|
||||
|
||||
<script type="text/javascript;version=1.8">
|
||||
"use strict";
|
||||
|
||||
var ctx = document.querySelector("canvas").getContext("2d");
|
||||
|
||||
function drawRect(fill, size) {
|
||||
ctx.fillStyle = fill;
|
||||
ctx.fillRect(size[0], size[1], size[2], size[3]);
|
||||
}
|
||||
|
||||
function drawScene() {
|
||||
ctx.clearRect(0, 0, 128, 128);
|
||||
drawRect("rgb(192, 192, 192)", [0, 0, 128, 128]);
|
||||
drawRect("rgba(0, 0, 192, 0.5)", [30, 30, 55, 50]);
|
||||
drawRect("rgba(192, 0, 0, 0.5)", [10, 10, 55, 50]);
|
||||
|
||||
window.setTimeout(drawScene, 50);
|
||||
}
|
||||
|
||||
drawScene();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -28,6 +28,8 @@ let mm = null
|
||||
|
||||
const FRAME_SCRIPT_UTILS_URL = "chrome://global/content/devtools/frame-script-utils.js";
|
||||
const EXAMPLE_URL = "http://example.com/browser/browser/devtools/canvasdebugger/test/";
|
||||
const SET_TIMEOUT_URL = EXAMPLE_URL + "doc_settimeout.html";
|
||||
const NO_CANVAS_URL = EXAMPLE_URL + "doc_no-canvas.html";
|
||||
const SIMPLE_CANVAS_URL = EXAMPLE_URL + "doc_simple-canvas.html";
|
||||
const SIMPLE_BITMASKS_URL = EXAMPLE_URL + "doc_simple-canvas-bitmasks.html";
|
||||
const SIMPLE_CANVAS_TRANSPARENT_URL = EXAMPLE_URL + "doc_simple-canvas-transparent.html";
|
||||
@@ -41,6 +43,8 @@ waitForExplicitFinish();
|
||||
|
||||
let gToolEnabled = Services.prefs.getBoolPref("devtools.canvasdebugger.enabled");
|
||||
|
||||
gDevTools.testing = true;
|
||||
|
||||
registerCleanupFunction(() => {
|
||||
info("finish() was called, cleaning up...");
|
||||
Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
|
||||
|
||||
@@ -606,16 +606,20 @@ DebuggerClient.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Attach to a process in order to get the form of a ChildProcessActor.
|
||||
* Fetch the ChromeActor for the main process or ChildProcessActor for a
|
||||
* a given child process ID.
|
||||
*
|
||||
* @param string aId
|
||||
* @param number aId
|
||||
* The ID for the process to attach (returned by `listProcesses`).
|
||||
* Connected to the main process if omitted, or is 0.
|
||||
*/
|
||||
attachProcess: function (aId) {
|
||||
getProcess: function (aId) {
|
||||
let packet = {
|
||||
to: 'root',
|
||||
type: 'attachProcess',
|
||||
id: aId
|
||||
to: "root",
|
||||
type: "getProcess"
|
||||
}
|
||||
if (typeof(aId) == "number") {
|
||||
packet.id = aId;
|
||||
}
|
||||
return this.request(packet);
|
||||
},
|
||||
|
||||
@@ -24,6 +24,7 @@ const commandModules = [
|
||||
"gcli/commands/paintflashing",
|
||||
"gcli/commands/qsa",
|
||||
"gcli/commands/restart",
|
||||
"gcli/commands/rulers",
|
||||
"gcli/commands/screenshot",
|
||||
"gcli/commands/tools",
|
||||
];
|
||||
|
||||
@@ -71,6 +71,7 @@ support-files =
|
||||
[browser_cmd_pref2.js]
|
||||
[browser_cmd_pref3.js]
|
||||
[browser_cmd_restart.js]
|
||||
[browser_cmd_rulers.js]
|
||||
[browser_cmd_screenshot.js]
|
||||
support-files =
|
||||
browser_cmd_screenshot.html
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Tests the various highlight command parameters and options
|
||||
|
||||
let TEST_PAGE = "data:text/html;charset=utf-8,foo";
|
||||
|
||||
function test() {
|
||||
return Task.spawn(spawnTest).then(finish, helpers.handleError);
|
||||
}
|
||||
|
||||
function* spawnTest() {
|
||||
let options = yield helpers.openTab(TEST_PAGE);
|
||||
yield helpers.openToolbar(options);
|
||||
|
||||
yield helpers.audit(options, [
|
||||
{
|
||||
setup: 'rulers',
|
||||
check: {
|
||||
input: 'rulers',
|
||||
markup: 'VVVVVV',
|
||||
status: 'VALID'
|
||||
}
|
||||
},
|
||||
{
|
||||
setup: 'rulers on',
|
||||
check: {
|
||||
input: 'rulers on',
|
||||
markup: 'VVVVVVVEE',
|
||||
status: 'ERROR'
|
||||
},
|
||||
exec: {
|
||||
output: 'Error: Too many arguments'
|
||||
}
|
||||
},
|
||||
{
|
||||
setup: 'rulers --visible',
|
||||
check: {
|
||||
input: 'rulers --visible',
|
||||
markup: 'VVVVVVVEEEEEEEEE',
|
||||
status: 'ERROR'
|
||||
},
|
||||
exec: {
|
||||
output: 'Error: Too many arguments'
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
yield helpers.closeToolbar(options);
|
||||
yield helpers.closeTab(options);
|
||||
}
|
||||
@@ -206,7 +206,9 @@ let DebuggerController = {
|
||||
|
||||
if (target.isAddon) {
|
||||
yield this._startAddonDebugging(actor);
|
||||
} else if (target.chrome) {
|
||||
} else if (!target.isTabActor) {
|
||||
// Some actors like AddonActor or RootActor for chrome debugging
|
||||
// do not support attach/detach and can be used directly
|
||||
yield this._startChromeDebugging(chromeDebugger);
|
||||
} else {
|
||||
yield this._startDebuggingTab();
|
||||
|
||||
@@ -94,6 +94,8 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
|
||||
}
|
||||
return (a in KNOWN_SOURCE_GROUPS) ? 1 : -1;
|
||||
};
|
||||
|
||||
this._addCommands();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -112,6 +114,22 @@ SourcesView.prototype = Heritage.extend(WidgetMethods, {
|
||||
this._cbTextbox.removeEventListener("keypress", this._onConditionalTextboxKeyPress, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add commands that XUL can fire.
|
||||
*/
|
||||
_addCommands: function() {
|
||||
utils.addCommands(this._commandset, {
|
||||
addBreakpointCommand: e => this._onCmdAddBreakpoint(e),
|
||||
addConditionalBreakpointCommand: e => this._onCmdAddConditionalBreakpoint(e),
|
||||
blackBoxCommand: () => this.toggleBlackBoxing(),
|
||||
unBlackBoxButton: () => this._onStopBlackBoxing(),
|
||||
prettyPrintCommand: () => this.togglePrettyPrint(),
|
||||
toggleBreakpointsCommand: () =>this.toggleBreakpoints(),
|
||||
nextSourceCommand: () => this.selectNextItem(),
|
||||
prevSourceCommand: () => this.selectPrevItem()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the preferred location to be selected in this sources container.
|
||||
* @param string aUrl
|
||||
@@ -1245,6 +1263,8 @@ TracerView.prototype = Heritage.extend(WidgetMethods, {
|
||||
|
||||
this._traceButton.setAttribute("tooltiptext", this._startTooltip);
|
||||
this.emptyText = this._tracingNotStartedString;
|
||||
|
||||
this._addCommands();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1263,6 +1283,17 @@ TracerView.prototype = Heritage.extend(WidgetMethods, {
|
||||
this._search.removeEventListener("input", this._onSearch, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add commands that XUL can fire.
|
||||
*/
|
||||
_addCommands: function() {
|
||||
utils.addCommands(document.getElementById('debuggerCommands'), {
|
||||
toggleTracing: () => this._onToggleTracing(),
|
||||
startTracing: () => this._onStartTracing(),
|
||||
clearTraces: () => this._onClear()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Function invoked by the "toggleTracing" command to switch the tracer state.
|
||||
*/
|
||||
@@ -2171,6 +2202,7 @@ WatchExpressionsView.prototype = Heritage.extend(WidgetMethods, {
|
||||
this.widget.addEventListener("click", this._onClick, false);
|
||||
|
||||
this.headerText = L10N.getStr("addWatchExpressionText");
|
||||
this._addCommands();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -2182,6 +2214,16 @@ WatchExpressionsView.prototype = Heritage.extend(WidgetMethods, {
|
||||
this.widget.removeEventListener("click", this._onClick, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add commands that XUL can fire.
|
||||
*/
|
||||
_addCommands: function() {
|
||||
utils.addCommands(document.getElementById('debuggerCommands'), {
|
||||
addWatchExpressionCommand: () => this._onCmdAddExpression(),
|
||||
removeAllWatchExpressionsCommand: () => this._onCmdRemoveAllExpressions()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a watch expression in this container.
|
||||
*
|
||||
|
||||
@@ -57,6 +57,7 @@ ToolbarView.prototype = {
|
||||
this._stepOverButton.setAttribute("tooltiptext", this._stepOverTooltip);
|
||||
this._stepInButton.setAttribute("tooltiptext", this._stepInTooltip);
|
||||
this._stepOutButton.setAttribute("tooltiptext", this._stepOutTooltip);
|
||||
this._addCommands();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -72,6 +73,18 @@ ToolbarView.prototype = {
|
||||
this._stepOutButton.removeEventListener("mousedown", this._onStepOutPressed, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add commands that XUL can fire.
|
||||
*/
|
||||
_addCommands: function() {
|
||||
utils.addCommands(document.getElementById('debuggerCommands'), {
|
||||
resumeCommand: () => this._onResumePressed(),
|
||||
stepOverCommand: () => this._onStepOverPressed(),
|
||||
stepInCommand: () => this._onStepInPressed(),
|
||||
stepOutCommand: () => this._onStepOutPressed()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Display a warning when trying to resume a debuggee while another is paused.
|
||||
* Debuggees must be unpaused in a Last-In-First-Out order.
|
||||
@@ -225,8 +238,9 @@ OptionsView.prototype = {
|
||||
this._showVariablesFilterBoxItem.setAttribute("checked", Prefs.variablesSearchboxVisible);
|
||||
this._showOriginalSourceItem.setAttribute("checked", Prefs.sourceMapsEnabled);
|
||||
this._autoBlackBoxItem.setAttribute("checked", Prefs.autoBlackBox);
|
||||
},
|
||||
|
||||
this._addCommands();
|
||||
},
|
||||
|
||||
/**
|
||||
* Destruction function, called when the debugger is closed.
|
||||
@@ -236,6 +250,22 @@ OptionsView.prototype = {
|
||||
// Nothing to do here yet.
|
||||
},
|
||||
|
||||
/**
|
||||
* Add commands that XUL can fire.
|
||||
*/
|
||||
_addCommands: function() {
|
||||
utils.addCommands(document.getElementById('debuggerCommands'), {
|
||||
toggleAutoPrettyPrint: () => this._toggleAutoPrettyPrint(),
|
||||
togglePauseOnExceptions: () => this._togglePauseOnExceptions(),
|
||||
toggleIgnoreCaughtExceptions: () => this._toggleIgnoreCaughtExceptions(),
|
||||
toggleShowPanesOnStartup: () => this._toggleShowPanesOnStartup(),
|
||||
toggleShowOnlyEnum: () => this._toggleShowVariablesOnlyEnum(),
|
||||
toggleShowVariablesFilterBox: () => this._toggleShowVariablesFilterBox(),
|
||||
toggleShowOriginalSource: () => this._toggleShowOriginalSource(),
|
||||
toggleAutoBlackBox: () => this._toggleAutoBlackBox()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Listener handling the 'gear menu' popup showing event.
|
||||
*/
|
||||
@@ -784,6 +814,8 @@ FilterView.prototype = {
|
||||
L10N.getFormatStr("searchPanelGoToLine", this._lineSearchKey));
|
||||
this._variableOperatorLabel.setAttribute("value",
|
||||
L10N.getFormatStr("searchPanelVariable", this._variableSearchKey));
|
||||
|
||||
this._addCommands();
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -799,6 +831,21 @@ FilterView.prototype = {
|
||||
this._searchbox.removeEventListener("blur", this._onBlur, false);
|
||||
},
|
||||
|
||||
/**
|
||||
* Add commands that XUL can fire.
|
||||
*/
|
||||
_addCommands: function() {
|
||||
utils.addCommands(document.getElementById('debuggerCommands'), {
|
||||
fileSearchCommand: () => this._doFileSearch(),
|
||||
globalSearchCommand: () => this._doGlobalSearch(),
|
||||
functionSearchCommand: () => this._doFunctionSearch(),
|
||||
tokenSearchCommand: () => this._doTokenSearch(),
|
||||
lineSearchCommand: () => this._doLineSearch(),
|
||||
variableSearchCommand: () => this._doVariableSearch(),
|
||||
variablesFocusCommand: () => this._doVariablesFocus()
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the entered operator and arguments in the searchbox.
|
||||
* @return array
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
<script type="application/javascript;version=1.8"
|
||||
src="chrome://global/content/devtools/theme-switching.js"/>
|
||||
<script type="text/javascript" src="chrome://global/content/globalOverlay.js"/>
|
||||
<script type="text/javascript" src="debugger/utils.js"/>
|
||||
<script type="text/javascript" src="debugger-controller.js"/>
|
||||
<script type="text/javascript" src="debugger-view.js"/>
|
||||
<script type="text/javascript" src="debugger-toolbar.js"/>
|
||||
@@ -31,72 +32,7 @@
|
||||
|
||||
<commandset id="editMenuCommands"/>
|
||||
|
||||
<commandset id="debuggerCommands">
|
||||
<command id="blackBoxCommand"
|
||||
oncommand="DebuggerView.Sources.toggleBlackBoxing()"/>
|
||||
<command id="unBlackBoxButton"
|
||||
oncommand="DebuggerView.Sources._onStopBlackBoxing()"/>
|
||||
<command id="prettyPrintCommand"
|
||||
oncommand="DebuggerView.Sources.togglePrettyPrint()"/>
|
||||
<command id="toggleBreakpointsCommand"
|
||||
oncommand="DebuggerView.Sources.toggleBreakpoints()"/>
|
||||
<command id="nextSourceCommand"
|
||||
oncommand="DebuggerView.Sources.selectNextItem()"/>
|
||||
<command id="prevSourceCommand"
|
||||
oncommand="DebuggerView.Sources.selectPrevItem()"/>
|
||||
<command id="resumeCommand"
|
||||
oncommand="DebuggerView.Toolbar._onResumePressed()"/>
|
||||
<command id="stepOverCommand"
|
||||
oncommand="DebuggerView.Toolbar._onStepOverPressed()"/>
|
||||
<command id="stepInCommand"
|
||||
oncommand="DebuggerView.Toolbar._onStepInPressed()"/>
|
||||
<command id="stepOutCommand"
|
||||
oncommand="DebuggerView.Toolbar._onStepOutPressed()"/>
|
||||
<command id="fileSearchCommand"
|
||||
oncommand="DebuggerView.Filtering._doFileSearch()"/>
|
||||
<command id="globalSearchCommand"
|
||||
oncommand="DebuggerView.Filtering._doGlobalSearch()"/>
|
||||
<command id="functionSearchCommand"
|
||||
oncommand="DebuggerView.Filtering._doFunctionSearch()"/>
|
||||
<command id="tokenSearchCommand"
|
||||
oncommand="DebuggerView.Filtering._doTokenSearch()"/>
|
||||
<command id="lineSearchCommand"
|
||||
oncommand="DebuggerView.Filtering._doLineSearch()"/>
|
||||
<command id="variableSearchCommand"
|
||||
oncommand="DebuggerView.Filtering._doVariableSearch()"/>
|
||||
<command id="variablesFocusCommand"
|
||||
oncommand="DebuggerView.Filtering._doVariablesFocus()"/>
|
||||
<command id="addBreakpointCommand"
|
||||
oncommand="DebuggerView.Sources._onCmdAddBreakpoint(event)"/>
|
||||
<command id="addConditionalBreakpointCommand"
|
||||
oncommand="DebuggerView.Sources._onCmdAddConditionalBreakpoint(event)"/>
|
||||
<command id="addWatchExpressionCommand"
|
||||
oncommand="DebuggerView.WatchExpressions._onCmdAddExpression()"/>
|
||||
<command id="removeAllWatchExpressionsCommand"
|
||||
oncommand="DebuggerView.WatchExpressions._onCmdRemoveAllExpressions()"/>
|
||||
<command id="toggleAutoPrettyPrint"
|
||||
oncommand="DebuggerView.Options._toggleAutoPrettyPrint()"/>
|
||||
<command id="togglePauseOnExceptions"
|
||||
oncommand="DebuggerView.Options._togglePauseOnExceptions()"/>
|
||||
<command id="toggleIgnoreCaughtExceptions"
|
||||
oncommand="DebuggerView.Options._toggleIgnoreCaughtExceptions()"/>
|
||||
<command id="toggleShowPanesOnStartup"
|
||||
oncommand="DebuggerView.Options._toggleShowPanesOnStartup()"/>
|
||||
<command id="toggleShowOnlyEnum"
|
||||
oncommand="DebuggerView.Options._toggleShowVariablesOnlyEnum()"/>
|
||||
<command id="toggleShowVariablesFilterBox"
|
||||
oncommand="DebuggerView.Options._toggleShowVariablesFilterBox()"/>
|
||||
<command id="toggleShowOriginalSource"
|
||||
oncommand="DebuggerView.Options._toggleShowOriginalSource()"/>
|
||||
<command id="toggleAutoBlackBox"
|
||||
oncommand="DebuggerView.Options._toggleAutoBlackBox()"/>
|
||||
<command id="toggleTracing"
|
||||
oncommand="DebuggerView.Tracer._onToggleTracing()"/>
|
||||
<command id="startTracing"
|
||||
oncommand="DebuggerView.Tracer._onStartTracing()"/>
|
||||
<command id="clearTraces"
|
||||
oncommand="DebuggerView.Tracer._onClear()"/>
|
||||
</commandset>
|
||||
<commandset id="debuggerCommands"></commandset>
|
||||
|
||||
<popupset id="debuggerPopupset">
|
||||
<menupopup id="sourceEditorContextMenu"
|
||||
|
||||
@@ -10,7 +10,7 @@ let gAddon, gClient, gThreadClient, gDebugger, gSources;
|
||||
let PREFS = [
|
||||
"devtools.canvasdebugger.enabled",
|
||||
"devtools.shadereditor.enabled",
|
||||
"devtools.profiler.enabled",
|
||||
"devtools.performance.enabled",
|
||||
"devtools.netmonitor.enabled"
|
||||
];
|
||||
function test() {
|
||||
|
||||
@@ -23,6 +23,7 @@ function test() {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
DebuggerServer.allowChromeProcess = true;
|
||||
|
||||
let transport = DebuggerServer.connectPipe();
|
||||
gClient = new DebuggerClient(transport);
|
||||
@@ -42,26 +43,26 @@ function test() {
|
||||
}
|
||||
|
||||
function testChromeActor() {
|
||||
gClient.listTabs(aResponse => {
|
||||
ok(aResponse.chromeDebugger.contains("chromeDebugger"),
|
||||
"Chrome debugger actor should identify itself accordingly.");
|
||||
|
||||
gClient.getProcess().then(aResponse => {
|
||||
gClient.addListener("newGlobal", onNewGlobal);
|
||||
gClient.addListener("newSource", onNewSource);
|
||||
|
||||
gClient.attachThread(aResponse.chromeDebugger, (aResponse, aThreadClient) => {
|
||||
gThreadClient = aThreadClient;
|
||||
let actor = aResponse.form.actor;
|
||||
gClient.attachTab(actor, (response, tabClient) => {
|
||||
tabClient.attachThread(null, (aResponse, aThreadClient) => {
|
||||
gThreadClient = aThreadClient;
|
||||
|
||||
if (aResponse.error) {
|
||||
ok(false, "Couldn't attach to the chrome debugger.");
|
||||
gAttached.reject();
|
||||
} else {
|
||||
ok(true, "Attached to the chrome debugger.");
|
||||
gAttached.resolve();
|
||||
if (aResponse.error) {
|
||||
ok(false, "Couldn't attach to the chrome debugger.");
|
||||
gAttached.reject();
|
||||
} else {
|
||||
ok(true, "Attached to the chrome debugger.");
|
||||
gAttached.resolve();
|
||||
|
||||
// Ensure that a new chrome global will be created.
|
||||
gBrowser.selectedTab = gBrowser.addTab("about:mozilla");
|
||||
}
|
||||
// Ensure that a new chrome global will be created.
|
||||
gBrowser.selectedTab = gBrowser.addTab("about:mozilla");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -563,6 +563,7 @@ AddonDebugger.prototype = {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
DebuggerServer.allowChromeProcess = true;
|
||||
|
||||
this.frame = document.createElement("iframe");
|
||||
this.frame.setAttribute("height", 400);
|
||||
@@ -581,7 +582,8 @@ AddonDebugger.prototype = {
|
||||
let targetOptions = {
|
||||
form: addonActor,
|
||||
client: this.client,
|
||||
chrome: true
|
||||
chrome: true,
|
||||
isTabActor: false
|
||||
};
|
||||
|
||||
let toolboxOptions = {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const utils = {
|
||||
/**
|
||||
* Create <command> elements within `commandset` with event handlers
|
||||
* bound to the `command` event
|
||||
*
|
||||
* @param commandset HTML Element
|
||||
* A <commandset> element
|
||||
* @param commands Object
|
||||
* An object where keys specify <command> ids and values
|
||||
* specify event handlers to be bound on the `command` event
|
||||
*/
|
||||
addCommands: function(commandset, commands) {
|
||||
Object.keys(commands).forEach(name => {
|
||||
let node = document.createElement('command');
|
||||
node.id = name;
|
||||
// XXX bug 371900: the command element must have an oncommand
|
||||
// attribute as a string set by `setAttribute` for keys to use it
|
||||
node.setAttribute('oncommand', ' ');
|
||||
node.addEventListener('command', commands[name]);
|
||||
commandset.appendChild(node);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -111,29 +111,31 @@ BrowserToolboxProcess.prototype = {
|
||||
* Initializes the debugger server.
|
||||
*/
|
||||
_initServer: function() {
|
||||
if (this.debuggerServer) {
|
||||
dumpn("The chrome toolbox server is already running.");
|
||||
return;
|
||||
}
|
||||
|
||||
dumpn("Initializing the chrome toolbox server.");
|
||||
|
||||
if (!this.loader) {
|
||||
// Create a separate loader instance, so that we can be sure to receive a
|
||||
// separate instance of the DebuggingServer from the rest of the devtools.
|
||||
// This allows us to safely use the tools against even the actors and
|
||||
// DebuggingServer itself, especially since we can mark this loader as
|
||||
// invisible to the debugger (unlike the usual loader settings).
|
||||
this.loader = new DevToolsLoader();
|
||||
this.loader.invisibleToDebugger = true;
|
||||
this.loader.main("devtools/server/main");
|
||||
this.debuggerServer = this.loader.DebuggerServer;
|
||||
dumpn("Created a separate loader instance for the DebuggerServer.");
|
||||
// Create a separate loader instance, so that we can be sure to receive a
|
||||
// separate instance of the DebuggingServer from the rest of the devtools.
|
||||
// This allows us to safely use the tools against even the actors and
|
||||
// DebuggingServer itself, especially since we can mark this loader as
|
||||
// invisible to the debugger (unlike the usual loader settings).
|
||||
this.loader = new DevToolsLoader();
|
||||
this.loader.invisibleToDebugger = true;
|
||||
this.loader.main("devtools/server/main");
|
||||
this.debuggerServer = this.loader.DebuggerServer;
|
||||
dumpn("Created a separate loader instance for the DebuggerServer.");
|
||||
|
||||
// Forward interesting events.
|
||||
this.debuggerServer.on("connectionchange", this.emit.bind(this));
|
||||
}
|
||||
// Forward interesting events.
|
||||
this.debuggerServer.on("connectionchange", this.emit.bind(this));
|
||||
|
||||
if (!this.debuggerServer.initialized) {
|
||||
this.debuggerServer.init();
|
||||
this.debuggerServer.addBrowserActors();
|
||||
dumpn("initialized and added the browser actors for the DebuggerServer.");
|
||||
}
|
||||
this.debuggerServer.init();
|
||||
this.debuggerServer.addBrowserActors();
|
||||
this.debuggerServer.allowChromeProcess = true;
|
||||
dumpn("initialized and added the browser actors for the DebuggerServer.");
|
||||
|
||||
let chromeDebuggingPort =
|
||||
Services.prefs.getIntPref("devtools.debugger.chrome-debugging-port");
|
||||
|
||||
@@ -129,11 +129,19 @@ let onConnectionReady = Task.async(function*(aType, aTraits) {
|
||||
let gParent = document.getElementById("globalActors");
|
||||
|
||||
// Build the Remote Process button
|
||||
if (Object.keys(globals).length > 1) {
|
||||
// If Fx<39, tab actors were used to be exposed on RootActor
|
||||
// but in Fx>=39, chrome is debuggable via getProcess() and ChromeActor
|
||||
if (globals.consoleActor || gClient.mainRoot.traits.allowChromeProcess) {
|
||||
let a = document.createElement("a");
|
||||
a.onclick = function() {
|
||||
openToolbox(globals, true);
|
||||
|
||||
if (gClient.mainRoot.traits.allowChromeProcess) {
|
||||
gClient.getProcess()
|
||||
.then(aResponse => {
|
||||
openToolbox(aResponse.form, true);
|
||||
});
|
||||
} else if (globals.consoleActor) {
|
||||
openToolbox(globals, true, "webconsole", false);
|
||||
}
|
||||
}
|
||||
a.title = a.textContent = window.l10n.GetStringFromName("mainProcess");
|
||||
a.className = "remote-process";
|
||||
@@ -162,7 +170,7 @@ let onConnectionReady = Task.async(function*(aType, aTraits) {
|
||||
function buildAddonLink(addon, parent) {
|
||||
let a = document.createElement("a");
|
||||
a.onclick = function() {
|
||||
openToolbox(addon, true, "jsdebugger");
|
||||
openToolbox(addon, true, "jsdebugger", false);
|
||||
}
|
||||
|
||||
a.textContent = addon.name;
|
||||
@@ -221,11 +229,12 @@ function handleConnectionTimeout() {
|
||||
* The user clicked on one of the buttons.
|
||||
* Opens the toolbox.
|
||||
*/
|
||||
function openToolbox(form, chrome=false, tool="webconsole") {
|
||||
function openToolbox(form, chrome=false, tool="webconsole", isTabActor) {
|
||||
let options = {
|
||||
form: form,
|
||||
client: gClient,
|
||||
chrome: chrome
|
||||
chrome: chrome,
|
||||
isTabActor: isTabActor
|
||||
};
|
||||
devtools.TargetFactory.forRemoteTab(options).then((target) => {
|
||||
let hostType = devtools.Toolbox.HostType.WINDOW;
|
||||
@@ -233,7 +242,7 @@ function openToolbox(form, chrome=false, tool="webconsole") {
|
||||
toolbox.once("destroyed", function() {
|
||||
gClient.close();
|
||||
});
|
||||
});
|
||||
}, console.error.bind(console));
|
||||
window.close();
|
||||
});
|
||||
}, console.error.bind(console));
|
||||
}
|
||||
|
||||
@@ -14,19 +14,23 @@ Cu.import("resource://gre/modules/devtools/Loader.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "promise",
|
||||
"resource://gre/modules/Promise.jsm", "Promise");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "console",
|
||||
"resource://gre/modules/devtools/Console.jsm");
|
||||
|
||||
//XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
|
||||
// "resource:///modules/CustomizableUI.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
|
||||
"resource://gre/modules/devtools/dbg-server.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
|
||||
"resource://gre/modules/devtools/dbg-client.jsm");
|
||||
|
||||
const EventEmitter = devtools.require("devtools/toolkit/event-emitter");
|
||||
const Telemetry = devtools.require("devtools/shared/telemetry");
|
||||
|
||||
const TABS_OPEN_PEAK_HISTOGRAM = "DEVTOOLS_TABS_OPEN_PEAK_LINEAR";
|
||||
const TABS_OPEN_AVG_HISTOGRAM = "DEVTOOLS_TABS_OPEN_AVERAGE_LINEAR";
|
||||
const TABS_PINNED_PEAK_HISTOGRAM = "DEVTOOLS_TABS_PINNED_PEAK_LINEAR";
|
||||
const TABS_PINNED_AVG_HISTOGRAM = "DEVTOOLS_TABS_PINNED_AVERAGE_LINEAR";
|
||||
|
||||
const FORBIDDEN_IDS = new Set(["toolbox", ""]);
|
||||
const MAX_ORDINAL = 99;
|
||||
|
||||
@@ -40,6 +44,7 @@ this.DevTools = function DevTools() {
|
||||
this._tools = new Map(); // Map<toolId, tool>
|
||||
this._themes = new Map(); // Map<themeId, theme>
|
||||
this._toolboxes = new Map(); // Map<target, toolbox>
|
||||
this._telemetry = new Telemetry();
|
||||
|
||||
// destroy() is an observer's handler so we need to preserve context.
|
||||
this.destroy = this.destroy.bind(this);
|
||||
@@ -467,6 +472,23 @@ DevTools.prototype = {
|
||||
return toolbox.destroy().then(() => true);
|
||||
},
|
||||
|
||||
_pingTelemetry: function() {
|
||||
let mean = function(arr) {
|
||||
if (arr.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let total = arr.reduce((a, b) => a + b);
|
||||
return Math.ceil(total / arr.length);
|
||||
};
|
||||
|
||||
let tabStats = gDevToolsBrowser._tabStats;
|
||||
this._telemetry.log(TABS_OPEN_PEAK_HISTOGRAM, tabStats.peakOpen);
|
||||
this._telemetry.log(TABS_OPEN_AVG_HISTOGRAM, mean(tabStats.histOpen));
|
||||
this._telemetry.log(TABS_PINNED_PEAK_HISTOGRAM, tabStats.peakPinned);
|
||||
this._telemetry.log(TABS_PINNED_AVG_HISTOGRAM, mean(tabStats.histPinned));
|
||||
},
|
||||
|
||||
/**
|
||||
* Called to tear down a tools provider.
|
||||
*/
|
||||
@@ -487,6 +509,9 @@ DevTools.prototype = {
|
||||
this.unregisterTool(key, true);
|
||||
}
|
||||
|
||||
this._pingTelemetry();
|
||||
this._telemetry = null;
|
||||
|
||||
// Cleaning down the toolboxes: i.e.
|
||||
// for (let [target, toolbox] of this._toolboxes) toolbox.destroy();
|
||||
// Is taken care of by the gDevToolsBrowser.forgetBrowserWindow
|
||||
@@ -522,6 +547,13 @@ let gDevToolsBrowser = {
|
||||
*/
|
||||
_trackedBrowserWindows: new Set(),
|
||||
|
||||
_tabStats: {
|
||||
peakOpen: 0,
|
||||
peakPinned: 0,
|
||||
histOpen: [],
|
||||
histPinned: []
|
||||
},
|
||||
|
||||
/**
|
||||
* This function is for the benefit of Tools:DevToolbox in
|
||||
* browser/base/content/browser-sets.inc and should not be used outside
|
||||
@@ -696,6 +728,7 @@ let gDevToolsBrowser = {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
DebuggerServer.allowChromeProcess = true;
|
||||
|
||||
let transport = DebuggerServer.connectPipe();
|
||||
let client = new DebuggerClient(transport);
|
||||
@@ -712,12 +745,13 @@ let gDevToolsBrowser = {
|
||||
return;
|
||||
}
|
||||
// Otherwise, arbitrary connect to the unique content process.
|
||||
client.attachProcess(contentProcesses[0].id)
|
||||
client.getProcess(contentProcesses[0].id)
|
||||
.then(response => {
|
||||
let options = {
|
||||
form: response.form,
|
||||
client: client,
|
||||
chrome: true
|
||||
chrome: true,
|
||||
isTabActor: false
|
||||
};
|
||||
return devtools.TargetFactory.forRemoteTab(options);
|
||||
})
|
||||
@@ -814,9 +848,12 @@ let gDevToolsBrowser = {
|
||||
broadcaster.removeAttribute("key");
|
||||
}
|
||||
|
||||
let tabContainer = win.document.getElementById("tabbrowser-tabs")
|
||||
tabContainer.addEventListener("TabSelect",
|
||||
gDevToolsBrowser._updateMenuCheckbox, false);
|
||||
let tabContainer = win.document.getElementById("tabbrowser-tabs");
|
||||
tabContainer.addEventListener("TabSelect", this, false);
|
||||
tabContainer.addEventListener("TabOpen", this, false);
|
||||
tabContainer.addEventListener("TabClose", this, false);
|
||||
tabContainer.addEventListener("TabPinned", this, false);
|
||||
tabContainer.addEventListener("TabUnpinned", this, false);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1170,6 +1207,7 @@ let gDevToolsBrowser = {
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Remove the menuitem for a tool to all open browser windows.
|
||||
*
|
||||
@@ -1238,9 +1276,40 @@ let gDevToolsBrowser = {
|
||||
}
|
||||
}
|
||||
|
||||
let tabContainer = win.document.getElementById("tabbrowser-tabs")
|
||||
tabContainer.removeEventListener("TabSelect",
|
||||
gDevToolsBrowser._updateMenuCheckbox, false);
|
||||
let tabContainer = win.document.getElementById("tabbrowser-tabs");
|
||||
tabContainer.removeEventListener("TabSelect", this, false);
|
||||
tabContainer.removeEventListener("TabOpen", this, false);
|
||||
tabContainer.removeEventListener("TabClose", this, false);
|
||||
tabContainer.removeEventListener("TabPinned", this, false);
|
||||
tabContainer.removeEventListener("TabUnpinned", this, false);
|
||||
},
|
||||
|
||||
handleEvent: function(event) {
|
||||
switch (event.type) {
|
||||
case "TabOpen":
|
||||
case "TabClose":
|
||||
case "TabPinned":
|
||||
case "TabUnpinned":
|
||||
let open = 0;
|
||||
let pinned = 0;
|
||||
|
||||
for (let win of this._trackedBrowserWindows) {
|
||||
let tabContainer = win.gBrowser.tabContainer;
|
||||
let numPinnedTabs = tabContainer.tabbrowser._numPinnedTabs;
|
||||
let numTabs = tabContainer.itemCount - numPinnedTabs;
|
||||
|
||||
open += numTabs;
|
||||
pinned += numPinnedTabs;
|
||||
}
|
||||
|
||||
this._tabStats.histOpen.push(open);
|
||||
this._tabStats.histPinned.push(pinned);
|
||||
this._tabStats.peakOpen = Math.max(open, this._tabStats.peakOpen);
|
||||
this._tabStats.peakPinned = Math.max(pinned, this._tabStats.peakPinned);
|
||||
break;
|
||||
case "TabSelect":
|
||||
gDevToolsBrowser._updateMenuCheckbox();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -175,6 +175,8 @@ function TabTarget(tab) {
|
||||
this._client = tab.client;
|
||||
this._chrome = tab.chrome;
|
||||
}
|
||||
// Default isTabActor to true if not explicitely specified
|
||||
this._isTabActor = typeof(tab.isTabActor) == "boolean" ? tab.isTabActor : true;
|
||||
}
|
||||
|
||||
TabTarget.prototype = {
|
||||
@@ -308,10 +310,21 @@ TabTarget.prototype = {
|
||||
return this._client;
|
||||
},
|
||||
|
||||
// Tells us if we are debugging content document
|
||||
// or if we are debugging chrome stuff.
|
||||
// Allows to controls which features are available against
|
||||
// a chrome or a content document.
|
||||
get chrome() {
|
||||
return this._chrome;
|
||||
},
|
||||
|
||||
// Tells us if the related actor implements TabActor interface
|
||||
// and requires to call `attach` request before being used
|
||||
// and `detach` during cleanup
|
||||
get isTabActor() {
|
||||
return this._isTabActor;
|
||||
},
|
||||
|
||||
get window() {
|
||||
// XXX - this is a footgun for e10s - there .contentWindow will be null,
|
||||
// and even though .contentWindowAsCPOW *might* work, it will not work
|
||||
@@ -429,12 +442,13 @@ TabTarget.prototype = {
|
||||
attachTab();
|
||||
});
|
||||
});
|
||||
} else if (!this.chrome) {
|
||||
} else if (this.isTabActor) {
|
||||
// In the remote debugging case, the protocol connection will have been
|
||||
// already initialized in the connection screen code.
|
||||
attachTab();
|
||||
} else {
|
||||
// Remote chrome debugging doesn't need anything at this point.
|
||||
// AddonActor and chrome debugging on RootActor doesn't inherits from TabActor and
|
||||
// doesn't need to be attached.
|
||||
this._remote.resolve(null);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ skip-if = e10s # Bug 1069044 - destroyInspector may hang during shutdown
|
||||
[browser_toolbox_sidebar_existing_tabs.js]
|
||||
[browser_toolbox_sidebar_overflow_menu.js]
|
||||
[browser_toolbox_tabsswitch_shortcuts.js]
|
||||
[browser_toolbox_textbox_context_menu.js]
|
||||
[browser_toolbox_tool_ready.js]
|
||||
[browser_toolbox_tool_remote_reopen.js]
|
||||
[browser_toolbox_transport_events.js]
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
let { DebuggerServer } =
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
|
||||
let { DebuggerClient } =
|
||||
Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
|
||||
let { devtools } =
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
|
||||
@@ -12,28 +8,19 @@ let { devtools } =
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
getChromeActors((client, response) => {
|
||||
let options = {
|
||||
form: response,
|
||||
client: client,
|
||||
chrome: true
|
||||
};
|
||||
|
||||
var client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
client.connect(() => {
|
||||
client.listTabs(response => {
|
||||
let options = {
|
||||
form: response,
|
||||
client: client,
|
||||
chrome: true
|
||||
};
|
||||
|
||||
devtools.TargetFactory.forRemoteTab(options).then(target => {
|
||||
target.on("close", () => {
|
||||
ok(true, "Target was closed");
|
||||
DebuggerServer.destroy();
|
||||
finish();
|
||||
});
|
||||
client.close();
|
||||
devtools.TargetFactory.forRemoteTab(options).then(target => {
|
||||
target.on("close", () => {
|
||||
ok(true, "Target was closed");
|
||||
finish();
|
||||
});
|
||||
client.close();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,10 +4,6 @@
|
||||
// Test support methods on Target, such as `hasActor`, `getActorDescription`,
|
||||
// `actorHasMethod` and `getTrait`.
|
||||
|
||||
let { DebuggerServer } =
|
||||
Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
|
||||
let { DebuggerClient } =
|
||||
Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
|
||||
let { devtools } =
|
||||
Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
|
||||
let { Task } =
|
||||
@@ -60,29 +56,20 @@ function* testTarget (client, target) {
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
getChromeActors((client, response) => {
|
||||
let options = {
|
||||
form: response,
|
||||
client: client,
|
||||
chrome: true
|
||||
};
|
||||
|
||||
var client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
client.connect(() => {
|
||||
client.listTabs(response => {
|
||||
let options = {
|
||||
form: response,
|
||||
client: client,
|
||||
chrome: true
|
||||
};
|
||||
|
||||
devtools.TargetFactory.forRemoteTab(options).then(Task.async(testTarget).bind(null, client));
|
||||
});
|
||||
devtools.TargetFactory.forRemoteTab(options).then(Task.async(testTarget).bind(null, client));
|
||||
});
|
||||
}
|
||||
|
||||
function close (target, client) {
|
||||
target.on("close", () => {
|
||||
ok(true, "Target was closed");
|
||||
DebuggerServer.destroy();
|
||||
finish();
|
||||
});
|
||||
client.close();
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
const URL = "data:text/html;charset=utf8,test for textbox context menu";
|
||||
|
||||
add_task(function*() {
|
||||
let tab = yield addTab(URL);
|
||||
let toolbox = yield gDevTools.showToolbox(TargetFactory.forTab(tab));
|
||||
let textboxContextMenu = toolbox.textboxContextMenuPopup;
|
||||
|
||||
ok(textboxContextMenu, "The textbox context menu is loaded in the toolbox");
|
||||
|
||||
let cmdUndo = textboxContextMenu.querySelector("[command=cmd_undo]");
|
||||
let cmdDelete = textboxContextMenu.querySelector("[command=cmd_delete]");
|
||||
let cmdSelectAll = textboxContextMenu.querySelector("[command=cmd_selectAll]");
|
||||
let cmdCut = textboxContextMenu.querySelector("[command=cmd_cut]");
|
||||
let cmdCopy = textboxContextMenu.querySelector("[command=cmd_copy]");
|
||||
let cmdPaste = textboxContextMenu.querySelector("[command=cmd_paste]");
|
||||
|
||||
info("Opening context menu");
|
||||
|
||||
let onContextMenuPopup = once(textboxContextMenu, "popupshowing");
|
||||
textboxContextMenu.openPopupAtScreen(0, 0, true);
|
||||
yield onContextMenuPopup;
|
||||
|
||||
is(cmdUndo.getAttribute("disabled"), "true", "cmdUndo is disabled");
|
||||
is(cmdDelete.getAttribute("disabled"), "true", "cmdDelete is disabled");
|
||||
is(cmdSelectAll.getAttribute("disabled"), "", "cmdSelectAll is enabled");
|
||||
is(cmdCut.getAttribute("disabled"), "true", "cmdCut is disabled");
|
||||
is(cmdCopy.getAttribute("disabled"), "true", "cmdCopy is disabled");
|
||||
is(cmdPaste.getAttribute("disabled"), "true", "cmdPaste is disabled");
|
||||
|
||||
yield cleanup(toolbox);
|
||||
});
|
||||
|
||||
function* cleanup(toolbox) {
|
||||
yield toolbox.destroy();
|
||||
gBrowser.removeCurrentTab();
|
||||
}
|
||||
@@ -152,3 +152,26 @@ function toggleAllTools(state) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getChromeActors(callback)
|
||||
{
|
||||
let { DebuggerServer } = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
|
||||
let { DebuggerClient } = Cu.import("resource://gre/modules/devtools/dbg-client.jsm", {});
|
||||
|
||||
if (!DebuggerServer.initialized) {
|
||||
DebuggerServer.init();
|
||||
DebuggerServer.addBrowserActors();
|
||||
}
|
||||
DebuggerServer.allowChromeProcess = true;
|
||||
|
||||
let client = new DebuggerClient(DebuggerServer.connectPipe());
|
||||
client.connect(() => {
|
||||
client.getProcess().then(response => {
|
||||
callback(client, response.form);
|
||||
});
|
||||
});
|
||||
|
||||
SimpleTest.registerCleanupFunction(() => {
|
||||
DebuggerServer.destroy();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
<vbox id="profiler-options" class="options-groupbox">
|
||||
<checkbox label="&options.showPlatformData.label;"
|
||||
tooltiptext="&options.showPlatformData.tooltip;"
|
||||
data-pref="devtools.profiler.ui.show-platform-data"/>
|
||||
data-pref="devtools.performance.ui.show-platform-data"/>
|
||||
</vbox>
|
||||
</vbox>
|
||||
|
||||
|
||||
@@ -38,10 +38,12 @@ let connect = Task.async(function*() {
|
||||
if (addonID) {
|
||||
gClient.listAddons(({addons}) => {
|
||||
let addonActor = addons.filter(addon => addon.id === addonID).pop();
|
||||
openToolbox(addonActor);
|
||||
openToolbox({ form: addonActor, chrome: true, isTabActor: false });
|
||||
});
|
||||
} else {
|
||||
gClient.listTabs(openToolbox);
|
||||
gClient.getProcess().then(aResponse => {
|
||||
openToolbox({ form: aResponse.form, chrome: true });
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -49,9 +51,10 @@ let connect = Task.async(function*() {
|
||||
// Certain options should be toggled since we can assume chrome debugging here
|
||||
function setPrefDefaults() {
|
||||
Services.prefs.setBoolPref("devtools.inspector.showUserAgentStyles", true);
|
||||
Services.prefs.setBoolPref("devtools.profiler.ui.show-platform-data", true);
|
||||
Services.prefs.setBoolPref("devtools.performance.ui.show-platform-data", true);
|
||||
Services.prefs.setBoolPref("browser.devedition.theme.showCustomizeButton", false);
|
||||
Services.prefs.setBoolPref("devtools.inspector.showAllAnonymousContent", true);
|
||||
Services.prefs.setBoolPref("browser.dom.window.dump.enabled", true);
|
||||
}
|
||||
|
||||
window.addEventListener("load", function() {
|
||||
@@ -65,11 +68,12 @@ function onCloseCommand(event) {
|
||||
window.close();
|
||||
}
|
||||
|
||||
function openToolbox(form) {
|
||||
function openToolbox({ form, chrome, isTabActor }) {
|
||||
let options = {
|
||||
form: form,
|
||||
client: gClient,
|
||||
chrome: true
|
||||
chrome: chrome,
|
||||
isTabActor: isTabActor
|
||||
};
|
||||
devtools.TargetFactory.forRemoteTab(options).then(target => {
|
||||
let frame = document.getElementById("toolbox-iframe");
|
||||
|
||||
@@ -10,10 +10,14 @@ const SPLITCONSOLE_ENABLED_PREF = "devtools.toolbox.splitconsoleEnabled";
|
||||
const SPLITCONSOLE_HEIGHT_PREF = "devtools.toolbox.splitconsoleHeight";
|
||||
const MIN_ZOOM = 0.5;
|
||||
const MAX_ZOOM = 2;
|
||||
const OS_HISTOGRAM = "DEVTOOLS_OS_ENUMERATED_PER_USER";
|
||||
const OS_IS_64_BITS = "DEVTOOLS_OS_IS_64_BITS_PER_USER";
|
||||
const SCREENSIZE_HISTOGRAM = "DEVTOOLS_SCREEN_RESOLUTION_ENUMERATED_PER_USER";
|
||||
|
||||
let {Cc, Ci, Cu} = require("chrome");
|
||||
let {Promise: promise} = require("resource://gre/modules/Promise.jsm");
|
||||
let EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
let Telemetry = require("devtools/shared/telemetry");
|
||||
let {getHighlighterUtils} = require("devtools/framework/toolbox-highlighter-utils");
|
||||
let {HUDService} = require("devtools/webconsole/hudservice");
|
||||
let {showDoorhanger} = require("devtools/shared/doorhanger");
|
||||
@@ -47,6 +51,20 @@ loader.lazyGetter(this, "toolboxStrings", () => {
|
||||
loader.lazyGetter(this, "Selection", () => require("devtools/framework/selection").Selection);
|
||||
loader.lazyGetter(this, "InspectorFront", () => require("devtools/server/actors/inspector").InspectorFront);
|
||||
loader.lazyRequireGetter(this, "DevToolsUtils", "devtools/toolkit/DevToolsUtils");
|
||||
loader.lazyRequireGetter(this, "getPerformanceActorsConnection", "devtools/performance/front", true);
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "screenManager", () => {
|
||||
return Cc["@mozilla.org/gfx/screenmanager;1"].getService(Ci.nsIScreenManager);
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "oscpu", () => {
|
||||
return Cc["@mozilla.org/network/protocol;1?name=http"]
|
||||
.getService(Ci.nsIHttpProtocolHandler).oscpu;
|
||||
});
|
||||
|
||||
XPCOMUtils.defineLazyGetter(this, "is64Bit", () => {
|
||||
return Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo).is64Bit;
|
||||
});
|
||||
|
||||
// White-list buttons that can be toggled to prevent adding prefs for
|
||||
// addons that have manually inserted toolbarbuttons into DOM.
|
||||
@@ -67,7 +85,8 @@ const ToolboxButtons = [
|
||||
{ id: "command-button-tilt" },
|
||||
{ id: "command-button-scratchpad" },
|
||||
{ id: "command-button-eyedropper" },
|
||||
{ id: "command-button-screenshot" }
|
||||
{ id: "command-button-screenshot" },
|
||||
{ id: "command-button-rulers"}
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -87,6 +106,7 @@ const ToolboxButtons = [
|
||||
function Toolbox(target, selectedTool, hostType, hostOptions) {
|
||||
this._target = target;
|
||||
this._toolPanels = new Map();
|
||||
this._telemetry = new Telemetry();
|
||||
|
||||
this._toolRegistered = this._toolRegistered.bind(this);
|
||||
this._toolUnregistered = this._toolUnregistered.bind(this);
|
||||
@@ -102,6 +122,7 @@ function Toolbox(target, selectedTool, hostType, hostOptions) {
|
||||
this._saveSplitConsoleHeight = this._saveSplitConsoleHeight.bind(this);
|
||||
this._onFocus = this._onFocus.bind(this);
|
||||
this._showDevEditionPromo = this._showDevEditionPromo.bind(this);
|
||||
this._updateTextboxMenuItems = this._updateTextboxMenuItems.bind(this);
|
||||
|
||||
this._target.on("close", this.destroy);
|
||||
|
||||
@@ -291,78 +312,98 @@ Toolbox.prototype = {
|
||||
/**
|
||||
* Open the toolbox
|
||||
*/
|
||||
open: function() {
|
||||
let deferred = promise.defer();
|
||||
|
||||
return this._host.create().then(iframe => {
|
||||
let deferred = promise.defer();
|
||||
|
||||
let domReady = () => {
|
||||
this.isReady = true;
|
||||
|
||||
let framesPromise = this._listFrames();
|
||||
|
||||
this.closeButton = this.doc.getElementById("toolbox-close");
|
||||
this.closeButton.addEventListener("command", this.destroy, true);
|
||||
|
||||
gDevTools.on("pref-changed", this._prefChanged);
|
||||
|
||||
let framesMenu = this.doc.getElementById("command-button-frames");
|
||||
framesMenu.addEventListener("command", this.selectFrame, true);
|
||||
|
||||
this._buildDockButtons();
|
||||
this._buildOptions();
|
||||
this._buildTabs();
|
||||
this._applyCacheSettings();
|
||||
this._applyServiceWorkersTestingSettings();
|
||||
this._addKeysToWindow();
|
||||
this._addReloadKeys();
|
||||
this._addHostListeners();
|
||||
if (this._hostOptions && this._hostOptions.zoom === false) {
|
||||
this._disableZoomKeys();
|
||||
} else {
|
||||
this._addZoomKeys();
|
||||
this._loadInitialZoom();
|
||||
}
|
||||
|
||||
this.webconsolePanel = this.doc.querySelector("#toolbox-panel-webconsole");
|
||||
this.webconsolePanel.height =
|
||||
Services.prefs.getIntPref(SPLITCONSOLE_HEIGHT_PREF);
|
||||
this.webconsolePanel.addEventListener("resize",
|
||||
this._saveSplitConsoleHeight);
|
||||
|
||||
let buttonsPromise = this._buildButtons();
|
||||
|
||||
this.selectTool(this._defaultToolId).then(panel => {
|
||||
|
||||
// Wait until the original tool is selected so that the split
|
||||
// console input will receive focus.
|
||||
let splitConsolePromise = promise.resolve();
|
||||
if (Services.prefs.getBoolPref(SPLITCONSOLE_ENABLED_PREF)) {
|
||||
splitConsolePromise = this.openSplitConsole();
|
||||
}
|
||||
|
||||
promise.all([
|
||||
splitConsolePromise,
|
||||
buttonsPromise,
|
||||
framesPromise
|
||||
]).then(() => {
|
||||
this.emit("ready");
|
||||
deferred.resolve();
|
||||
}, deferred.reject);
|
||||
});
|
||||
};
|
||||
open: function () {
|
||||
return Task.spawn(function*() {
|
||||
let iframe = yield this._host.create();
|
||||
let domReady = promise.defer();
|
||||
|
||||
// Load the toolbox-level actor fronts and utilities now
|
||||
this._target.makeRemote().then(() => {
|
||||
iframe.setAttribute("src", this._URL);
|
||||
iframe.setAttribute("aria-label", toolboxStrings("toolbox.label"))
|
||||
let domHelper = new DOMHelpers(iframe.contentWindow);
|
||||
domHelper.onceDOMReady(domReady);
|
||||
});
|
||||
yield this._target.makeRemote();
|
||||
iframe.setAttribute("src", this._URL);
|
||||
iframe.setAttribute("aria-label", toolboxStrings("toolbox.label"));
|
||||
let domHelper = new DOMHelpers(iframe.contentWindow);
|
||||
domHelper.onceDOMReady(() => domReady.resolve());
|
||||
|
||||
return deferred.promise;
|
||||
}).then(null, console.error.bind(console));
|
||||
yield domReady.promise;
|
||||
|
||||
this.isReady = true;
|
||||
let framesPromise = this._listFrames();
|
||||
|
||||
this.closeButton = this.doc.getElementById("toolbox-close");
|
||||
this.closeButton.addEventListener("command", this.destroy, true);
|
||||
|
||||
gDevTools.on("pref-changed", this._prefChanged);
|
||||
|
||||
let framesMenu = this.doc.getElementById("command-button-frames");
|
||||
framesMenu.addEventListener("command", this.selectFrame, true);
|
||||
|
||||
this.textboxContextMenuPopup =
|
||||
this.doc.getElementById("toolbox-textbox-context-popup");
|
||||
this.textboxContextMenuPopup.addEventListener("popupshowing",
|
||||
this._updateTextboxMenuItems, true);
|
||||
|
||||
this._buildDockButtons();
|
||||
this._buildOptions();
|
||||
this._buildTabs();
|
||||
this._applyCacheSettings();
|
||||
this._applyServiceWorkersTestingSettings();
|
||||
this._addKeysToWindow();
|
||||
this._addReloadKeys();
|
||||
this._addHostListeners();
|
||||
if (this._hostOptions && this._hostOptions.zoom === false) {
|
||||
this._disableZoomKeys();
|
||||
} else {
|
||||
this._addZoomKeys();
|
||||
this._loadInitialZoom();
|
||||
}
|
||||
|
||||
this.webconsolePanel = this.doc.querySelector("#toolbox-panel-webconsole");
|
||||
this.webconsolePanel.height = Services.prefs.getIntPref(SPLITCONSOLE_HEIGHT_PREF);
|
||||
this.webconsolePanel.addEventListener("resize", this._saveSplitConsoleHeight);
|
||||
|
||||
let buttonsPromise = this._buildButtons();
|
||||
|
||||
this._pingTelemetry();
|
||||
|
||||
let panel = yield this.selectTool(this._defaultToolId);
|
||||
|
||||
// Wait until the original tool is selected so that the split
|
||||
// console input will receive focus.
|
||||
let splitConsolePromise = promise.resolve();
|
||||
if (Services.prefs.getBoolPref(SPLITCONSOLE_ENABLED_PREF)) {
|
||||
splitConsolePromise = this.openSplitConsole();
|
||||
}
|
||||
|
||||
yield promise.all([
|
||||
splitConsolePromise,
|
||||
buttonsPromise,
|
||||
framesPromise
|
||||
]);
|
||||
|
||||
// Lazily connect to the profiler here and don't wait for it to complete,
|
||||
// used to intercept console.profile calls before the performance tools are open.
|
||||
let profilerReady = this._connectProfiler();
|
||||
|
||||
// However, while testing, we must wait for the performance connection to finish,
|
||||
// as most tests shut down without waiting for a toolbox destruction event,
|
||||
// resulting in the shared profiler connection being opened and closed
|
||||
// outside of the test that originally opened the toolbox.
|
||||
if (gDevTools.testing) {
|
||||
yield profilerReady;
|
||||
}
|
||||
|
||||
this.emit("ready");
|
||||
}.bind(this)).then(null, console.error.bind(console));
|
||||
},
|
||||
|
||||
_pingTelemetry: function() {
|
||||
this._telemetry.toolOpened("toolbox");
|
||||
|
||||
this._telemetry.logOncePerBrowserVersion(OS_HISTOGRAM,
|
||||
this._getOsCpu());
|
||||
this._telemetry.logOncePerBrowserVersion(OS_IS_64_BITS, is64Bit ? 1 : 0);
|
||||
this._telemetry.logOncePerBrowserVersion(SCREENSIZE_HISTOGRAM,
|
||||
this._getScreenDimensions());
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -1589,6 +1630,41 @@ Toolbox.prototype = {
|
||||
return this.doc.getElementById("toolbox-notificationbox");
|
||||
},
|
||||
|
||||
_getScreenDimensions: function() {
|
||||
let width = {};
|
||||
let height = {};
|
||||
|
||||
screenManager.primaryScreen.GetRect({}, {}, width, height);
|
||||
let dims = width.value + "x" + height.value;
|
||||
|
||||
if (width.value < 800 || height.value < 600) return 0;
|
||||
if (dims === "800x600") return 1;
|
||||
if (dims === "1024x768") return 2;
|
||||
if (dims === "1280x800") return 3;
|
||||
if (dims === "1280x1024") return 4;
|
||||
if (dims === "1366x768") return 5;
|
||||
if (dims === "1440x900") return 6;
|
||||
if (dims === "1920x1080") return 7;
|
||||
if (dims === "2560×1440") return 8;
|
||||
if (dims === "2560×1600") return 9;
|
||||
if (dims === "2880x1800") return 10;
|
||||
if (width.value > 2880 || height.value > 1800) return 12;
|
||||
|
||||
return 11; // Other dimension such as a VM.
|
||||
},
|
||||
|
||||
_getOsCpu: function() {
|
||||
if (oscpu.includes("NT 5.1") || oscpu.includes("NT 5.2")) return 0;
|
||||
if (oscpu.includes("NT 6.0")) return 1;
|
||||
if (oscpu.includes("NT 6.1")) return 2;
|
||||
if (oscpu.includes("NT 6.2")) return 3;
|
||||
if (oscpu.includes("NT 6.3")) return 4;
|
||||
if (oscpu.includes("OS X")) return 5;
|
||||
if (oscpu.includes("Linux")) return 6;
|
||||
|
||||
return 12; // Other OS.
|
||||
},
|
||||
|
||||
/**
|
||||
* Destroy the current host, and remove event listeners from its frame.
|
||||
*
|
||||
@@ -1634,6 +1710,8 @@ Toolbox.prototype = {
|
||||
this._saveSplitConsoleHeight);
|
||||
}
|
||||
this.closeButton.removeEventListener("command", this.destroy, true);
|
||||
this.textboxContextMenuPopup.removeEventListener("popupshowing",
|
||||
this._updateTextboxMenuItems, true);
|
||||
|
||||
let outstanding = [];
|
||||
for (let [id, panel] of this._toolPanels) {
|
||||
@@ -1666,12 +1744,17 @@ Toolbox.prototype = {
|
||||
}
|
||||
}));
|
||||
|
||||
// Destroy the profiler connection
|
||||
outstanding.push(this._disconnectProfiler());
|
||||
|
||||
// We need to grab a reference to win before this._host is destroyed.
|
||||
let win = this.frame.ownerGlobal;
|
||||
|
||||
if (this._requisition) {
|
||||
this._requisition.destroy();
|
||||
}
|
||||
this._telemetry.toolClosed("toolbox");
|
||||
this._telemetry.destroy();
|
||||
|
||||
// Finish all outstanding tasks (which means finish destroying panels and
|
||||
// then destroying the host, successfully or not) before destroying the
|
||||
@@ -1744,5 +1827,50 @@ Toolbox.prototype = {
|
||||
}
|
||||
let window = this.frame.contentWindow;
|
||||
showDoorhanger({ window, type: "deveditionpromo" });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Enable / disable necessary textbox menu items using globalOverlay.js.
|
||||
*/
|
||||
_updateTextboxMenuItems: function() {
|
||||
let window = this.doc.defaultView;
|
||||
['cmd_undo', 'cmd_delete', 'cmd_cut',
|
||||
'cmd_copy', 'cmd_paste','cmd_selectAll'].forEach(window.goUpdateCommand);
|
||||
},
|
||||
|
||||
getPerformanceActorsConnection: function() {
|
||||
if (!this._performanceConnection) {
|
||||
this._performanceConnection = getPerformanceActorsConnection(this.target);
|
||||
}
|
||||
return this._performanceConnection;
|
||||
},
|
||||
|
||||
/**
|
||||
* Connects to the SPS profiler when the developer tools are open. This is
|
||||
* necessary because of the WebConsole's `profile` and `profileEnd` methods.
|
||||
*/
|
||||
_connectProfiler: Task.async(function*() {
|
||||
// If target does not have profiler actor (addons), do not
|
||||
// even register the shared performance connection.
|
||||
if (!this.target.hasActor("profiler")) {
|
||||
return;
|
||||
}
|
||||
|
||||
yield this.getPerformanceActorsConnection().open();
|
||||
// Emit an event when connected, but don't wait on startup for this.
|
||||
this.emit("profiler-connected");
|
||||
}),
|
||||
|
||||
/**
|
||||
* Disconnects the underlying Performance Actor Connection. If the connection
|
||||
* has not finished initializing, as opening a toolbox does not wait,
|
||||
* the performance connection destroy method will wait for it on its own.
|
||||
*/
|
||||
_disconnectProfiler: Task.async(function*() {
|
||||
if (!this._performanceConnection) {
|
||||
return;
|
||||
}
|
||||
yield this._performanceConnection.destroy();
|
||||
this._performanceConnection = null;
|
||||
}),
|
||||
};
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
<!-- This Source Code Form is subject to the terms of the Mozilla Public
|
||||
- License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
|
||||
<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
|
||||
|
||||
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
|
||||
<!DOCTYPE window [
|
||||
<!ENTITY % toolboxDTD SYSTEM "chrome://global/locale/devtools/toolbox.dtd" >
|
||||
%toolboxDTD;
|
||||
]>
|
||||
<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
|
||||
<?xml-stylesheet href="chrome://global/skin/devtools/common.css" type="text/css"?>
|
||||
<?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
|
||||
|
||||
<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
||||
|
||||
@@ -65,6 +66,19 @@
|
||||
modifiers="accel"/>
|
||||
</keyset>
|
||||
|
||||
<popupset>
|
||||
<menupopup id="toolbox-textbox-context-popup">
|
||||
<menuitem id="cMenu_undo"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="cMenu_cut"/>
|
||||
<menuitem id="cMenu_copy"/>
|
||||
<menuitem id="cMenu_paste"/>
|
||||
<menuitem id="cMenu_delete"/>
|
||||
<menuseparator/>
|
||||
<menuitem id="cMenu_selectAll"/>
|
||||
</menupopup>
|
||||
</popupset>
|
||||
|
||||
<notificationbox id="toolbox-notificationbox" flex="1">
|
||||
<toolbar class="devtools-tabbar">
|
||||
<hbox id="toolbox-picker-container" />
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const EventEmitter = require("devtools/toolkit/event-emitter");
|
||||
const eventEmitter = new EventEmitter();
|
||||
const events = require("sdk/event/core");
|
||||
|
||||
const gcli = require("gcli/index");
|
||||
require("devtools/server/actors/inspector");
|
||||
const { RulersHighlighter } = require("devtools/server/actors/highlighter");
|
||||
|
||||
const highlighters = new WeakMap();
|
||||
|
||||
exports.items = [
|
||||
{
|
||||
name: "rulers",
|
||||
description: gcli.lookup("rulersDesc"),
|
||||
manual: gcli.lookup("rulersManual"),
|
||||
buttonId: "command-button-rulers",
|
||||
buttonClass: "command-button command-button-invertable",
|
||||
tooltipText: gcli.lookup("rulersTooltip"),
|
||||
state: {
|
||||
isChecked: function(aTarget) {
|
||||
if (aTarget.isLocalTab) {
|
||||
let window = aTarget.tab.linkedBrowser.contentWindow;
|
||||
|
||||
if (window) {
|
||||
return highlighters.has(window.document);
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
throw new Error("Unsupported target");
|
||||
}
|
||||
},
|
||||
onChange: function(aTarget, aChangeHandler) {
|
||||
eventEmitter.on("changed", aChangeHandler);
|
||||
},
|
||||
offChange: function(aTarget, aChangeHandler) {
|
||||
eventEmitter.off("changed", aChangeHandler);
|
||||
},
|
||||
},
|
||||
exec: function(args, context) {
|
||||
let env = context.environment;
|
||||
let { target } = env;
|
||||
|
||||
if (highlighters.has(env.document)) {
|
||||
highlighters.get(env.document).highlighter.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
// Build a tab context for the highlighter (which normally takes a
|
||||
// TabActor as parameter to its constructor)
|
||||
let tabContext = {
|
||||
browser: env.chromeWindow.gBrowser.getBrowserForDocument(env.document),
|
||||
window: env.window
|
||||
};
|
||||
|
||||
let emitToContext = (type, data) =>
|
||||
events.emit(tabContext, type, Object.assign({isTopLevel: true}, data))
|
||||
|
||||
target.once("navigate", emitToContext);
|
||||
target.once("will-navigate", emitToContext);
|
||||
|
||||
let highlighter = new RulersHighlighter(tabContext);
|
||||
|
||||
highlighters.set(env.document, { highlighter, listener: emitToContext });
|
||||
|
||||
events.once(highlighter, "destroy", () => {
|
||||
if (highlighters.has(env.document)) {
|
||||
let { highlighter, listener } = highlighters.get(env.document);
|
||||
|
||||
target.off("navigate", listener);
|
||||
target.off("will-navigate", listener);
|
||||
|
||||
highlighters.delete(env.document);
|
||||
}
|
||||
|
||||
eventEmitter.emit("changed", { target });
|
||||
});
|
||||
|
||||
highlighter.show();
|
||||
|
||||
eventEmitter.emit("changed", { target });
|
||||
}
|
||||
}
|
||||
];
|
||||
@@ -21,6 +21,7 @@ EXTRA_JS_MODULES.devtools.gcli.commands += [
|
||||
'commands/paintflashing.js',
|
||||
'commands/qsa.js',
|
||||
'commands/restart.js',
|
||||
'commands/rulers.js',
|
||||
'commands/screenshot.js',
|
||||
'commands/tools.js',
|
||||
]
|
||||
|
||||
@@ -57,6 +57,8 @@ skip-if = e10s # GCLI isn't e10s compatible. See bug 1128988.
|
||||
[browser_inspector_highlighter-options.js]
|
||||
[browser_inspector_highlighter-rect_01.js]
|
||||
[browser_inspector_highlighter-rect_02.js]
|
||||
[browser_inspector_highlighter-rulers_01.js]
|
||||
[browser_inspector_highlighter-rulers_02.js]
|
||||
[browser_inspector_highlighter-selector_01.js]
|
||||
[browser_inspector_highlighter-selector_02.js]
|
||||
[browser_inspector_highlighter-zoom.js]
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test the creation of the geometry highlighter elements.
|
||||
|
||||
const TEST_URL = "data:text/html;charset=utf-8," +
|
||||
"<div style='position:absolute;left: 0; top: 0; width: 40000px; height: 8000px'></div>"
|
||||
|
||||
const ID = "rulers-highlighter-";
|
||||
|
||||
// Maximum size, in pixel, for the horizontal ruler and vertical ruler
|
||||
// used by RulersHighlighter
|
||||
const RULERS_MAX_X_AXIS = 10000;
|
||||
const RULERS_MAX_Y_AXIS = 15000;
|
||||
// Number of steps after we add a text in RulersHighliter;
|
||||
// currently the unit is in pixel.
|
||||
const RULERS_TEXT_STEP = 100;
|
||||
|
||||
add_task(function*() {
|
||||
let { inspector, toolbox } = yield openInspectorForURL(TEST_URL);
|
||||
let front = inspector.inspector;
|
||||
|
||||
let highlighter = yield front.getHighlighterByType("RulersHighlighter");
|
||||
|
||||
yield isHiddenByDefault(highlighter, inspector);
|
||||
yield hasRightLabelsContent(highlighter, inspector);
|
||||
|
||||
yield highlighter.finalize();
|
||||
});
|
||||
|
||||
function* isHiddenByDefault(highlighterFront, inspector) {
|
||||
info("Checking the highlighter is hidden by default");
|
||||
|
||||
let hidden = yield getHighlighterNodeAttribute(highlighterFront,
|
||||
ID + "elements", "hidden");
|
||||
|
||||
is(hidden, "true", "highlighter is hidden by default");
|
||||
|
||||
info("Checking the highlighter is displayed when asked");
|
||||
// the rulers doesn't need any node, but as highligher it seems mandatory
|
||||
// ones, so the body is given
|
||||
let body = yield getNodeFront("body", inspector);
|
||||
yield highlighterFront.show(body);
|
||||
|
||||
hidden = yield getHighlighterNodeAttribute(highlighterFront,
|
||||
ID + "elements", "hidden");
|
||||
|
||||
isnot(hidden, "true", "highlighter is visible after show");
|
||||
}
|
||||
|
||||
function* hasRightLabelsContent(highlighterFront, inspector) {
|
||||
info("Checking the rulers have the proper text, based on rulers' size");
|
||||
|
||||
let contentX = yield getHighlighterNodeTextContent(highlighterFront, `${ID}x-axis-text`);
|
||||
let contentY = yield getHighlighterNodeTextContent(highlighterFront, `${ID}y-axis-text`);
|
||||
|
||||
let expectedX = "";
|
||||
for (let i = RULERS_TEXT_STEP; i < RULERS_MAX_X_AXIS; i+= RULERS_TEXT_STEP)
|
||||
expectedX += i;
|
||||
|
||||
is(contentX, expectedX, "x axis text content is correct");
|
||||
|
||||
let expectedY = "";
|
||||
for (let i = RULERS_TEXT_STEP; i < RULERS_MAX_Y_AXIS; i+= RULERS_TEXT_STEP)
|
||||
expectedY += i;
|
||||
|
||||
is(contentY, expectedY, "y axis text content is correct");
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test the creation of the geometry highlighter elements.
|
||||
|
||||
const TEST_URL = "data:text/html;charset=utf-8," +
|
||||
"<div style='position:absolute;left: 0; top: 0; width: 40000px; height: 8000px'></div>"
|
||||
|
||||
const ID = "rulers-highlighter-";
|
||||
|
||||
add_task(function*() {
|
||||
let { inspector, toolbox } = yield openInspectorForURL(TEST_URL);
|
||||
let front = inspector.inspector;
|
||||
|
||||
let highlighter = yield front.getHighlighterByType("RulersHighlighter");
|
||||
|
||||
// the rulers doesn't need any node, but as highligher it seems mandatory
|
||||
// ones, so the body is given
|
||||
let body = yield getNodeFront("body", inspector);
|
||||
yield highlighter.show(body);
|
||||
|
||||
yield isUpdatedAfterScroll(highlighter, inspector);
|
||||
|
||||
yield highlighter.finalize();
|
||||
});
|
||||
|
||||
function* isUpdatedAfterScroll(highlighterFront, inspector) {
|
||||
info("Checking the rulers' position by default");
|
||||
|
||||
let xAxisRulerTransform = yield getHighlighterNodeAttribute(highlighterFront,
|
||||
`${ID}x-axis-ruler`, "transform");
|
||||
let xAxisTextTransform = yield getHighlighterNodeAttribute(highlighterFront,
|
||||
`${ID}x-axis-text`, "transform");
|
||||
let yAxisRulerTransform = yield getHighlighterNodeAttribute(highlighterFront,
|
||||
`${ID}y-axis-ruler`, "transform");
|
||||
let yAxisTextTransform = yield getHighlighterNodeAttribute(highlighterFront,
|
||||
`${ID}y-axis-text`, "transform");
|
||||
|
||||
is(xAxisRulerTransform, null, "x axis ruler is positioned properly");
|
||||
is(xAxisTextTransform, null, "x axis text are positioned properly");
|
||||
is(yAxisRulerTransform, null, "y axis ruler is positioned properly");
|
||||
is(yAxisRulerTransform, null, "y axis text are positioned properly");
|
||||
|
||||
info("Asking the content window to scroll to specific coords");
|
||||
|
||||
let x = 200, y = 300;
|
||||
|
||||
let { data } = yield executeInContent("Test:ScrollWindow", { x, y });
|
||||
|
||||
is(data.x, x, "window scrolled properly horizontally");
|
||||
is(data.y, y, "window scrolled properly vertically");
|
||||
|
||||
info("Checking the rulers are properly positioned after the scrolling");
|
||||
|
||||
xAxisRulerTransform = yield getHighlighterNodeAttribute(highlighterFront,
|
||||
`${ID}x-axis-ruler`, "transform");
|
||||
xAxisTextTransform = yield getHighlighterNodeAttribute(highlighterFront,
|
||||
`${ID}x-axis-text`, "transform");
|
||||
yAxisRulerTransform = yield getHighlighterNodeAttribute(highlighterFront,
|
||||
`${ID}y-axis-ruler`, "transform");
|
||||
yAxisTextTransform = yield getHighlighterNodeAttribute(highlighterFront,
|
||||
`${ID}y-axis-text`, "transform");
|
||||
|
||||
is(xAxisRulerTransform, `translate(-${x})`, "x axis ruler is positioned properly");
|
||||
is(xAxisTextTransform, `translate(-${x})`, "x axis text are positioned properly");
|
||||
is(yAxisRulerTransform, `translate(0, -${y})`, "y axis ruler is positioned properly");
|
||||
is(yAxisRulerTransform, `translate(0, -${y})`, "y axis text are positioned properly");
|
||||
|
||||
info("Asking the content window to scroll relative to the current position");
|
||||
|
||||
({ data }) = yield executeInContent("Test:ScrollWindow", {
|
||||
x: -50,
|
||||
y: -60,
|
||||
relative: true
|
||||
});
|
||||
|
||||
is(data.x, x - 50, "window scrolled properly horizontally");
|
||||
is(data.y, y - 60, "window scrolled properly vertically");
|
||||
|
||||
info("Checking the rulers are properly positioned after the relative scrolling");
|
||||
|
||||
xAxisRulerTransform = yield getHighlighterNodeAttribute(highlighterFront,
|
||||
`${ID}x-axis-ruler`, "transform");
|
||||
xAxisTextTransform = yield getHighlighterNodeAttribute(highlighterFront,
|
||||
`${ID}x-axis-text`, "transform");
|
||||
yAxisRulerTransform = yield getHighlighterNodeAttribute(highlighterFront,
|
||||
`${ID}y-axis-ruler`, "transform");
|
||||
yAxisTextTransform = yield getHighlighterNodeAttribute(highlighterFront,
|
||||
`${ID}y-axis-text`, "transform");
|
||||
|
||||
is(xAxisRulerTransform, `translate(-${x - 50})`, "x axis ruler is positioned properly");
|
||||
is(xAxisTextTransform, `translate(-${x - 50})`, "x axis text are positioned properly");
|
||||
is(yAxisRulerTransform, `translate(0, -${y - 60})`, "y axis ruler is positioned properly");
|
||||
is(yAxisRulerTransform, `translate(0, -${y - 60})`, "y axis text are positioned properly");
|
||||
}
|
||||
@@ -60,7 +60,7 @@ add_task(function* () {
|
||||
info("Waiting for inspector selection to update");
|
||||
yield onNodeReselected;
|
||||
|
||||
ok(content.document.body.outerHTML.contains(clipboard.get()),
|
||||
ok(content.document.body.outerHTML.includes(clipboard.get()),
|
||||
"Clipboard content was pasted into the node's outer HTML.");
|
||||
ok(!getNode(outerHTMLSelector, { expectNoMatch: true }),
|
||||
"The original node was removed.");
|
||||
|
||||
@@ -279,6 +279,36 @@ addMessageListener("Test:HasPseudoClassLock", function(msg) {
|
||||
sendAsyncMessage("Test:HasPseudoClassLock", DOMUtils.hasPseudoClassLock(node, pseudo));
|
||||
});
|
||||
|
||||
/**
|
||||
* Scrolls the window to a particular set of coordinates in the document, or
|
||||
* by the given amount if `relative` is set to `true`.
|
||||
*
|
||||
* @param {Object} data
|
||||
* - {Number} x
|
||||
* - {Number} y
|
||||
* - {Boolean} relative
|
||||
*
|
||||
* @return {Object} An object with x / y properties, representing the number
|
||||
* of pixels that the document has been scrolled horizontally and vertically.
|
||||
*/
|
||||
addMessageListener("Test:ScrollWindow", function(msg) {
|
||||
let {x, y, relative} = msg.data;
|
||||
|
||||
if (isNaN(x) || isNaN(y)) {
|
||||
sendAsyncMessage("Test:ScrollWindow", {});
|
||||
return;
|
||||
}
|
||||
|
||||
content.addEventListener("scroll", function onScroll(event) {
|
||||
this.removeEventListener("scroll", onScroll);
|
||||
|
||||
let data = {x: content.scrollX, y: content.scrollY};
|
||||
sendAsyncMessage("Test:ScrollWindow", data);
|
||||
});
|
||||
|
||||
content[relative ? "scrollBy" : "scrollTo"](x, y);
|
||||
});
|
||||
|
||||
/**
|
||||
* Like document.querySelector but can go into iframes too.
|
||||
* ".container iframe || .sub-container div" will first try to find the node
|
||||
|
||||
@@ -76,10 +76,13 @@ toolkit.jar:
|
||||
content/global/devtools/debugger/event-listeners-view.js (debugger/views/event-listeners-view.js)
|
||||
content/global/devtools/debugger/global-search-view.js (debugger/views/global-search-view.js)
|
||||
content/global/devtools/debugger-panes.js (debugger/debugger-panes.js)
|
||||
content/global/devtools/debugger/utils.js (debugger/utils.js)
|
||||
content/global/devtools/shadereditor.xul (shadereditor/shadereditor.xul)
|
||||
content/global/devtools/shadereditor.js (shadereditor/shadereditor.js)
|
||||
content/global/devtools/canvasdebugger.xul (canvasdebugger/canvasdebugger.xul)
|
||||
content/global/devtools/canvasdebugger.js (canvasdebugger/canvasdebugger.js)
|
||||
content/global/devtools/canvasdebugger/snapshotslist.js (canvasdebugger/snapshotslist.js)
|
||||
content/global/devtools/canvasdebugger/callslist.js (canvasdebugger/callslist.js)
|
||||
content/global/devtools/d3.js (shared/d3.js)
|
||||
content/global/devtools/webaudioeditor.xul (webaudioeditor/webaudioeditor.xul)
|
||||
content/global/devtools/dagre-d3.js (webaudioeditor/lib/dagre-d3.js)
|
||||
@@ -91,11 +94,6 @@ toolkit.jar:
|
||||
content/global/devtools/webaudioeditor/views/inspector.js (webaudioeditor/views/inspector.js)
|
||||
content/global/devtools/webaudioeditor/views/properties.js (webaudioeditor/views/properties.js)
|
||||
content/global/devtools/webaudioeditor/views/automation.js (webaudioeditor/views/automation.js)
|
||||
content/global/devtools/profiler.xul (profiler/profiler.xul)
|
||||
content/global/devtools/profiler.js (profiler/profiler.js)
|
||||
content/global/devtools/ui-recordings.js (profiler/ui-recordings.js)
|
||||
content/global/devtools/ui-profile.js (profiler/ui-profile.js)
|
||||
#ifdef MOZ_DEVTOOLS_PERFTOOLS
|
||||
content/global/devtools/performance.xul (performance/performance.xul)
|
||||
content/global/devtools/performance/performance-controller.js (performance/performance-controller.js)
|
||||
content/global/devtools/performance/performance-view.js (performance/performance-view.js)
|
||||
@@ -109,7 +107,7 @@ toolkit.jar:
|
||||
content/global/devtools/performance/views/details-memory-call-tree.js (performance/views/details-memory-call-tree.js)
|
||||
content/global/devtools/performance/views/details-memory-flamegraph.js (performance/views/details-memory-flamegraph.js)
|
||||
content/global/devtools/performance/views/recordings.js (performance/views/recordings.js)
|
||||
#endif
|
||||
content/global/devtools/performance/views/jit-optimizations.js (performance/views/jit-optimizations.js)
|
||||
content/global/devtools/responsivedesign/resize-commands.js (responsivedesign/resize-commands.js)
|
||||
content/global/devtools/commandline.css (commandline/commandline.css)
|
||||
content/global/devtools/commandlineoutput.xhtml (commandline/commandlineoutput.xhtml)
|
||||
@@ -150,6 +148,4 @@ toolkit.jar:
|
||||
content/global/devtools/eyedropper.xul (eyedropper/eyedropper.xul)
|
||||
content/global/devtools/eyedropper/crosshairs.css (eyedropper/crosshairs.css)
|
||||
content/global/devtools/eyedropper/nocursor.css (eyedropper/nocursor.css)
|
||||
content/global/devtools/timeline/timeline.xul (timeline/timeline.xul)
|
||||
content/global/devtools/timeline/timeline.js (timeline/timeline.js)
|
||||
#endif
|
||||
|
||||
@@ -40,7 +40,7 @@ if CONFIG['MOZ_DEVTOOLS']:
|
||||
'layoutview',
|
||||
'markupview',
|
||||
'netmonitor',
|
||||
'profiler',
|
||||
'performance',
|
||||
'projecteditor',
|
||||
'responsivedesign',
|
||||
'scratchpad',
|
||||
@@ -49,19 +49,10 @@ if CONFIG['MOZ_DEVTOOLS']:
|
||||
'storage',
|
||||
'styleeditor',
|
||||
'tilt',
|
||||
'timeline',
|
||||
'webaudioeditor',
|
||||
'webide',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_DEVTOOLS_PERFTOOLS']:
|
||||
DIRS += ['performance']
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'devtools-clhandler.js',
|
||||
'devtools-clhandler.manifest',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.devtools += [
|
||||
'async-utils.js',
|
||||
'content-observer.js',
|
||||
@@ -84,11 +75,6 @@ EXTRA_JS_MODULES.devtools += [
|
||||
'Require.jsm',
|
||||
]
|
||||
|
||||
if CONFIG['MOZ_DEVTOOLS']:
|
||||
EXTRA_JS_MODULES.devtools += [
|
||||
'main.js',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES.devtools.server.actors += [
|
||||
'server/actors/highlighter.css'
|
||||
]
|
||||
|
||||
@@ -211,7 +211,9 @@ let NetMonitorController = {
|
||||
|
||||
let target = this._target;
|
||||
let { client, form } = target;
|
||||
if (target.chrome) {
|
||||
// Some actors like AddonActor or RootActor for chrome debugging
|
||||
// do not support attach/detach and can be used directly
|
||||
if (!target.isTabActor) {
|
||||
this._startChromeMonitoring(client, form.consoleActor, deferred.resolve);
|
||||
} else {
|
||||
this._startMonitoringTab(client, form, deferred.resolve);
|
||||
|
||||
@@ -35,9 +35,6 @@ function test() {
|
||||
function testPrefs() {
|
||||
let { Prefs } = aMonitor.panelWin;
|
||||
|
||||
is(Prefs._root, "devtools.netmonitor",
|
||||
"The preferences object should have a correct root path.");
|
||||
|
||||
is(Prefs.networkDetailsWidth,
|
||||
Services.prefs.getIntPref("devtools.netmonitor.panes-network-details-width"),
|
||||
"Getting a pref should work correctly.");
|
||||
|
||||
@@ -4,13 +4,73 @@
|
||||
"use strict";
|
||||
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
loader.lazyRequireGetter(this, "promise");
|
||||
loader.lazyRequireGetter(this, "EventEmitter",
|
||||
"devtools/toolkit/event-emitter");
|
||||
loader.lazyRequireGetter(this, "RecordingUtils",
|
||||
"devtools/performance/recording-utils", true);
|
||||
|
||||
const REQUIRED_MEMORY_ACTOR_METHODS = [
|
||||
"attach", "detach", "startRecordingAllocations", "stopRecordingAllocations", "getAllocations"
|
||||
];
|
||||
|
||||
/**
|
||||
* Constructor for a facade around an underlying ProfilerFront.
|
||||
*/
|
||||
function ProfilerFront (target) {
|
||||
this._target = target;
|
||||
}
|
||||
|
||||
ProfilerFront.prototype = {
|
||||
// Connects to the targets underlying real ProfilerFront.
|
||||
connect: Task.async(function*() {
|
||||
let target = this._target;
|
||||
// Chrome and content process targets already have obtained a reference
|
||||
// to the profiler tab actor. Use it immediately.
|
||||
if (target.form && target.form.profilerActor) {
|
||||
this._profiler = target.form.profilerActor;
|
||||
}
|
||||
// Check if we already have a grip to the `listTabs` response object
|
||||
// and, if we do, use it to get to the profiler actor.
|
||||
else if (target.root && target.root.profilerActor) {
|
||||
this._profiler = target.root.profilerActor;
|
||||
}
|
||||
// Otherwise, call `listTabs`.
|
||||
else {
|
||||
this._profiler = (yield listTabs(target.client)).profilerActor;
|
||||
}
|
||||
|
||||
// Fetch and store information about the SPS profiler and
|
||||
// server profiler.
|
||||
this.traits = {};
|
||||
this.traits.filterable = target.getTrait("profilerDataFilterable");
|
||||
}),
|
||||
|
||||
/**
|
||||
* Makes a request to the underlying real profiler actor. Handles
|
||||
* backwards compatibility differences based off of the features
|
||||
* and traits of the actor.
|
||||
*/
|
||||
_request: function (method, ...args) {
|
||||
let deferred = promise.defer();
|
||||
let data = args[0] || {};
|
||||
data.to = this._profiler;
|
||||
data.type = method;
|
||||
this._target.client.request(data, res => {
|
||||
// If the backend does not support filtering by start and endtime on platform (< Fx40),
|
||||
// do it on the client (much slower).
|
||||
if (method === "getProfile" && !this.traits.filterable) {
|
||||
RecordingUtils.filterSamples(res.profile, data.startTime || 0);
|
||||
}
|
||||
|
||||
deferred.resolve(res);
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
};
|
||||
|
||||
exports.ProfilerFront = ProfilerFront;
|
||||
|
||||
/**
|
||||
* A dummy front decorated with the provided methods.
|
||||
*
|
||||
@@ -107,3 +167,14 @@ function timelineActorSupported(target) {
|
||||
return target.hasActor("timeline");
|
||||
}
|
||||
exports.timelineActorSupported = Task.async(timelineActorSupported);
|
||||
|
||||
/**
|
||||
* Returns a promise resolved with a listing of all the tabs in the
|
||||
* provided thread client.
|
||||
*/
|
||||
function listTabs(client) {
|
||||
let deferred = promise.defer();
|
||||
client.listTabs(deferred.resolve);
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
const { Cc, Ci, Cu, Cr } = require("chrome");
|
||||
const { Task } = require("resource://gre/modules/Task.jsm");
|
||||
const { extend } = require("sdk/util/object");
|
||||
const { RecordingModel } = require("devtools/performance/recording-model");
|
||||
|
||||
loader.lazyRequireGetter(this, "Services");
|
||||
loader.lazyRequireGetter(this, "promise");
|
||||
@@ -26,10 +27,22 @@ loader.lazyImporter(this, "setTimeout",
|
||||
"resource://gre/modules/Timer.jsm");
|
||||
loader.lazyImporter(this, "clearTimeout",
|
||||
"resource://gre/modules/Timer.jsm");
|
||||
loader.lazyImporter(this, "Promise",
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
|
||||
|
||||
// How often do we pull allocation sites from the memory actor.
|
||||
const DEFAULT_ALLOCATION_SITES_PULL_TIMEOUT = 200; // ms
|
||||
|
||||
// Events to pipe from PerformanceActorsConnection to the PerformanceFront
|
||||
const CONNECTION_PIPE_EVENTS = [
|
||||
"console-profile-start", "console-profile-ending", "console-profile-end",
|
||||
"timeline-data", "profiler-already-active", "profiler-activated"
|
||||
];
|
||||
|
||||
// Events to listen to from the profiler actor
|
||||
const PROFILER_EVENTS = ["console-api-profiler", "profiler-stopped"];
|
||||
|
||||
/**
|
||||
* A cache of all PerformanceActorsConnection instances.
|
||||
* The keys are Target objects.
|
||||
@@ -71,15 +84,25 @@ function PerformanceActorsConnection(target) {
|
||||
this._target = target;
|
||||
this._client = this._target.client;
|
||||
this._request = this._request.bind(this);
|
||||
this._pendingConsoleRecordings = [];
|
||||
this._sitesPullTimeout = 0;
|
||||
this._recordings = [];
|
||||
|
||||
this._onTimelineMarkers = this._onTimelineMarkers.bind(this);
|
||||
this._onTimelineFrames = this._onTimelineFrames.bind(this);
|
||||
this._onTimelineMemory = this._onTimelineMemory.bind(this);
|
||||
this._onTimelineTicks = this._onTimelineTicks.bind(this);
|
||||
this._onProfilerEvent = this._onProfilerEvent.bind(this);
|
||||
this._pullAllocationSites = this._pullAllocationSites.bind(this);
|
||||
|
||||
Services.obs.notifyObservers(null, "performance-actors-connection-created", null);
|
||||
}
|
||||
|
||||
PerformanceActorsConnection.prototype = {
|
||||
|
||||
// Properties set when mocks are being used
|
||||
_usingMockMemory: false,
|
||||
_usingMockTimeline: false,
|
||||
// Properties set based off of server actor support
|
||||
_memorySupported: true,
|
||||
_timelineSupported: true,
|
||||
|
||||
/**
|
||||
* Initializes a connection to the profiler and other miscellaneous actors.
|
||||
@@ -89,10 +112,14 @@ PerformanceActorsConnection.prototype = {
|
||||
* A promise that is resolved once the connection is established.
|
||||
*/
|
||||
open: Task.async(function*() {
|
||||
if (this._connected) {
|
||||
return;
|
||||
if (this._connecting) {
|
||||
return this._connecting.promise;
|
||||
}
|
||||
|
||||
// Create a promise that gets resolved upon connecting, so that
|
||||
// other attempts to open the connection use the same resolution promise
|
||||
this._connecting = Promise.defer();
|
||||
|
||||
// Local debugging needs to make the target remote.
|
||||
yield this._target.makeRemote();
|
||||
|
||||
@@ -104,8 +131,11 @@ PerformanceActorsConnection.prototype = {
|
||||
yield this._connectTimelineActor();
|
||||
yield this._connectMemoryActor();
|
||||
|
||||
yield this._registerListeners();
|
||||
|
||||
this._connected = true;
|
||||
|
||||
this._connecting.resolve();
|
||||
Services.obs.notifyObservers(null, "performance-actors-connection-opened", null);
|
||||
}),
|
||||
|
||||
@@ -113,33 +143,27 @@ PerformanceActorsConnection.prototype = {
|
||||
* Destroys this connection.
|
||||
*/
|
||||
destroy: Task.async(function*() {
|
||||
if (this._connecting && !this._connected) {
|
||||
yield this._connecting.promise;
|
||||
} else if (!this._connected) {
|
||||
return;
|
||||
}
|
||||
|
||||
yield this._unregisterListeners();
|
||||
yield this._disconnectActors();
|
||||
|
||||
this._memory = this._timeline = this._profiler = this._target = this._client = null;
|
||||
this._connected = false;
|
||||
this._connecting = null;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Initializes a connection to the profiler actor.
|
||||
* Initializes a connection to the profiler actor. Uses a facade around the ProfilerFront
|
||||
* for similarity to the other actors in the shared connection.
|
||||
*/
|
||||
_connectProfilerActor: Task.async(function*() {
|
||||
// Chrome debugging targets have already obtained a reference
|
||||
// to the profiler actor.
|
||||
if (this._target.chrome) {
|
||||
this._profiler = this._target.form.profilerActor;
|
||||
}
|
||||
// When we are debugging content processes, we already have the tab
|
||||
// specific one. Use it immediately.
|
||||
else if (this._target.form && this._target.form.profilerActor) {
|
||||
this._profiler = this._target.form.profilerActor;
|
||||
}
|
||||
// Check if we already have a grip to the `listTabs` response object
|
||||
// and, if we do, use it to get to the profiler actor.
|
||||
else if (this._target.root && this._target.root.profilerActor) {
|
||||
this._profiler = this._target.root.profilerActor;
|
||||
}
|
||||
// Otherwise, call `listTabs`.
|
||||
else {
|
||||
this._profiler = (yield listTabs(this._client)).profilerActor;
|
||||
}
|
||||
this._profiler = new compatibility.ProfilerFront(this._target);
|
||||
yield this._profiler.connect();
|
||||
}),
|
||||
|
||||
/**
|
||||
@@ -150,9 +174,9 @@ PerformanceActorsConnection.prototype = {
|
||||
if (supported) {
|
||||
this._timeline = new TimelineFront(this._target.client, this._target.form);
|
||||
} else {
|
||||
this._usingMockTimeline = true;
|
||||
this._timeline = new compatibility.MockTimelineFront();
|
||||
}
|
||||
this._timelineSupported = supported;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -163,9 +187,38 @@ PerformanceActorsConnection.prototype = {
|
||||
if (supported) {
|
||||
this._memory = new MemoryFront(this._target.client, this._target.form);
|
||||
} else {
|
||||
this._usingMockMemory = true;
|
||||
this._memory = new compatibility.MockMemoryFront();
|
||||
}
|
||||
this._memorySupported = supported;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Registers listeners on events from the underlying
|
||||
* actors, so the connection can handle them.
|
||||
*/
|
||||
_registerListeners: Task.async(function*() {
|
||||
// Pipe events from TimelineActor to the PerformanceFront
|
||||
this._timeline.on("markers", this._onTimelineMarkers);
|
||||
this._timeline.on("frames", this._onTimelineFrames);
|
||||
this._timeline.on("memory", this._onTimelineMemory);
|
||||
this._timeline.on("ticks", this._onTimelineTicks);
|
||||
|
||||
// Register events on the profiler actor to hook into `console.profile*` calls.
|
||||
yield this._request("profiler", "registerEventNotifications", { events: PROFILER_EVENTS });
|
||||
this._client.addListener("eventNotification", this._onProfilerEvent);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Unregisters listeners on events on the underlying actors.
|
||||
*/
|
||||
_unregisterListeners: Task.async(function*() {
|
||||
this._timeline.off("markers", this._onTimelineMarkers);
|
||||
this._timeline.off("frames", this._onTimelineFrames);
|
||||
this._timeline.off("memory", this._onTimelineMemory);
|
||||
this._timeline.off("ticks", this._onTimelineTicks);
|
||||
|
||||
yield this._request("profiler", "unregisterEventNotifications", { events: PROFILER_EVENTS });
|
||||
this._client.removeListener("eventNotification", this._onProfilerEvent);
|
||||
}),
|
||||
|
||||
/**
|
||||
@@ -192,12 +245,7 @@ PerformanceActorsConnection.prototype = {
|
||||
_request: function(actor, method, ...args) {
|
||||
// Handle requests to the profiler actor.
|
||||
if (actor == "profiler") {
|
||||
let deferred = promise.defer();
|
||||
let data = args[0] || {};
|
||||
data.to = this._profiler;
|
||||
data.type = method;
|
||||
this._client.request(data, deferred.resolve);
|
||||
return deferred.promise;
|
||||
return this._profiler._request(method, ...args);
|
||||
}
|
||||
|
||||
// Handle requests to the timeline actor.
|
||||
@@ -209,75 +257,207 @@ PerformanceActorsConnection.prototype = {
|
||||
if (actor == "memory") {
|
||||
return this._memory[method].apply(this._memory, args);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A thin wrapper around a shared PerformanceActorsConnection for the parent target.
|
||||
* Handles manually starting and stopping a recording.
|
||||
*
|
||||
* @param PerformanceActorsConnection connection
|
||||
* The shared instance for the parent target.
|
||||
*/
|
||||
function PerformanceFront(connection) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this._request = connection._request;
|
||||
|
||||
// Pipe events from TimelineActor to the PerformanceFront
|
||||
connection._timeline.on("markers", markers => this.emit("markers", markers));
|
||||
connection._timeline.on("frames", (delta, frames) => this.emit("frames", delta, frames));
|
||||
connection._timeline.on("memory", (delta, measurement) => this.emit("memory", delta, measurement));
|
||||
connection._timeline.on("ticks", (delta, timestamps) => this.emit("ticks", delta, timestamps));
|
||||
|
||||
// Set when mocks are being used
|
||||
this._usingMockMemory = connection._usingMockMemory;
|
||||
this._usingMockTimeline = connection._usingMockTimeline;
|
||||
|
||||
this._pullAllocationSites = this._pullAllocationSites.bind(this);
|
||||
this._sitesPullTimeout = 0;
|
||||
}
|
||||
|
||||
PerformanceFront.prototype = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Manually begins a recording session.
|
||||
* Invoked whenever a registered event was emitted by the profiler actor.
|
||||
*
|
||||
* @param object response
|
||||
* The data received from the backend.
|
||||
*/
|
||||
_onProfilerEvent: function (_, { topic, subject, details }) {
|
||||
if (topic === "console-api-profiler") {
|
||||
if (subject.action === "profile") {
|
||||
this._onConsoleProfileStart(details);
|
||||
} else if (subject.action === "profileEnd") {
|
||||
this._onConsoleProfileEnd(details);
|
||||
}
|
||||
} else if (topic === "profiler-stopped") {
|
||||
this._onProfilerUnexpectedlyStopped();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* TODO handle bug 1144438
|
||||
*/
|
||||
_onProfilerUnexpectedlyStopped: function () {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Invoked whenever `console.profile` is called.
|
||||
*
|
||||
* @param string profileLabel
|
||||
* The provided string argument if available; undefined otherwise.
|
||||
* @param number currentTime
|
||||
* The time (in milliseconds) when the call was made, relative to when
|
||||
* the nsIProfiler module was started.
|
||||
*/
|
||||
_onConsoleProfileStart: Task.async(function *({ profileLabel, currentTime: startTime }) {
|
||||
let recordings = this._recordings;
|
||||
|
||||
// Abort if a profile with this label already exists.
|
||||
if (recordings.find(e => e.getLabel() === profileLabel)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ensure the performance front is set up and ready.
|
||||
// Slight performance overhead for this, should research some more.
|
||||
// This is to ensure that there is a front to receive the events for
|
||||
// the console profiles.
|
||||
yield gDevTools.getToolbox(this._target).loadTool("performance");
|
||||
|
||||
let model = yield this.startRecording(extend(getRecordingModelPrefs(), {
|
||||
console: true,
|
||||
label: profileLabel
|
||||
}));
|
||||
|
||||
this.emit("console-profile-start", model);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Invoked whenever `console.profileEnd` is called.
|
||||
*
|
||||
* @param string profileLabel
|
||||
* The provided string argument if available; undefined otherwise.
|
||||
* @param number currentTime
|
||||
* The time (in milliseconds) when the call was made, relative to when
|
||||
* the nsIProfiler module was started.
|
||||
*/
|
||||
_onConsoleProfileEnd: Task.async(function *(data) {
|
||||
// If no data, abort; can occur if profiler isn't running and we get a surprise
|
||||
// call to console.profileEnd()
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
let { profileLabel, currentTime: endTime } = data;
|
||||
|
||||
let pending = this._recordings.filter(r => r.isConsole() && r.isRecording());
|
||||
if (pending.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let model;
|
||||
// Try to find the corresponding `console.profile` call if
|
||||
// a label was used in profileEnd(). If no matches, abort.
|
||||
if (profileLabel) {
|
||||
model = pending.find(e => e.getLabel() === profileLabel);
|
||||
}
|
||||
// If no label supplied, pop off the most recent pending console recording
|
||||
else {
|
||||
model = pending[pending.length - 1];
|
||||
}
|
||||
|
||||
// If `profileEnd()` was called with a label, and there are no matching
|
||||
// sessions, abort.
|
||||
if (!model) {
|
||||
Cu.reportError("console.profileEnd() called with label that does not match a recording.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.emit("console-profile-ending", model);
|
||||
yield this.stopRecording(model);
|
||||
this.emit("console-profile-end", model);
|
||||
}),
|
||||
|
||||
/**
|
||||
* Handlers for TimelineActor events. All pipe to `_onTimelineData`
|
||||
* with the appropriate event name.
|
||||
*/
|
||||
_onTimelineMarkers: function (markers) { this._onTimelineData("markers", markers); },
|
||||
_onTimelineFrames: function (delta, frames) { this._onTimelineData("frames", delta, frames); },
|
||||
_onTimelineMemory: function (delta, measurement) { this._onTimelineData("memory", delta, measurement); },
|
||||
_onTimelineTicks: function (delta, timestamps) { this._onTimelineData("ticks", delta, timestamps); },
|
||||
|
||||
/**
|
||||
* Called whenever there is timeline data of any of the following types:
|
||||
* - markers
|
||||
* - frames
|
||||
* - memory
|
||||
* - ticks
|
||||
* - allocations
|
||||
*
|
||||
* Populate our internal store of recordings for all currently recording sessions.
|
||||
*/
|
||||
|
||||
_onTimelineData: function (...data) {
|
||||
this._recordings.forEach(e => e.addTimelineData.apply(e, data));
|
||||
this.emit("timeline-data", ...data);
|
||||
},
|
||||
|
||||
/**
|
||||
* Begins a recording session
|
||||
*
|
||||
* @param object options
|
||||
* An options object to pass to the actors. Supported properties are
|
||||
* `withTicks`, `withMemory` and `withAllocations`.
|
||||
* `withTicks`, `withMemory` and `withAllocations`, `probability`, and `maxLogLength`.
|
||||
* @return object
|
||||
* A promise that is resolved once recording has started.
|
||||
*/
|
||||
startRecording: Task.async(function*(options = {}) {
|
||||
let model = new RecordingModel(options);
|
||||
// All actors are started asynchronously over the remote debugging protocol.
|
||||
// Get the corresponding start times from each one of them.
|
||||
let profilerStartTime = yield this._startProfiler();
|
||||
let profilerStartTime = yield this._startProfiler(options);
|
||||
let timelineStartTime = yield this._startTimeline(options);
|
||||
let memoryStartTime = yield this._startMemory(options);
|
||||
|
||||
return {
|
||||
let data = {
|
||||
profilerStartTime,
|
||||
timelineStartTime,
|
||||
memoryStartTime
|
||||
};
|
||||
|
||||
// Signify to the model that the recording has started,
|
||||
// populate with data and store the recording model here.
|
||||
model.populate(data);
|
||||
this._recordings.push(model);
|
||||
|
||||
return model;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Manually ends the current recording session.
|
||||
* Manually ends the recording session for the corresponding RecordingModel.
|
||||
*
|
||||
* @param object options
|
||||
* @see PerformanceFront.prototype.startRecording
|
||||
* @return object
|
||||
* A promise that is resolved once recording has stopped,
|
||||
* with the profiler and memory data, along with all the end times.
|
||||
* @param RecordingModel model
|
||||
* The corresponding RecordingModel that belongs to the recording session wished to stop.
|
||||
* @return RecordingModel
|
||||
* Returns the same model, populated with the profiling data.
|
||||
*/
|
||||
stopRecording: Task.async(function*(options = {}) {
|
||||
let memoryEndTime = yield this._stopMemory(options);
|
||||
let timelineEndTime = yield this._stopTimeline(options);
|
||||
let profilerData = yield this._request("profiler", "getProfile");
|
||||
stopRecording: Task.async(function*(model) {
|
||||
// If model isn't in the PerformanceActorsConnections internal store,
|
||||
// then do nothing.
|
||||
if (this._recordings.indexOf(model) === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
return {
|
||||
// Currently there are two ways profiles stop recording. Either manually in the
|
||||
// performance tool, or via console.profileEnd. Once a recording is done,
|
||||
// we want to deliver the model to the performance tool (either as a return
|
||||
// from the PerformanceFront or via `console-profile-end` event) and then
|
||||
// remove it from the internal store.
|
||||
//
|
||||
// In the case where a console.profile is generated via the console (so the tools are
|
||||
// open), we initialize the Performance tool so it can listen to those events.
|
||||
this._recordings.splice(this._recordings.indexOf(model), 1);
|
||||
|
||||
let config = model.getConfiguration();
|
||||
let startTime = model.getProfilerStartTime();
|
||||
let profilerData = yield this._request("profiler", "getProfile", { startTime });
|
||||
let memoryEndTime = Date.now();
|
||||
let timelineEndTime = Date.now();
|
||||
|
||||
// Only if there are no more sessions recording do we stop
|
||||
// the underlying memory and timeline actors. If we're still recording,
|
||||
// juse use Date.now() for the memory and timeline end times, as those
|
||||
// are only used in tests.
|
||||
if (!this.isRecording()) {
|
||||
memoryEndTime = yield this._stopMemory(config);
|
||||
timelineEndTime = yield this._stopTimeline(config);
|
||||
}
|
||||
|
||||
// Set the results on the RecordingModel itself.
|
||||
model._onStopRecording({
|
||||
// Data available only at the end of a recording.
|
||||
profile: profilerData.profile,
|
||||
|
||||
@@ -285,13 +465,25 @@ PerformanceFront.prototype = {
|
||||
profilerEndTime: profilerData.currentTime,
|
||||
timelineEndTime: timelineEndTime,
|
||||
memoryEndTime: memoryEndTime
|
||||
};
|
||||
});
|
||||
|
||||
return model;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Checks all currently stored recording models and returns a boolean
|
||||
* if there is a session currently being recorded.
|
||||
*
|
||||
* @return Boolean
|
||||
*/
|
||||
isRecording: function () {
|
||||
return this._recordings.some(recording => recording.isRecording());
|
||||
},
|
||||
|
||||
/**
|
||||
* Starts the profiler actor, if necessary.
|
||||
*/
|
||||
_startProfiler: Task.async(function *() {
|
||||
_startProfiler: Task.async(function *(options={}) {
|
||||
// Start the profiler only if it wasn't already active. The built-in
|
||||
// nsIPerformance module will be kept recording, because it's the same instance
|
||||
// for all targets and interacts with the whole platform, so we don't want
|
||||
@@ -302,8 +494,13 @@ PerformanceFront.prototype = {
|
||||
return profilerStatus.currentTime;
|
||||
}
|
||||
|
||||
// Extend the profiler options so that protocol.js doesn't modify the original.
|
||||
let profilerOptions = extend({}, this._customProfilerOptions);
|
||||
// Translate options from the recording model into profiler-specific
|
||||
// options for the nsIProfiler
|
||||
let profilerOptions = {
|
||||
entries: options.bufferSize,
|
||||
interval: options.sampleFrequency ? (1000 / (options.sampleFrequency * 1000)) : void 0
|
||||
};
|
||||
|
||||
yield this._request("profiler", "startProfiler", profilerOptions);
|
||||
|
||||
this.emit("profiler-activated");
|
||||
@@ -327,26 +524,51 @@ PerformanceFront.prototype = {
|
||||
}),
|
||||
|
||||
/**
|
||||
* Starts the timeline actor, if necessary.
|
||||
* Starts polling for allocations from the memory actor, if necessary.
|
||||
*/
|
||||
_startMemory: Task.async(function *(options) {
|
||||
if (!options.withAllocations) {
|
||||
return 0;
|
||||
}
|
||||
yield this._request("memory", "attach");
|
||||
let memoryStartTime = yield this._request("memory", "startRecordingAllocations");
|
||||
let memoryStartTime = yield this._startRecordingAllocations(options);
|
||||
yield this._pullAllocationSites();
|
||||
return memoryStartTime;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Stops the timeline actor, if necessary.
|
||||
* Stops polling for allocations from the memory actor, if necessary.
|
||||
*/
|
||||
_stopMemory: Task.async(function *(options) {
|
||||
if (!options.withAllocations) {
|
||||
return 0;
|
||||
}
|
||||
// Since `_pullAllocationSites` is usually running inside a timeout, and
|
||||
// it's performing asynchronous requests to the server, a recording may
|
||||
// be stopped before that method finishes executing. Therefore, we need to
|
||||
// wait for the last request to `getAllocations` to finish before actually
|
||||
// stopping recording allocations.
|
||||
yield this._lastPullAllocationSitesFinished;
|
||||
clearTimeout(this._sitesPullTimeout);
|
||||
|
||||
return yield this._stopRecordingAllocations();
|
||||
}),
|
||||
|
||||
/**
|
||||
* Starts recording allocations in the memory actor.
|
||||
*/
|
||||
_startRecordingAllocations: Task.async(function*(options) {
|
||||
yield this._request("memory", "attach");
|
||||
let memoryStartTime = yield this._request("memory", "startRecordingAllocations", {
|
||||
probability: options.allocationsSampleProbability,
|
||||
maxLogLength: options.allocationsMaxLogLength
|
||||
});
|
||||
return memoryStartTime;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Stops recording allocations in the memory actor.
|
||||
*/
|
||||
_stopRecordingAllocations: Task.async(function*() {
|
||||
let memoryEndTime = yield this._request("memory", "stopRecordingAllocations");
|
||||
yield this._request("memory", "detach");
|
||||
return memoryEndTime;
|
||||
@@ -357,6 +579,11 @@ PerformanceFront.prototype = {
|
||||
* them to consumers.
|
||||
*/
|
||||
_pullAllocationSites: Task.async(function *() {
|
||||
let isDetached = (yield this._request("memory", "getState")) !== "attached";
|
||||
if (isDetached) {
|
||||
return;
|
||||
}
|
||||
|
||||
let memoryData = yield this._request("memory", "getAllocations");
|
||||
let isStillAttached = yield this._request("memory", "getState") == "attached";
|
||||
|
||||
@@ -373,38 +600,86 @@ PerformanceFront.prototype = {
|
||||
}
|
||||
}),
|
||||
|
||||
toString: () => "[object PerformanceActorsConnection]"
|
||||
};
|
||||
|
||||
/**
|
||||
* A thin wrapper around a shared PerformanceActorsConnection for the parent target.
|
||||
* Handles manually starting and stopping a recording.
|
||||
*
|
||||
* @param PerformanceActorsConnection connection
|
||||
* The shared instance for the parent target.
|
||||
*/
|
||||
function PerformanceFront(connection) {
|
||||
EventEmitter.decorate(this);
|
||||
|
||||
this._connection = connection;
|
||||
this._request = connection._request;
|
||||
|
||||
// Set when mocks are being used
|
||||
this._memorySupported = connection._memorySupported;
|
||||
this._timelineSupported = connection._timelineSupported;
|
||||
|
||||
// Pipe the console profile events from the connection
|
||||
// to the front so that the UI can listen.
|
||||
CONNECTION_PIPE_EVENTS.forEach(eventName => this._connection.on(eventName, () => this.emit.apply(this, arguments)));
|
||||
}
|
||||
|
||||
PerformanceFront.prototype = {
|
||||
|
||||
/**
|
||||
* Overrides the options sent to the built-in profiler module when activating,
|
||||
* such as the maximum entries count, the sampling interval etc.
|
||||
* Manually begins a recording session and creates a RecordingModel.
|
||||
* Calls the underlying PerformanceActorsConnection's startRecording method.
|
||||
*
|
||||
* Used in tests and for older backend implementations.
|
||||
* @param object options
|
||||
* An options object to pass to the actors. Supported properties are
|
||||
* `withTicks`, `withMemory` and `withAllocations`,
|
||||
* `probability` and `maxLogLength`.
|
||||
* @return object
|
||||
* A promise that is resolved once recording has started.
|
||||
*/
|
||||
_customProfilerOptions: {
|
||||
entries: 1000000,
|
||||
interval: 1,
|
||||
features: ["js"],
|
||||
threadFilters: ["GeckoMain"]
|
||||
startRecording: function (options) {
|
||||
return this._connection.startRecording(options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an object indicating if mock actors are being used or not.
|
||||
* Manually ends the recording session for the corresponding RecordingModel.
|
||||
* Calls the underlying PerformanceActorsConnection's
|
||||
*
|
||||
* @param RecordingModel model
|
||||
* The corresponding RecordingModel that belongs to the recording session wished to stop.
|
||||
* @return RecordingModel
|
||||
* Returns the same model, populated with the profiling data.
|
||||
*/
|
||||
getMocksInUse: function () {
|
||||
stopRecording: function (model) {
|
||||
return this._connection.stopRecording(model);
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns an object indicating what server actors are available and
|
||||
* initialized. A falsy value indicates that the server does not support
|
||||
* that feature, or that mock actors were explicitly requested (tests).
|
||||
*/
|
||||
getActorSupport: function () {
|
||||
return {
|
||||
memory: this._usingMockMemory,
|
||||
timeline: this._usingMockTimeline
|
||||
memory: this._memorySupported,
|
||||
timeline: this._timelineSupported
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a promise resolved with a listing of all the tabs in the
|
||||
* provided thread client.
|
||||
* Creates an object of configurations based off of preferences for a RecordingModel.
|
||||
*/
|
||||
function listTabs(client) {
|
||||
let deferred = promise.defer();
|
||||
client.listTabs(deferred.resolve);
|
||||
return deferred.promise;
|
||||
function getRecordingModelPrefs () {
|
||||
return {
|
||||
withMarkers: true,
|
||||
withMemory: Services.prefs.getBoolPref("devtools.performance.ui.enable-memory"),
|
||||
withTicks: Services.prefs.getBoolPref("devtools.performance.ui.enable-framerate"),
|
||||
withAllocations: Services.prefs.getBoolPref("devtools.performance.ui.enable-memory"),
|
||||
allocationsSampleProbability: +Services.prefs.getCharPref("devtools.performance.memory.sample-probability"),
|
||||
allocationsMaxLogLength: Services.prefs.getIntPref("devtools.performance.memory.max-log-length")
|
||||
};
|
||||
}
|
||||
|
||||
exports.getPerformanceActorsConnection = target => SharedPerformanceActors.forTarget(target);
|
||||
|
||||
@@ -0,0 +1,409 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* This file contains the base line graph that all Performance line graphs use.
|
||||
*/
|
||||
|
||||
const {Cc, Ci, Cu, Cr} = require("chrome");
|
||||
|
||||
Cu.import("resource:///modules/devtools/Graphs.jsm");
|
||||
Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
|
||||
Cu.import("resource://gre/modules/Task.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
const { colorUtils: { setAlpha }} = require("devtools/css-color");
|
||||
const { getColor } = require("devtools/shared/theme");
|
||||
|
||||
loader.lazyRequireGetter(this, "ProfilerGlobal",
|
||||
"devtools/shared/profiler/global");
|
||||
loader.lazyRequireGetter(this, "TimelineGlobal",
|
||||
"devtools/shared/timeline/global");
|
||||
loader.lazyRequireGetter(this, "MarkersOverview",
|
||||
"devtools/shared/timeline/markers-overview", true);
|
||||
loader.lazyRequireGetter(this, "EventEmitter",
|
||||
"devtools/toolkit/event-emitter");
|
||||
|
||||
/**
|
||||
* For line graphs
|
||||
*/
|
||||
const HEIGHT = 35; // px
|
||||
const STROKE_WIDTH = 1; // px
|
||||
const DAMPEN_VALUES = 0.95;
|
||||
const CLIPHEAD_LINE_COLOR = "#666";
|
||||
const SELECTION_LINE_COLOR = "#555";
|
||||
const SELECTION_BACKGROUND_COLOR_NAME = "highlight-blue";
|
||||
const FRAMERATE_GRAPH_COLOR_NAME = "highlight-green";
|
||||
const MEMORY_GRAPH_COLOR_NAME = "highlight-blue";
|
||||
|
||||
/**
|
||||
* For timeline overview
|
||||
*/
|
||||
const MARKERS_GRAPH_HEADER_HEIGHT = 14; // px
|
||||
const MARKERS_GRAPH_ROW_HEIGHT = 10; // px
|
||||
const MARKERS_GROUP_VERTICAL_PADDING = 4; // px
|
||||
|
||||
/**
|
||||
* A base class for performance graphs to inherit from.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the overview.
|
||||
* @param string metric
|
||||
* The unit of measurement for this graph.
|
||||
*/
|
||||
function PerformanceGraph(parent, metric) {
|
||||
LineGraphWidget.call(this, parent, { metric });
|
||||
this.setTheme();
|
||||
}
|
||||
|
||||
PerformanceGraph.prototype = Heritage.extend(LineGraphWidget.prototype, {
|
||||
strokeWidth: STROKE_WIDTH,
|
||||
dampenValuesFactor: DAMPEN_VALUES,
|
||||
fixedHeight: HEIGHT,
|
||||
clipheadLineColor: CLIPHEAD_LINE_COLOR,
|
||||
selectionLineColor: SELECTION_LINE_COLOR,
|
||||
withTooltipArrows: false,
|
||||
withFixedTooltipPositions: true,
|
||||
|
||||
/**
|
||||
* Disables selection and empties this graph.
|
||||
*/
|
||||
clearView: function() {
|
||||
this.selectionEnabled = false;
|
||||
this.dropSelection();
|
||||
this.setData([]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets the theme via `theme` to either "light" or "dark",
|
||||
* and updates the internal styling to match. Requires a redraw
|
||||
* to see the effects.
|
||||
*/
|
||||
setTheme: function (theme) {
|
||||
theme = theme || "light";
|
||||
let mainColor = getColor(this.mainColor || "highlight-blue", theme);
|
||||
this.backgroundColor = getColor("body-background", theme);
|
||||
this.strokeColor = mainColor;
|
||||
this.backgroundGradientStart = setAlpha(mainColor, 0.2);
|
||||
this.backgroundGradientEnd = setAlpha(mainColor, 0.2);
|
||||
this.selectionBackgroundColor = setAlpha(getColor(SELECTION_BACKGROUND_COLOR_NAME, theme), 0.25);
|
||||
this.selectionStripesColor = "rgba(255, 255, 255, 0.1)";
|
||||
this.maximumLineColor = setAlpha(mainColor, 0.4);
|
||||
this.averageLineColor = setAlpha(mainColor, 0.7);
|
||||
this.minimumLineColor = setAlpha(mainColor, 0.9);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Constructor for the framerate graph. Inherits from PerformanceGraph.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the overview.
|
||||
*/
|
||||
function FramerateGraph(parent) {
|
||||
PerformanceGraph.call(this, parent, ProfilerGlobal.L10N.getStr("graphs.fps"));
|
||||
}
|
||||
|
||||
FramerateGraph.prototype = Heritage.extend(PerformanceGraph.prototype, {
|
||||
mainColor: FRAMERATE_GRAPH_COLOR_NAME,
|
||||
setPerformanceData: function ({ duration, ticks }, resolution) {
|
||||
this.dataDuration = duration;
|
||||
return this.setDataFromTimestamps(ticks, resolution);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Constructor for the memory graph. Inherits from PerformanceGraph.
|
||||
*
|
||||
* @param nsIDOMNode parent
|
||||
* The parent node holding the overview.
|
||||
*/
|
||||
function MemoryGraph(parent) {
|
||||
PerformanceGraph.call(this, parent, TimelineGlobal.L10N.getStr("graphs.memory"));
|
||||
}
|
||||
|
||||
MemoryGraph.prototype = Heritage.extend(PerformanceGraph.prototype, {
|
||||
mainColor: MEMORY_GRAPH_COLOR_NAME,
|
||||
setPerformanceData: function ({ duration, memory }) {
|
||||
this.dataDuration = duration;
|
||||
return this.setData(memory);
|
||||
}
|
||||
});
|
||||
|
||||
function TimelineGraph(parent, blueprint) {
|
||||
MarkersOverview.call(this, parent, blueprint);
|
||||
}
|
||||
|
||||
TimelineGraph.prototype = Heritage.extend(MarkersOverview.prototype, {
|
||||
headerHeight: MARKERS_GRAPH_HEADER_HEIGHT,
|
||||
rowHeight: MARKERS_GRAPH_ROW_HEIGHT,
|
||||
groupPadding: MARKERS_GROUP_VERTICAL_PADDING,
|
||||
setPerformanceData: MarkersOverview.prototype.setData
|
||||
});
|
||||
|
||||
/**
|
||||
* Definitions file for GraphsController, indicating the constructor,
|
||||
* selector and other meta for each of the graphs controller by
|
||||
* GraphsController.
|
||||
*/
|
||||
const GRAPH_DEFINITIONS = {
|
||||
memory: {
|
||||
constructor: MemoryGraph,
|
||||
selector: "#memory-overview",
|
||||
},
|
||||
framerate: {
|
||||
constructor: FramerateGraph,
|
||||
selector: "#time-framerate",
|
||||
},
|
||||
timeline: {
|
||||
constructor: TimelineGraph,
|
||||
selector: "#markers-overview",
|
||||
needsBlueprints: true,
|
||||
primaryLink: true
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A controller for orchestrating the performance's tool overview graphs. Constructs,
|
||||
* syncs, toggles displays and defines the memory, framerate and timeline view.
|
||||
*
|
||||
* @param {object} definition
|
||||
* @param {DOMElement} root
|
||||
* @param {function} getBlueprint
|
||||
* @param {function} getTheme
|
||||
*/
|
||||
function GraphsController ({ definition, root, getBlueprint, getTheme }) {
|
||||
this._graphs = {};
|
||||
this._enabled = new Set();
|
||||
this._definition = definition || GRAPH_DEFINITIONS;
|
||||
this._root = root;
|
||||
this._getBlueprint = getBlueprint;
|
||||
this._getTheme = getTheme;
|
||||
this._primaryLink = Object.keys(this._definition).filter(name => this._definition[name].primaryLink)[0];
|
||||
this.$ = root.ownerDocument.querySelector.bind(root.ownerDocument);
|
||||
|
||||
EventEmitter.decorate(this);
|
||||
this._onSelecting = this._onSelecting.bind(this);
|
||||
}
|
||||
|
||||
GraphsController.prototype = {
|
||||
|
||||
/**
|
||||
* Returns the corresponding graph by `graphName`.
|
||||
*/
|
||||
get: function (graphName) {
|
||||
return this._graphs[graphName];
|
||||
},
|
||||
|
||||
/**
|
||||
* Iterates through all graphs and renders the data
|
||||
* from a RecordingModel. Takes a resolution value used in
|
||||
* some graphs.
|
||||
* Saves rendering progress as a promise to be consumed by `destroy`,
|
||||
* to wait for cleaning up rendering during destruction.
|
||||
*/
|
||||
render: Task.async(function *(recordingData, resolution) {
|
||||
// Get the previous render promise so we don't start rendering
|
||||
// until the previous render cycle completes, which can occur
|
||||
// especially when a recording is finished, and triggers a
|
||||
// fresh rendering at a higher rate
|
||||
yield (this._rendering && this._rendering.promise);
|
||||
|
||||
// Check after yielding to ensure we're not tearing down,
|
||||
// as this can create a race condition in tests
|
||||
if (this._destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._rendering = Promise.defer();
|
||||
for (let graph of (yield this._getEnabled())) {
|
||||
yield graph.setPerformanceData(recordingData, resolution);
|
||||
this.emit("rendered", graph.graphName);
|
||||
}
|
||||
this._rendering.resolve();
|
||||
}),
|
||||
|
||||
/**
|
||||
* Destroys the underlying graphs.
|
||||
*/
|
||||
destroy: Task.async(function *() {
|
||||
let primary = this._getPrimaryLink();
|
||||
|
||||
this._destroyed = true;
|
||||
|
||||
if (primary) {
|
||||
primary.off("selecting", this._onSelecting);
|
||||
}
|
||||
|
||||
// If there was rendering, wait until the most recent render cycle
|
||||
// has finished
|
||||
if (this._rendering) {
|
||||
yield this._rendering.promise;
|
||||
}
|
||||
|
||||
for (let graphName in this._graphs) {
|
||||
yield this._graphs[graphName].destroy();
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Applies the theme to the underlying graphs. Optionally takes
|
||||
* a `redraw` boolean in the options to force redraw.
|
||||
*/
|
||||
setTheme: function (options={}) {
|
||||
let theme = options.theme || this._getTheme();
|
||||
for (let graph in this._graphs) {
|
||||
this._graphs[graph].setTheme(theme);
|
||||
this._graphs[graph].refresh({ force: options.redraw });
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets up the graph, if needed. Returns a promise resolving
|
||||
* to the graph if it is enabled once it's ready, or otherwise returns
|
||||
* null if disabled.
|
||||
*/
|
||||
isAvailable: Task.async(function *(graphName) {
|
||||
if (!this._enabled.has(graphName)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let graph = this.get(graphName);
|
||||
|
||||
if (!graph) {
|
||||
graph = yield this._construct(graphName);
|
||||
}
|
||||
|
||||
yield graph.ready();
|
||||
return graph;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Enable or disable a subgraph controlled by GraphsController.
|
||||
* This determines what graphs are visible and get rendered.
|
||||
*/
|
||||
enable: function (graphName, isEnabled) {
|
||||
let el = this.$(this._definition[graphName].selector);
|
||||
el.hidden = !isEnabled;
|
||||
|
||||
// If no status change, just return
|
||||
if (this._enabled.has(graphName) === isEnabled) {
|
||||
return;
|
||||
}
|
||||
if (isEnabled) {
|
||||
this._enabled.add(graphName);
|
||||
} else {
|
||||
this._enabled.delete(graphName);
|
||||
}
|
||||
|
||||
// Invalidate our cache of ready-to-go graphs
|
||||
this._enabledGraphs = null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Disables all graphs controller by the GraphsController, and
|
||||
* also hides the root element. This is a one way switch, and used
|
||||
* when older platforms do not have any timeline data.
|
||||
*/
|
||||
disableAll: function () {
|
||||
this._root.hidden = true;
|
||||
// Hide all the subelements
|
||||
Object.keys(this._definition).forEach(graphName => this.enable(graphName, false));
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets a mapped selection on the graph that is the main controller
|
||||
* for keeping the graphs' selections in sync.
|
||||
*/
|
||||
setMappedSelection: function (selection, { mapStart, mapEnd }) {
|
||||
return this._getPrimaryLink().setMappedSelection(selection, { mapStart, mapEnd });
|
||||
},
|
||||
|
||||
getMappedSelection: function ({ mapStart, mapEnd }) {
|
||||
return this._getPrimaryLink().getMappedSelection({ mapStart, mapEnd });
|
||||
},
|
||||
|
||||
/**
|
||||
* Drops the selection.
|
||||
*/
|
||||
dropSelection: function () {
|
||||
if (this._getPrimaryLink()) {
|
||||
return this._getPrimaryLink().dropSelection();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Makes sure the selection is enabled or disabled in all the graphs.
|
||||
*/
|
||||
selectionEnabled: Task.async(function *(enabled) {
|
||||
for (let graph of (yield this._getEnabled())) {
|
||||
graph.selectionEnabled = enabled;
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Creates the graph `graphName` and initializes it.
|
||||
*/
|
||||
_construct: Task.async(function *(graphName) {
|
||||
let def = this._definition[graphName];
|
||||
let el = this.$(def.selector);
|
||||
let blueprint = def.needsBlueprints ? this._getBlueprint() : void 0;
|
||||
let graph = this._graphs[graphName] = new def.constructor(el, blueprint);
|
||||
graph.graphName = graphName;
|
||||
|
||||
yield graph.ready();
|
||||
|
||||
// Sync the graphs' animations and selections together
|
||||
if (def.primaryLink) {
|
||||
graph.on("selecting", this._onSelecting);
|
||||
} else {
|
||||
CanvasGraphUtils.linkAnimation(this._getPrimaryLink(), graph);
|
||||
CanvasGraphUtils.linkSelection(this._getPrimaryLink(), graph);
|
||||
}
|
||||
|
||||
this.setTheme();
|
||||
return graph;
|
||||
}),
|
||||
|
||||
/**
|
||||
* Returns the main graph for this collection, that all graphs
|
||||
* are bound to for syncing and selection.
|
||||
*/
|
||||
_getPrimaryLink: function () {
|
||||
return this.get(this._primaryLink);
|
||||
},
|
||||
|
||||
/**
|
||||
* Emitted when a selection occurs.
|
||||
*/
|
||||
_onSelecting: function () {
|
||||
this.emit("selecting");
|
||||
},
|
||||
|
||||
/**
|
||||
* Resolves to an array with all graphs that are enabled, and
|
||||
* creates them if needed. Different than just iterating over `this._graphs`,
|
||||
* as those could be enabled. Uses caching, as rendering happens many times per second,
|
||||
* compared to how often which graphs/features are changed (rarely).
|
||||
*/
|
||||
_getEnabled: Task.async(function *() {
|
||||
if (this._enabledGraphs) {
|
||||
return this._enabledGraphs;
|
||||
}
|
||||
let enabled = [];
|
||||
for (let graphName of this._enabled) {
|
||||
let graph;
|
||||
if (graph = yield this.isAvailable(graphName)) {
|
||||
enabled.push(graph);
|
||||
}
|
||||
}
|
||||
return this._enabledGraphs = enabled;
|
||||
}),
|
||||
};
|
||||
|
||||
exports.FramerateGraph = FramerateGraph;
|
||||
exports.MemoryGraph = MemoryGraph;
|
||||
exports.TimelineGraph = TimelineGraph;
|
||||
exports.GraphsController = GraphsController;
|
||||
@@ -140,7 +140,15 @@ function convertLegacyData (legacyData) {
|
||||
memory: [],
|
||||
ticks: ticksData,
|
||||
allocations: { sites: [], timestamps: [], frames: [], counts: [] },
|
||||
profile: profilerData.profile
|
||||
profile: profilerData.profile,
|
||||
// Fake a configuration object here if there's tick data,
|
||||
// so that it can be rendered
|
||||
configuration: {
|
||||
withTicks: !!ticksData.length,
|
||||
withMarkers: false,
|
||||
withMemory: false,
|
||||
withAllocations: false
|
||||
}
|
||||
};
|
||||
|
||||
return data;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user