From b5f375d725638c926321f2e17916bd050233f859 Mon Sep 17 00:00:00 2001 From: Victor Porof Date: Wed, 6 Aug 2014 11:25:17 -0400 Subject: [PATCH] Bug 879008 - Remove the old Profiler frontend, r=rcampbell --- browser/devtools/framework/gDevTools.jsm | 15 - browser/devtools/jar.mn | 15 - browser/devtools/main.js | 2 +- browser/devtools/profiler/cleopatra.js | 167 -- .../profiler/cleopatra/cleopatra.html | 28 - .../profiler/cleopatra/css/devtools.css | 11 - .../devtools/profiler/cleopatra/css/tree.css | 236 -- .../devtools/profiler/cleopatra/css/ui.css | 340 --- .../profiler/cleopatra/images/circlearrow.svg | 27 - .../profiler/cleopatra/images/noise.png | 0 .../profiler/cleopatra/images/throbber.svg | 23 - .../profiler/cleopatra/images/treetwisty.svg | 32 - .../profiler/cleopatra/js/ProgressReporter.js | 187 -- .../profiler/cleopatra/js/devtools.js | 244 -- .../devtools/profiler/cleopatra/js/parser.js | 271 --- .../profiler/cleopatra/js/parserWorker.js | 1664 -------------- .../devtools/profiler/cleopatra/js/strings.js | 29 - .../devtools/profiler/cleopatra/js/tree.js | 702 ------ browser/devtools/profiler/cleopatra/js/ui.js | 1992 ----------------- browser/devtools/profiler/commands.js | 137 -- browser/devtools/profiler/consts.js | 16 - browser/devtools/profiler/controller.js | 411 ---- browser/devtools/profiler/moz.build | 12 - browser/devtools/profiler/panel.js | 598 +---- browser/devtools/profiler/profiler.xul | 48 - browser/devtools/profiler/sidebar.js | 127 -- browser/devtools/profiler/test/browser.ini | 20 - ...wser_profiler_bug_834878_source_buttons.js | 33 - ...owser_profiler_bug_855244_multiple_tabs.js | 103 - .../profiler/test/browser_profiler_cmd.js | 110 - .../test/browser_profiler_console_api.js | 35 - .../browser_profiler_console_api_content.js | 55 - .../browser_profiler_console_api_mixed.js | 37 - .../browser_profiler_console_api_named.js | 56 - .../test/browser_profiler_controller.js | 64 - .../profiler/test/browser_profiler_escape.js | 43 - .../test/browser_profiler_gecko_data.js | 52 - .../profiler/test/browser_profiler_io.js | 80 - .../profiler/test/browser_profiler_remote.js | 55 - .../profiler/test/browser_profiler_run.js | 119 - browser/devtools/profiler/test/head.js | 127 +- .../profiler/test/mock_console_api.html | 21 - .../test/mock_profiler_bug_834878_page.html | 14 - .../test/mock_profiler_bug_834878_script.js | 7 - .../chrome/browser/devtools/profiler.dtd | 8 - .../browser/devtools/profiler.properties | 93 +- browser/themes/osx/devtools/profiler.css | 2 +- .../themes/shared/devtools/profiler.inc.css | 76 +- toolkit/devtools/server/actors/profiler.js | 241 -- .../tests/unit/test_profiler_activation.js | 66 - .../server/tests/unit/test_profiler_actor.js | 191 -- .../devtools/server/tests/unit/xpcshell.ini | 4 - 52 files changed, 9 insertions(+), 9037 deletions(-) delete mode 100644 browser/devtools/profiler/cleopatra.js delete mode 100755 browser/devtools/profiler/cleopatra/cleopatra.html delete mode 100644 browser/devtools/profiler/cleopatra/css/devtools.css delete mode 100755 browser/devtools/profiler/cleopatra/css/tree.css delete mode 100755 browser/devtools/profiler/cleopatra/css/ui.css delete mode 100755 browser/devtools/profiler/cleopatra/images/circlearrow.svg delete mode 100644 browser/devtools/profiler/cleopatra/images/noise.png delete mode 100755 browser/devtools/profiler/cleopatra/images/throbber.svg delete mode 100755 browser/devtools/profiler/cleopatra/images/treetwisty.svg delete mode 100755 browser/devtools/profiler/cleopatra/js/ProgressReporter.js delete mode 100644 browser/devtools/profiler/cleopatra/js/devtools.js delete mode 100755 browser/devtools/profiler/cleopatra/js/parser.js delete mode 100644 browser/devtools/profiler/cleopatra/js/parserWorker.js delete mode 100644 browser/devtools/profiler/cleopatra/js/strings.js delete mode 100755 browser/devtools/profiler/cleopatra/js/tree.js delete mode 100755 browser/devtools/profiler/cleopatra/js/ui.js delete mode 100644 browser/devtools/profiler/commands.js delete mode 100644 browser/devtools/profiler/consts.js delete mode 100644 browser/devtools/profiler/controller.js delete mode 100644 browser/devtools/profiler/sidebar.js delete mode 100644 browser/devtools/profiler/test/browser_profiler_bug_834878_source_buttons.js delete mode 100644 browser/devtools/profiler/test/browser_profiler_bug_855244_multiple_tabs.js delete mode 100644 browser/devtools/profiler/test/browser_profiler_cmd.js delete mode 100644 browser/devtools/profiler/test/browser_profiler_console_api.js delete mode 100644 browser/devtools/profiler/test/browser_profiler_console_api_content.js delete mode 100644 browser/devtools/profiler/test/browser_profiler_console_api_mixed.js delete mode 100644 browser/devtools/profiler/test/browser_profiler_console_api_named.js delete mode 100644 browser/devtools/profiler/test/browser_profiler_controller.js delete mode 100644 browser/devtools/profiler/test/browser_profiler_escape.js delete mode 100644 browser/devtools/profiler/test/browser_profiler_gecko_data.js delete mode 100644 browser/devtools/profiler/test/browser_profiler_io.js delete mode 100644 browser/devtools/profiler/test/browser_profiler_remote.js delete mode 100644 browser/devtools/profiler/test/browser_profiler_run.js delete mode 100644 browser/devtools/profiler/test/mock_console_api.html delete mode 100644 browser/devtools/profiler/test/mock_profiler_bug_834878_page.html delete mode 100644 browser/devtools/profiler/test/mock_profiler_bug_834878_script.js delete mode 100644 toolkit/devtools/server/tests/unit/test_profiler_activation.js delete mode 100644 toolkit/devtools/server/tests/unit/test_profiler_actor.js diff --git a/browser/devtools/framework/gDevTools.jsm b/browser/devtools/framework/gDevTools.jsm index 0ae25dee8c88..d7294f5b2ce3 100644 --- a/browser/devtools/framework/gDevTools.jsm +++ b/browser/devtools/framework/gDevTools.jsm @@ -897,21 +897,6 @@ let gDevToolsBrowser = { * Connects to the SPS profiler when the developer tools are open. */ _connectToProfiler: function DT_connectToProfiler() { - let ProfilerController = devtools.require("devtools/profiler/controller"); - - for (let win of gDevToolsBrowser._trackedBrowserWindows) { - if (devtools.TargetFactory.isKnownTab(win.gBrowser.selectedTab)) { - let target = devtools.TargetFactory.forTab(win.gBrowser.selectedTab); - if (gDevTools._toolboxes.has(target)) { - target.makeRemote().then(() => { - let profiler = new ProfilerController(target); - profiler.connect(); - }).then(null, Cu.reportError); - - return; - } - } - } }, /** diff --git a/browser/devtools/jar.mn b/browser/devtools/jar.mn index 2f0b13453a1b..dda555093ac0 100644 --- a/browser/devtools/jar.mn +++ b/browser/devtools/jar.mn @@ -78,21 +78,6 @@ browser.jar: content/browser/devtools/webaudioeditor-controller.js (webaudioeditor/webaudioeditor-controller.js) content/browser/devtools/webaudioeditor-view.js (webaudioeditor/webaudioeditor-view.js) content/browser/devtools/profiler.xul (profiler/profiler.xul) - content/browser/devtools/cleopatra.html (profiler/cleopatra/cleopatra.html) - content/browser/devtools/profiler/cleopatra/css/ui.css (profiler/cleopatra/css/ui.css) - content/browser/devtools/profiler/cleopatra/css/tree.css (profiler/cleopatra/css/tree.css) - content/browser/devtools/profiler/cleopatra/css/devtools.css (profiler/cleopatra/css/devtools.css) - content/browser/devtools/profiler/cleopatra/js/strings.js (profiler/cleopatra/js/strings.js) - content/browser/devtools/profiler/cleopatra/js/parser.js (profiler/cleopatra/js/parser.js) - content/browser/devtools/profiler/cleopatra/js/parserWorker.js (profiler/cleopatra/js/parserWorker.js) - content/browser/devtools/profiler/cleopatra/js/tree.js (profiler/cleopatra/js/tree.js) - content/browser/devtools/profiler/cleopatra/js/ui.js (profiler/cleopatra/js/ui.js) - content/browser/devtools/profiler/cleopatra/js/ProgressReporter.js (profiler/cleopatra/js/ProgressReporter.js) - content/browser/devtools/profiler/cleopatra/js/devtools.js (profiler/cleopatra/js/devtools.js) - content/browser/devtools/profiler/cleopatra/images/circlearrow.svg (profiler/cleopatra/images/circlearrow.svg) - content/browser/devtools/profiler/cleopatra/images/noise.png (profiler/cleopatra/images/noise.png) - content/browser/devtools/profiler/cleopatra/images/throbber.svg (profiler/cleopatra/images/throbber.svg) - content/browser/devtools/profiler/cleopatra/images/treetwisty.svg (profiler/cleopatra/images/treetwisty.svg) content/browser/devtools/responsivedesign/resize-commands.js (responsivedesign/resize-commands.js) content/browser/devtools/commandline.css (commandline/commandline.css) content/browser/devtools/commandlineoutput.xhtml (commandline/commandlineoutput.xhtml) diff --git a/browser/devtools/main.js b/browser/devtools/main.js index b9a306cc434d..508b28d0efad 100644 --- a/browser/devtools/main.js +++ b/browser/devtools/main.js @@ -264,7 +264,7 @@ Tools.webAudioEditor = { Tools.jsprofiler = { id: "jsprofiler", accesskey: l10n("profiler.accesskey", profilerStrings), - key: l10n("profiler2.commandkey", profilerStrings), + key: l10n("profiler.commandkey2", profilerStrings), ordinal: 7, modifiers: "shift", visibilityswitch: "devtools.profiler.enabled", diff --git a/browser/devtools/profiler/cleopatra.js b/browser/devtools/profiler/cleopatra.js deleted file mode 100644 index 9bfe0a7a70e4..000000000000 --- a/browser/devtools/profiler/cleopatra.js +++ /dev/null @@ -1,167 +0,0 @@ -/* 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"; - -let { Cu } = require("chrome"); -let { defer } = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise; -let EventEmitter = require("devtools/toolkit/event-emitter"); - -const { PROFILE_IDLE, PROFILE_COMPLETED, PROFILE_RUNNING } = require("devtools/profiler/consts"); - -/** - * An implementation of a profile visualization that uses Cleopatra. - * It consists of an iframe with Cleopatra loaded in it and some - * surrounding meta-data (such as UIDs). - * - * Cleopatra is also an event emitter. It emits the following events: - * - ready, when Cleopatra is done loading (you can also check the isReady - * property to see if a particular instance has been loaded yet. - * - * @param number uid - * Unique ID for this profile. - * @param ProfilerPanel panel - * A reference to the container panel. - */ -function Cleopatra(panel, opts) { - let doc = panel.document; - let win = panel.window; - let { uid, name } = opts; - let spd = opts.showPlatformData; - let ext = opts.external; - - EventEmitter.decorate(this); - - this.isReady = false; - this.isStarted = false; - this.isFinished = false; - - this.panel = panel; - this.uid = uid; - this.name = name; - - this.iframe = doc.createElement("iframe"); - this.iframe.setAttribute("flex", "1"); - this.iframe.setAttribute("id", "profiler-cleo-" + uid); - this.iframe.setAttribute("src", "cleopatra.html?uid=" + uid + "&spd=" + spd + "&ext=" + ext); - this.iframe.setAttribute("hidden", "true"); - - // Append our iframe and subscribe to postMessage events. - // They'll tell us when the underlying page is done loading - // or when user clicks on start/stop buttons. - - doc.getElementById("profiler-report").appendChild(this.iframe); - win.addEventListener("message", (event) => { - if (parseInt(event.data.uid, 10) !== parseInt(this.uid, 10)) { - return; - } - - switch (event.data.status) { - case "loaded": - this.isReady = true; - this.emit("ready"); - break; - case "displaysource": - this.panel.displaySource(event.data.data); - } - }); -} - -Cleopatra.prototype = { - /** - * Returns a contentWindow of the iframe pointing to Cleopatra - * if it exists and can be accessed. Otherwise returns null. - */ - get contentWindow() { - if (!this.iframe) { - return null; - } - - try { - return this.iframe.contentWindow; - } catch (err) { - return null; - } - }, - - show: function () { - this.iframe.removeAttribute("hidden"); - }, - - hide: function () { - this.iframe.setAttribute("hidden", true); - }, - - /** - * Send raw profiling data to Cleopatra for parsing. - * - * @param object data - * Raw profiling data from the SPS Profiler. - * @param function onParsed - * A callback to be called when Cleopatra finishes - * parsing and displaying results. - * - */ - parse: function (data, onParsed) { - if (!this.isReady) { - return void this.on("ready", this.parse.bind(this, data, onParsed)); - } - - this.message({ task: "receiveProfileData", rawProfile: data }).then(() => { - let poll = () => { - let wait = this.panel.window.setTimeout.bind(null, poll, 100); - let trail = this.contentWindow.gBreadcrumbTrail; - - if (!trail) { - return wait(); - } - - if (!trail._breadcrumbs || !trail._breadcrumbs.length) { - return wait(); - } - - onParsed(); - }; - - poll(); - }); - }, - - /** - * Send a message to Cleopatra instance. If a message cannot be - * sent, this method queues it for later. - * - * @param object data JSON data to send (must be serializable) - * @return promise - */ - message: function (data) { - let deferred = defer(); - data = JSON.stringify(data); - - let send = () => { - if (!this.contentWindow) - setTimeout(send, 50); - - this.contentWindow.postMessage(data, "*"); - deferred.resolve(); - }; - - send(); - return deferred.promise; - }, - - /** - * Destroys the ProfileUI instance. - */ - destroy: function () { - this.isReady = null; - this.panel = null; - this.uid = null; - this.iframe = null; - this.messages = null; - } -}; - -module.exports = Cleopatra; - diff --git a/browser/devtools/profiler/cleopatra/cleopatra.html b/browser/devtools/profiler/cleopatra/cleopatra.html deleted file mode 100755 index afc5bfa9fb4c..000000000000 --- a/browser/devtools/profiler/cleopatra/cleopatra.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - Firefox Profiler (SPS) - - - - - - - - - - - - - - - - - - diff --git a/browser/devtools/profiler/cleopatra/css/devtools.css b/browser/devtools/profiler/cleopatra/css/devtools.css deleted file mode 100644 index 0b4ebc6bebfa..000000000000 --- a/browser/devtools/profiler/cleopatra/css/devtools.css +++ /dev/null @@ -1,11 +0,0 @@ -/* 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/. */ - -#mainarea { -} - -/* De-emphasize chrome functions */ -.resourceIcon[data-resource^=otherhost_] + .functionName { - color: #999; -} \ No newline at end of file diff --git a/browser/devtools/profiler/cleopatra/css/tree.css b/browser/devtools/profiler/cleopatra/css/tree.css deleted file mode 100755 index 3b092a9767e2..000000000000 --- a/browser/devtools/profiler/cleopatra/css/tree.css +++ /dev/null @@ -1,236 +0,0 @@ -/* 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/. */ - -.treeViewContainer { - -moz-user-select: none; - user-select: none; - cursor: default; - line-height: 16px; - height: 100%; - outline: none; /* override the browser's focus styling */ - position: relative; -} - -.treeHeader { - position: absolute; - top: 0; - right: 0; - left: 0; - height: 16px; - margin: 0; - padding: 0; -} - -.treeColumnHeader { - position: absolute; - display: block; - background: linear-gradient(#FFF 45%, #EEE 60%); - margin: 0; - padding: 0; - top: 0; - height: 15px; - line-height: 15px; - border: 0 solid #CCC; - border-bottom-width: 1px; - text-indent: 5px; -} - -.treeColumnHeader:not(:last-child) { - border-right-width: 1px; -} - -.treeColumnHeader0 { - left: 0; - width: 86px; -} - -.treeColumnHeader1 { - left: 99px; - width: 35px; -} - -.treeColumnHeader0, -.treeColumnHeader1 { - text-align: right; - padding-right: 12px; -} - -.treeColumnHeader2 { - left: 147px; - right: 0; -} - -.treeViewVerticalScrollbox { - position: absolute; - top: 16px; - left: 0; - right: 0; - bottom: 0; - overflow-y: scroll; - overflow-x: hidden; -} - -.treeViewNode, -.treeViewHorizontalScrollbox { - display: block; - margin: 0; - padding: 0; -} - -.treeViewNode { - min-width: -moz-min-content; - white-space: nowrap; -} - -.treeViewHorizontalScrollbox { - padding-left: 150px; - overflow: hidden; -} - -.treeViewVerticalScrollbox, -.treeViewHorizontalScrollbox { - background: linear-gradient(white, white 50%, #F0F5FF 50%, #F0F5FF); - background-size: 100px 32px; -} - -.leftColumnBackground { - background: linear-gradient(left, transparent, transparent 98px, #CCC 98px, #CCC 99px, transparent 99px), - linear-gradient(white, white 50%, #F0F5FF 50%, #F0F5FF); - background-size: auto, 100px 32px; - position: absolute; - top: 0; - left: 0; - width: 146px; - min-height: 100%; - border-right: 1px solid #CCC; -} - -.sampleCount, -.samplePercentage, -.selfSampleCount { - position: absolute; - text-align: right; -} - -.sampleCount { - left: 2px; - width: 50px; -} - -.samplePercentage { - left: 55px; - width: 40px; -} - -.selfSampleCount { - left: 98px; - width: 45px; - padding-right: 2px; - border: solid #CCC; - border-width: 0 1px; -} - -.libraryName { - margin-left: 10px; - color: #999; -} - -.treeViewNode > .treeViewNodeList { - margin-left: 1em; -} - -.treeViewNode.collapsed > .treeViewNodeList { - display: none; -} - -.treeLine { - /* extend the selection background almost infinitely to the left */ - margin-left: -10000px; - padding-left: 10000px; -} - -.treeLine.selected { - color: black; - background-color: -moz-dialog; -} - -.treeLine.selected > .sampleCount { - background-color: inherit; - margin-left: -2px; - padding-left: 2px; - padding-right: 95px; - margin-right: -95px; -} - -.treeViewContainer:focus .treeLine.selected { - color: highlighttext; - background-color: highlight; -} - -.treeViewContainer:focus .treeLine.selected > .libraryName { - color: #CCC; -} - -.expandCollapseButton, -.focusCallstackButton { - background: none 0 0 no-repeat transparent; - margin: 0; - padding: 0; - border: 0; - width: 16px; - height: 16px; - overflow: hidden; - vertical-align: top; - color: transparent; - font-size: 0; -} - -.expandCollapseButton { - background-image: url(../images/treetwisty.svg); -} - -.focusCallstackButton { - background-image: url(../images/circlearrow.svg); - margin-left: 5px; - visibility: hidden; -} - -.expandCollapseButton:active:hover, -.focusCallstackButton:active:hover { - background-position: -16px 0; -} - -.treeViewNode.collapsed > .treeLine > .expandCollapseButton { - background-position: 0 -16px; -} - -.treeViewNode.collapsed > .treeLine > .expandCollapseButton:active:hover { - background-position: -16px -16px; -} - -.treeViewContainer:focus .treeLine.selected > .expandCollapseButton, -.treeViewContainer:focus .treeLine.selected > .focusCallstackButton { - background-position: -32px 0; -} - -.treeViewContainer:focus .treeViewNode.collapsed > .treeLine.selected > .expandCollapseButton { - background-position: -32px -16px; -} - -.treeViewContainer:focus .treeLine.selected > .expandCollapseButton:active:hover, -.treeViewContainer:focus .treeLine.selected > .focusCallstackButton:active:hover { - background-position: -48px 0; -} - -.treeViewContainer:focus .treeViewNode.collapsed > .treeLine.selected > .expandCollapseButton:active:hover { - background-position: -48px -16px; -} - -.treeViewNode.leaf > * > .expandCollapseButton { - visibility: hidden; -} - -.treeLine:hover > .focusCallstackButton { - visibility: visible; -} diff --git a/browser/devtools/profiler/cleopatra/css/ui.css b/browser/devtools/profiler/cleopatra/css/ui.css deleted file mode 100755 index bb9b63ca31dc..000000000000 --- a/browser/devtools/profiler/cleopatra/css/ui.css +++ /dev/null @@ -1,340 +0,0 @@ -/* 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/. */ - -body { - margin: 0; - font-family: "Lucida Grande", sans-serif; - font-size: 11px; -} -#mainarea { - position: absolute; - top: 0; - left: 0px; - bottom: 0; - right: 0; -} -.finishedProfilePane, -.finishedProfilePaneBackgroundCover, -.profileEntryPane, -.profileProgressPane { - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; -} -.profileEntryPane { - overflow: auto; -} -.profileEntryPane, -.profileProgressPane { - padding: 20px; - background-color: rgb(229,229,229); - background-image: url(../images/noise.png), - linear-gradient(rgba(255,255,255,.5),rgba(255,255,255,.2)); - text-shadow: rgba(255, 255, 255, 0.4) 0 1px; -} -.profileEntryPane h1 { - margin-top: 0; - font-size: 13px; - font-weight: normal; -} -.profileEntryPane input[type="file"] { - margin-bottom: 1em; -} -.profileProgressPane a { - position: absolute; - top: 30%; - left: 30%; - width: 40%; - height: 16px; -} -.profileProgressPane progress { - position: absolute; - top: 40%; - left: 30%; - width: 40%; - height: 16px; -} -.finishedProfilePaneBackgroundCover { - animation: darken 300ms cubic-bezier(0, 0, 1, 0); - background-color: rgba(0, 0, 0, 0.5); -} - -.finishedProfilePane { - animation: appear 300ms ease-out; -} - -@keyframes darken { - from { - opacity: 0; - } - to { - opacity: 1; - } -} -@keyframes appear { - from { - transform: scale(0.3); - opacity: 0; - pointer-events: none; - } - to { - transform: scale(1); - opacity: 1; - pointer-events: auto; - } -} -.breadcrumbTrail { - top: 0; - right: 0; - height: 29px; - left: 0; - background: linear-gradient(#FFF 50%, #F3F3F3 55%); - border-bottom: 1px solid #CCC; - margin: 0; - padding: 0; - overflow: hidden; -} -.breadcrumbTrailItem { - background: linear-gradient(#FFF 50%, #F3F3F3 55%); - display: block; - margin: 0; - padding: 0; - float: left; - line-height: 29px; - padding: 0 10px; - font-size: 12px; - -moz-user-select: none; - user-select: none; - cursor: default; - border-right: 1px solid #CCC; - max-width: 250px; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - position: relative; -} -@keyframes slide-out { - from { - margin-left: -270px; - opacity: 0; - } - to { - margin-left: 0; - opacity: 1; - } -} -@keyframes slide-out { - from { - margin-left: -270px; - opacity: 0; - } - to { - margin-left: 0; - opacity: 1; - } -} -.breadcrumbTrailItem:not(:first-child) { - animation: slide-out; - animation-duration: 400ms; - animation-timing-function: ease-out; -} -.breadcrumbTrailItem.selected { - background: linear-gradient(#E5E5E5 50%, #DADADA 55%); -} -.breadcrumbTrailItem:not(.selected):active:hover { - background: linear-gradient(#F2F2F2 50%, #E6E6E6 55%); -} -.breadcrumbTrailItem.deleted { - transition: 400ms ease-out; - transition-property: opacity, margin-left; - opacity: 0; - margin-left: -270px; -} -.treeContainer { - /*For asbolute position child*/ - position: relative; -} -.tree { - height: 100%; -} -#sampleBar { - position: absolute; - float: right; - left: auto; - top: 0; - right: 0; - height: 100%; -} -#fileList { - position: absolute; - top: 0; - left: 0; - bottom: 360px; - width: 199px; - overflow: auto; - padding: 0; - margin: 0; - background: #DBDFE7; - border-right: 1px solid #BBB; - cursor: pointer; -} -#infoBar dl { - margin: 0; -} -#infoBar dt, -#infoBar dd { - display: inline; -} -#infoBar dt { - font-weight: bold; -} -#infoBar dt::after { - content: " "; - white-space: pre; -} -#infoBar dd { - margin-left: 0; -} -#infoBar dd::after { - content: "\a"; - white-space:pre; -} -.sideBar { - box-sizing: border-box; - position: absolute; - left: 0; - bottom: 0; - width: 200px; - height: 480px; - overflow: auto; - padding: 3px; - background: #EEE; - border-top: 1px solid #BBB; - border-right: 1px solid #BBB; -} -.sideBar h2 { - font-size: 1em; - padding: 1px 3px; - margin: 3px -3px; - background: rgba(255, 255, 255, 0.6); - border: solid #CCC; - border-width: 1px 0; -} -.sideBar h2:first-child { - margin-top: -4px; -} -.sideBar ul { - margin: 2px 0; - padding-left: 18px; -} -.pluginview { - position: absolute; - right: 0; - bottom: 0; - left: 0; - z-index: 1; - background-color: white; -} -.pluginviewIFrame { - border-style: none; - width: 100%; - height: 100%; -} -.histogram { - position: relative; - height: 60px; - right: 0; - left: 0; - border-bottom: 1px solid #CCC; - background: linear-gradient(#EEE, #CCC); -} -.histogramHilite { - position: absolute; - pointer-events: none; -} -.histogramHilite:not(.collapsed) { - background: rgba(150, 150, 150, 0.5); -} -.histogramMouseMarker { - position: absolute; - pointer-events: none; - top: 0; - width: 1px; - height: 100%; -} -.histogramMouseMarker:not(.collapsed) { - background: rgba(0, 0, 150, 0.7); -} -#iconbox { - display: none; -} -#filter, #showall { - cursor: pointer; -} -.markers { - display: none; -} -.hidden { - display: none !important; -} -.fileListItem { - display: block; - margin: 0; - padding: 0; - height: 40px; - text-indent: 8px; -} -.fileListItem.selected { - background: linear-gradient(#4B91D7 1px, #5FA9E4 1px, #5FA9E4 2px, #58A0DE 3px, #2B70C7 39px, #2763B4 39px); - color: #FFF; - text-shadow: 0 1px rgba(0, 0, 0, 0.3); -} -.fileListItemTitle { - display: block; - padding-top: 6px; - font-size: 12px; -} -.fileListItemDescription { - display: block; - line-height: 15px; - font-size: 9px; -} -.busyCover { - position: absolute; - top: 0; - right: 0; - bottom: 0; - left: 0; - visibility: hidden; - opacity: 0; - pointer-events: none; - background: rgba(120, 120, 120, 0.2); - transition: 200ms ease-in-out; - transition-property: visibility, opacity; -} -.busyCover.busy { - visibility: visible; - opacity: 1; -} -.busyCover::before { - content: url(../images/throbber.svg); - position: absolute; - top: 50%; - left: 50%; - margin: -12px; -} -label { - -moz-user-select: none; -} -.videoPane { - background-color: white; - width: 100%; -} -.video { - display: block; - margin-left: auto; - margin-right: auto; -} diff --git a/browser/devtools/profiler/cleopatra/images/circlearrow.svg b/browser/devtools/profiler/cleopatra/images/circlearrow.svg deleted file mode 100755 index 70a7c54efead..000000000000 --- a/browser/devtools/profiler/cleopatra/images/circlearrow.svg +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/browser/devtools/profiler/cleopatra/images/noise.png b/browser/devtools/profiler/cleopatra/images/noise.png deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/browser/devtools/profiler/cleopatra/images/throbber.svg b/browser/devtools/profiler/cleopatra/images/throbber.svg deleted file mode 100755 index d185ef8d5b15..000000000000 --- a/browser/devtools/profiler/cleopatra/images/throbber.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/browser/devtools/profiler/cleopatra/images/treetwisty.svg b/browser/devtools/profiler/cleopatra/images/treetwisty.svg deleted file mode 100755 index b51aa4bd58da..000000000000 --- a/browser/devtools/profiler/cleopatra/images/treetwisty.svg +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/browser/devtools/profiler/cleopatra/js/ProgressReporter.js b/browser/devtools/profiler/cleopatra/js/ProgressReporter.js deleted file mode 100755 index 3ae1c9fbd0cf..000000000000 --- a/browser/devtools/profiler/cleopatra/js/ProgressReporter.js +++ /dev/null @@ -1,187 +0,0 @@ -/* 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/. */ - -/** - * ProgressReporter - * - * This class is used by long-winded tasks to report progress to observers. - * If a task has subtasks that want to report their own progress, these - * subtasks can have their own progress reporters which are hooked up to the - * parent progress reporter, resulting in a tree structure. A parent progress - * reporter will calculate its progress value as a weighted sum of its - * subreporters' progress values. - * - * A progress reporter has a state, an action, and a progress value. - * - * - state is one of STATE_WAITING, STATE_DOING and STATE_FINISHED. - * - action is a string that describes the current task. - * - progress is the progress value as a number between 0 and 1, or NaN if - * indeterminate. - * - * A progress reporter starts out in the WAITING state. The DOING state is - * entered with the begin method which also sets the action. While the task is - * executing, the progress value can be updated with the setProgress method. - * When a task has finished, it can call the finish method which is just a - * shorthand for setProgress(1); this will set the state to FINISHED. - * - * Progress observers can be added with the addListener method which takes a - * function callback. Whenever the progress value or state change, all - * listener callbacks will be called with the progress reporter object. The - * observer can get state, progress value and action by calling the getter - * methods getState(), getProgress() and getAction(). - * - * Creating child progress reporters for subtasks can be done with the - * addSubreporter(s) methods. If a progress reporter has subreporters, normal - * progress report functions (setProgress and finish) can no longer be called. - * Instead, the parent reporter will listen to progress changes on its - * subreporters and update its state automatically, and then notify its own - * listeners. - * When adding a subreporter, you are expected to provide an estimated - * duration for the subtask. This value will be used as a weight when - * calculating the progress of the parent reporter. - */ - -"use strict"; - -const gDebugExpectedDurations = false; - -function ProgressReporter() { - this._observers = []; - this._subreporters = []; - this._subreporterExpectedDurationsSum = 0; - this._progress = 0; - this._state = ProgressReporter.STATE_WAITING; - this._action = ""; -} - -ProgressReporter.STATE_WAITING = 0; -ProgressReporter.STATE_DOING = 1; -ProgressReporter.STATE_FINISHED = 2; - -ProgressReporter.prototype = { - getProgress: function () { - return this._progress; - }, - getState: function () { - return this._state; - }, - setAction: function (action) { - this._action = action; - this._reportProgress(); - }, - getAction: function () { - switch (this._state) { - case ProgressReporter.STATE_WAITING: - return "Waiting for preceding tasks to finish..."; - case ProgressReporter.STATE_DOING: - return this._action; - case ProgressReporter.STATE_FINISHED: - return "Finished."; - default: - throw "Broken state"; - } - }, - addListener: function (callback) { - this._observers.push(callback); - }, - addSubreporter: function (expectedDuration) { - this._subreporterExpectedDurationsSum += expectedDuration; - var subreporter = new ProgressReporter(); - var self = this; - subreporter.addListener(function (progress) { - self._recalculateProgressFromSubreporters(); - self._recalculateStateAndActionFromSubreporters(); - self._reportProgress(); - }); - this._subreporters.push({ expectedDuration: expectedDuration, reporter: subreporter }); - return subreporter; - }, - addSubreporters: function (expectedDurations) { - var reporters = {}; - for (var key in expectedDurations) { - reporters[key] = this.addSubreporter(expectedDurations[key]); - } - return reporters; - }, - begin: function (action) { - this._startTime = Date.now(); - this._state = ProgressReporter.STATE_DOING; - this._action = action; - this._reportProgress(); - }, - setProgress: function (progress) { - if (this._subreporters.length > 0) - throw "Can't call setProgress on a progress reporter with subreporters"; - if (progress != this._progress && - (progress == 1 || - (isNaN(progress) != isNaN(this._progress)) || - (progress - this._progress >= 0.01))) { - this._progress = progress; - if (progress == 1) - this._transitionToFinished(); - this._reportProgress(); - } - }, - finish: function () { - this.setProgress(1); - }, - _recalculateProgressFromSubreporters: function () { - if (this._subreporters.length == 0) - throw "Can't _recalculateProgressFromSubreporters on a progress reporter without any subreporters"; - this._progress = 0; - for (var i = 0; i < this._subreporters.length; i++) { - var expectedDuration = this._subreporters[i].expectedDuration; - var reporter = this._subreporters[i].reporter; - this._progress += reporter.getProgress() * expectedDuration / this._subreporterExpectedDurationsSum; - } - }, - _recalculateStateAndActionFromSubreporters: function () { - if (this._subreporters.length == 0) - throw "Can't _recalculateStateAndActionFromSubreporters on a progress reporter without any subreporters"; - var actions = []; - var allWaiting = true; - var allFinished = true; - for (var i = 0; i < this._subreporters.length; i++) { - var expectedDuration = this._subreporters[i].expectedDuration; - var reporter = this._subreporters[i].reporter; - var state = reporter.getState(); - if (state != ProgressReporter.STATE_WAITING) - allWaiting = false; - if (state != ProgressReporter.STATE_FINISHED) - allFinished = false; - if (state == ProgressReporter.STATE_DOING) - actions.push(reporter.getAction()); - } - if (allFinished) { - this._transitionToFinished(); - } else if (!allWaiting) { - this._state = ProgressReporter.STATE_DOING; - if (actions.length == 0) { - this._action = "About to start next task..." - } else { - this._action = actions.join("\n"); - } - } - }, - _transitionToFinished: function () { - this._state = ProgressReporter.STATE_FINISHED; - - if (gDebugExpectedDurations) { - this._realDuration = Date.now() - this._startTime; - if (this._subreporters.length) { - for (var i = 0; i < this._subreporters.length; i++) { - var expectedDuration = this._subreporters[i].expectedDuration; - var reporter = this._subreporters[i].reporter; - var realDuration = reporter._realDuration; - dump("For reporter with expectedDuration " + expectedDuration + ", real duration was " + realDuration + "\n"); - } - } - } - }, - _reportProgress: function () { - for (var i = 0; i < this._observers.length; i++) { - this._observers[i](this); - } - }, -}; diff --git a/browser/devtools/profiler/cleopatra/js/devtools.js b/browser/devtools/profiler/cleopatra/js/devtools.js deleted file mode 100644 index 4f30d9f6c338..000000000000 --- a/browser/devtools/profiler/cleopatra/js/devtools.js +++ /dev/null @@ -1,244 +0,0 @@ -/* 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"; - -var gInstanceUID; -var gParsedQS; -var gHideSourceLinks; - -function getParam(key) { - if (gParsedQS) - return gParsedQS[key]; - - var query = window.location.search.substring(1); - gParsedQS = {}; - - query.split("&").forEach(function (pair) { - pair = pair.split("="); - gParsedQS[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]); - }); - - return gParsedQS[key]; -} - -/** - * Sends a message to the parent window with a status - * update. - * - * @param string status - * Status to send to the parent page: - * - loaded, when page is loaded. - * - displaysource, when user wants to display source - * @param object data (optional) - * Additional data to send to the parent page. - */ -function notifyParent(status, data={}) { - if (!gInstanceUID) { - gInstanceUID = getParam("uid"); - } - - window.parent.postMessage({ - uid: gInstanceUID, - status: status, - data: data - }, "*"); -} - -/** - * A listener for incoming messages from the parent - * page. All incoming messages must be stringified - * JSON objects to be compatible with Cleopatra's - * format: - * - * { - * task: string, - * ... - * } - * - * This listener recognizes two tasks: onStarted and - * onStopped. - * - * @param object event - * PostMessage event object. - */ -function onParentMessage(event) { - var start = document.getElementById("startWrapper"); - var stop = document.getElementById("stopWrapper"); - var profilerMessage = document.getElementById("profilerMessage"); - var msg = JSON.parse(event.data); - - if (msg.task !== "receiveProfileData" && !msg.isCurrent) { - return; - } - - switch (msg.task) { - case "onStarted": - start.style.display = "none"; - start.querySelector("button").removeAttribute("disabled"); - stop.style.display = "inline"; - break; - case "onStopped": - stop.style.display = "none"; - stop.querySelector("button").removeAttribute("disabled"); - start.style.display = "inline"; - break; - case "receiveProfileData": - loadProfile(JSON.stringify(msg.rawProfile)); - } -} - -window.addEventListener("message", onParentMessage); - -/** - * Main entry point. This function initializes Cleopatra - * in the light mode and creates all the UI we need. - */ -function initUI() { - gHideSourceLinks = getParam("ext") === "true"; - gFileList = { profileParsingFinished: function () {} }; - gInfoBar = { display: function () {} }; - - var container = document.createElement("div"); - container.id = "ui"; - - gMainArea = document.createElement("div"); - gMainArea.id = "mainarea"; - - container.appendChild(gMainArea); - document.body.appendChild(container); -} - -/** - * Modified copy of Cleopatra's enterFinishedProfileUI. - * By overriding the function we don't need to modify ui.js which helps - * with updating from upstream. - */ -function enterFinishedProfileUI() { - var cover = document.createElement("div"); - cover.className = "finishedProfilePaneBackgroundCover"; - - var pane = document.createElement("table"); - var rowIndex = 0; - var currRow; - - pane.style.width = "100%"; - pane.style.height = "100%"; - pane.border = "0"; - pane.cellPadding = "0"; - pane.cellSpacing = "0"; - pane.borderCollapse = "collapse"; - pane.className = "finishedProfilePane"; - - gBreadcrumbTrail = new BreadcrumbTrail(); - currRow = pane.insertRow(rowIndex++); - currRow.insertCell(0).appendChild(gBreadcrumbTrail.getContainer()); - - gHistogramView = new HistogramView(); - currRow = pane.insertRow(rowIndex++); - currRow.insertCell(0).appendChild(gHistogramView.getContainer()); - - if (gMeta && gMeta.videoCapture) { - gVideoPane = new VideoPane(gMeta.videoCapture); - gVideoPane.onTimeChange(videoPaneTimeChange); - currRow = pane.insertRow(rowIndex++); - currRow.insertCell(0).appendChild(gVideoPane.getContainer()); - } - - var tree = document.createElement("div"); - tree.className = "treeContainer"; - tree.style.width = "100%"; - tree.style.height = "100%"; - - gTreeManager = new ProfileTreeManager(); - gTreeManager.treeView.setColumns([ - { name: "sampleCount", title: gStrings["Running Time"] }, - { name: "selfSampleCount", title: gStrings["Self"] }, - { name: "resource", title: "" } - ]); - - currRow = pane.insertRow(rowIndex++); - currRow.style.height = "100%"; - - var cell = currRow.insertCell(0); - cell.appendChild(tree); - tree.appendChild(gTreeManager.getContainer()); - - gPluginView = new PluginView(); - tree.appendChild(gPluginView.getContainer()); - - gMainArea.appendChild(cover); - gMainArea.appendChild(pane); - - var currentBreadcrumb = gSampleFilters; - gBreadcrumbTrail.add({ - title: gStrings["Complete Profile"], - enterCallback: function () { - gSampleFilters = []; - filtersChanged(); - } - }); - - if (currentBreadcrumb == null || currentBreadcrumb.length == 0) { - gTreeManager.restoreSerializedSelectionSnapshot(gRestoreSelection); - viewOptionsChanged(); - } - - for (var i = 0; i < currentBreadcrumb.length; i++) { - var filter = currentBreadcrumb[i]; - var forceSelection = null; - if (gRestoreSelection != null && i == currentBreadcrumb.length - 1) { - forceSelection = gRestoreSelection; - } - switch (filter.type) { - case "FocusedFrameSampleFilter": - focusOnSymbol(filter.name, filter.symbolName); - gBreadcrumbTrail.enterLastItem(forceSelection); - case "FocusedCallstackPrefixSampleFilter": - focusOnCallstack(filter.focusedCallstack, filter.name, false); - gBreadcrumbTrail.enterLastItem(forceSelection); - case "FocusedCallstackPostfixSampleFilter": - focusOnCallstack(filter.focusedCallstack, filter.name, true); - gBreadcrumbTrail.enterLastItem(forceSelection); - case "RangeSampleFilter": - gHistogramView.selectRange(filter.start, filter.end); - gBreadcrumbTrail.enterLastItem(forceSelection); - } - } - - // Show platform data? - if (getParam("spd") !== "true") - toggleJavascriptOnly(); -} - -function enterProgressUI() { - var pane = document.createElement("div"); - var label = document.createElement("a"); - var bar = document.createElement("progress"); - var string = gStrings.getStr("profiler.loading"); - - pane.className = "profileProgressPane"; - pane.appendChild(label); - pane.appendChild(bar); - - var reporter = new ProgressReporter(); - reporter.addListener(function (rep) { - var progress = rep.getProgress(); - - if (label.textContent !== string) { - label.textContent = string; - } - - if (isNaN(progress)) { - bar.removeAttribute("value"); - } else { - bar.value = progress; - } - }); - - gMainArea.appendChild(pane); - Parser.updateLogSetting(); - - return reporter; -} diff --git a/browser/devtools/profiler/cleopatra/js/parser.js b/browser/devtools/profiler/cleopatra/js/parser.js deleted file mode 100755 index 042096ea1d13..000000000000 --- a/browser/devtools/profiler/cleopatra/js/parser.js +++ /dev/null @@ -1,271 +0,0 @@ -/* 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"; - -Array.prototype.clone = function() { return this.slice(0); } - -function makeSample(frames, extraInfo, lines) { - return { - frames: frames, - extraInfo: extraInfo, - lines: lines - }; -} - -function cloneSample(sample) { - return makeSample(sample.frames.clone(), sample.extraInfo, sample.lines.clone()); -} - -function bucketsBySplittingArray(array, maxItemsPerBucket) { - var buckets = []; - while (buckets.length * maxItemsPerBucket < array.length) { - buckets.push(array.slice(buckets.length * maxItemsPerBucket, - (buckets.length + 1) * maxItemsPerBucket)); - } - return buckets; -} - -var gParserWorker = new Worker("profiler/cleopatra/js/parserWorker.js"); -gParserWorker.nextRequestID = 0; - -function WorkerRequest(worker) { - var self = this; - this._eventListeners = {}; - var requestID = worker.nextRequestID++; - this._requestID = requestID; - this._worker = worker; - this._totalReporter = new ProgressReporter(); - this._totalReporter.addListener(function (reporter) { - self._fireEvent("progress", reporter.getProgress(), reporter.getAction()); - }) - this._sendChunkReporter = this._totalReporter.addSubreporter(500); - this._executeReporter = this._totalReporter.addSubreporter(3000); - this._receiveChunkReporter = this._totalReporter.addSubreporter(100); - this._totalReporter.begin("Processing task in worker..."); - var partialResult = null; - function onMessageFromWorker(msg) { - pendingMessages.push(msg); - scheduleMessageProcessing(); - } - function processMessage(msg) { - var startTime = Date.now(); - var data = msg.data; - var readTime = Date.now() - startTime; - - if (data.requestID == requestID || !data.requestID) { - switch(data.type) { - case "error": - self._sendChunkReporter.setAction("Error in worker: " + data.error); - self._executeReporter.setAction("Error in worker: " + data.error); - self._receiveChunkReporter.setAction("Error in worker: " + data.error); - self._totalReporter.setAction("Error in worker: " + data.error); - PROFILERERROR("Error in worker: " + data.error); - self._fireEvent("error", data.error); - break; - case "progress": - self._executeReporter.setProgress(data.progress); - break; - case "finished": - self._executeReporter.finish(); - self._receiveChunkReporter.begin("Receiving data from worker..."); - self._receiveChunkReporter.finish(); - self._fireEvent("finished", data.result); - worker.removeEventListener("message", onMessageFromWorker); - break; - case "finishedStart": - partialResult = null; - self._totalReceiveChunks = data.numChunks; - self._gotReceiveChunks = 0; - self._executeReporter.finish(); - self._receiveChunkReporter.begin("Receiving data from worker..."); - break; - case "finishedChunk": - partialResult = partialResult ? partialResult.concat(data.chunk) : data.chunk; - var chunkIndex = self._gotReceiveChunks++; - self._receiveChunkReporter.setProgress((chunkIndex + 1) / self._totalReceiveChunks); - break; - case "finishedEnd": - self._receiveChunkReporter.finish(); - self._fireEvent("finished", partialResult); - worker.removeEventListener("message", onMessageFromWorker); - break; - } - // dump log if present - if (data.log) { - for (var line in data.log) { - PROFILERLOG(line); - } - } - } - } - var pendingMessages = []; - var messageProcessingTimer = 0; - function processMessages() { - messageProcessingTimer = 0; - processMessage(pendingMessages.shift()); - if (pendingMessages.length) - scheduleMessageProcessing(); - } - function scheduleMessageProcessing() { - if (messageProcessingTimer) - return; - messageProcessingTimer = setTimeout(processMessages, 10); - } - worker.addEventListener("message", onMessageFromWorker); -} - -WorkerRequest.prototype = { - send: function WorkerRequest_send(task, taskData) { - this._sendChunkReporter.begin("Sending data to worker..."); - var startTime = Date.now(); - this._worker.postMessage({ - requestID: this._requestID, - task: task, - taskData: taskData - }); - var postTime = Date.now() - startTime; - this._sendChunkReporter.finish(); - this._executeReporter.begin("Processing worker request..."); - }, - sendInChunks: function WorkerRequest_sendInChunks(task, taskData, params, maxChunkSize) { - this._sendChunkReporter.begin("Sending data to worker..."); - var self = this; - var chunks = bucketsBySplittingArray(taskData, maxChunkSize); - var pendingMessages = [ - { - requestID: this._requestID, - task: "chunkedStart", - numChunks: chunks.length - } - ].concat(chunks.map(function (chunk) { - return { - requestID: self._requestID, - task: "chunkedChunk", - chunk: chunk - }; - })).concat([ - { - requestID: this._requestID, - task: "chunkedEnd" - }, - { - requestID: this._requestID, - params: params, - task: task - }, - ]); - var totalMessages = pendingMessages.length; - var numSentMessages = 0; - function postMessage(msg) { - var msgIndex = numSentMessages++; - var startTime = Date.now(); - self._worker.postMessage(msg); - var postTime = Date.now() - startTime; - self._sendChunkReporter.setProgress((msgIndex + 1) / totalMessages); - } - var messagePostingTimer = 0; - function postMessages() { - messagePostingTimer = 0; - postMessage(pendingMessages.shift()); - if (pendingMessages.length) { - scheduleMessagePosting(); - } else { - self._sendChunkReporter.finish(); - self._executeReporter.begin("Processing worker request..."); - } - } - function scheduleMessagePosting() { - if (messagePostingTimer) - return; - messagePostingTimer = setTimeout(postMessages, 10); - } - scheduleMessagePosting(); - }, - - // TODO: share code with TreeView - addEventListener: function WorkerRequest_addEventListener(eventName, callbackFunction) { - if (!(eventName in this._eventListeners)) - this._eventListeners[eventName] = []; - if (this._eventListeners[eventName].indexOf(callbackFunction) != -1) - return; - this._eventListeners[eventName].push(callbackFunction); - }, - removeEventListener: function WorkerRequest_removeEventListener(eventName, callbackFunction) { - if (!(eventName in this._eventListeners)) - return; - var index = this._eventListeners[eventName].indexOf(callbackFunction); - if (index == -1) - return; - this._eventListeners[eventName].splice(index, 1); - }, - _fireEvent: function WorkerRequest__fireEvent(eventName, eventObject, p1) { - if (!(eventName in this._eventListeners)) - return; - this._eventListeners[eventName].forEach(function (callbackFunction) { - callbackFunction(eventObject, p1); - }); - }, -} - -var Parser = { - parse: function Parser_parse(data, params) { - var request = new WorkerRequest(gParserWorker); - request.sendInChunks("parseRawProfile", data, params, 3000000); - return request; - }, - - updateFilters: function Parser_updateFilters(filters) { - var request = new WorkerRequest(gParserWorker); - request.send("updateFilters", { - filters: filters, - profileID: 0 - }); - return request; - }, - - updateViewOptions: function Parser_updateViewOptions(options) { - var request = new WorkerRequest(gParserWorker); - request.send("updateViewOptions", { - options: options, - profileID: 0 - }); - return request; - }, - - getSerializedProfile: function Parser_getSerializedProfile(complete, callback) { - var request = new WorkerRequest(gParserWorker); - request.send("getSerializedProfile", { - profileID: 0, - complete: complete - }); - request.addEventListener("finished", callback); - }, - - calculateHistogramData: function Parser_calculateHistogramData() { - var request = new WorkerRequest(gParserWorker); - request.send("calculateHistogramData", { - profileID: 0 - }); - return request; - }, - - calculateDiagnosticItems: function Parser_calculateDiagnosticItems(meta) { - var request = new WorkerRequest(gParserWorker); - request.send("calculateDiagnosticItems", { - profileID: 0, - meta: meta - }); - return request; - }, - - updateLogSetting: function Parser_updateLogSetting() { - var request = new WorkerRequest(gParserWorker); - request.send("initWorker", { - debugLog: gDebugLog, - debugTrace: gDebugTrace, - }); - return request; - }, -}; diff --git a/browser/devtools/profiler/cleopatra/js/parserWorker.js b/browser/devtools/profiler/cleopatra/js/parserWorker.js deleted file mode 100644 index 499be4a38aca..000000000000 --- a/browser/devtools/profiler/cleopatra/js/parserWorker.js +++ /dev/null @@ -1,1664 +0,0 @@ -/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ - -/* 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"; - -importScripts("ProgressReporter.js"); - -var gProfiles = []; - -var partialTaskData = {}; - -var gNextProfileID = 0; - -var gLogLines = []; - -var gDebugLog = false; -var gDebugTrace = false; -// Use for verbose tracing, otherwise use log -function PROFILDERTRACE(msg) { - if (gDebugTrace) - PROFILERLOG(msg); -} -function PROFILERLOG(msg) { - if (gDebugLog) { - msg = "Cleo: " + msg; - //if (window.dump) - // window.dump(msg + "\n"); - } -} -function PROFILERERROR(msg) { - msg = "Cleo: " + msg; - //if (window.dump) - // window.dump(msg + "\n"); -} - -// http://stackoverflow.com/a/2548133 -function endsWith(str, suffix) { - return str.indexOf(suffix, this.length - suffix.length) !== -1; -}; - -// https://bugzilla.mozilla.org/show_bug.cgi?id=728780 -if (!String.prototype.startsWith) { - String.prototype.startsWith = - function(s) { return this.lastIndexOf(s, 0) === 0; } -} - -// functions for which lr is unconditionally valid. These are -// largely going to be atomics and other similar functions -// that don't touch lr. This is currently populated with -// some functions from bionic, largely via manual inspection -// of the assembly in e.g. -// http://androidxref.com/source/xref/bionic/libc/arch-arm/syscalls/ -var sARMFunctionsWithValidLR = [ - "__atomic_dec", - "__atomic_inc", - "__atomic_cmpxchg", - "__atomic_swap", - "__atomic_dec", - "__atomic_inc", - "__atomic_cmpxchg", - "__atomic_swap", - "__futex_syscall3", - "__futex_wait", - "__futex_wake", - "__futex_syscall3", - "__futex_wait", - "__futex_wake", - "__futex_syscall4", - "__ioctl", - "__brk", - "__wait4", - "epoll_wait", - "fsync", - "futex", - "nanosleep", - "pause", - "sched_yield", - "syscall" -]; - -function log() { - var z = []; - for (var i = 0; i < arguments.length; ++i) - z.push(arguments[i]); - gLogLines.push(z.join(" ")); -} - -self.onmessage = function (msg) { - try { - var requestID = msg.data.requestID; - var task = msg.data.task; - var taskData = msg.data.taskData; - if (!taskData && - (["chunkedStart", "chunkedChunk", "chunkedEnd"].indexOf(task) == -1)) { - taskData = partialTaskData[requestID]; - delete partialTaskData[requestID]; - } - PROFILERLOG("Start task: " + task); - - gLogLines = []; - - switch (task) { - case "initWorker": - gDebugLog = taskData.debugLog; - gDebugTrace = taskData.debugTrace; - PROFILERLOG("Init logging in parserWorker"); - return; - case "chunkedStart": - partialTaskData[requestID] = null; - break; - case "chunkedChunk": - if (partialTaskData[requestID] === null) - partialTaskData[requestID] = msg.data.chunk; - else - partialTaskData[requestID] = partialTaskData[requestID].concat(msg.data.chunk); - break; - case "chunkedEnd": - break; - case "parseRawProfile": - parseRawProfile(requestID, msg.data.params, taskData); - break; - case "updateFilters": - updateFilters(requestID, taskData.profileID, taskData.filters); - break; - case "updateViewOptions": - updateViewOptions(requestID, taskData.profileID, taskData.options); - break; - case "getSerializedProfile": - getSerializedProfile(requestID, taskData.profileID, taskData.complete); - break; - case "calculateHistogramData": - calculateHistogramData(requestID, taskData.profileID); - break; - case "calculateDiagnosticItems": - calculateDiagnosticItems(requestID, taskData.profileID, taskData.meta); - break; - default: - sendError(requestID, "Unknown task " + task); - break; - } - PROFILERLOG("Complete task: " + task); - } catch (e) { - PROFILERERROR("Exception: " + e + " (" + e.fileName + ":" + e.lineNumber + ")"); - sendError(requestID, "Exception: " + e + " (" + e.fileName + ":" + e.lineNumber + ")"); - } -} - -function sendError(requestID, error) { - // support sendError(msg) - if (error == null) { - error = requestID; - requestID = null; - } - - self.postMessage({ - requestID: requestID, - type: "error", - error: error, - log: gLogLines - }); -} - -function sendProgress(requestID, progress) { - self.postMessage({ - requestID: requestID, - type: "progress", - progress: progress - }); -} - -function sendFinished(requestID, result) { - self.postMessage({ - requestID: requestID, - type: "finished", - result: result, - log: gLogLines - }); -} - -function bucketsBySplittingArray(array, maxCostPerBucket, costOfElementCallback) { - var buckets = []; - var currentBucket = []; - var currentBucketCost = 0; - for (var i = 0; i < array.length; i++) { - var element = array[i]; - var costOfCurrentElement = costOfElementCallback ? costOfElementCallback(element) : 1; - if (currentBucketCost + costOfCurrentElement > maxCostPerBucket) { - buckets.push(currentBucket); - currentBucket = []; - currentBucketCost = 0; - } - currentBucket.push(element); - currentBucketCost += costOfCurrentElement; - } - buckets.push(currentBucket); - return buckets; -} - -function sendFinishedInChunks(requestID, result, maxChunkCost, costOfElementCallback) { - if (result.length === undefined || result.slice === undefined) - throw new Error("Can't slice result into chunks"); - self.postMessage({ - requestID: requestID, - type: "finishedStart" - }); - var chunks = bucketsBySplittingArray(result, maxChunkCost, costOfElementCallback); - for (var i = 0; i < chunks.length; i++) { - self.postMessage({ - requestID: requestID, - type: "finishedChunk", - chunk: chunks[i] - }); - } - self.postMessage({ - requestID: requestID, - type: "finishedEnd", - log: gLogLines - }); -} - -function makeSample(frames, extraInfo) { - return { - frames: frames, - extraInfo: extraInfo - }; -} - -function cloneSample(sample) { - return makeSample(sample.frames.slice(0), sample.extraInfo); -} - -function parseRawProfile(requestID, params, rawProfile) { - var progressReporter = new ProgressReporter(); - progressReporter.addListener(function (r) { - sendProgress(requestID, r.getProgress()); - }); - progressReporter.begin("Parsing..."); - - var symbolicationTable = {}; - var symbols = []; - var symbolIndices = {}; - var resources = {}; - var functions = []; - var functionIndices = {}; - var samples = []; - var meta = {}; - var armIncludePCIndex = {}; - - if (rawProfile == null) { - throw "rawProfile is null"; - } - - if (typeof rawProfile == "string" && rawProfile[0] == "{") { - // rawProfile is a JSON string. - rawProfile = JSON.parse(rawProfile); - if (rawProfile === null) { - throw "rawProfile couldn't not successfully be parsed using JSON.parse. Make sure that the profile is a valid JSON encoding."; - } - } - - if (rawProfile.profileJSON && !rawProfile.profileJSON.meta && rawProfile.meta) { - rawProfile.profileJSON.meta = rawProfile.meta; - } - - if (typeof rawProfile == "object") { - switch (rawProfile.format || null) { - case "profileStringWithSymbolicationTable,1": - symbolicationTable = rawProfile.symbolicationTable; - parseProfileString(rawProfile.profileString); - break; - case "profileJSONWithSymbolicationTable,1": - symbolicationTable = rawProfile.symbolicationTable; - parseProfileJSON(rawProfile.profileJSON); - break; - default: - parseProfileJSON(rawProfile); - } - } else { - parseProfileString(rawProfile); - } - - if (params.profileId) { - meta.profileId = params.profileId; - } - - function cleanFunctionName(functionName) { - var ignoredPrefix = "non-virtual thunk to "; - if (functionName.startsWith(ignoredPrefix)) - return functionName.substr(ignoredPrefix.length); - return functionName; - } - - function resourceNameForAddon(addon) { - if (!addon) - return ""; - - var iconHTML = ""; - if (addon.iconURL) - iconHTML = " " - return iconHTML + " " + (/@jetpack$/.exec(addon.id) ? "Jetpack: " : "") + addon.name; - } - - function addonWithID(addonID) { - return firstMatch(meta.addons, function addonHasID(addon) { - return addon.id.toLowerCase() == addonID.toLowerCase(); - }) - } - - function resourceNameForAddonWithID(addonID) { - return resourceNameForAddon(addonWithID(addonID)); - } - - function findAddonForChromeURIHost(host) { - return firstMatch(meta.addons, function addonUsesChromeURIHost(addon) { - return addon.chromeURIHosts && addon.chromeURIHosts.indexOf(host) != -1; - }); - } - - function ensureResource(name, resourceDescription) { - if (!(name in resources)) { - resources[name] = resourceDescription; - } - return name; - } - - function resourceNameFromLibrary(library) { - return ensureResource("lib_" + library, { - type: "library", - name: library - }); - } - - function getAddonForScriptURI(url, host) { - if (!meta || !meta.addons) - return null; - - if (url.startsWith("resource:") && endsWith(host, "-at-jetpack")) { - // Assume this is a jetpack url - var jetpackID = host.substring(0, host.length - 11) + "@jetpack"; - return addonWithID(jetpackID); - } - - if (url.startsWith("file:///") && url.indexOf("/extensions/") != -1) { - var unpackedAddonNameMatch = /\/extensions\/(.*?)\//.exec(url); - if (unpackedAddonNameMatch) - return addonWithID(decodeURIComponent(unpackedAddonNameMatch[1])); - return null; - } - - if (url.startsWith("jar:file:///") && url.indexOf("/extensions/") != -1) { - var packedAddonNameMatch = /\/extensions\/(.*?).xpi/.exec(url); - if (packedAddonNameMatch) - return addonWithID(decodeURIComponent(packedAddonNameMatch[1])); - return null; - } - - if (url.startsWith("chrome://")) { - var chromeURIMatch = /chrome\:\/\/(.*?)\//.exec(url); - if (chromeURIMatch) - return findAddonForChromeURIHost(chromeURIMatch[1]); - return null; - } - - return null; - } - - function resourceNameFromURI(url) { - if (!url) - return ensureResource("unknown", {type: "unknown", name: ""}); - - var match = /^(.*):\/\/(.*?)\//.exec(url); - - if (!match) { - // Can this happen? If so, we should change the regular expression above. - return ensureResource("url_" + url, {type: "url", name: url}); - } - - var urlRoot = match[0]; - var protocol = match[1]; - var host = match[2]; - - var addon = getAddonForScriptURI(url, host); - if (addon) { - return ensureResource("addon_" + addon.id, { - type: "addon", - name: addon.name, - addonID: addon.id, - icon: addon.iconURL - }); - } - - if (protocol.startsWith("http")) { - return ensureResource("webhost_" + host, { - type: "webhost", - name: host, - icon: urlRoot + "favicon.ico" - }); - } - - if (protocol.startsWith("file")) { - return ensureResource("file_" + host, { - type: "file", - name: host - }); - } - - return ensureResource("otherhost_" + host, { - type: "otherhost", - name: host - }); - } - - function parseScriptFile(url) { - var match = /([^\/]*)$/.exec(url); - if (match && match[1]) - return match[1]; - - return url; - } - - // JS File information sometimes comes with multiple URIs which are chained - // with " -> ". We only want the last URI in this list. - function getRealScriptURI(url) { - if (url) { - var urls = url.split(" -> "); - return urls[urls.length - 1]; - } - return url; - } - - function getFunctionInfo(fullName) { - - function getCPPFunctionInfo(fullName) { - var match = - /^(.*) \(in ([^\)]*)\) (\+ [0-9]+)$/.exec(fullName) || - /^(.*) \(in ([^\)]*)\) (\(.*:.*\))$/.exec(fullName) || - /^(.*) \(in ([^\)]*)\)$/.exec(fullName); - - if (!match) - return null; - - return { - functionName: cleanFunctionName(match[1]), - libraryName: resourceNameFromLibrary(match[2]), - lineInformation: match[3] || "", - isRoot: false, - isJSFrame: false - }; - } - - function getJSFunctionInfo(fullName) { - var jsMatch = - /^(.*) \((.*):([0-9]+)\)$/.exec(fullName) || - /^()(.*):([0-9]+)$/.exec(fullName); - - if (!jsMatch) - return null; - - var functionName = jsMatch[1] || ""; - var scriptURI = getRealScriptURI(jsMatch[2]); - var lineNumber = jsMatch[3]; - var scriptFile = parseScriptFile(scriptURI); - var resourceName = resourceNameFromURI(scriptURI); - - return { - functionName: functionName + "() @ " + scriptFile + ":" + lineNumber, - libraryName: resourceName, - lineInformation: "", - isRoot: false, - isJSFrame: true, - scriptLocation: { - scriptURI: scriptURI, - lineInformation: lineNumber - } - }; - } - - function getFallbackFunctionInfo(fullName) { - return { - functionName: cleanFunctionName(fullName), - libraryName: "", - lineInformation: "", - isRoot: fullName == "(root)", - isJSFrame: false - }; - } - - return getCPPFunctionInfo(fullName) || - getJSFunctionInfo(fullName) || - getFallbackFunctionInfo(fullName); - } - - function indexForFunction(symbol, info) { - var resolve = info.functionName + "__" + info.libraryName; - if (resolve in functionIndices) - return functionIndices[resolve]; - var newIndex = functions.length; - info.symbol = symbol; - functions[newIndex] = info; - functionIndices[resolve] = newIndex; - return newIndex; - } - - function parseSymbol(symbol) { - var info = getFunctionInfo(symbol); - //dump("Parse symbol: " + symbol + "\n"); - return { - symbolName: symbol, - functionName: info.functionName, - functionIndex: indexForFunction(symbol, info), - lineInformation: info.lineInformation, - isRoot: info.isRoot, - isJSFrame: info.isJSFrame, - scriptLocation: info.scriptLocation - }; - } - - function translatedSymbol(symbol) { - return symbolicationTable[symbol] || symbol; - } - - function indexForSymbol(symbol) { - if (symbol in symbolIndices) - return symbolIndices[symbol]; - var newIndex = symbols.length; - symbols[newIndex] = parseSymbol(translatedSymbol(symbol)); - symbolIndices[symbol] = newIndex; - return newIndex; - } - - function clearRegExpLastMatch() { - /./.exec(" "); - } - - function shouldIncludeARMLRForPC(pcIndex) { - if (pcIndex in armIncludePCIndex) - return armIncludePCIndex[pcIndex]; - - var pcName = symbols[pcIndex].functionName; - var include = sARMFunctionsWithValidLR.indexOf(pcName) != -1; - armIncludePCIndex[pcIndex] = include; - return include; - } - - function parseProfileString(data) { - var extraInfo = {}; - var lines = data.split("\n"); - var sample = null; - for (var i = 0; i < lines.length; ++i) { - var line = lines[i]; - if (line.length < 2 || line[1] != '-') { - // invalid line, ignore it - continue; - } - var info = line.substring(2); - switch (line[0]) { - //case 'l': - // // leaf name - // if ("leafName" in extraInfo) { - // extraInfo.leafName += ":" + info; - // } else { - // extraInfo.leafName = info; - // } - // break; - case 'm': - // marker - if (!("marker" in extraInfo)) { - extraInfo.marker = []; - } - extraInfo.marker.push(info); - break; - case 's': - // sample - var sampleName = info; - sample = makeSample([indexForSymbol(sampleName)], extraInfo); - samples.push(sample); - extraInfo = {}; // reset the extra info for future rounds - break; - case 'c': - case 'l': - // continue sample - if (sample) { // ignore the case where we see a 'c' before an 's' - sample.frames.push(indexForSymbol(info)); - } - break; - case 'L': - // continue sample; this is an ARM LR record. Stick it before the - // PC if it's one of the functions where we know LR is good. - if (sample && sample.frames.length > 1) { - var pcIndex = sample.frames[sample.frames.length - 1]; - if (shouldIncludeARMLRForPC(pcIndex)) { - sample.frames.splice(-1, 0, indexForSymbol(info)); - } - } - break; - case 't': - // time - if (sample) { - sample.extraInfo["time"] = parseFloat(info); - } - break; - case 'r': - // responsiveness - if (sample) { - sample.extraInfo["responsiveness"] = parseFloat(info); - } - break; - } - progressReporter.setProgress((i + 1) / lines.length); - } - } - - function parseProfileJSON(profile) { - // Thread 0 will always be the main thread of interest - // TODO support all the thread in the profile - var profileSamples = null; - meta = profile.meta || {}; - if (params.appendVideoCapture) { - meta.videoCapture = { - src: params.appendVideoCapture, - }; - } - // Support older format that aren't thread aware - if (profile.threads != null) { - profileSamples = profile.threads[0].samples; - } else { - profileSamples = profile; - } - var rootSymbol = null; - var insertCommonRoot = false; - var frameStart = {}; - meta.frameStart = frameStart; - for (var j = 0; j < profileSamples.length; j++) { - var sample = profileSamples[j]; - var indicedFrames = []; - if (!sample) { - // This sample was filtered before saving - samples.push(null); - progressReporter.setProgress((j + 1) / profileSamples.length); - continue; - } - for (var k = 0; sample.frames && k < sample.frames.length; k++) { - var frame = sample.frames[k]; - var pcIndex; - if (frame.location !== undefined) { - pcIndex = indexForSymbol(frame.location); - } else { - pcIndex = indexForSymbol(frame); - } - - if (frame.lr !== undefined && shouldIncludeARMLRForPC(pcIndex)) { - indicedFrames.push(indexForSymbol(frame.lr)); - } - - indicedFrames.push(pcIndex); - } - if (indicedFrames.length >= 1) { - if (rootSymbol && rootSymbol != indicedFrames[0]) { - insertCommonRoot = true; - } - rootSymbol = rootSymbol || indicedFrames[0]; - } - if (sample.extraInfo == null) { - sample.extraInfo = {}; - } - if (sample.responsiveness) { - sample.extraInfo["responsiveness"] = sample.responsiveness; - } - if (sample.marker) { - sample.extraInfo["marker"] = sample.marker; - } - if (sample.time) { - sample.extraInfo["time"] = sample.time; - } - if (sample.frameNumber) { - sample.extraInfo["frameNumber"] = sample.frameNumber; - //dump("Got frame number: " + sample.frameNumber + "\n"); - frameStart[sample.frameNumber] = samples.length; - } - samples.push(makeSample(indicedFrames, sample.extraInfo)); - progressReporter.setProgress((j + 1) / profileSamples.length); - } - if (insertCommonRoot) { - var rootIndex = indexForSymbol("(root)"); - for (var i = 0; i < samples.length; i++) { - var sample = samples[i]; - if (!sample) continue; - // If length == 0 then the sample was filtered when saving the profile - if (sample.frames.length >= 1 && sample.frames[0] != rootIndex) - sample.frames.unshift(rootIndex) - } - } - } - - progressReporter.finish(); - // Don't increment the profile ID now because (1) it's buggy - // and (2) for now there's no point in storing each profile - // here if we're storing them in the local storage. - //var profileID = gNextProfileID++; - var profileID = gNextProfileID; - gProfiles[profileID] = JSON.parse(JSON.stringify({ - meta: meta, - symbols: symbols, - functions: functions, - resources: resources, - allSamples: samples - })); - clearRegExpLastMatch(); - sendFinished(requestID, { - meta: meta, - numSamples: samples.length, - profileID: profileID, - symbols: symbols, - functions: functions, - resources: resources - }); -} - -function getSerializedProfile(requestID, profileID, complete) { - var profile = gProfiles[profileID]; - var symbolicationTable = {}; - if (complete || !profile.filterSettings.mergeFunctions) { - for (var symbolIndex in profile.symbols) { - symbolicationTable[symbolIndex] = profile.symbols[symbolIndex].symbolName; - } - } else { - for (var functionIndex in profile.functions) { - var f = profile.functions[functionIndex]; - symbolicationTable[functionIndex] = f.symbol; - } - } - var serializedProfile = JSON.stringify({ - format: "profileJSONWithSymbolicationTable,1", - meta: profile.meta, - profileJSON: complete ? profile.allSamples : profile.filteredSamples, - symbolicationTable: symbolicationTable - }); - sendFinished(requestID, serializedProfile); -} - -function TreeNode(name, parent, startCount) { - this.name = name; - this.children = []; - this.counter = startCount; - this.parent = parent; -} -TreeNode.prototype.getDepth = function TreeNode__getDepth() { - if (this.parent) - return this.parent.getDepth() + 1; - return 0; -}; -TreeNode.prototype.findChild = function TreeNode_findChild(name) { - for (var i = 0; i < this.children.length; i++) { - var child = this.children[i]; - if (child.name == name) - return child; - } - return null; -} -// path is an array of strings which is matched to our nodes' names. -// Try to walk path in our own tree and return the last matching node. The -// length of the match can be calculated by the caller by comparing the -// returned node's depth with the depth of the path's start node. -TreeNode.prototype.followPath = function TreeNode_followPath(path) { - if (path.length == 0) - return this; - - var matchingChild = this.findChild(path[0]); - if (!matchingChild) - return this; - - return matchingChild.followPath(path.slice(1)); -}; -TreeNode.prototype.incrementCountersInParentChain = function TreeNode_incrementCountersInParentChain() { - this.counter++; - if (this.parent) - this.parent.incrementCountersInParentChain(); -}; - -function convertToCallTree(samples, isReverse) { - function areSamplesMultiroot(samples) { - var previousRoot; - for (var i = 0; i < samples.length; ++i) { - if (!previousRoot) { - previousRoot = samples[i].frames[0]; - continue; - } - if (previousRoot != samples[i].frames[0]) { - return true; - } - } - return false; - } - samples = samples.filter(function noNullSamples(sample) { - return sample != null; - }); - if (samples.length == 0) - return new TreeNode("(empty)", null, 0); - var firstRoot = null; - for (var i = 0; i < samples.length; ++i) { - firstRoot = samples[i].frames[0]; - break; - } - if (firstRoot == null) { - return new TreeNode("(all filtered)", null, 0); - } - var multiRoot = areSamplesMultiroot(samples); - var treeRoot = new TreeNode((isReverse || multiRoot) ? "(total)" : firstRoot, null, 0); - for (var i = 0; i < samples.length; ++i) { - var sample = samples[i]; - var callstack = sample.frames.slice(0); - callstack.shift(); - if (isReverse) - callstack.reverse(); - var deepestExistingNode = treeRoot.followPath(callstack); - var remainingCallstack = callstack.slice(deepestExistingNode.getDepth()); - deepestExistingNode.incrementCountersInParentChain(); - var node = deepestExistingNode; - for (var j = 0; j < remainingCallstack.length; ++j) { - var frame = remainingCallstack[j]; - var child = new TreeNode(frame, node, 1); - node.children.push(child); - node = child; - } - } - return treeRoot; -} - -function filterByJank(samples, filterThreshold) { - return samples.map(function nullNonJank(sample) { - if (!sample || - !("responsiveness" in sample.extraInfo) || - sample.extraInfo["responsiveness"] < filterThreshold) - return null; - return sample; - }); -} - -function filterBySymbol(samples, symbolOrFunctionIndex) { - return samples.map(function filterSample(origSample) { - if (!origSample) - return null; - var sample = cloneSample(origSample); - for (var i = 0; i < sample.frames.length; i++) { - if (symbolOrFunctionIndex == sample.frames[i]) { - sample.frames = sample.frames.slice(i); - return sample; - } - } - return null; // no frame matched; filter out complete sample - }); -} - -function filterByCallstackPrefix(samples, symbols, functions, callstack, appliesToJS, useFunctions) { - var isJSFrameOrRoot = useFunctions ? function isJSFunctionOrRoot(functionIndex) { - return (functionIndex in functions) && (functions[functionIndex].isJSFrame || functions[functionIndex].isRoot); - } : function isJSSymbolOrRoot(symbolIndex) { - return (symbolIndex in symbols) && (symbols[symbolIndex].isJSFrame || symbols[symbolIndex].isRoot); - }; - return samples.map(function filterSample(sample) { - if (!sample) - return null; - if (sample.frames.length < callstack.length) - return null; - for (var i = 0, j = 0; j < callstack.length; i++) { - if (i >= sample.frames.length) - return null; - if (appliesToJS && !isJSFrameOrRoot(sample.frames[i])) - continue; - if (sample.frames[i] != callstack[j]) - return null; - j++; - } - return makeSample(sample.frames.slice(i - 1), sample.extraInfo); - }); -} - -function filterByCallstackPostfix(samples, symbols, functions, callstack, appliesToJS, useFunctions) { - var isJSFrameOrRoot = useFunctions ? function isJSFunctionOrRoot(functionIndex) { - return (functionIndex in functions) && (functions[functionIndex].isJSFrame || functions[functionIndex].isRoot); - } : function isJSSymbolOrRoot(symbolIndex) { - return (symbolIndex in symbols) && (symbols[symbolIndex].isJSFrame || symbols[symbolIndex].isRoot); - }; - return samples.map(function filterSample(sample) { - if (!sample) - return null; - if (sample.frames.length < callstack.length) - return null; - for (var i = 0, j = 0; j < callstack.length; i++) { - if (i >= sample.frames.length) - return null; - if (appliesToJS && !isJSFrameOrRoot(sample.frames[sample.frames.length - i - 1])) - continue; - if (sample.frames[sample.frames.length - i - 1] != callstack[j]) - return null; - j++; - } - var newFrames = sample.frames.slice(0, sample.frames.length - i + 1); - return makeSample(newFrames, sample.extraInfo); - }); -} - -function chargeNonJSToCallers(samples, symbols, functions, useFunctions) { - var isJSFrameOrRoot = useFunctions ? function isJSFunctionOrRoot(functionIndex) { - return (functionIndex in functions) && (functions[functionIndex].isJSFrame || functions[functionIndex].isRoot); - } : function isJSSymbolOrRoot(symbolIndex) { - return (symbolIndex in symbols) && (symbols[symbolIndex].isJSFrame || symbols[symbolIndex].isRoot); - }; - samples = samples.slice(0); - for (var i = 0; i < samples.length; ++i) { - var sample = samples[i]; - if (!sample) - continue; - var newFrames = sample.frames.filter(isJSFrameOrRoot); - if (!newFrames.length) { - samples[i] = null; - } else { - samples[i].frames = newFrames; - } - } - return samples; -} - -function filterByName(samples, symbols, functions, filterName, useFunctions) { - function getSymbolOrFunctionName(index, useFunctions) { - if (useFunctions) { - if (!(index in functions)) - return ""; - return functions[index].functionName; - } - if (!(index in symbols)) - return ""; - return symbols[index].symbolName; - } - function getLibraryName(index, useFunctions) { - if (useFunctions) { - if (!(index in functions)) - return ""; - return functions[index].libraryName; - } - if (!(index in symbols)) - return ""; - return symbols[index].libraryName; - } - samples = samples.slice(0); - filterName = filterName.toLowerCase(); - calltrace_it: for (var i = 0; i < samples.length; ++i) { - var sample = samples[i]; - if (!sample) - continue; - var callstack = sample.frames; - for (var j = 0; j < callstack.length; ++j) { - var symbolOrFunctionName = getSymbolOrFunctionName(callstack[j], useFunctions); - var libraryName = getLibraryName(callstack[j], useFunctions); - if (symbolOrFunctionName.toLowerCase().indexOf(filterName) != -1 || - libraryName.toLowerCase().indexOf(filterName) != -1) { - continue calltrace_it; - } - } - samples[i] = null; - } - return samples; -} - -function discardLineLevelInformation(samples, symbols, functions) { - var data = samples; - var filteredData = []; - for (var i = 0; i < data.length; i++) { - if (!data[i]) { - filteredData.push(null); - continue; - } - filteredData.push(cloneSample(data[i])); - var frames = filteredData[i].frames; - for (var j = 0; j < frames.length; j++) { - if (!(frames[j] in symbols)) - continue; - frames[j] = symbols[frames[j]].functionIndex; - } - } - return filteredData; -} - -function mergeUnbranchedCallPaths(root) { - var mergedNames = [root.name]; - var node = root; - while (node.children.length == 1 && node.counter == node.children[0].counter) { - node = node.children[0]; - mergedNames.push(node.name); - } - if (node != root) { - // Merge path from root to node into root. - root.children = node.children; - root.mergedNames = mergedNames; - //root.name = clipText(root.name, 50) + " to " + this._clipText(node.name, 50); - } - for (var i = 0; i < root.children.length; i++) { - mergeUnbranchedCallPaths(root.children[i]); - } -} - -function FocusedFrameSampleFilter(focusedSymbol) { - this._focusedSymbol = focusedSymbol; -} -FocusedFrameSampleFilter.prototype = { - filter: function FocusedFrameSampleFilter_filter(samples, symbols, functions, useFunctions) { - return filterBySymbol(samples, this._focusedSymbol); - } -}; - -function FocusedCallstackPrefixSampleFilter(focusedCallstack, appliesToJS) { - this._focusedCallstackPrefix = focusedCallstack; - this._appliesToJS = appliesToJS; -} -FocusedCallstackPrefixSampleFilter.prototype = { - filter: function FocusedCallstackPrefixSampleFilter_filter(samples, symbols, functions, useFunctions) { - return filterByCallstackPrefix(samples, symbols, functions, this._focusedCallstackPrefix, this._appliesToJS, useFunctions); - } -}; - -function FocusedCallstackPostfixSampleFilter(focusedCallstack, appliesToJS) { - this._focusedCallstackPostfix = focusedCallstack; - this._appliesToJS = appliesToJS; -} -FocusedCallstackPostfixSampleFilter.prototype = { - filter: function FocusedCallstackPostfixSampleFilter_filter(samples, symbols, functions, useFunctions) { - return filterByCallstackPostfix(samples, symbols, functions, this._focusedCallstackPostfix, this._appliesToJS, useFunctions); - } -}; - -function RangeSampleFilter(start, end) { - this._start = start; - this._end = end; -} -RangeSampleFilter.prototype = { - filter: function RangeSampleFilter_filter(samples, symbols, functions) { - return samples.slice(this._start, this._end); - } -} - -function unserializeSampleFilters(filters) { - return filters.map(function (filter) { - switch (filter.type) { - case "FocusedFrameSampleFilter": - return new FocusedFrameSampleFilter(filter.focusedSymbol); - case "FocusedCallstackPrefixSampleFilter": - return new FocusedCallstackPrefixSampleFilter(filter.focusedCallstack, filter.appliesToJS); - case "FocusedCallstackPostfixSampleFilter": - return new FocusedCallstackPostfixSampleFilter(filter.focusedCallstack, filter.appliesToJS); - case "RangeSampleFilter": - return new RangeSampleFilter(filter.start, filter.end); - case "PluginView": - return null; - default: - throw new Error("Unknown filter"); - } - }) -} - -var gJankThreshold = 50 /* ms */; - -function updateFilters(requestID, profileID, filters) { - var profile = gProfiles[profileID]; - var samples = profile.allSamples; - var symbols = profile.symbols; - var functions = profile.functions; - - if (filters.mergeFunctions) { - samples = discardLineLevelInformation(samples, symbols, functions); - } - if (filters.nameFilter) { - try { - samples = filterByName(samples, symbols, functions, filters.nameFilter, filters.mergeFunctions); - } catch (e) { - dump("Could not filer by name: " + e + "\n"); - } - } - samples = unserializeSampleFilters(filters.sampleFilters).reduce(function (filteredSamples, currentFilter) { - if (currentFilter===null) return filteredSamples; - return currentFilter.filter(filteredSamples, symbols, functions, filters.mergeFunctions); - }, samples); - if (filters.jankOnly) { - samples = filterByJank(samples, gJankThreshold); - } - if (filters.javascriptOnly) { - samples = chargeNonJSToCallers(samples, symbols, functions, filters.mergeFunctions); - } - - gProfiles[profileID].filterSettings = filters; - gProfiles[profileID].filteredSamples = samples; - sendFinishedInChunks(requestID, samples, 40000, - function (sample) { return sample ? sample.frames.length : 1; }); -} - -function updateViewOptions(requestID, profileID, options) { - var profile = gProfiles[profileID]; - var samples = profile.filteredSamples; - var symbols = profile.symbols; - var functions = profile.functions; - - var treeData = convertToCallTree(samples, options.invertCallstack); - if (options.mergeUnbranched) - mergeUnbranchedCallPaths(treeData); - sendFinished(requestID, treeData); -} - -// The responsiveness threshold (in ms) after which the sample shuold become -// completely red in the histogram. -var kDelayUntilWorstResponsiveness = 1000; - -function calculateHistogramData(requestID, profileID) { - - function getStepColor(step) { - if (step.extraInfo && "responsiveness" in step.extraInfo) { - var res = step.extraInfo.responsiveness; - var redComponent = Math.round(255 * Math.min(1, res / kDelayUntilWorstResponsiveness)); - return "rgb(" + redComponent + ",0,0)"; - } - - return "rgb(0,0,0)"; - } - - var profile = gProfiles[profileID]; - var data = profile.filteredSamples; - var histogramData = []; - var maxHeight = 0; - for (var i = 0; i < data.length; ++i) { - if (!data[i]) - continue; - var value = data[i].frames.length; - if (maxHeight < value) - maxHeight = value; - } - maxHeight += 1; - var nextX = 0; - // The number of data items per histogramData rects. - // Except when seperated by a marker. - // This is used to cut down the number of rects, since - // there's no point in having more rects then pixels - var samplesPerStep = Math.max(1, Math.floor(data.length / 2000)); - var frameStart = {}; - for (var i = 0; i < data.length; i++) { - var step = data[i]; - if (!step) { - // Add a gap for the sample that was filtered out. - nextX += 1 / samplesPerStep; - continue; - } - nextX = Math.ceil(nextX); - var value = step.frames.length / maxHeight; - var frames = step.frames; - var currHistogramData = histogramData[histogramData.length-1]; - if (step.extraInfo && "marker" in step.extraInfo) { - // A new marker boundary has been discovered. - histogramData.push({ - frames: "marker", - x: nextX, - width: 2, - value: 1, - marker: step.extraInfo.marker, - color: "fuchsia" - }); - nextX += 2; - histogramData.push({ - frames: [step.frames], - x: nextX, - width: 1, - value: value, - color: getStepColor(step), - }); - nextX += 1; - } else if (currHistogramData != null && - currHistogramData.frames.length < samplesPerStep && - !(step.extraInfo && "frameNumber" in step.extraInfo)) { - currHistogramData.frames.push(step.frames); - // When merging data items take the average: - currHistogramData.value = - (currHistogramData.value * (currHistogramData.frames.length - 1) + value) / - currHistogramData.frames.length; - // Merge the colors? For now we keep the first color set. - } else { - // A new name boundary has been discovered. - currHistogramData = { - frames: [step.frames], - x: nextX, - width: 1, - value: value, - color: getStepColor(step), - }; - if (step.extraInfo && "frameNumber" in step.extraInfo) { - currHistogramData.frameNumber = step.extraInfo.frameNumber; - frameStart[step.extraInfo.frameNumber] = histogramData.length; - } - histogramData.push(currHistogramData); - nextX += 1; - } - } - sendFinished(requestID, { histogramData: histogramData, frameStart: frameStart, widthSum: Math.ceil(nextX) }); -} - -var diagnosticList = [ - // *************** Known bugs first (highest priority) - { - image: "io.png", - title: "Main Thread IO - Bug 765135 - TISCreateInputSourceList", - check: function(frames, symbols, meta) { - - if (!stepContains('TISCreateInputSourceList', frames, symbols)) - return false; - - return stepContains('__getdirentries64', frames, symbols) - || stepContains('__read', frames, symbols) - || stepContains('__open', frames, symbols) - || stepContains('stat$INODE64', frames, symbols) - ; - }, - }, - - { - image: "js.png", - title: "Bug 772916 - Gradients are slow on mobile", - bugNumber: "772916", - check: function(frames, symbols, meta) { - - return stepContains('PaintGradient', frames, symbols) - && stepContains('ClientTiledLayerBuffer::PaintThebesSingleBufferDraw', frames, symbols) - ; - }, - }, - { - image: "cache.png", - title: "Bug 717761 - Main thread can be blocked by IO on the cache thread", - bugNumber: "717761", - check: function(frames, symbols, meta) { - - return stepContains('nsCacheEntryDescriptor::GetStoragePolicy', frames, symbols) - ; - }, - }, - { - image: "js.png", - title: "Web Content Shutdown Notification", - check: function(frames, symbols, meta) { - - return stepContains('nsAppStartup::Quit', frames, symbols) - && stepContains('nsDocShell::FirePageHideNotification', frames, symbols) - ; - }, - }, - { - image: "js.png", - title: "Bug 789193 - AMI_startup() takes 200ms on startup", - bugNumber: "789193", - check: function(frames, symbols, meta) { - - return stepContains('AMI_startup()', frames, symbols) - ; - }, - }, - { - image: "js.png", - title: "Bug 818296 - [Shutdown] js::NukeCrossCompartmentWrappers takes up 300ms on shutdown", - bugNumber: "818296", - check: function(frames, symbols, meta) { - return stepContains('js::NukeCrossCompartmentWrappers', frames, symbols) - && (stepContains('WindowDestroyedEvent', frames, symbols) || stepContains('DoShutdown', frames, symbols)) - ; - }, - }, - { - image: "js.png", - title: "Bug 818274 - [Shutdown] Telemetry takes ~10ms on shutdown", - bugNumber: "818274", - check: function(frames, symbols, meta) { - return stepContains('TelemetryPing.js', frames, symbols) - ; - }, - }, - { - image: "plugin.png", - title: "Bug 818265 - [Shutdown] Plug-in shutdown takes ~90ms on shutdown", - bugNumber: "818265", - check: function(frames, symbols, meta) { - return stepContains('PluginInstanceParent::Destroy', frames, symbols) - ; - }, - }, - { - image: "snapshot.png", - title: "Bug 720575 - Make thumbnailing faster and/or asynchronous", - bugNumber: "720575", - check: function(frames, symbols, meta) { - return stepContains('Thumbnails_capture()', frames, symbols) - ; - }, - }, - - { - image: "js.png", - title: "Bug 789185 - LoginManagerStorage_mozStorage.init() takes 300ms on startup ", - bugNumber: "789185", - check: function(frames, symbols, meta) { - - return stepContains('LoginManagerStorage_mozStorage.prototype.init()', frames, symbols) - ; - }, - }, - - { - image: "js.png", - title: "JS - Bug 767070 - Text selection performance is bad on android", - bugNumber: "767070", - check: function(frames, symbols, meta) { - - if (!stepContains('FlushPendingNotifications', frames, symbols)) - return false; - - return stepContains('sh_', frames, symbols) - && stepContains('browser.js', frames, symbols) - ; - }, - }, - - { - image: "js.png", - title: "JS - Bug 765930 - Reader Mode: Optimize readability check", - bugNumber: "765930", - check: function(frames, symbols, meta) { - - return stepContains('Readability.js', frames, symbols) - ; - }, - }, - - // **************** General issues - { - image: "js.png", - title: "JS is triggering a sync reflow", - check: function(frames, symbols, meta) { - return symbolSequence(['js::RunScript','layout::DoReflow'], frames, symbols) || - symbolSequence(['js::RunScript','layout::Flush'], frames, symbols) - ; - }, - }, - - { - image: "gc.png", - title: "Garbage Collection Slice", - canMergeWithGC: false, - check: function(frames, symbols, meta, step) { - var slice = findGCSlice(frames, symbols, meta, step); - - if (slice) { - var gcEvent = findGCEvent(frames, symbols, meta, step); - //dump("found event matching diagnostic\n"); - //dump(JSON.stringify(gcEvent) + "\n"); - return true; - } - return false; - }, - details: function(frames, symbols, meta, step) { - var slice = findGCSlice(frames, symbols, meta, step); - if (slice) { - return "" + - "Reason: " + slice.reason + "\n" + - "Slice: " + slice.slice + "\n" + - "Pause: " + slice.pause + " ms"; - } - return null; - }, - onclickDetails: function(frames, symbols, meta, step) { - var gcEvent = findGCEvent(frames, symbols, meta, step); - if (gcEvent) { - return JSON.stringify(gcEvent); - } else { - return null; - } - }, - }, - { - image: "cc.png", - title: "Cycle Collect", - check: function(frames, symbols, meta, step) { - var ccEvent = findCCEvent(frames, symbols, meta, step); - - if (ccEvent) { - return true; - } - return false; - }, - details: function(frames, symbols, meta, step) { - var ccEvent = findCCEvent(frames, symbols, meta, step); - if (ccEvent) { - return "" + - "Duration: " + ccEvent.duration + " ms\n" + - "Suspected: " + ccEvent.suspected; - } - return null; - }, - onclickDetails: function(frames, symbols, meta, step) { - var ccEvent = findCCEvent(frames, symbols, meta, step); - if (ccEvent) { - return JSON.stringify(ccEvent); - } else { - return null; - } - }, - }, - { - image: "gc.png", - title: "Garbage Collection", - canMergeWithGC: false, - check: function(frames, symbols, meta) { - return stepContainsRegEx(/.*Collect.*Runtime.*Invocation.*/, frames, symbols) - || stepContains('GarbageCollectNow', frames, symbols) // Label - || stepContains('JS_GC(', frames, symbols) // Label - || stepContains('CycleCollect__', frames, symbols) // Label - ; - }, - }, - { - image: "cc.png", - title: "Cycle Collect", - check: function(frames, symbols, meta) { - return stepContains('nsCycleCollector::Collect', frames, symbols) - || stepContains('CycleCollect__', frames, symbols) // Label - || stepContains('nsCycleCollectorRunner::Collect', frames, symbols) // Label - ; - }, - }, - { - image: "plugin.png", - title: "Sync Plugin Constructor", - check: function(frames, symbols, meta) { - return stepContains('CallPPluginInstanceConstructor', frames, symbols) - || stepContains('CallPCrashReporterConstructor', frames, symbols) - || stepContains('PPluginModuleParent::CallNP_Initialize', frames, symbols) - || stepContains('GeckoChildProcessHost::SyncLaunch', frames, symbols) - ; - }, - }, - { - image: "text.png", - title: "Font Loading", - check: function(frames, symbols, meta) { - return stepContains('gfxFontGroup::BuildFontList', frames, symbols); - }, - }, - { - image: "io.png", - title: "Main Thread IO!", - check: function(frames, symbols, meta) { - return stepContains('__getdirentries64', frames, symbols) - || stepContains('__open', frames, symbols) - || stepContains('NtFlushBuffersFile', frames, symbols) - || stepContains('storage:::Statement::ExecuteStep', frames, symbols) - || stepContains('__unlink', frames, symbols) - || stepContains('fsync', frames, symbols) - || stepContains('stat$INODE64', frames, symbols) - ; - }, - }, -]; - -function hasJSFrame(frames, symbols) { - for (var i = 0; i < frames.length; i++) { - if (symbols[frames[i]].isJSFrame === true) { - return true; - } - } - return false; -} -function findCCEvent(frames, symbols, meta, step) { - if (!step || !step.extraInfo || !step.extraInfo.time || !meta || !meta.gcStats) - return null; - - var time = step.extraInfo.time; - - for (var i = 0; i < meta.gcStats.ccEvents.length; i++) { - var ccEvent = meta.gcStats.ccEvents[i]; - if (ccEvent.start_timestamp <= time && ccEvent.end_timestamp >= time) { - //dump("JSON: " + js_beautify(JSON.stringify(ccEvent)) + "\n"); - return ccEvent; - } - } - - return null; -} -function findGCEvent(frames, symbols, meta, step) { - if (!step || !step.extraInfo || !step.extraInfo.time || !meta || !meta.gcStats) - return null; - - var time = step.extraInfo.time; - - for (var i = 0; i < meta.gcStats.gcEvents.length; i++) { - var gcEvent = meta.gcStats.gcEvents[i]; - if (!gcEvent.slices) - continue; - for (var j = 0; j < gcEvent.slices.length; j++) { - var slice = gcEvent.slices[j]; - if (slice.start_timestamp <= time && slice.end_timestamp >= time) { - return gcEvent; - } - } - } - - return null; -} -function findGCSlice(frames, symbols, meta, step) { - if (!step || !step.extraInfo || !step.extraInfo.time || !meta || !meta.gcStats) - return null; - - var time = step.extraInfo.time; - - for (var i = 0; i < meta.gcStats.gcEvents.length; i++) { - var gcEvent = meta.gcStats.gcEvents[i]; - if (!gcEvent.slices) - continue; - for (var j = 0; j < gcEvent.slices.length; j++) { - var slice = gcEvent.slices[j]; - if (slice.start_timestamp <= time && slice.end_timestamp >= time) { - return slice; - } - } - } - - return null; -} -function stepContains(substring, frames, symbols) { - for (var i = 0; frames && i < frames.length; i++) { - if (!(frames[i] in symbols)) - continue; - var frameSym = symbols[frames[i]].functionName || symbols[frames[i]].symbolName; - if (frameSym.indexOf(substring) != -1) { - return true; - } - } - return false; -} -function stepContainsRegEx(regex, frames, symbols) { - for (var i = 0; frames && i < frames.length; i++) { - if (!(frames[i] in symbols)) - continue; - var frameSym = symbols[frames[i]].functionName || symbols[frames[i]].symbolName; - if (regex.exec(frameSym)) { - return true; - } - } - return false; -} -function symbolSequence(symbolsOrder, frames, symbols) { - var symbolIndex = 0; - for (var i = 0; frames && i < frames.length; i++) { - if (!(frames[i] in symbols)) - continue; - var frameSym = symbols[frames[i]].functionName || symbols[frames[i]].symbolName; - var substring = symbolsOrder[symbolIndex]; - if (frameSym.indexOf(substring) != -1) { - symbolIndex++; - if (symbolIndex == symbolsOrder.length) { - return true; - } - } - } - return false; -} -function firstMatch(array, matchFunction) { - for (var i = 0; i < array.length; i++) { - if (matchFunction(array[i])) - return array[i]; - } - return undefined; -} - -function calculateDiagnosticItems(requestID, profileID, meta) { - /* - if (!histogramData || histogramData.length < 1) { - sendFinished(requestID, []); - return; - }*/ - - var profile = gProfiles[profileID]; - //var symbols = profile.symbols; - var symbols = profile.functions; - var data = profile.filteredSamples; - - var lastStep = data[data.length-1]; - var widthSum = data.length; - var pendingDiagnosticInfo = null; - - var diagnosticItems = []; - - function finishPendingDiagnostic(endX) { - if (!pendingDiagnosticInfo) - return; - - var diagnostic = pendingDiagnosticInfo.diagnostic; - var currDiagnostic = { - x: pendingDiagnosticInfo.x / widthSum, - width: (endX - pendingDiagnosticInfo.x) / widthSum, - imageFile: pendingDiagnosticInfo.diagnostic.image, - title: pendingDiagnosticInfo.diagnostic.title, - details: pendingDiagnosticInfo.details, - onclickDetails: pendingDiagnosticInfo.onclickDetails - }; - - if (!currDiagnostic.onclickDetails && diagnostic.bugNumber) { - currDiagnostic.onclickDetails = "bug " + diagnostic.bugNumber; - } - - diagnosticItems.push(currDiagnostic); - - pendingDiagnosticInfo = null; - } - -/* - dump("meta: " + meta.gcStats + "\n"); - if (meta && meta.gcStats) { - dump("GC Stats: " + JSON.stringify(meta.gcStats) + "\n"); - } -*/ - - data.forEach(function diagnoseStep(step, x) { - if (step) { - var frames = step.frames; - - var diagnostic = firstMatch(diagnosticList, function (diagnostic) { - return diagnostic.check(frames, symbols, meta, step); - }); - } - - if (!diagnostic) { - finishPendingDiagnostic(x); - return; - } - - var details = diagnostic.details ? diagnostic.details(frames, symbols, meta, step) : null; - - if (pendingDiagnosticInfo) { - // We're already inside a diagnostic range. - if (diagnostic == pendingDiagnosticInfo.diagnostic && pendingDiagnosticInfo.details == details) { - // We're still inside the same diagnostic. - return; - } - - // We have left the old diagnostic and found a new one. Finish the old one. - finishPendingDiagnostic(x); - } - - pendingDiagnosticInfo = { - diagnostic: diagnostic, - x: x, - details: details, - onclickDetails: diagnostic.onclickDetails ? diagnostic.onclickDetails(frames, symbols, meta, step) : null - }; - }); - if (pendingDiagnosticInfo) - finishPendingDiagnostic(data.length); - - sendFinished(requestID, diagnosticItems); -} diff --git a/browser/devtools/profiler/cleopatra/js/strings.js b/browser/devtools/profiler/cleopatra/js/strings.js deleted file mode 100644 index a263b581d1fa..000000000000 --- a/browser/devtools/profiler/cleopatra/js/strings.js +++ /dev/null @@ -1,29 +0,0 @@ -"use strict"; - -const Cu = Components.utils; -const require = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; -const { L10N_BUNDLE } = require("devtools/profiler/consts"); - -Cu.import("resource:///modules/devtools/ViewHelpers.jsm"); -var L10N = new ViewHelpers.L10N(L10N_BUNDLE); - -/** - * Shortcuts for the L10N helper functions. Used in Cleopatra. - */ -var gStrings = { - // This strings are here so that Cleopatra code could use a simple object - // lookup. This makes it easier to merge upstream changes. - "Complete Profile": L10N.getStr("profiler.completeProfile"), - "Sample Range": L10N.getStr("profiler.sampleRange"), - "Running Time": L10N.getStr("profiler.runningTime"), - "Self": L10N.getStr("profiler.self"), - "Symbol Name": L10N.getStr("profiler.symbolName"), - - getStr: function (name) { - return L10N.getStr(name); - }, - - getFormatStr: function (name, params) { - return L10N.getFormatStr(name, params); - } -}; diff --git a/browser/devtools/profiler/cleopatra/js/tree.js b/browser/devtools/profiler/cleopatra/js/tree.js deleted file mode 100755 index 97cd8ad35521..000000000000 --- a/browser/devtools/profiler/cleopatra/js/tree.js +++ /dev/null @@ -1,702 +0,0 @@ -/* 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"; - -var kMaxChunkDuration = 30; // ms - -function escapeHTML(html) { - var pre = document.createElementNS("http://www.w3.org/1999/xhtml", "pre"); - var text = document.createTextNode(html); - pre.appendChild(text); - return pre.innerHTML; -} - -RegExp.escape = function(text) { - return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"); -} - -var requestAnimationFrame = window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function(callback, element) { - return window.setTimeout(callback, 1000 / 60); - }; - -var cancelAnimationFrame = window.webkitCancelAnimationFrame || - window.mozCancelAnimationFrame || - window.oCancelAnimationFrame || - window.msCancelAnimationFrame || - function(req) { - window.clearTimeout(req); - }; - -function TreeView() { - this._eventListeners = {}; - this._pendingActions = []; - this._pendingActionsProcessingCallback = null; - - this._container = document.createElement("div"); - this._container.className = "treeViewContainer"; - this._container.setAttribute("tabindex", "0"); // make it focusable - - this._header = document.createElement("ul"); - this._header.className = "treeHeader"; - this._container.appendChild(this._header); - - this._verticalScrollbox = document.createElement("div"); - this._verticalScrollbox.className = "treeViewVerticalScrollbox"; - this._container.appendChild(this._verticalScrollbox); - - this._leftColumnBackground = document.createElement("div"); - this._leftColumnBackground.className = "leftColumnBackground"; - this._verticalScrollbox.appendChild(this._leftColumnBackground); - - this._horizontalScrollbox = document.createElement("div"); - this._horizontalScrollbox.className = "treeViewHorizontalScrollbox"; - this._verticalScrollbox.appendChild(this._horizontalScrollbox); - - this._styleElement = document.createElement("style"); - this._styleElement.setAttribute("type", "text/css"); - this._container.appendChild(this._styleElement); - - this._contextMenu = document.createElement("menu"); - this._contextMenu.setAttribute("type", "context"); - this._contextMenu.id = "contextMenuForTreeView" + TreeView.instanceCounter++; - this._container.appendChild(this._contextMenu); - - this._busyCover = document.createElement("div"); - this._busyCover.className = "busyCover"; - this._container.appendChild(this._busyCover); - this._abortToggleAll = false; - this.initSelection = true; - - var self = this; - this._container.onkeydown = function (e) { - self._onkeypress(e); - }; - this._container.onkeypress = function (e) { - // on key down gives us '8' and mapping shift+8='*' may not be portable. - if (String.fromCharCode(e.charCode) == '*') - self._onkeypress(e); - }; - this._container.onclick = function (e) { - self._onclick(e); - }; - this._verticalScrollbox.addEventListener("contextmenu", function(event) { - self._populateContextMenu(event); - }, true); - this._setUpScrolling(); -}; -TreeView.instanceCounter = 0; - -TreeView.prototype = { - getContainer: function TreeView_getContainer() { - return this._container; - }, - setColumns: function TreeView_setColumns(columns) { - this._header.innerHTML = ""; - for (var i = 0; i < columns.length; i++) { - var li = document.createElement("li"); - li.className = "treeColumnHeader treeColumnHeader" + i; - li.id = columns[i].name + "Header"; - li.textContent = columns[i].title; - this._header.appendChild(li); - } - }, - dataIsOutdated: function TreeView_dataIsOutdated() { - this._busyCover.classList.add("busy"); - }, - display: function TreeView_display(data, resources, filterByName) { - this._busyCover.classList.remove("busy"); - this._filterByName = filterByName; - this._resources = resources; - this._addResourceIconStyles(); - this._filterByNameReg = null; // lazy init - if (this._filterByName === "") - this._filterByName = null; - this._horizontalScrollbox.innerHTML = ""; - this._horizontalScrollbox.data = data[0].getData(); - if (this._pendingActionsProcessingCallback) { - cancelAnimationFrame(this._pendingActionsProcessingCallback); - this._pendingActionsProcessingCallback = 0; - } - this._pendingActions = []; - - this._pendingActions.push({ - parentElement: this._horizontalScrollbox, - parentNode: null, - data: data[0].getData() - }); - this._processPendingActionsChunk(); - changeFocus(this._container); - }, - // Provide a snapshot of the reverse selection to restore with 'invert callback' - getReverseSelectionSnapshot: function TreeView__getReverseSelectionSnapshot(isJavascriptOnly) { - var snapshot = []; - - if (!this._selectedNode) { - return snapshot; - } - - var curr = this._selectedNode.data; - - while(curr) { - if (isJavascriptOnly && curr.isJSFrame || !isJavascriptOnly) { - snapshot.push(curr.name); - //dump(JSON.stringify(curr.name) + "\n"); - } - if (curr.treeChildren && curr.treeChildren.length >= 1) { - curr = curr.treeChildren[0].getData(); - } else { - break; - } - } - - return snapshot.reverse(); - }, - // Provide a snapshot of the current selection to restore - getSelectionSnapshot: function TreeView__getSelectionSnapshot(isJavascriptOnly) { - var snapshot = []; - var curr = this._selectedNode; - - while(curr) { - if (isJavascriptOnly && curr.data.isJSFrame || !isJavascriptOnly) { - snapshot.push(curr.data.name); - //dump(JSON.stringify(curr.data.name) + "\n"); - } - curr = curr.treeParent; - } - - return snapshot.reverse(); - }, - setSelection: function TreeView_setSelection(frames) { - this.restoreSelectionSnapshot(frames, false); - }, - // Take a selection snapshot and restore the selection - restoreSelectionSnapshot: function TreeView_restoreSelectionSnapshot(snapshot, allowNonContiguous) { - var currNode = this._horizontalScrollbox.firstChild; - if (currNode.data.name == snapshot[0] || snapshot[0] == "(total)") { - snapshot.shift(); - } - //dump("len: " + snapshot.length + "\n"); - next_level: while (currNode && snapshot.length > 0) { - this._toggle(currNode, false, true); - this._syncProcessPendingActionProcessing(); - for (var i = 0; i < currNode.treeChildren.length; i++) { - if (currNode.treeChildren[i].data.name == snapshot[0]) { - snapshot.shift(); - this._toggle(currNode, false, true); - currNode = currNode.treeChildren[i]; - continue next_level; - } - } - if (allowNonContiguous) { - // We need to do a Breadth-first search to find a match - var pendingSearch = [currNode.data]; - while (pendingSearch.length > 0) { - var node = pendingSearch.shift(); - if (!node.treeChildren) - continue; - for (var i = 0; i < node.treeChildren.length; i++) { - var childNode = node.treeChildren[i].getData(); - if (childNode.name == snapshot[0]) { - //dump("found: " + childNode.name + "\n"); - snapshot.shift(); - var nodesToToggle = [childNode]; - while (nodesToToggle[0].name != currNode.data.name) { - nodesToToggle.splice(0, 0, nodesToToggle[0].parent); - } - var lastToggle = currNode; - for (var j = 0; j < nodesToToggle.length; j++) { - for (var k = 0; k < lastToggle.treeChildren.length; k++) { - if (lastToggle.treeChildren[k].data.name == nodesToToggle[j].name) { - //dump("Expend: " + nodesToToggle[j].name + "\n"); - this._toggle(lastToggle.treeChildren[k], false, true); - lastToggle = lastToggle.treeChildren[k]; - this._syncProcessPendingActionProcessing(); - } - } - } - currNode = lastToggle; - continue next_level; - } - //dump("pending: " + childNode.name + "\n"); - pendingSearch.push(childNode); - } - } - } - break; // Didn't find child node matching - } - - if (currNode == this._horizontalScrollbox) { - PROFILERERROR("Failed to restore selection, could not find root.\n"); - return; - } - - this._toggle(currNode, true, true); - this._select(currNode); - }, - _processPendingActionsChunk: function TreeView__processPendingActionsChunk(isSync) { - this._pendingActionsProcessingCallback = 0; - - var startTime = Date.now(); - var endTime = startTime + kMaxChunkDuration; - while ((isSync == true || Date.now() < endTime) && this._pendingActions.length > 0) { - this._processOneAction(this._pendingActions.shift()); - } - this._scrollHeightChanged(); - - this._schedulePendingActionProcessing(); - }, - _schedulePendingActionProcessing: function TreeView__schedulePendingActionProcessing() { - if (!this._pendingActionsProcessingCallback && this._pendingActions.length > 0) { - var self = this; - this._pendingActionsProcessingCallback = requestAnimationFrame(function () { - self._processPendingActionsChunk(); - }); - } - }, - _syncProcessPendingActionProcessing: function TreeView__syncProcessPendingActionProcessing() { - this._processPendingActionsChunk(true); - }, - _processOneAction: function TreeView__processOneAction(action) { - var li = this._createTree(action.parentElement, action.parentNode, action.data); - if ("allChildrenCollapsedValue" in action) { - if (this._abortToggleAll) - return; - this._toggleAll(li, action.allChildrenCollapsedValue, true); - } - }, - addEventListener: function TreeView_addEventListener(eventName, callbackFunction) { - if (!(eventName in this._eventListeners)) - this._eventListeners[eventName] = []; - if (this._eventListeners[eventName].indexOf(callbackFunction) != -1) - return; - this._eventListeners[eventName].push(callbackFunction); - }, - removeEventListener: function TreeView_removeEventListener(eventName, callbackFunction) { - if (!(eventName in this._eventListeners)) - return; - var index = this._eventListeners[eventName].indexOf(callbackFunction); - if (index == -1) - return; - this._eventListeners[eventName].splice(index, 1); - }, - _fireEvent: function TreeView__fireEvent(eventName, eventObject) { - if (!(eventName in this._eventListeners)) - return; - this._eventListeners[eventName].forEach(function (callbackFunction) { - callbackFunction(eventObject); - }); - }, - _setUpScrolling: function TreeView__setUpScrolling() { - var waitingForPaint = false; - var accumulatedDeltaX = 0; - var accumulatedDeltaY = 0; - var self = this; - function scrollListener(e) { - if (!waitingForPaint) { - requestAnimationFrame(function () { - self._horizontalScrollbox.scrollLeft += accumulatedDeltaX; - self._verticalScrollbox.scrollTop += accumulatedDeltaY; - accumulatedDeltaX = 0; - accumulatedDeltaY = 0; - waitingForPaint = false; - }); - waitingForPaint = true; - } - if (e.axis == e.HORIZONTAL_AXIS) { - accumulatedDeltaX += e.detail; - } else { - accumulatedDeltaY += e.detail; - } - e.preventDefault(); - } - this._verticalScrollbox.addEventListener("MozMousePixelScroll", scrollListener, false); - this._verticalScrollbox.cleanUp = function () { - self._verticalScrollbox.removeEventListener("MozMousePixelScroll", scrollListener, false); - }; - }, - _scrollHeightChanged: function TreeView__scrollHeightChanged() { - if (!this._pendingScrollHeightChanged) { - var self = this; - this._pendingScrollHeightChanged = setTimeout(function() { - self._leftColumnBackground.style.height = self._horizontalScrollbox.getBoundingClientRect().height + 'px'; - self._pendingScrollHeightChanged = null; - }, 0); - } - }, - _createTree: function TreeView__createTree(parentElement, parentNode, data) { - var div = document.createElement("div"); - div.className = "treeViewNode collapsed"; - var hasChildren = ("children" in data) && (data.children.length > 0); - if (!hasChildren) - div.classList.add("leaf"); - var treeLine = document.createElement("div"); - treeLine.className = "treeLine"; - treeLine.innerHTML = this._HTMLForFunction(data); - div.depth = parentNode ? parentNode.depth + 1 : 0; - div.style.marginLeft = div.depth + "em"; - // When this item is toggled we will expand its children - div.pendingExpand = []; - div.treeLine = treeLine; - div.data = data; - // Useful for debugging - //this.uniqueID = this.uniqueID || 0; - //div.id = "Node" + this.uniqueID++; - div.appendChild(treeLine); - div.treeChildren = []; - div.treeParent = parentNode; - if (hasChildren) { - for (var i = 0; i < data.children.length; ++i) { - div.pendingExpand.push({parentElement: this._horizontalScrollbox, parentNode: div, data: data.children[i].getData() }); - } - } - if (parentNode) { - parentNode.treeChildren.push(div); - } - if (parentNode != null) { - var nextTo; - if (parentNode.treeChildren.length > 1) { - nextTo = parentNode.treeChildren[parentNode.treeChildren.length-2].nextSibling; - } else { - nextTo = parentNode.nextSibling; - } - parentElement.insertBefore(div, nextTo); - } else { - parentElement.appendChild(div); - } - return div; - }, - _addResourceIconStyles: function TreeView__addResourceIconStyles() { - var styles = []; - for (var resourceName in this._resources) { - var resource = this._resources[resourceName]; - if (resource.icon) { - styles.push('.resourceIcon[data-resource="' + resourceName + '"] { background-image: url("' + resource.icon + '"); }'); - } - } - this._styleElement.textContent = styles.join("\n"); - }, - _populateContextMenu: function TreeView__populateContextMenu(event) { - this._verticalScrollbox.setAttribute("contextmenu", ""); - - var target = event.target; - if (target.classList.contains("expandCollapseButton") || - target.classList.contains("focusCallstackButton")) - return; - - var li = this._getParentTreeViewNode(target); - if (!li) - return; - - this._select(li); - - this._contextMenu.innerHTML = ""; - - var self = this; - this._contextMenuForFunction(li.data).forEach(function (menuItem) { - var menuItemNode = document.createElement("menuitem"); - menuItemNode.onclick = (function (menuItem) { - return function() { - self._contextMenuClick(li.data, menuItem); - }; - })(menuItem); - menuItemNode.label = menuItem; - self._contextMenu.appendChild(menuItemNode); - }); - - this._verticalScrollbox.setAttribute("contextmenu", this._contextMenu.id); - }, - _contextMenuClick: function TreeView__contextMenuClick(node, menuItem) { - this._fireEvent("contextMenuClick", { node: node, menuItem: menuItem }); - }, - _contextMenuForFunction: function TreeView__contextMenuForFunction(node) { - // TODO move me outside tree.js - var menu = []; - if (node.library && ( - node.library.toLowerCase() == "lib_xul" || - node.library.toLowerCase() == "lib_xul.dll" - )) { - menu.push("View Source"); - } - if (node.isJSFrame && node.scriptLocation) { - menu.push("View JS Source"); - } - menu.push("Focus Frame"); - menu.push("Focus Callstack"); - menu.push("Google Search"); - menu.push("Plugin View: Pie"); - menu.push("Plugin View: Tree"); - return menu; - }, - _HTMLForFunction: function TreeView__HTMLForFunction(node) { - var nodeName = escapeHTML(node.name); - var resource = this._resources[node.library] || {}; - var libName = escapeHTML(resource.name || ""); - if (this._filterByName) { - if (!this._filterByNameReg) { - this._filterByName = RegExp.escape(this._filterByName); - this._filterByNameReg = new RegExp("(" + this._filterByName + ")","gi"); - } - nodeName = nodeName.replace(this._filterByNameReg, "$1"); - libName = libName.replace(this._filterByNameReg, "$1"); - } - var samplePercentage; - if (isNaN(node.ratio)) { - samplePercentage = ""; - } else { - samplePercentage = (100 * node.ratio).toFixed(1) + "%"; - } - return ' ' + - '' + node.counter + ' ' + - '' + samplePercentage + ' ' + - '' + node.selfCounter + ' ' + - ' ' + - '' + nodeName + '' + - '' + libName + '' + - ((nodeName === '(total)' || gHideSourceLinks) ? '' : - ''); - }, - _resolveChildren: function TreeView__resolveChildren(div, childrenCollapsedValue) { - while (div.pendingExpand != null && div.pendingExpand.length > 0) { - var pendingExpand = div.pendingExpand.shift(); - pendingExpand.allChildrenCollapsedValue = childrenCollapsedValue; - this._pendingActions.push(pendingExpand); - this._schedulePendingActionProcessing(); - } - }, - _showChild: function TreeView__showChild(div, isVisible) { - for (var i = 0; i < div.treeChildren.length; i++) { - div.treeChildren[i].style.display = isVisible?"":"none"; - if (!isVisible) { - div.treeChildren[i].classList.add("collapsed"); - this._showChild(div.treeChildren[i], isVisible); - } - } - }, - _toggle: function TreeView__toggle(div, /* optional */ newCollapsedValue, /* optional */ suppressScrollHeightNotification) { - var currentCollapsedValue = this._isCollapsed(div); - if (newCollapsedValue === undefined) - newCollapsedValue = !currentCollapsedValue; - if (newCollapsedValue) { - div.classList.add("collapsed"); - this._showChild(div, false); - } else { - this._resolveChildren(div, true); - div.classList.remove("collapsed"); - this._showChild(div, true); - } - if (!suppressScrollHeightNotification) - this._scrollHeightChanged(); - }, - _toggleAll: function TreeView__toggleAll(subtreeRoot, /* optional */ newCollapsedValue, /* optional */ suppressScrollHeightNotification) { - - // Reset abort - this._abortToggleAll = false; - - // Expands / collapses all child nodes, too. - - if (newCollapsedValue === undefined) - newCollapsedValue = !this._isCollapsed(subtreeRoot); - if (!newCollapsedValue) { - // expanding - this._resolveChildren(subtreeRoot, newCollapsedValue); - } - this._toggle(subtreeRoot, newCollapsedValue, true); - for (var i = 0; i < subtreeRoot.treeChildren.length; ++i) { - this._toggleAll(subtreeRoot.treeChildren[i], newCollapsedValue, true); - } - if (!suppressScrollHeightNotification) - this._scrollHeightChanged(); - }, - _getParent: function TreeView__getParent(div) { - return div.treeParent; - }, - _getFirstChild: function TreeView__getFirstChild(div) { - if (this._isCollapsed(div)) - return null; - var child = div.treeChildren[0]; - return child; - }, - _getLastChild: function TreeView__getLastChild(div) { - if (this._isCollapsed(div)) - return div; - var lastChild = div.treeChildren[div.treeChildren.length-1]; - if (lastChild == null) - return div; - return this._getLastChild(lastChild); - }, - _getPrevSib: function TreeView__getPevSib(div) { - if (div.treeParent == null) - return null; - var nodeIndex = div.treeParent.treeChildren.indexOf(div); - if (nodeIndex == 0) - return null; - return div.treeParent.treeChildren[nodeIndex-1]; - }, - _getNextSib: function TreeView__getNextSib(div) { - if (div.treeParent == null) - return null; - var nodeIndex = div.treeParent.treeChildren.indexOf(div); - if (nodeIndex == div.treeParent.treeChildren.length - 1) - return this._getNextSib(div.treeParent); - return div.treeParent.treeChildren[nodeIndex+1]; - }, - _scheduleScrollIntoView: function TreeView__scheduleScrollIntoView(element, maxImportantWidth) { - // Schedule this on the animation frame otherwise we may run this more then once per frames - // causing more work then needed. - var self = this; - if (self._pendingAnimationFrame != null) { - return; - } - self._pendingAnimationFrame = requestAnimationFrame(function anim_frame() { - cancelAnimationFrame(self._pendingAnimationFrame); - self._pendingAnimationFrame = null; - self._scrollIntoView(element, maxImportantWidth); - }); - }, - _scrollIntoView: function TreeView__scrollIntoView(element, maxImportantWidth) { - // Make sure that element is inside the visible part of our scrollbox by - // adjusting the scroll positions. If element is wider or - // higher than the scroll port, the left and top edges are prioritized over - // the right and bottom edges. - // If maxImportantWidth is set, parts of the beyond this widths are - // considered as not important; they'll not be moved into view. - - if (maxImportantWidth === undefined) - maxImportantWidth = Infinity; - - var visibleRect = { - left: this._horizontalScrollbox.getBoundingClientRect().left + 150, // TODO: un-hardcode 150 - top: this._verticalScrollbox.getBoundingClientRect().top, - right: this._horizontalScrollbox.getBoundingClientRect().right, - bottom: this._verticalScrollbox.getBoundingClientRect().bottom - } - var r = element.getBoundingClientRect(); - var right = Math.min(r.right, r.left + maxImportantWidth); - var leftCutoff = visibleRect.left - r.left; - var rightCutoff = right - visibleRect.right; - var topCutoff = visibleRect.top - r.top; - var bottomCutoff = r.bottom - visibleRect.bottom; - if (leftCutoff > 0) - this._horizontalScrollbox.scrollLeft -= leftCutoff; - else if (rightCutoff > 0) - this._horizontalScrollbox.scrollLeft += Math.min(rightCutoff, -leftCutoff); - if (topCutoff > 0) - this._verticalScrollbox.scrollTop -= topCutoff; - else if (bottomCutoff > 0) - this._verticalScrollbox.scrollTop += Math.min(bottomCutoff, -topCutoff); - }, - _select: function TreeView__select(li) { - if (this._selectedNode != null) { - this._selectedNode.treeLine.classList.remove("selected"); - this._selectedNode = null; - } - if (li) { - li.treeLine.classList.add("selected"); - this._selectedNode = li; - var functionName = li.treeLine.querySelector(".functionName"); - this._scheduleScrollIntoView(functionName, 400); - this._fireEvent("select", li.data); - } - updateDocumentURL(); - }, - _isCollapsed: function TreeView__isCollapsed(div) { - return div.classList.contains("collapsed"); - }, - _getParentTreeViewNode: function TreeView__getParentTreeViewNode(node) { - while (node) { - if (node.nodeType != node.ELEMENT_NODE) - break; - if (node.classList.contains("treeViewNode")) - return node; - node = node.parentNode; - } - return null; - }, - _onclick: function TreeView__onclick(event) { - var target = event.target; - var node = this._getParentTreeViewNode(target); - if (!node) - return; - if (target.classList.contains("expandCollapseButton")) { - if (event.altKey) - this._toggleAll(node); - else - this._toggle(node); - } else if (target.classList.contains("focusCallstackButton")) { - this._fireEvent("focusCallstackButtonClicked", node.data); - } else { - this._select(node); - if (event.detail == 2) // dblclick - this._toggle(node); - } - }, - _onkeypress: function TreeView__onkeypress(event) { - if (event.ctrlKey || event.altKey || event.metaKey) - return; - - this._abortToggleAll = true; - - var selected = this._selectedNode; - if (event.keyCode < 37 || event.keyCode > 40) { - if (event.keyCode != 0 || - String.fromCharCode(event.charCode) != '*') { - return; - } - } - event.stopPropagation(); - event.preventDefault(); - if (!selected) - return; - if (event.keyCode == 37) { // KEY_LEFT - var isCollapsed = this._isCollapsed(selected); - if (!isCollapsed) { - this._toggle(selected); - } else { - var parent = this._getParent(selected); - if (parent != null) { - this._select(parent); - } - } - } else if (event.keyCode == 38) { // KEY_UP - var prevSib = this._getPrevSib(selected); - var parent = this._getParent(selected); - if (prevSib != null) { - this._select(this._getLastChild(prevSib)); - } else if (parent != null) { - this._select(parent); - } - } else if (event.keyCode == 39) { // KEY_RIGHT - var isCollapsed = this._isCollapsed(selected); - if (isCollapsed) { - this._toggle(selected); - this._syncProcessPendingActionProcessing(); - } else { - // Do KEY_DOWN - var nextSib = this._getNextSib(selected); - var child = this._getFirstChild(selected); - if (child != null) { - this._select(child); - } else if (nextSib) { - this._select(nextSib); - } - } - } else if (event.keyCode == 40) { // KEY_DOWN - var nextSib = this._getNextSib(selected); - var child = this._getFirstChild(selected); - if (child != null) { - this._select(child); - } else if (nextSib) { - this._select(nextSib); - } - } else if (String.fromCharCode(event.charCode) == '*') { - this._toggleAll(selected); - } - }, -}; - diff --git a/browser/devtools/profiler/cleopatra/js/ui.js b/browser/devtools/profiler/cleopatra/js/ui.js deleted file mode 100755 index 92ecc895b016..000000000000 --- a/browser/devtools/profiler/cleopatra/js/ui.js +++ /dev/null @@ -1,1992 +0,0 @@ -/* 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"; - -var EIDETICKER_BASE_URL = "http://eideticker.wrla.ch/"; - -var gDebugLog = false; -var gDebugTrace = false; -var gLocation = window.location + ""; -if (gLocation.indexOf("file:") == 0) { - gDebugLog = true; - gDebugTrace = true; - PROFILERLOG("Turning on logging+tracing since cleopatra is served from the file protocol"); -} -// Use for verbose tracing, otherwise use log -function PROFILERTRACE(msg) { - if (gDebugTrace) - PROFILERLOG(msg); -} -function PROFILERLOG(msg) { - if (gDebugLog) { - msg = "Cleo: " + msg; - console.log(msg); - if (window.dump) - window.dump(msg + "\n"); - } -} -function PROFILERERROR(msg) { - msg = "Cleo: " + msg; - console.log(msg); - if (window.dump) - window.dump(msg + "\n"); -} -function enableProfilerTracing() { - gDebugLog = true; - gDebugTrace = true; - Parser.updateLogSetting(); -} -function enableProfilerLogging() { - gDebugLog = true; - Parser.updateLogSetting(); -} - -function removeAllChildren(element) { - while (element.firstChild) { - element.removeChild(element.firstChild); - } -} - -function FileList() { - this._container = document.createElement("ul"); - this._container.id = "fileList"; - this._selectedFileItem = null; - this._fileItemList = []; -} - -FileList.prototype = { - getContainer: function FileList_getContainer() { - return this._container; - }, - - clearFiles: function FileList_clearFiles() { - this.fileItemList = []; - this._selectedFileItem = null; - this._container.innerHTML = ""; - }, - - loadProfileListFromLocalStorage: function FileList_loadProfileListFromLocalStorage() { - var self = this; - gLocalStorage.getProfileList(function(profileList) { - for (var i = profileList.length - 1; i >= 0; i--) { - (function closure() { - // This only carries info about the profile and the access key to retrieve it. - var profileInfo = profileList[i]; - //PROFILERTRACE("Profile list from local storage: " + JSON.stringify(profileInfo)); - var dateObj = new Date(profileInfo.date); - var fileEntry = self.addFile(profileInfo, dateObj.toLocaleString(), function fileEntryClick() { - PROFILERLOG("open: " + profileInfo.profileKey + "\n"); - loadLocalStorageProfile(profileInfo.profileKey); - }); - })(); - } - }); - gLocalStorage.onProfileListChange(function(profileList) { - self.clearFiles(); - self.loadProfileListFromLocalStorage(); - }); - }, - - addFile: function FileList_addFile(profileInfo, description, onselect) { - var li = document.createElement("li"); - - var fileName; - if (profileInfo.profileKey && profileInfo.profileKey.indexOf("http://profile-store.commondatastorage.googleapis.com/") >= 0) { - fileName = profileInfo.profileKey.substring(54); - fileName = fileName.substring(0, 8) + "..." + fileName.substring(28); - } else { - fileName = profileInfo.name; - } - li.fileName = fileName || "(New Profile)"; - li.description = description || "(empty)"; - - li.className = "fileListItem"; - if (!this._selectedFileItem) { - li.classList.add("selected"); - this._selectedFileItem = li; - } - - var self = this; - li.onclick = function() { - self.setSelection(li); - if (onselect) - onselect(); - } - - var fileListItemTitleSpan = document.createElement("span"); - fileListItemTitleSpan.className = "fileListItemTitle"; - fileListItemTitleSpan.textContent = li.fileName; - li.appendChild(fileListItemTitleSpan); - - var fileListItemDescriptionSpan = document.createElement("span"); - fileListItemDescriptionSpan.className = "fileListItemDescription"; - fileListItemDescriptionSpan.textContent = li.description; - li.appendChild(fileListItemDescriptionSpan); - - this._container.appendChild(li); - - this._fileItemList.push(li); - - return li; - }, - - setSelection: function FileList_setSelection(fileEntry) { - if (this._selectedFileItem) { - this._selectedFileItem.classList.remove("selected"); - } - this._selectedFileItem = fileEntry; - fileEntry.classList.add("selected"); - if (this._selectedFileItem.onselect) - this._selectedFileItem.onselect(); - }, - - profileParsingFinished: function FileList_profileParsingFinished() { - //this._container.querySelector(".fileListItemTitle").textContent = "Current Profile"; - //this._container.querySelector(".fileListItemDescription").textContent = gNumSamples + " Samples"; - } -} - -function treeObjSort(a, b) { - return b.counter - a.counter; -} - -function ProfileTreeManager() { - this.treeView = new TreeView(); - this.treeView.setColumns([ - { name: "sampleCount", title: gStrings["Running Time"] }, - { name: "selfSampleCount", title: gStrings["Self"] }, - { name: "resource", title: "" }, - { name: "symbolName", title: gStrings["Symbol Name"] } - ]); - var self = this; - this.treeView.addEventListener("select", function (frameData) { - self.highlightFrame(frameData); - if (window.comparator_setSelection) { - window.comparator_setSelection(gTreeManager.serializeCurrentSelectionSnapshot(), frameData); - } - }); - this.treeView.addEventListener("contextMenuClick", function (e) { - self._onContextMenuClick(e); - }); - this.treeView.addEventListener("focusCallstackButtonClicked", function (frameData) { - // NOTE: Not in the original Cleopatra source code. - notifyParent("displaysource", { - line: frameData.scriptLocation.lineInformation, - uri: frameData.scriptLocation.scriptURI, - isChrome: /^otherhost_*/.test(frameData.library) - }); - }); - this._container = document.createElement("div"); - this._container.className = "tree"; - this._container.appendChild(this.treeView.getContainer()); - - // If this is set when the tree changes the snapshot is immediately restored. - this._savedSnapshot = null; - this._allowNonContiguous = false; -} -ProfileTreeManager.prototype = { - getContainer: function ProfileTreeManager_getContainer() { - return this._container; - }, - highlightFrame: function Treedisplay_highlightFrame(frameData) { - setHighlightedCallstack(this._getCallstackUpTo(frameData), this._getHeaviestCallstack(frameData)); - }, - dataIsOutdated: function ProfileTreeManager_dataIsOutdated() { - this.treeView.dataIsOutdated(); - }, - saveSelectionSnapshot: function ProfileTreeManager_saveSelectionSnapshot(isJavascriptOnly) { - this._savedSnapshot = this.treeView.getSelectionSnapshot(isJavascriptOnly); - }, - saveReverseSelectionSnapshot: function ProfileTreeManager_saveReverseSelectionSnapshot(isJavascriptOnly) { - this._savedSnapshot = this.treeView.getReverseSelectionSnapshot(isJavascriptOnly); - }, - hasNonTrivialSelection: function ProfileTreeManager_hasNonTrivialSelection() { - return this.treeView.getSelectionSnapshot().length > 1; - }, - serializeCurrentSelectionSnapshot: function ProfileTreeManager_serializeCurrentSelectionSnapshot() { - return JSON.stringify(this.treeView.getSelectionSnapshot()); - }, - restoreSerializedSelectionSnapshot: function ProfileTreeManager_restoreSerializedSelectionSnapshot(selection) { - this._savedSnapshot = JSON.parse(selection); - }, - _restoreSelectionSnapshot: function ProfileTreeManager__restoreSelectionSnapshot(snapshot, allowNonContiguous) { - return this.treeView.restoreSelectionSnapshot(snapshot, allowNonContiguous); - }, - setSelection: function ProfileTreeManager_setSelection(frames) { - return this.treeView.setSelection(frames); - }, - _getCallstackUpTo: function ProfileTreeManager__getCallstackUpTo(frame) { - var callstack = []; - var curr = frame; - while (curr != null) { - if (curr.name != null) { - var subCallstack = curr.fullFrameNamesAsInSample.clone(); - subCallstack.reverse(); - callstack = callstack.concat(subCallstack); - } - curr = curr.parent; - } - callstack.reverse(); - if (gInvertCallstack) - callstack.shift(); // remove (total) - return callstack; - }, - _getHeaviestCallstack: function ProfileTreeManager__getHeaviestCallstack(frame) { - // FIXME: This gets the first leaf which is not the heaviest leaf. - while(frame.children && frame.children.length > 0) { - var nextFrame = frame.children[0].getData(); - if (!nextFrame) - break; - frame = nextFrame; - } - return this._getCallstackUpTo(frame); - }, - _onContextMenuClick: function ProfileTreeManager__onContextMenuClick(e) { - var node = e.node; - var menuItem = e.menuItem; - - if (menuItem == "View Source") { - // Remove anything after ( since MXR doesn't handle search with the arguments. - var symbol = node.name.split("(")[0]; - window.open("http://mxr.mozilla.org/mozilla-central/search?string=" + symbol, "View Source"); - } else if (menuItem == "View JS Source") { - viewJSSource(node); - } else if (menuItem == "Plugin View: Pie") { - focusOnPluginView("protovis", {type:"pie"}); - } else if (menuItem == "Plugin View: Tree") { - focusOnPluginView("protovis", {type:"tree"}); - } else if (menuItem == "Google Search") { - var symbol = node.name; - window.open("https://www.google.ca/search?q=" + symbol, "View Source"); - } else if (menuItem == "Focus Frame") { - var symbol = node.fullFrameNamesAsInSample[0]; // TODO: we only function one symbol when callpath merging is on, fix that - focusOnSymbol(symbol, node.name); - } else if (menuItem == "Focus Callstack") { - var focusedCallstack = this._getCallstackUpTo(node); - focusOnCallstack(focusedCallstack, node.name); - } - }, - setAllowNonContiguous: function ProfileTreeManager_setAllowNonContiguous() { - this._allowNonContiguous = true; - }, - display: function ProfileTreeManager_display(tree, symbols, functions, resources, useFunctions, filterByName) { - this.treeView.display(this.convertToJSTreeData(tree, symbols, functions, useFunctions), resources, filterByName); - if (this._savedSnapshot) { - var old = this._savedSnapshot.clone(); - this._restoreSelectionSnapshot(this._savedSnapshot, this._allowNonContiguous); - this._savedSnapshot = old; - this._allowNonContiguous = false; - } - }, - convertToJSTreeData: function ProfileTreeManager__convertToJSTreeData(rootNode, symbols, functions, useFunctions) { - var totalSamples = rootNode.counter; - function createTreeViewNode(node, parent) { - var curObj = {}; - curObj.parent = parent; - curObj.counter = node.counter; - var selfCounter = node.counter; - for (var i = 0; i < node.children.length; ++i) { - selfCounter -= node.children[i].counter; - } - curObj.selfCounter = selfCounter; - curObj.ratio = node.counter / totalSamples; - curObj.fullFrameNamesAsInSample = node.mergedNames ? node.mergedNames : [node.name]; - if (!(node.name in (useFunctions ? functions : symbols))) { - curObj.name = node.name; - curObj.library = ""; - } else { - var functionObj = useFunctions ? functions[node.name] : functions[symbols[node.name].functionIndex]; - var info = { - functionName: functionObj.functionName, - libraryName: functionObj.libraryName, - lineInformation: useFunctions ? "" : symbols[node.name].lineInformation - }; - curObj.name = (info.functionName + " " + info.lineInformation).trim(); - curObj.library = info.libraryName; - curObj.isJSFrame = functionObj.isJSFrame; - if (functionObj.scriptLocation) { - curObj.scriptLocation = functionObj.scriptLocation; - } - } - if (node.children.length) { - curObj.children = getChildrenObjects(node.children, curObj); - } - return curObj; - } - function getChildrenObjects(children, parent) { - var sortedChildren = children.slice(0).sort(treeObjSort); - return sortedChildren.map(function (child) { - var createdNode = null; - return { - getData: function () { - if (!createdNode) { - createdNode = createTreeViewNode(child, parent); - } - return createdNode; - } - }; - }); - } - return getChildrenObjects([rootNode], null); - }, -}; - -function SampleBar() { - this._container = document.createElement("div"); - this._container.id = "sampleBar"; - this._container.className = "sideBar"; - - this._header = document.createElement("h2"); - this._header.innerHTML = "Selection - Most time spent in:"; - this._header.alt = "This shows the heaviest leaf of the selected sample. Use this to get a quick glimpse of where the selection is spending most of its time."; - this._container.appendChild(this._header); - - this._text = document.createElement("ul"); - this._text.style.whiteSpace = "pre"; - this._text.innerHTML = "Sample text"; - this._container.appendChild(this._text); -} - -SampleBar.prototype = { - getContainer: function SampleBar_getContainer() { - return this._container; - }, - setSample: function SampleBar_setSample(sample) { - var str = ""; - var list = []; - - this._text.innerHTML = ""; - - for (var i = 0; i < sample.length; i++) { - var functionObj = gMergeFunctions ? gFunctions[sample[i]] : gFunctions[symbols[sample[i]].functionIndex]; - if (!functionObj) - continue; - var functionItem = document.createElement("li"); - var functionLink = document.createElement("a"); - functionLink.textContent = functionLink.title = functionObj.functionName; - functionLink.href = "#"; - functionItem.appendChild(functionLink); - this._text.appendChild(functionItem); - list.push(functionObj.functionName); - functionLink.selectIndex = i; - functionLink.onclick = function() { - var selectedFrames = []; - if (gInvertCallstack) { - for (var i = 0; i <= this.selectIndex; i++) { - var functionObj = gMergeFunctions ? gFunctions[sample[i]] : gFunctions[symbols[sample[i]].functionIndex]; - selectedFrames.push(functionObj.functionName); - } - } else { - for (var i = sample.length - 1; i >= this.selectIndex; i--) { - var functionObj = gMergeFunctions ? gFunctions[sample[i]] : gFunctions[symbols[sample[i]].functionIndex]; - selectedFrames.push(functionObj.functionName); - } - } - gTreeManager.setSelection(selectedFrames); - return false; - } - } - return list; - }, -} - - -function PluginView() { - this._container = document.createElement("div"); - this._container.className = "pluginview"; - this._container.style.visibility = 'hidden'; - this._iframe = document.createElement("iframe"); - this._iframe.className = "pluginviewIFrame"; - this._container.appendChild(this._iframe); - this._container.style.top = ""; -} -PluginView.prototype = { - getContainer: function PluginView_getContainer() { - return this._container; - }, - hide: function() { - // get rid of the scrollbars - this._container.style.top = ""; - this._container.style.visibility = 'hidden'; - }, - show: function() { - // This creates extra scrollbar so only do it when needed - this._container.style.top = "0px"; - this._container.style.visibility = ''; - }, - display: function(pluginName, param, data) { - this._iframe.src = "js/plugins/" + pluginName + "/index.html"; - var self = this; - this._iframe.onload = function() { - self._iframe.contentWindow.initCleopatraPlugin(data, param, gSymbols); - } - this.show(); - }, -} - -function HistogramView() { - this._container = document.createElement("div"); - this._container.className = "histogram"; - - this._canvas = this._createCanvas(); - this._container.appendChild(this._canvas); - - this._rangeSelector = new RangeSelector(this._canvas, this); - this._rangeSelector.enableRangeSelectionOnHistogram(); - this._container.appendChild(this._rangeSelector.getContainer()); - - this._busyCover = document.createElement("div"); - this._busyCover.className = "busyCover"; - this._container.appendChild(this._busyCover); - - this._histogramData = []; -} -HistogramView.prototype = { - dataIsOutdated: function HistogramView_dataIsOutdated() { - this._busyCover.classList.add("busy"); - }, - _createCanvas: function HistogramView__createCanvas() { - var canvas = document.createElement("canvas"); - canvas.height = 60; - canvas.style.width = "100%"; - canvas.style.height = "100%"; - return canvas; - }, - getContainer: function HistogramView_getContainer() { - return this._container; - }, - selectRange: function HistogramView_selectRange(start, end) { - this._rangeSelector._finishSelection(start, end); - }, - showVideoFramePosition: function HistogramView_showVideoFramePosition(frame) { - if (!this._frameStart || !this._frameStart[frame]) - return; - var frameStart = this._frameStart[frame]; - // Now we look for the frame end. Because we can swap frame we don't present we have to look ahead - // in the stream if frame+1 doesn't exist. - var frameEnd = this._frameStart[frame+1]; - for (var i = 0; i < 10 && !frameEnd; i++) { - frameEnd = this._frameStart[frame+1+i]; - } - this._rangeSelector.showVideoRange(frameStart, frameEnd); - }, - showVideoPosition: function HistogramView_showVideoPosition(position) { - // position in 0..1 - this._rangeSelector.showVideoPosition(position); - }, - _gatherMarkersList: function HistogramView__gatherMarkersList(histogramData) { - var markers = []; - for (var i = 0; i < histogramData.length; ++i) { - var step = histogramData[i]; - if ("marker" in step) { - markers.push({ - index: i, - name: step.marker - }); - } - } - return markers; - }, - _calculateWidthMultiplier: function () { - var minWidth = 2000; - return Math.ceil(minWidth / this._widthSum); - }, - histogramClick: function HistogramView_histogramClick(index) { - var sample = this._histogramData[index]; - var frames = sample.frames; - var list = gSampleBar.setSample(frames[0]); - gTreeManager.setSelection(list); - setHighlightedCallstack(frames[0], frames[0]); - }, - display: function HistogramView_display(histogramData, frameStart, widthSum, highlightedCallstack) { - this._histogramData = histogramData; - this._frameStart = frameStart; - this._widthSum = widthSum; - this._widthMultiplier = this._calculateWidthMultiplier(); - this._canvas.width = this._widthMultiplier * this._widthSum; - this._render(highlightedCallstack); - this._busyCover.classList.remove("busy"); - }, - _scheduleRender: function HistogramView__scheduleRender(highlightedCallstack) { - var self = this; - if (self._pendingAnimationFrame != null) { - return; - } - self._pendingAnimationFrame = requestAnimationFrame(function anim_frame() { - cancelAnimationFrame(self._pendingAnimationFrame); - self._pendingAnimationFrame = null; - self._render(highlightedCallstack); - self._busyCover.classList.remove("busy"); - }); - }, - _render: function HistogramView__render(highlightedCallstack) { - var ctx = this._canvas.getContext("2d"); - var height = this._canvas.height; - ctx.setTransform(this._widthMultiplier, 0, 0, 1, 0, 0); - ctx.font = "20px Georgia"; - ctx.clearRect(0, 0, this._widthSum, height); - - var self = this; - var markerCount = 0; - for (var i = 0; i < this._histogramData.length; i++) { - var step = this._histogramData[i]; - var isSelected = self._isStepSelected(step, highlightedCallstack); - var isInRangeSelector = self._isInRangeSelector(i); - if (isSelected) { - ctx.fillStyle = "green"; - } else if (isInRangeSelector) { - ctx.fillStyle = "blue"; - } else { - ctx.fillStyle = step.color; - } - var roundedHeight = Math.round(step.value * height); - ctx.fillRect(step.x, height - roundedHeight, step.width, roundedHeight); - } - - this._finishedRendering = true; - }, - highlightedCallstackChanged: function HistogramView_highlightedCallstackChanged(highlightedCallstack) { - this._scheduleRender(highlightedCallstack); - }, - _isInRangeSelector: function HistogramView_isInRangeSelector(index) { - return false; - }, - _isStepSelected: function HistogramView__isStepSelected(step, highlightedCallstack) { - if ("marker" in step) - return false; - - search_frames: for (var i = 0; i < step.frames.length; i++) { - var frames = step.frames[i]; - - if (frames.length < highlightedCallstack.length || - highlightedCallstack.length <= (gInvertCallstack ? 0 : 1)) - continue; - - var compareFrames = frames; - if (gInvertCallstack) { - for (var j = 0; j < highlightedCallstack.length; j++) { - var compareFrameIndex = compareFrames.length - 1 - j; - if (highlightedCallstack[j] != compareFrames[compareFrameIndex]) { - continue search_frames; - } - } - } else { - for (var j = 0; j < highlightedCallstack.length; j++) { - var compareFrameIndex = j; - if (highlightedCallstack[j] != compareFrames[compareFrameIndex]) { - continue search_frames; - } - } - } - return true; - }; - return false; - }, - getHistogramData: function HistogramView__getHistogramData() { - return this._histogramData; - }, - _getStepColor: function HistogramView__getStepColor(step) { - if ("responsiveness" in step.extraInfo) { - var res = step.extraInfo.responsiveness; - var redComponent = Math.round(255 * Math.min(1, res / kDelayUntilWorstResponsiveness)); - return "rgb(" + redComponent + ",0,0)"; - } - - return "rgb(0,0,0)"; - }, -}; - -function RangeSelector(graph, histogram) { - this._histogram = histogram; - this.container = document.createElement("div"); - this.container.className = "rangeSelectorContainer"; - this._graph = graph; - this._selectedRange = { startX: 0, endX: 0 }; - this._selectedSampleRange = { start: 0, end: 0 }; - - this._highlighter = document.createElement("div"); - this._highlighter.className = "histogramHilite collapsed"; - this.container.appendChild(this._highlighter); - - this._mouseMarker = document.createElement("div"); - this._mouseMarker.className = "histogramMouseMarker"; - this.container.appendChild(this._mouseMarker); -} -RangeSelector.prototype = { - getContainer: function RangeSelector_getContainer() { - return this.container; - }, - // echo the location off the mouse on the histogram - drawMouseMarker: function RangeSelector_drawMouseMarker(x) { - var mouseMarker = this._mouseMarker; - mouseMarker.style.left = x + "px"; - }, - showVideoPosition: function RangeSelector_showVideoPosition(position) { - this.drawMouseMarker(position * (this._graph.parentNode.clientWidth-1)); - PROFILERLOG("Show video position: " + position); - }, - drawHiliteRectangle: function RangeSelector_drawHiliteRectangle(x, y, width, height) { - var hilite = this._highlighter; - hilite.style.left = x + "px"; - hilite.style.top = "0"; - hilite.style.width = width + "px"; - hilite.style.height = height + "px"; - }, - clearCurrentRangeSelection: function RangeSelector_clearCurrentRangeSelection() { - try { - this.changeEventSuppressed = true; - var children = this.selector.childNodes; - for (var i = 0; i < children.length; ++i) { - children[i].selected = false; - } - } finally { - this.changeEventSuppressed = false; - } - }, - showVideoRange: function RangeSelector_showVideoRange(startIndex, endIndex) { - if (!endIndex || endIndex < 0) - endIndex = gCurrentlyShownSampleData.length; - - var len = this._graph.parentNode.getBoundingClientRect().right - this._graph.parentNode.getBoundingClientRect().left; - this._selectedRange.startX = startIndex * len / this._histogram._histogramData.length; - this._selectedRange.endX = endIndex * len / this._histogram._histogramData.length; - var width = this._selectedRange.endX - this._selectedRange.startX; - var height = this._graph.parentNode.clientHeight; - this._highlighter.classList.remove("collapsed"); - this.drawHiliteRectangle(this._selectedRange.startX, 0, width, height); - //this._finishSelection(startIndex, endIndex); - }, - enableRangeSelectionOnHistogram: function RangeSelector_enableRangeSelectionOnHistogram() { - var graph = this._graph; - var isDrawingRectangle = false; - var origX, origY; - var self = this; - // Compute this on the mouse down rather then forcing a sync reflow - // every frame. - var boundingRect = null; - function histogramClick(clickX, clickY) { - clickX = Math.min(clickX, graph.parentNode.getBoundingClientRect().right); - clickX = clickX - graph.parentNode.getBoundingClientRect().left; - var index = self._histogramIndexFromPoint(clickX); - self._histogram.histogramClick(index); - } - function updateHiliteRectangle(newX, newY) { - newX = Math.min(newX, boundingRect.right); - var startX = Math.min(newX, origX) - boundingRect.left; - var startY = 0; - var width = Math.abs(newX - origX); - var height = graph.parentNode.clientHeight; - if (startX < 0) { - width += startX; - startX = 0; - } - self._selectedRange.startX = startX; - self._selectedRange.endX = startX + width; - self.drawHiliteRectangle(startX, startY, width, height); - } - function updateMouseMarker(newX) { - self.drawMouseMarker(newX - graph.parentNode.getBoundingClientRect().left); - } - graph.addEventListener("mousedown", function(e) { - if (e.button != 0) - return; - graph.style.cursor = "col-resize"; - isDrawingRectangle = true; - self.beginHistogramSelection(); - origX = e.pageX; - origY = e.pageY; - boundingRect = graph.parentNode.getBoundingClientRect(); - if (this.setCapture) - this.setCapture(); - // Reset the highlight rectangle - updateHiliteRectangle(e.pageX, e.pageY); - e.preventDefault(); - this._movedDuringClick = false; - }, false); - graph.addEventListener("mouseup", function(e) { - graph.style.cursor = "default"; - if (!this._movedDuringClick) { - isDrawingRectangle = false; - // Handle as a click on the histogram. Select the sample: - histogramClick(e.pageX, e.pageY); - } else if (isDrawingRectangle) { - isDrawingRectangle = false; - updateHiliteRectangle(e.pageX, e.pageY); - self.finishHistogramSelection(e.pageX != origX); - if (e.pageX == origX) { - // Simple click in the histogram - var index = self._sampleIndexFromPoint(e.pageX - graph.parentNode.getBoundingClientRect().left); - // TODO Select this sample in the tree view - var sample = gCurrentlyShownSampleData[index]; - } - } - }, false); - graph.addEventListener("mousemove", function(e) { - this._movedDuringClick = true; - if (isDrawingRectangle) { - updateMouseMarker(-1); // Clear - updateHiliteRectangle(e.pageX, e.pageY); - } else { - updateMouseMarker(e.pageX); - } - }, false); - graph.addEventListener("mouseout", function(e) { - updateMouseMarker(-1); // Clear - }, false); - }, - beginHistogramSelection: function RangeSelector_beginHistgramSelection() { - var hilite = this._highlighter; - hilite.classList.remove("finished"); - hilite.classList.add("selecting"); - hilite.classList.remove("collapsed"); - if (this._transientRestrictionEnteringAffordance) { - this._transientRestrictionEnteringAffordance.discard(); - } - }, - _finishSelection: function RangeSelector__finishSelection(start, end) { - var newFilterChain = gSampleFilters.concat({ type: "RangeSampleFilter", start: start, end: end }); - var self = this; - self._transientRestrictionEnteringAffordance = gBreadcrumbTrail.add({ - title: gStrings["Sample Range"] + " [" + start + ", " + (end + 1) + "]", - enterCallback: function () { - gSampleFilters = newFilterChain; - self.collapseHistogramSelection(); - filtersChanged(); - } - }); - }, - finishHistogramSelection: function RangeSelector_finishHistgramSelection(isSomethingSelected) { - var self = this; - var hilite = this._highlighter; - hilite.classList.remove("selecting"); - if (isSomethingSelected) { - hilite.classList.add("finished"); - var start = this._sampleIndexFromPoint(this._selectedRange.startX); - var end = this._sampleIndexFromPoint(this._selectedRange.endX); - self._finishSelection(start, end); - } else { - hilite.classList.add("collapsed"); - } - }, - collapseHistogramSelection: function RangeSelector_collapseHistogramSelection() { - var hilite = this._highlighter; - hilite.classList.add("collapsed"); - }, - _sampleIndexFromPoint: function RangeSelector__sampleIndexFromPoint(x) { - // XXX this is completely wrong, fix please - var totalSamples = parseFloat(gCurrentlyShownSampleData.length); - var width = parseFloat(this._graph.parentNode.clientWidth); - var factor = totalSamples / width; - return parseInt(parseFloat(x) * factor); - }, - _histogramIndexFromPoint: function RangeSelector__histogramIndexFromPoint(x) { - // XXX this is completely wrong, fix please - var totalSamples = parseFloat(this._histogram._histogramData.length); - var width = parseFloat(this._graph.parentNode.clientWidth); - var factor = totalSamples / width; - return parseInt(parseFloat(x) * factor); - }, -}; - -function videoPaneTimeChange(video) { - if (!gMeta || !gMeta.frameStart) - return; - - var frame = gVideoPane.getCurrentFrameNumber(); - //var frameStart = gMeta.frameStart[frame]; - //var frameEnd = gMeta.frameStart[frame+1]; // If we don't have a frameEnd assume the end of the profile - - gHistogramView.showVideoFramePosition(frame); -} - - -window.onpopstate = function(ev) { - return; // Conflicts with document url - if (!gBreadcrumbTrail) - return; - - gBreadcrumbTrail.pop(); - if (ev.state) { - if (ev.state.action === "popbreadcrumb") { - //gBreadcrumbTrail.pop(); - } - } -} - -function BreadcrumbTrail() { - this._breadcrumbs = []; - this._selectedBreadcrumbIndex = -1; - - this._containerElement = document.createElement("div"); - this._containerElement.className = "breadcrumbTrail"; - var self = this; - this._containerElement.addEventListener("click", function (e) { - if (!e.target.classList.contains("breadcrumbTrailItem")) - return; - self._enter(e.target.breadcrumbIndex); - }); -} -BreadcrumbTrail.prototype = { - getContainer: function BreadcrumbTrail_getContainer() { - return this._containerElement; - }, - /** - * Add a breadcrumb. The breadcrumb parameter is an object with the following - * properties: - * - title: The text that will be shown in the breadcrumb's button. - * - enterCallback: A function that will be called when entering this - * breadcrumb. - */ - add: function BreadcrumbTrail_add(breadcrumb) { - for (var i = this._breadcrumbs.length - 1; i > this._selectedBreadcrumbIndex; i--) { - var rearLi = this._breadcrumbs[i]; - if (!rearLi.breadcrumbIsTransient) - throw "Can only add new breadcrumbs if after the current one there are only transient ones."; - rearLi.breadcrumbDiscarder.discard(); - } - var div = document.createElement("div"); - div.className = "breadcrumbTrailItem"; - div.textContent = breadcrumb.title; - var index = this._breadcrumbs.length; - div.breadcrumbIndex = index; - div.breadcrumbEnterCallback = breadcrumb.enterCallback; - div.breadcrumbIsTransient = true; - div.style.zIndex = 1000 - index; - this._containerElement.appendChild(div); - this._breadcrumbs.push(div); - if (index == 0) - this._enter(index); - var self = this; - div.breadcrumbDiscarder = { - discard: function () { - if (div.breadcrumbIsTransient) { - self._deleteBeyond(index - 1); - delete div.breadcrumbIsTransient; - delete div.breadcrumbDiscarder; - } - } - }; - return div.breadcrumbDiscarder; - }, - addAndEnter: function BreadcrumbTrail_addAndEnter(breadcrumb) { - var removalHandle = this.add(breadcrumb); - this._enter(this._breadcrumbs.length - 1); - }, - pop : function BreadcrumbTrail_pop() { - if (this._breadcrumbs.length-2 >= 0) - this._enter(this._breadcrumbs.length-2); - }, - enterLastItem: function BreadcrumbTrail_enterLastItem(forceSelection) { - this._enter(this._breadcrumbs.length-1, forceSelection); - }, - _enter: function BreadcrumbTrail__select(index, forceSelection) { - if (index == this._selectedBreadcrumbIndex) - return; - if (forceSelection) { - gTreeManager.restoreSerializedSelectionSnapshot(forceSelection); - } else { - gTreeManager.saveSelectionSnapshot(); - } - var prevSelected = this._breadcrumbs[this._selectedBreadcrumbIndex]; - if (prevSelected) - prevSelected.classList.remove("selected"); - var li = this._breadcrumbs[index]; - if (this === gBreadcrumbTrail && index != 0) { - // Support for back button, disabled until the forward button is implemented. - //var state = {action: "popbreadcrumb",}; - //window.history.pushState(state, "Cleopatra"); - } - - delete li.breadcrumbIsTransient; - li.classList.add("selected"); - this._deleteBeyond(index); - this._selectedBreadcrumbIndex = index; - li.breadcrumbEnterCallback(); - // Add history state - }, - _deleteBeyond: function BreadcrumbTrail__deleteBeyond(index) { - while (this._breadcrumbs.length > index + 1) { - this._hide(this._breadcrumbs[index + 1]); - this._breadcrumbs.splice(index + 1, 1); - } - }, - _hide: function BreadcrumbTrail__hide(breadcrumb) { - delete breadcrumb.breadcrumbIsTransient; - breadcrumb.classList.add("deleted"); - setTimeout(function () { - breadcrumb.parentNode.removeChild(breadcrumb); - }, 1000); - }, -}; - -function maxResponsiveness() { - var data = gCurrentlyShownSampleData; - var maxRes = 0.0; - for (var i = 0; i < data.length; ++i) { - if (!data[i] || !data[i].extraInfo || !data[i].extraInfo["responsiveness"]) - continue; - if (maxRes < data[i].extraInfo["responsiveness"]) - maxRes = data[i].extraInfo["responsiveness"]; - } - return maxRes; -} - -function effectiveInterval() { - var data = gCurrentlyShownSampleData; - var interval = 0.0; - var sampleCount = 0; - var timeCount = 0; - var lastTime = null; - for (var i = 0; i < data.length; ++i) { - if (!data[i] || !data[i].extraInfo || !data[i].extraInfo["time"]) { - lastTime = null; - continue; - } - if (lastTime) { - sampleCount++; - timeCount += data[i].extraInfo["time"] - lastTime; - } - lastTime = data[i].extraInfo["time"]; - } - var effectiveInterval = timeCount/sampleCount; - // Biggest diff - var biggestDiff = 0; - lastTime = null; - for (var i = 0; i < data.length; ++i) { - if (!data[i] || !data[i].extraInfo || !data[i].extraInfo["time"]) { - lastTime = null; - continue; - } - if (lastTime) { - if (biggestDiff < Math.abs(effectiveInterval - (data[i].extraInfo["time"] - lastTime))) - biggestDiff = Math.abs(effectiveInterval - (data[i].extraInfo["time"] - lastTime)); - } - lastTime = data[i].extraInfo["time"]; - } - - if (effectiveInterval != effectiveInterval) - return "Time info not collected"; - - return (effectiveInterval).toFixed(2) + "Ā ms Ā±" + biggestDiff.toFixed(2); -} - -function numberOfCurrentlyShownSamples() { - var data = gCurrentlyShownSampleData; - var num = 0; - for (var i = 0; i < data.length; ++i) { - if (data[i]) - num++; - } - return num; -} - -function avgResponsiveness() { - var data = gCurrentlyShownSampleData; - var totalRes = 0.0; - for (var i = 0; i < data.length; ++i) { - if (!data[i] || !data[i].extraInfo || !data[i].extraInfo["responsiveness"]) - continue; - totalRes += data[i].extraInfo["responsiveness"]; - } - return totalRes / numberOfCurrentlyShownSamples(); -} - -function copyProfile() { - window.prompt ("Copy to clipboard: Ctrl+C, Enter", document.getElementById("data").value); -} - -function saveProfileToLocalStorage() { - Parser.getSerializedProfile(true, function (serializedProfile) { - gLocalStorage.storeLocalProfile(serializedProfile, gMeta.profileId, function profileSaved() { - - }); - }); -} -function downloadProfile() { - Parser.getSerializedProfile(true, function (serializedProfile) { - var blob = new Blob([serializedProfile], { "type": "application/octet-stream" }); - location.href = window.URL.createObjectURL(blob); - }); -} - -function promptUploadProfile(selected) { - var overlay = document.createElement("div"); - overlay.style.position = "absolute"; - overlay.style.top = 0; - overlay.style.left = 0; - overlay.style.width = "100%"; - overlay.style.height = "100%"; - overlay.style.backgroundColor = "transparent"; - - var bg = document.createElement("div"); - bg.style.position = "absolute"; - bg.style.top = 0; - bg.style.left = 0; - bg.style.width = "100%"; - bg.style.height = "100%"; - bg.style.opacity = "0.6"; - bg.style.backgroundColor = "#aaaaaa"; - overlay.appendChild(bg); - - var contentDiv = document.createElement("div"); - contentDiv.className = "sideBar"; - contentDiv.style.position = "absolute"; - contentDiv.style.top = "50%"; - contentDiv.style.left = "50%"; - contentDiv.style.width = "40em"; - contentDiv.style.height = "20em"; - contentDiv.style.marginLeft = "-20em"; - contentDiv.style.marginTop = "-10em"; - contentDiv.style.padding = "10px"; - contentDiv.style.border = "2px solid black"; - contentDiv.style.backgroundColor = "rgb(219, 223, 231)"; - overlay.appendChild(contentDiv); - - var noticeHTML = ""; - noticeHTML += "

Upload Profile - Privacy Notice

"; - noticeHTML += "You're about to upload your profile publicly where anyone will be able to access it. "; - noticeHTML += "To better diagnose performance problems profiles include the following information:"; - noticeHTML += "
    "; - noticeHTML += "
  • The URLs and scripts of the tabs that were executing.
  • "; - noticeHTML += "
  • The metadata of all your Add-ons to identify slow Add-ons.
  • "; - noticeHTML += "
  • Firefox build and runtime configuration.
  • "; - noticeHTML += "

"; - noticeHTML += "To view all the information you can download the full profile to a file and open the json structure with a text editor.

"; - contentDiv.innerHTML = noticeHTML; - - var cancelButton = document.createElement("input"); - cancelButton.style.position = "absolute"; - cancelButton.style.bottom = "10px"; - cancelButton.type = "button"; - cancelButton.value = "Cancel"; - cancelButton.onclick = function() { - document.body.removeChild(overlay); - } - contentDiv.appendChild(cancelButton); - - var uploadButton = document.createElement("input"); - uploadButton.style.position = "absolute"; - uploadButton.style.right = "10px"; - uploadButton.style.bottom = "10px"; - uploadButton.type = "button"; - uploadButton.value = "Upload"; - uploadButton.onclick = function() { - document.body.removeChild(overlay); - uploadProfile(selected); - } - contentDiv.appendChild(uploadButton); - - document.body.appendChild(overlay); -} - -function uploadProfile(selected) { - Parser.getSerializedProfile(!selected, function (dataToUpload) { - var oXHR = new XMLHttpRequest(); - oXHR.onload = function (oEvent) { - if (oXHR.status == 200) { - gReportID = oXHR.responseText; - updateDocumentURL(); - document.getElementById("upload_status").innerHTML = "Success! Use this link"; - document.getElementById("linkElem").href = document.URL; - } else { - document.getElementById("upload_status").innerHTML = "Error " + oXHR.status + " occurred uploading your file."; - } - }; - oXHR.onerror = function (oEvent) { - document.getElementById("upload_status").innerHTML = "Error " + oXHR.status + " occurred uploading your file."; - } - oXHR.upload.onprogress = function(oEvent) { - if (oEvent.lengthComputable) { - var progress = Math.round((oEvent.loaded / oEvent.total)*100); - if (progress == 100) { - document.getElementById("upload_status").innerHTML = "Uploading: Waiting for server side compression"; - } else { - document.getElementById("upload_status").innerHTML = "Uploading: " + Math.round((oEvent.loaded / oEvent.total)*100) + "%"; - } - } - }; - - var dataSize; - if (dataToUpload.length > 1024*1024) { - dataSize = (dataToUpload.length/1024/1024).toFixed(1) + " MB(s)"; - } else { - dataSize = (dataToUpload.length/1024).toFixed(1) + " KB(s)"; - } - - var formData = new FormData(); - formData.append("file", dataToUpload); - document.getElementById("upload_status").innerHTML = "Uploading Profile (" + dataSize + ")"; - oXHR.open("POST", "http://profile-store.appspot.com/store", true); - oXHR.send(formData); - }); -} - -function populate_skip_symbol() { - var skipSymbolCtrl = document.getElementById('skipsymbol') - //skipSymbolCtrl.options = gSkipSymbols; - for (var i = 0; i < gSkipSymbols.length; i++) { - var elOptNew = document.createElement('option'); - elOptNew.text = gSkipSymbols[i]; - elOptNew.value = gSkipSymbols[i]; - elSel.add(elOptNew); - } - -} - -function delete_skip_symbol() { - var skipSymbol = document.getElementById('skipsymbol').value -} - -function add_skip_symbol() { - -} - -var gFilterChangeCallback = null; -var gFilterChangeDelay = 1200; -function filterOnChange() { - if (gFilterChangeCallback != null) { - clearTimeout(gFilterChangeCallback); - gFilterChangeCallback = null; - } - - gFilterChangeCallback = setTimeout(filterUpdate, gFilterChangeDelay); -} -function filterUpdate() { - gFilterChangeCallback = null; - - filtersChanged(); - - var filterNameInput = document.getElementById("filterName"); - if (filterNameInput != null) { - changeFocus(filterNameInput); - } -} - -// Maps document id to a tooltip description -var tooltip = { - "mergeFunctions" : "Ignore line information and merge samples based on function names.", - "showJank" : "Show only samples with >50ms responsiveness.", - "showJS" : "Show only samples which involve running chrome or content Javascript code.", - "mergeUnbranched" : "Collapse unbranched call paths in the call tree into a single node.", - "filterName" : "Show only samples with a frame containing the filter as a substring.", - "invertCallstack" : "Invert the callstack (Heavy view) to find the most expensive leaf functions.", - "upload" : "Upload the full profile to public cloud storage to share with others.", - "upload_select" : "Upload only the selected view.", - "download" : "Initiate a download of the full profile.", -} - -function addTooltips() { - for (var elemId in tooltip) { - var elem = document.getElementById(elemId); - if (!elem) - continue; - if (elem.parentNode.nodeName.toLowerCase() == "label") - elem = elem.parentNode; - elem.title = tooltip[elemId]; - } -} - -function InfoBar() { - this._container = document.createElement("div"); - this._container.id = "infoBar"; - this._container.className = "sideBar"; -} - -InfoBar.prototype = { - getContainer: function InfoBar_getContainer() { - return this._container; - }, - display: function InfoBar_display() { - function getMetaFeatureString() { - features = "
Stackwalk:
" + (gMeta.stackwalk ? "True" : "False") + "
"; - features += "
Jank:
" + (gMeta.stackwalk ? "True" : "False") + "
"; - return features; - } - function getPlatformInfo() { - return gMeta.oscpu + " (" + gMeta.toolkit + ")"; - } - var infobar = this._container; - var infoText = ""; - - if (gMeta) { - infoText += "

Profile Info

\n
\n"; - infoText += "
Product:
" + gMeta.product + "
"; - infoText += "
Platform:
" + getPlatformInfo() + "
"; - infoText += getMetaFeatureString(); - infoText += "
Interval:
" + gMeta.interval + "Ā ms
"; - } - infoText += "

Selection Info

\n
\n"; - infoText += "
Avg. Responsiveness:
" + avgResponsiveness().toFixed(2) + "Ā ms
\n"; - infoText += "
Max Responsiveness:
" + maxResponsiveness().toFixed(2) + "Ā ms
\n"; - infoText += "
Real Interval:
" + effectiveInterval() + "
"; - infoText += "
\n"; - infoText += "

Pre Filtering

\n"; - // Disable for now since it's buggy and not useful - //infoText += "
\n"; - - var filterNameInputOld = document.getElementById("filterName"); - infoText += "Filter:\n"; - infoText += "\n"; - - infoText += "

Post Filtering

\n"; - infoText += "\n"; - infoText += "

View Options

\n"; - infoText += "
\n"; - infoText += "
\n"; - infoText += "
\n"; - - infoText += "

Share

\n"; - infoText += "
No upload in progress

\n"; - infoText += "\n"; - infoText += "
\n"; - infoText += "\n"; - - infoText += "

Compare

\n"; - infoText += "\n"; - - //infoText += "
\n"; - //infoText += "Skip functions:
\n"; - //infoText += "
" - //infoText += "
\n"; - //infoText += "
\n"; - - infobar.innerHTML = infoText; - addTooltips(); - - var filterNameInputNew = document.getElementById("filterName"); - if (filterNameInputOld != null && filterNameInputNew != null) { - filterNameInputNew.parentNode.replaceChild(filterNameInputOld, filterNameInputNew); - //filterNameInputNew.value = filterNameInputOld.value; - } else if (gQueryParamFilterName != null) { - filterNameInputNew.value = gQueryParamFilterName; - gQueryParamFilterName = null; - } - document.getElementById('compare').onclick = function() { - openProfileCompare(); - } - document.getElementById('upload').onclick = function() { - promptUploadProfile(false); - }; - document.getElementById('download').onclick = downloadProfile; - document.getElementById('upload_select').onclick = function() { - promptUploadProfile(true); - }; - //document.getElementById('delete_skipsymbol').onclick = delete_skip_symbol; - //document.getElementById('add_skipsymbol').onclick = add_skip_symbol; - - //populate_skip_symbol(); - } -} - -var gNumSamples = 0; -var gMeta = null; -var gSymbols = {}; -var gFunctions = {}; -var gResources = {}; -var gHighlightedCallstack = []; -var gFrameView = null; -var gTreeManager = null; -var gSampleBar = null; -var gBreadcrumbTrail = null; -var gHistogramView = null; -var gDiagnosticBar = null; -var gVideoPane = null; -var gPluginView = null; -var gFileList = null; -var gInfoBar = null; -var gMainArea = null; -var gCurrentlyShownSampleData = null; -var gSkipSymbols = ["test2", "test1"]; -var gAppendVideoCapture = null; -var gQueryParamFilterName = null; -var gRestoreSelection = null; -var gReportID = null; - -function getTextData() { - var data = []; - var samples = gCurrentlyShownSampleData; - for (var i = 0; i < samples.length; i++) { - data.push(samples[i].lines.join("\n")); - } - return data.join("\n"); -} - -function loadProfileFile(fileList) { - if (fileList.length == 0) - return; - var file = fileList[0]; - var reporter = enterProgressUI(); - var subreporters = reporter.addSubreporters({ - fileLoading: 1000, - parsing: 1000 - }); - - var reader = new FileReader(); - reader.onloadend = function () { - subreporters.fileLoading.finish(); - loadRawProfile(subreporters.parsing, reader.result); - }; - reader.onprogress = function (e) { - subreporters.fileLoading.setProgress(e.loaded / e.total); - }; - reader.readAsText(file, "utf-8"); - subreporters.fileLoading.begin("Reading local file..."); -} - -function loadLocalStorageProfile(profileKey) { - var reporter = enterProgressUI(); - var subreporters = reporter.addSubreporters({ - fileLoading: 1000, - parsing: 1000 - }); - - gLocalStorage.getProfile(profileKey, function(profile) { - subreporters.fileLoading.finish(); - loadRawProfile(subreporters.parsing, profile, profileKey); - }); - subreporters.fileLoading.begin("Reading local storage..."); -} - -function appendVideoCapture(videoCapture) { - if (videoCapture.indexOf("://") == -1) { - videoCapture = EIDETICKER_BASE_URL + videoCapture; - } - gAppendVideoCapture = videoCapture; -} - -function loadZippedProfileURL(url) { - var reporter = enterProgressUI(); - var subreporters = reporter.addSubreporters({ - fileLoading: 1000, - parsing: 1000 - }); - - // Crude way to detect if we're using a relative URL or not :( - if (url.indexOf("://") == -1) { - url = EIDETICKER_BASE_URL + url; - } - reporter.begin("Fetching " + url); - PROFILERTRACE("Fetch url: " + url); - - function onerror(e) { - PROFILERERROR("zip.js error"); - PROFILERERROR(JSON.stringify(e)); - } - - zip.workerScriptsPath = "js/zip.js/"; - zip.createReader(new zip.HttpReader(url), function(zipReader) { - subreporters.fileLoading.setProgress(0.4); - zipReader.getEntries(function(entries) { - for (var i = 0; i < entries.length; i++) { - var entry = entries[i]; - PROFILERTRACE("Zip file: " + entry.filename); - if (entry.filename === "symbolicated_profile.txt") { - reporter.begin("Decompressing " + url); - subreporters.fileLoading.setProgress(0.8); - entry.getData(new zip.TextWriter(), function(profileText) { - subreporters.fileLoading.finish(); - loadRawProfile(subreporters.parsing, profileText); - }); - return; - } - onerror("symbolicated_profile.txt not found in zip file."); - } - }); - }, onerror); -} - -function loadProfileURL(url) { - var reporter = enterProgressUI(); - var subreporters = reporter.addSubreporters({ - fileLoading: 1000, - parsing: 1000 - }); - - var xhr = new XMLHttpRequest(); - xhr.open("GET", url, true); - xhr.responseType = "text"; - xhr.onreadystatechange = function (e) { - if (xhr.readyState === 4 && (xhr.status === 200 || xhr.status === 0)) { - subreporters.fileLoading.finish(); - PROFILERLOG("Got profile from '" + url + "'."); - if (xhr.responseText == null || xhr.responseText === "") { - subreporters.fileLoading.begin("Profile '" + url + "' is empty. Did you set the CORS headers?"); - return; - } - loadRawProfile(subreporters.parsing, xhr.responseText, url); - } - }; - xhr.onerror = function (e) { - subreporters.fileLoading.begin("Error fetching profile :(. URL: '" + url + "'. Did you set the CORS headers?"); - } - xhr.onprogress = function (e) { - if (e.lengthComputable && (e.loaded <= e.total)) { - subreporters.fileLoading.setProgress(e.loaded / e.total); - } else { - subreporters.fileLoading.setProgress(NaN); - } - }; - xhr.send(null); - subreporters.fileLoading.begin("Loading remote file..."); -} - -function loadProfile(rawProfile) { - if (!rawProfile) - return; - var reporter = enterProgressUI(); - loadRawProfile(reporter, rawProfile); -} - -function loadRawProfile(reporter, rawProfile, profileId) { - PROFILERLOG("Parse raw profile: ~" + rawProfile.length + " bytes"); - reporter.begin("Parsing..."); - if (rawProfile == null || rawProfile.length === 0) { - reporter.begin("Profile is null or empty"); - return; - } - var startTime = Date.now(); - var parseRequest = Parser.parse(rawProfile, { - appendVideoCapture : gAppendVideoCapture, - profileId: profileId, - }); - parseRequest.addEventListener("progress", function (progress, action) { - if (action) - reporter.setAction(action); - reporter.setProgress(progress); - }); - parseRequest.addEventListener("finished", function (result) { - reporter.finish(); - gMeta = result.meta; - gNumSamples = result.numSamples; - gSymbols = result.symbols; - gFunctions = result.functions; - gResources = result.resources; - enterFinishedProfileUI(); - gFileList.profileParsingFinished(); - }); -} - -var gImportFromAddonSubreporters = null; - -window.addEventListener("message", function messageFromAddon(msg) { - // This is triggered by the profiler add-on. - var o = JSON.parse(msg.data); - switch (o.task) { - case "importFromAddonStart": - var totalReporter = enterProgressUI(); - gImportFromAddonSubreporters = totalReporter.addSubreporters({ - import: 10000, - parsing: 1000 - }); - gImportFromAddonSubreporters.import.begin("Symbolicating..."); - break; - case "importFromAddonProgress": - gImportFromAddonSubreporters.import.setProgress(o.progress); - if (o.action != null) { - gImportFromAddonSubreporters.import.setAction(o.action); - } - break; - case "importFromAddonFinish": - importFromAddonFinish(o.rawProfile); - break; - } -}); - -function importFromAddonFinish(rawProfile) { - gImportFromAddonSubreporters.import.finish(); - loadRawProfile(gImportFromAddonSubreporters.parsing, rawProfile); -} - -var gInvertCallstack = false; -function toggleInvertCallStack() { - gTreeManager.saveReverseSelectionSnapshot(gJavascriptOnly); - gInvertCallstack = !gInvertCallstack; - var startTime = Date.now(); - viewOptionsChanged(); -} - -var gMergeUnbranched = false; -function toggleMergeUnbranched() { - gMergeUnbranched = !gMergeUnbranched; - viewOptionsChanged(); -} - -var gMergeFunctions = true; -function toggleMergeFunctions() { - gMergeFunctions = !gMergeFunctions; - filtersChanged(); -} - -var gJankOnly = false; -var gJankThreshold = 50 /* ms */; -function toggleJank(/* optional */ threshold) { - // Currently we have no way to change the threshold in the UI - // once we add this we will need to change the tooltip. - gJankOnly = !gJankOnly; - if (threshold != null ) { - gJankThreshold = threshold; - } - filtersChanged(); -} - -var gJavascriptOnly = false; -function toggleJavascriptOnly() { - if (gJavascriptOnly) { - // When going from JS only to non js there's going to be new C++ - // frames in the selection so we need to restore the selection - // while allowing non contigous symbols to be in the stack (the c++ ones) - gTreeManager.setAllowNonContiguous(); - } - gJavascriptOnly = !gJavascriptOnly; - gTreeManager.saveSelectionSnapshot(gJavascriptOnly); - filtersChanged(); -} - -var gSampleFilters = []; -function focusOnSymbol(focusSymbol, name) { - var newFilterChain = gSampleFilters.concat([{type: "FocusedFrameSampleFilter", name: name, focusedSymbol: focusSymbol}]); - gBreadcrumbTrail.addAndEnter({ - title: name, - enterCallback: function () { - gSampleFilters = newFilterChain; - filtersChanged(); - } - }); -} - -function focusOnCallstack(focusedCallstack, name, overwriteCallstack) { - var invertCallstack = gInvertCallstack; - - if (overwriteCallstack != null) { - invertCallstack = overwriteCallstack; - } - var filter = { - type: !invertCallstack ? "FocusedCallstackPostfixSampleFilter" : "FocusedCallstackPrefixSampleFilter", - name: name, - focusedCallstack: focusedCallstack, - appliesToJS: gJavascriptOnly - }; - var newFilterChain = gSampleFilters.concat([filter]); - gBreadcrumbTrail.addAndEnter({ - title: name, - enterCallback: function () { - gSampleFilters = newFilterChain; - filtersChanged(); - } - }) -} - -function focusOnPluginView(pluginName, param) { - var filter = { - type: "PluginView", - pluginName: pluginName, - param: param, - }; - var newFilterChain = gSampleFilters.concat([filter]); - gBreadcrumbTrail.addAndEnter({ - title: "Plugin View: " + pluginName, - enterCallback: function () { - gSampleFilters = newFilterChain; - filtersChanged(); - } - }) -} - -function viewJSSource(sample) { - var sourceView = new SourceView(); - sourceView.setScriptLocation(sample.scriptLocation); - sourceView.setSource(gMeta.js.source[sample.scriptLocation.scriptURI]); - gMainArea.appendChild(sourceView.getContainer()); - -} - -function setHighlightedCallstack(samples, heaviestSample) { - PROFILERTRACE("highlight: " + samples); - gHighlightedCallstack = samples; - gHistogramView.highlightedCallstackChanged(gHighlightedCallstack); - if (!gInvertCallstack) { - // Always show heavy - heaviestSample = heaviestSample.clone().reverse(); - } - - if (gSampleBar) { - gSampleBar.setSample(heaviestSample); - } -} - -function enterMainUI() { - var uiContainer = document.createElement("div"); - uiContainer.id = "ui"; - - gFileList = new FileList(); - uiContainer.appendChild(gFileList.getContainer()); - - //gFileList.addFile(); - gFileList.loadProfileListFromLocalStorage(); - - gInfoBar = new InfoBar(); - uiContainer.appendChild(gInfoBar.getContainer()); - - gMainArea = document.createElement("div"); - gMainArea.id = "mainarea"; - uiContainer.appendChild(gMainArea); - document.body.appendChild(uiContainer); - - var profileEntryPane = document.createElement("div"); - profileEntryPane.className = "profileEntryPane"; - profileEntryPane.innerHTML = '' + - '

Upload your profile here:

' + - '' + - '

Or, alternatively, enter your profile data here:

' + - '' + - '

' + - ''; - - gMainArea.appendChild(profileEntryPane); -} - -function enterProgressUI() { - var profileProgressPane = document.createElement("div"); - profileProgressPane.className = "profileProgressPane"; - - var progressLabel = document.createElement("a"); - profileProgressPane.appendChild(progressLabel); - - var progressBar = document.createElement("progress"); - profileProgressPane.appendChild(progressBar); - - var totalProgressReporter = new ProgressReporter(); - totalProgressReporter.addListener(function (r) { - var progress = r.getProgress(); - progressLabel.innerHTML = r.getAction(); - if (isNaN(progress)) - progressBar.removeAttribute("value"); - else - progressBar.value = progress; - }); - - gMainArea.appendChild(profileProgressPane); - - Parser.updateLogSetting(); - - return totalProgressReporter; -} - -function enterFinishedProfileUI() { - saveProfileToLocalStorage(); - - var finishedProfilePaneBackgroundCover = document.createElement("div"); - finishedProfilePaneBackgroundCover.className = "finishedProfilePaneBackgroundCover"; - - var finishedProfilePane = document.createElement("table"); - var rowIndex = 0; - var currRow; - finishedProfilePane.style.width = "100%"; - finishedProfilePane.style.height = "100%"; - finishedProfilePane.border = "0"; - finishedProfilePane.cellPadding = "0"; - finishedProfilePane.cellSpacing = "0"; - finishedProfilePane.borderCollapse = "collapse"; - finishedProfilePane.className = "finishedProfilePane"; - setTimeout(function() { - // Work around a webkit bug. For some reason the table doesn't show up - // until some actions happen such as focusing this box - var filterNameInput = document.getElementById("filterName"); - if (filterNameInput != null) { - changeFocus(filterNameInput); - } - }, 100); - - gBreadcrumbTrail = new BreadcrumbTrail(); - currRow = finishedProfilePane.insertRow(rowIndex++); - currRow.insertCell(0).appendChild(gBreadcrumbTrail.getContainer()); - - gHistogramView = new HistogramView(); - currRow = finishedProfilePane.insertRow(rowIndex++); - currRow.insertCell(0).appendChild(gHistogramView.getContainer()); - - if (false && gLocation.indexOf("file:") == 0) { - // Local testing for frameView - gFrameView = new FrameView(); - currRow = finishedProfilePane.insertRow(rowIndex++); - currRow.insertCell(0).appendChild(gFrameView.getContainer()); - } - - gDiagnosticBar = new DiagnosticBar(); - gDiagnosticBar.setDetailsListener(function(details) { - if (details.indexOf("bug ") == 0) { - window.open('https://bugzilla.mozilla.org/show_bug.cgi?id=' + details.substring(4)); - } else { - var sourceView = new SourceView(); - sourceView.setText("Diagnostic", js_beautify(details)); - gMainArea.appendChild(sourceView.getContainer()); - } - }); - currRow = finishedProfilePane.insertRow(rowIndex++); - currRow.insertCell(0).appendChild(gDiagnosticBar.getContainer()); - - // For testing: - //gMeta.videoCapture = { - // src: "http://videos-cdn.mozilla.net/brand/Mozilla_Firefox_Manifesto_v0.2_640.webm", - //}; - - if (gMeta && gMeta.videoCapture) { - gVideoPane = new VideoPane(gMeta.videoCapture); - gVideoPane.onTimeChange(videoPaneTimeChange); - currRow = finishedProfilePane.insertRow(rowIndex++); - currRow.insertCell(0).appendChild(gVideoPane.getContainer()); - } - - var treeContainerDiv = document.createElement("div"); - treeContainerDiv.className = "treeContainer"; - treeContainerDiv.style.width = "100%"; - treeContainerDiv.style.height = "100%"; - - gTreeManager = new ProfileTreeManager(); - currRow = finishedProfilePane.insertRow(rowIndex++); - currRow.style.height = "100%"; - var cell = currRow.insertCell(0); - cell.appendChild(treeContainerDiv); - treeContainerDiv.appendChild(gTreeManager.getContainer()); - - gSampleBar = new SampleBar(); - treeContainerDiv.appendChild(gSampleBar.getContainer()); - - // sampleBar - - gPluginView = new PluginView(); - //currRow = finishedProfilePane.insertRow(4); - treeContainerDiv.appendChild(gPluginView.getContainer()); - - gMainArea.appendChild(finishedProfilePaneBackgroundCover); - gMainArea.appendChild(finishedProfilePane); - - var currentBreadcrumb = gSampleFilters; - gBreadcrumbTrail.add({ - title: gStrings["Complete Profile"], - enterCallback: function () { - gSampleFilters = []; - filtersChanged(); - } - }); - if (currentBreadcrumb == null || currentBreadcrumb.length == 0) { - gTreeManager.restoreSerializedSelectionSnapshot(gRestoreSelection); - viewOptionsChanged(); - } - for (var i = 0; i < currentBreadcrumb.length; i++) { - var filter = currentBreadcrumb[i]; - var forceSelection = null; - if (gRestoreSelection != null && i == currentBreadcrumb.length - 1) { - forceSelection = gRestoreSelection; - } - switch (filter.type) { - case "FocusedFrameSampleFilter": - focusOnSymbol(filter.name, filter.symbolName); - gBreadcrumbTrail.enterLastItem(forceSelection); - case "FocusedCallstackPrefixSampleFilter": - focusOnCallstack(filter.focusedCallstack, filter.name, false); - gBreadcrumbTrail.enterLastItem(forceSelection); - case "FocusedCallstackPostfixSampleFilter": - focusOnCallstack(filter.focusedCallstack, filter.name, true); - gBreadcrumbTrail.enterLastItem(forceSelection); - case "RangeSampleFilter": - gHistogramView.selectRange(filter.start, filter.end); - gBreadcrumbTrail.enterLastItem(forceSelection); - } - } -} - -// Make all focus change events go through this function. -// This function will mediate the focus changes in case -// that we're in a compare view. In a compare view an inactive -// instance of cleopatra should not steal focus from the active -// cleopatra instance. -function changeFocus(elem) { - if (window.comparator_changeFocus) { - window.comparator_changeFocus(elem); - } else { - PROFILERLOG("FOCUS\n\n\n\n\n\n\n\n\n"); - elem.focus(); - } -} - -function comparator_receiveSelection(snapshot, frameData) { - gTreeManager.restoreSerializedSelectionSnapshot(snapshot); - if (frameData) - gTreeManager.highlightFrame(frameData); - viewOptionsChanged(); -} - -function filtersChanged() { - if (window.comparator_setSelection) { - // window.comparator_setSelection(gTreeManager.serializeCurrentSelectionSnapshot(), null); - } - updateDocumentURL(); - var data = { symbols: {}, functions: {}, samples: [] }; - - gHistogramView.dataIsOutdated(); - var filterNameInput = document.getElementById("filterName"); - var updateRequest = Parser.updateFilters({ - mergeFunctions: gMergeFunctions, - nameFilter: (filterNameInput && filterNameInput.value) || gQueryParamFilterName || "", - sampleFilters: gSampleFilters, - jankOnly: gJankOnly, - javascriptOnly: gJavascriptOnly - }); - var start = Date.now(); - updateRequest.addEventListener("finished", function (filteredSamples) { - gCurrentlyShownSampleData = filteredSamples; - gInfoBar.display(); - - if (gSampleFilters.length > 0 && gSampleFilters[gSampleFilters.length-1].type === "PluginView") { - start = Date.now(); - gPluginView.display(gSampleFilters[gSampleFilters.length-1].pluginName, gSampleFilters[gSampleFilters.length-1].param, - gCurrentlyShownSampleData, gHighlightedCallstack); - } else { - gPluginView.hide(); - } - }); - - var histogramRequest = Parser.calculateHistogramData(); - histogramRequest.addEventListener("finished", function (data) { - start = Date.now(); - gHistogramView.display(data.histogramData, data.frameStart, data.widthSum, gHighlightedCallstack); - if (gFrameView) - gFrameView.display(data.histogramData, data.frameStart, data.widthSum, gHighlightedCallstack); - }); - - if (gDiagnosticBar) { - var diagnosticsRequest = Parser.calculateDiagnosticItems(gMeta); - diagnosticsRequest.addEventListener("finished", function (diagnosticItems) { - start = Date.now(); - gDiagnosticBar.display(diagnosticItems); - }); - } - - viewOptionsChanged(); -} - -function viewOptionsChanged() { - gTreeManager.dataIsOutdated(); - var filterNameInput = document.getElementById("filterName"); - var updateViewOptionsRequest = Parser.updateViewOptions({ - invertCallstack: gInvertCallstack, - mergeUnbranched: gMergeUnbranched - }); - updateViewOptionsRequest.addEventListener("finished", function (calltree) { - var start = Date.now(); - gTreeManager.display(calltree, gSymbols, gFunctions, gResources, gMergeFunctions, filterNameInput && filterNameInput.value); - }); -} - -function loadQueryData(queryDataOriginal) { - var isFiltersChanged = false; - var queryData = {}; - for (var i in queryDataOriginal) { - queryData[i] = unQueryEscape(queryDataOriginal[i]); - } - if (queryData.search) { - gQueryParamFilterName = queryData.search; - isFiltersChanged = true; - } - if (queryData.jankOnly) { - gJankOnly = queryData.jankOnly; - isFiltersChanged = true; - } - if (queryData.javascriptOnly) { - gJavascriptOnly = queryData.javascriptOnly; - isFiltersChanged = true; - } - if (queryData.mergeUnbranched) { - gMergeUnbranched = queryData.mergeUnbranched; - isFiltersChanged = true; - } - if (queryData.invertCallback) { - gInvertCallstack = queryData.invertCallback; - isFiltersChanged = true; - } - if (queryData.report) { - gReportID = queryData.report; - } - if (queryData.filter) { - var filterChain = JSON.parse(queryData.filter); - gSampleFilters = filterChain; - } - if (queryData.selection) { - var selection = queryData.selection; - gRestoreSelection = selection; - } - - if (isFiltersChanged) { - //filtersChanged(); - } -} - -function unQueryEscape(str) { - return decodeURIComponent(str); -} - -function queryEscape(str) { - return encodeURIComponent(encodeURIComponent(str)); -} - -function updateDocumentURL() { - location.hash = getDocumentHashString(); - return document.location; -} - -function getDocumentHashString() { - var query = ""; - if (gReportID) { - if (query != "") - query += "&"; - query += "report=" + queryEscape(gReportID); - } - if (document.getElementById("filterName") != null && - document.getElementById("filterName").value != null && - document.getElementById("filterName").value != "") { - if (query != "") - query += "&"; - query += "search=" + queryEscape(document.getElementById("filterName").value); - } - // For now don't restore the view rest - return query; - if (gJankOnly) { - if (query != "") - query += "&"; - query += "jankOnly=" + queryEscape(gJankOnly); - } - if (gJavascriptOnly) { - if (query != "") - query += "&"; - query += "javascriptOnly=" + queryEscape(gJavascriptOnly); - } - if (gMergeUnbranched) { - if (query != "") - query += "&"; - query += "mergeUnbranched=" + queryEscape(gMergeUnbranched); - } - if (gInvertCallstack) { - if (query != "") - query += "&"; - query += "invertCallback=" + queryEscape(gInvertCallstack); - } - if (gSampleFilters && gSampleFilters.length != 0) { - if (query != "") - query += "&"; - query += "filter=" + queryEscape(JSON.stringify(gSampleFilters)); - } - if (gTreeManager.hasNonTrivialSelection()) { - if (query != "") - query += "&"; - query += "selection=" + queryEscape(gTreeManager.serializeCurrentSelectionSnapshot()); - } - if (!gReportID) { - query = "uploadProfileFirst!"; - } - - return query; -} - diff --git a/browser/devtools/profiler/commands.js b/browser/devtools/profiler/commands.js deleted file mode 100644 index 81a94e90abb4..000000000000 --- a/browser/devtools/profiler/commands.js +++ /dev/null @@ -1,137 +0,0 @@ -/* 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 gcli = require('gcli/index'); - -loader.lazyGetter(this, "gDevTools", - () => Cu.import("resource:///modules/devtools/gDevTools.jsm", {}).gDevTools); - - -module.exports.items = [ -{ - name: "profiler", - description: gcli.lookup("profilerDesc"), - manual: gcli.lookup("profilerManual") -}, -{ - name: "profiler open", - description: gcli.lookup("profilerOpenDesc"), - exec: function (args, context) { - return gDevTools.showToolbox(context.environment.target, "jsprofiler") - .then(function () null); - } -}, -{ - name: "profiler close", - description: gcli.lookup("profilerCloseDesc"), - exec: function (args, context) { - let toolbox = gDevTools.getToolbox(context.environment.target); - let panel = (toolbox == null) ? null : toolbox.getPanel(id); - if (panel == null) - return; - - return gDevTools.closeToolbox(context.environment.target) - .then(function () null); - } -}, -{ - name: "profiler start", - description: gcli.lookup("profilerStartDesc"), - returnType: "string", - exec: function (args, context) { - let target = context.environment.target - return gDevTools.showToolbox(target, "jsprofiler").then(toolbox => { - let panel = toolbox.getCurrentPanel(); - - if (panel.recordingProfile) - throw gcli.lookup("profilerAlreadyStarted2"); - - panel.toggleRecording(); - return gcli.lookup("profilerStarted2"); - }); - } -}, -{ - name: "profiler stop", - description: gcli.lookup("profilerStopDesc"), - returnType: "string", - exec: function (args, context) { - let target = context.environment.target - return gDevTools.showToolbox(target, "jsprofiler").then(toolbox => { - let panel = toolbox.getCurrentPanel(); - - if (!panel.recordingProfile) - throw gcli.lookup("profilerNotStarted3"); - - panel.toggleRecording(); - return gcli.lookup("profilerStopped"); - }); - } -}, -{ - name: "profiler list", - description: gcli.lookup("profilerListDesc"), - returnType: "profileList", - exec: function (args, context) { - let toolbox = gDevTools.getToolbox(context.environment.target); - let panel = (toolbox == null) ? null : toolbox.getPanel("jsprofiler"); - - if (panel == null) { - throw gcli.lookup("profilerNotReady"); - } - - let profileList = []; - for ([ uid, profile ] of panel.profiles) { - profileList.push({ name: profile.name, started: profile.isStarted }); - } - return profileList; - } -}, -{ - item: "converter", - from: "profileList", - to: "view", - exec: function(profileList, context) { - return { - html: "
" + - "
    " + - "
  1. ${profile.name}
  2. " + - " ${profile.name} ${profile.started ? '*' : ''}" + - " " + - "
" + - "
", - data: { profiles: profileList.profiles }, - options: { allowEval: true } - }; - }, -}, -{ - name: "profiler show", - description: gcli.lookup("profilerShowDesc"), - params: [ - { - name: "name", - type: "string", - manual: gcli.lookup("profilerShowManual") - } - ], - - exec: function (args, context) { - let toolbox = gDevTools.getToolbox(context.environment.target); - let panel = (toolbox == null) ? null : toolbox.getPanel(id); - - if (!panel) { - throw gcli.lookup("profilerNotReady"); - } - - let profile = panel.getProfileByName(args.name); - if (!profile) { - throw gcli.lookup("profilerNotFound"); - } - - panel.sidebar.selectedItem = panel.sidebar.getItemByProfile(profile); - } -}]; diff --git a/browser/devtools/profiler/consts.js b/browser/devtools/profiler/consts.js deleted file mode 100644 index dd76df6a1644..000000000000 --- a/browser/devtools/profiler/consts.js +++ /dev/null @@ -1,16 +0,0 @@ -/* 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"; - -module.exports = { - L10N_BUNDLE: "chrome://browser/locale/devtools/profiler.properties", - - PROFILER_ENABLED: "devtools.profiler.enabled", - SHOW_PLATFORM_DATA: "devtools.profiler.ui.show-platform-data", - - PROFILE_IDLE: 0, - PROFILE_RUNNING: 1, - PROFILE_COMPLETED: 2 -}; \ No newline at end of file diff --git a/browser/devtools/profiler/controller.js b/browser/devtools/profiler/controller.js deleted file mode 100644 index 4588fb518c66..000000000000 --- a/browser/devtools/profiler/controller.js +++ /dev/null @@ -1,411 +0,0 @@ -/* 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"; - -var isJSM = typeof require !== "function"; - -// This code is needed because, for whatever reason, mochitest can't -// find any requirejs module so we have to load it old school way. :( - -if (isJSM) { - var Cu = this["Components"].utils; - let XPCOMUtils = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {}).XPCOMUtils; - this["loader"] = { lazyGetter: XPCOMUtils.defineLazyGetter.bind(XPCOMUtils) }; - this["require"] = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}).devtools.require; -} else { - var { Cu } = require("chrome"); -} - -const { L10N_BUNDLE } = require("devtools/profiler/consts"); - -var EventEmitter = require("devtools/toolkit/event-emitter"); - -Cu.import("resource://gre/modules/devtools/dbg-client.jsm"); -Cu.import("resource://gre/modules/devtools/Console.jsm"); -Cu.import("resource://gre/modules/AddonManager.jsm"); -Cu.import("resource:///modules/devtools/ViewHelpers.jsm"); - -loader.lazyGetter(this, "L10N", () => new ViewHelpers.L10N(L10N_BUNDLE)); - -loader.lazyGetter(this, "gDevTools", - () => Cu.import("resource:///modules/devtools/gDevTools.jsm", {}).gDevTools); - -loader.lazyGetter(this, "DebuggerServer", - () => Cu.import("resource:///modules/devtools/dbg-server.jsm", {}).DebuggerServer); - -/** - * Data structure that contains information that has - * to be shared between separate ProfilerController - * instances. - */ -const sharedData = { - data: new WeakMap(), - controllers: new WeakMap(), -}; - -/** - * Makes a structure representing an individual profile. - */ -function makeProfile(name, def={}) { - if (def.timeStarted == null) - def.timeStarted = null; - - if (def.timeEnded == null) - def.timeEnded = null; - - return { - name: name, - timeStarted: def.timeStarted, - timeEnded: def.timeEnded, - fromConsole: def.fromConsole || false - }; -} - -// Three functions below all operate with sharedData -// structure defined above. They should be self-explanatory. - -function addTarget(target) { - sharedData.data.set(target, new Map()); -} - -function getProfiles(target) { - return sharedData.data.get(target); -} - -/** - * Object to control the JavaScript Profiler over the remote - * debugging protocol. - * - * @param Target target - * A target object as defined in Target.jsm - */ -function ProfilerController(target) { - if (sharedData.controllers.has(target)) { - return sharedData.controllers.get(target); - } - - this.target = target; - this.client = target.client; - this.isConnected = false; - this.consoleProfiles = []; - this.reservedNames = {}; - - addTarget(target); - - // Chrome debugging targets have already obtained a reference - // to the profiler actor. - if (target.chrome) { - this.isConnected = true; - this.actor = target.form.profilerActor; - } - - sharedData.controllers.set(target, this); - EventEmitter.decorate(this); -}; - -ProfilerController.prototype = { - target: null, - client: null, - isConnected: null, - consoleProfiles: null, - reservedNames: null, - - /** - * Return a map of profile results for the current target. - * - * @return Map - */ - get profiles() { - return getProfiles(this.target); - }, - - /** - * Checks whether the profile is currently recording. - * - * @param object profile - * An object made by calling makeProfile function. - * @return boolean - */ - isProfileRecording: function PC_isProfileRecording(profile) { - return profile.timeStarted !== null && profile.timeEnded === null; - }, - - getProfileName: function PC_getProfileName() { - let num = 1; - let name = L10N.getFormatStr("profiler.profileName", [num]); - - while (this.reservedNames[name]) { - num += 1; - name = L10N.getFormatStr("profiler.profileName", [num]); - } - - this.reservedNames[name] = true; - return name; - }, - - /** - * A listener that fires whenever console.profile or console.profileEnd - * is called. - * - * @param string type - * Type of a call. Either 'profile' or 'profileEnd'. - * @param object data - * Event data. - */ - onConsoleEvent: function (type, data) { - let name = data.extra.name; - - let profileStart = () => { - if (name && this.profiles.has(name)) - return; - - // Add profile structure to shared data. - let profile = makeProfile(name || this.getProfileName(), { - timeStarted: data.extra.currentTime, - fromConsole: true - }); - - this.profiles.set(profile.name, profile); - this.consoleProfiles.push(profile.name); - this.emit("profileStart", profile); - }; - - let profileEnd = () => { - if (!name && !this.consoleProfiles.length) - return; - - if (!name) - name = this.consoleProfiles.pop(); - else - this.consoleProfiles.filter((n) => n !== name); - - if (!this.profiles.has(name)) - return; - - let profile = this.profiles.get(name); - if (!this.isProfileRecording(profile)) - return; - - let profileData = data.extra.profile; - profileData.threads = profileData.threads.map((thread) => { - let samples = thread.samples.filter((sample) => { - return sample.time >= profile.timeStarted; - }); - - return { samples: samples }; - }); - - profile.timeEnded = data.extra.currentTime; - profile.data = profileData; - - this.emit("profileEnd", profile); - }; - - if (type === "profile") - profileStart(); - - if (type === "profileEnd") - profileEnd(); - }, - - /** - * Connects to the client unless we're already connected. - * - * @param function cb - * Function to be called once we're connected. If - * the controller is already connected, this function - * will be called immediately (synchronously). - */ - connect: function (cb=function(){}) { - if (this.isConnected) { - return void cb(); - } - - // Check if we already have a grip to the listTabs response object - // and, if we do, use it to get to the profilerActor. Otherwise, - // call listTabs. The problem is that if we call listTabs twice - // webconsole tests fail (see bug 872826). - - let register = () => { - let data = { events: ["console-api-profiler"] }; - - // Check if Gecko Profiler Addon [1] is installed and, if it is, - // don't register our own console event listeners. Gecko Profiler - // Addon takes care of console.profile and console.profileEnd methods - // and we don't want to break it. - // - // [1] - https://github.com/bgirard/Gecko-Profiler-Addon/ - - AddonManager.getAddonByID("jid0-edalmuivkozlouyij0lpdx548bc@jetpack", (addon) => { - if (addon && !addon.userDisabled && !addon.softDisabled) - return void cb(); - - this.request("registerEventNotifications", data, (resp) => { - this.client.addListener("eventNotification", (type, resp) => { - let toolbox = gDevTools.getToolbox(this.target); - if (toolbox == null) - return; - - this.onConsoleEvent(resp.subject.action, resp.data); - }); - }); - - cb(); - }); - }; - - if (this.target.root) { - this.actor = this.target.root.profilerActor; - this.isConnected = true; - return void register(); - } - - this.client.listTabs((resp) => { - this.actor = resp.profilerActor; - this.isConnected = true; - register(); - }); - }, - - /** - * Adds actor and type information to data and sends the request over - * the remote debugging protocol. - * - * @param string type - * Method to call on the other side - * @param object data - * Data to send with the request - * @param function cb - * A callback function - */ - request: function (type, data, cb) { - data.to = this.actor; - data.type = type; - this.client.request(data, cb); - }, - - /** - * Checks whether the profiler is active. - * - * @param function cb - * Function to be called with a response from the - * client. It will be called with two arguments: - * an error object (may be null) and a boolean - * value indicating if the profiler is active or not. - */ - isActive: function (cb) { - this.request("isActive", {}, (resp) => { - cb(resp.error, resp.isActive, resp.currentTime); - }); - }, - - /** - * Creates a new profile and starts the profiler, if needed. - * - * @param string name - * Name of the profile. - * @param function cb - * Function to be called once the profiler is started - * or we get an error. It will be called with a single - * argument: an error object (may be null). - */ - start: function PC_start(name, cb) { - if (this.profiles.has(name)) { - return; - } - - let profile = makeProfile(name); - this.consoleProfiles.push(name); - this.profiles.set(name, profile); - - // If profile is already running, no need to do anything. - if (this.isProfileRecording(profile)) { - return void cb(); - } - - this.isActive((err, isActive, currentTime) => { - if (isActive) { - profile.timeStarted = currentTime; - return void cb(); - } - - let params = { - entries: 1000000, - interval: 1, - features: ["js"], - }; - - this.request("startProfiler", params, (resp) => { - if (resp.error) { - return void cb(resp.error); - } - - profile.timeStarted = 0; - cb(); - }); - }); - }, - - /** - * Stops the profiler. NOTE, that we don't stop the actual - * SPS Profiler here. It will be stopped as soon as all - * clients disconnect from the profiler actor. - * - * @param string name - * Name of the profile that needs to be stopped. - * @param function cb - * Function to be called once the profiler is stopped - * or we get an error. It will be called with a single - * argument: an error object (may be null). - */ - stop: function PC_stop(name, cb) { - if (!this.profiles.has(name)) { - return; - } - - let profile = this.profiles.get(name); - if (!this.isProfileRecording(profile)) { - return; - } - - this.request("getProfile", {}, (resp) => { - if (resp.error) { - Cu.reportError("Failed to fetch profile data."); - return void cb(resp.error, null); - } - - let data = resp.profile; - profile.timeEnded = resp.currentTime; - - // Filter out all samples that fall out of current - // profile's range. - - data.threads = data.threads.map((thread) => { - let samples = thread.samples.filter((sample) => { - return sample.time >= profile.timeStarted; - }); - - return { samples: samples }; - }); - - cb(null, data); - }); - }, - - /** - * Cleanup. - */ - destroy: function PC_destroy() { - this.client = null; - this.target = null; - this.actor = null; - } -}; - -if (isJSM) { - var EXPORTED_SYMBOLS = ["ProfilerController"]; -} else { - module.exports = ProfilerController; -} \ No newline at end of file diff --git a/browser/devtools/profiler/moz.build b/browser/devtools/profiler/moz.build index 680371ee3e19..89251dc3962a 100644 --- a/browser/devtools/profiler/moz.build +++ b/browser/devtools/profiler/moz.build @@ -1,16 +1,4 @@ -# -*- 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/. - -BROWSER_CHROME_MANIFESTS += ['test/browser.ini'] - -EXTRA_JS_MODULES.devtools.profiler += [ - 'cleopatra.js', - 'commands.js', - 'consts.js', - 'controller.js', - 'panel.js', - 'sidebar.js', -] diff --git a/browser/devtools/profiler/panel.js b/browser/devtools/profiler/panel.js index 5eb434e3710b..55c1d18c41f5 100644 --- a/browser/devtools/profiler/panel.js +++ b/browser/devtools/profiler/panel.js @@ -1,600 +1,6 @@ +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ /* 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 { Cu, Cc, Ci, components } = require("chrome"); - -const { - PROFILE_IDLE, - PROFILE_RUNNING, - PROFILE_COMPLETED, - SHOW_PLATFORM_DATA, - L10N_BUNDLE -} = require("devtools/profiler/consts"); - -const { TextEncoder } = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {}); - -var EventEmitter = require("devtools/toolkit/event-emitter"); -var Cleopatra = require("devtools/profiler/cleopatra"); -var Sidebar = require("devtools/profiler/sidebar"); -var ProfilerController = require("devtools/profiler/controller"); -var { Promise: promise } = Cu.import("resource://gre/modules/Promise.jsm", {}); - -Cu.import("resource:///modules/devtools/gDevTools.jsm"); -Cu.import("resource://gre/modules/devtools/Console.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource:///modules/devtools/ViewHelpers.jsm"); -Cu.import("resource://gre/modules/osfile.jsm"); -Cu.import("resource://gre/modules/NetUtil.jsm"); - -loader.lazyGetter(this, "L10N", () => new ViewHelpers.L10N(L10N_BUNDLE)); - -/** - * Profiler panel. It is responsible for creating and managing - * different profile instances (see cleopatra.js). - * - * ProfilerPanel is an event emitter. It can emit the following - * events: - * - * - ready: after the panel is done loading everything, - * including the default profile instance. - * - started: after the panel successfuly starts our SPS - * profiler. - * - stopped: after the panel successfuly stops our SPS - * profiler and is ready to hand over profiling - * data - * - parsed: after Cleopatra finishes parsing profiling - * data. - * - destroyed: after the panel cleans up after itself and - * is ready to be destroyed. - * - * The following events are used mainly by tests to prevent - * accidential oranges: - * - * - profileCreated: after a new profile is created. - * - profileSwitched: after user switches to a different - * profile. - */ -function ProfilerPanel(frame, toolbox) { - this.isReady = false; - this.window = frame.window; - this.document = frame.document; - this.target = toolbox.target; - - this.profiles = new Map(); - this._uid = 0; - this._msgQueue = {}; - - EventEmitter.decorate(this); -} - -ProfilerPanel.prototype = { - isReady: null, - window: null, - document: null, - target: null, - controller: null, - profiles: null, - sidebar: null, - - _uid: null, - _activeUid: null, - _runningUid: null, - _browserWin: null, - _msgQueue: null, - - get controls() { - let doc = this.document; - - return { - get record() doc.querySelector("#profiler-start"), - get import() doc.querySelector("#profiler-import"), - }; - }, - - get activeProfile() { - return this.profiles.get(this._activeUid); - }, - - set activeProfile(profile) { - if (this._activeUid === profile.uid) - return; - - if (this.activeProfile) - this.activeProfile.hide(); - - this._activeUid = profile.uid; - profile.show(); - }, - - set recordingProfile(profile) { - let btn = this.controls.record; - this._runningUid = profile ? profile.uid : null; - - if (this._runningUid) - btn.setAttribute("checked", true); - else - btn.removeAttribute("checked"); - }, - - get recordingProfile() { - return this.profiles.get(this._runningUid); - }, - - get browserWindow() { - if (this._browserWin) { - return this._browserWin; - } - - let win = this.window.top; - let type = win.document.documentElement.getAttribute("windowtype"); - - if (type !== "navigator:browser") { - win = Services.wm.getMostRecentWindow("navigator:browser"); - } - - return this._browserWin = win; - }, - - get showPlatformData() { - return Services.prefs.getBoolPref(SHOW_PLATFORM_DATA); - }, - - set showPlatformData(enabled) { - Services.prefs.setBoolPref(SHOW_PLATFORM_DATA, enabled); - }, - - /** - * Open a debug connection and, on success, switch to the newly created - * profile. - * - * @return Promise - */ - open: function PP_open() { - // Local profiling needs to make the target remote. - let target = this.target; - let targetPromise = !target.isRemote ? target.makeRemote() : promise.resolve(target); - - return targetPromise - .then((target) => { - let deferred = promise.defer(); - - this.controller = new ProfilerController(this.target); - this.sidebar = new Sidebar(this.document.querySelector("#profiles-list")); - - this.sidebar.on("save", (_, uid) => { - let profile = this.profiles.get(uid); - - if (!profile.data) - return void Cu.reportError("Can't save profile because there's no data."); - - this.openFileDialog({ mode: "save", name: profile.name }).then((file) => { - if (file) - this.saveProfile(file, profile.data); - }); - }); - - this.sidebar.on("select", (_, uid) => { - let profile = this.profiles.get(uid); - this.activeProfile = profile; - - if (profile.isReady) { - return void this.emit("profileSwitched", profile.uid); - } - - profile.once("ready", () => { - this.emit("profileSwitched", profile.uid); - }); - }); - - this.controller.connect(() => { - let btn = this.controls.record; - btn.addEventListener("click", () => this.toggleRecording(), false); - btn.removeAttribute("disabled"); - - let imp = this.controls.import; - imp.addEventListener("click", () => { - this.openFileDialog({ mode: "open" }).then((file) => { - if (file) - this.loadProfile(file); - }); - }, false); - imp.removeAttribute("disabled"); - - // Import queued profiles. - for (let [name, data] of this.controller.profiles) { - this.importProfile(name, data.data); - } - - this.isReady = true; - this.emit("ready"); - deferred.resolve(this); - }); - - this.controller.on("profileEnd", (_, data) => { - this.importProfile(data.name, data.data); - - if (this.recordingProfile && !data.fromConsole) - this.recordingProfile = null; - - this.emit("stopped"); - }); - - return deferred.promise; - }) - .then(null, (reason) => - Cu.reportError("ProfilePanel open failed: " + reason.message)); - }, - - /** - * Creates a new profile instance (see cleopatra.js) and - * adds an appropriate item to the sidebar. Note that - * this method doesn't automatically switch user to - * the newly created profile, they have do to switch - * explicitly. - * - * @param string name - * (optional) name of the new profile - * - * @return Profile - */ - createProfile: function (name, opts={}) { - if (name && this.getProfileByName(name)) { - return this.getProfileByName(name); - } - - let uid = ++this._uid; - let name = name || this.controller.getProfileName(); - let profile = new Cleopatra(this, { - uid: uid, - name: name, - showPlatformData: this.showPlatformData, - external: opts.external - }); - - this.profiles.set(uid, profile); - this.sidebar.addProfile(profile); - this.emit("profileCreated", uid); - - return profile; - }, - - /** - * Imports profile data - * - * @param string name, new profile name - * @param object data, profile data to import - * @param object opts, (optional) if property 'external' is found - * Cleopatra will hide arrow buttons. - * - * @return Profile - */ - importProfile: function (name, data, opts={}) { - let profile = this.createProfile(name, { external: opts.external }); - profile.isStarted = false; - profile.isFinished = true; - profile.data = data; - profile.parse(data, () => this.emit("parsed")); - - this.sidebar.setProfileState(profile, PROFILE_COMPLETED); - if (!this.sidebar.selectedItem) - this.sidebar.selectedItem = this.sidebar.getItemByProfile(profile); - - return profile; - }, - - /** - * Starts or stops profile recording. - */ - toggleRecording: function () { - let profile = this.recordingProfile; - - if (!profile) { - profile = this.createProfile(); - - this.startProfiling(profile.name, () => { - profile.isStarted = true; - - this.sidebar.setProfileState(profile, PROFILE_RUNNING); - this.recordingProfile = profile; - this.emit("started"); - }); - - return; - } - - this.stopProfiling(profile.name, (data) => { - profile.isStarted = false; - profile.isFinished = true; - profile.data = data; - profile.parse(data, () => this.emit("parsed")); - - this.sidebar.setProfileState(profile, PROFILE_COMPLETED); - this.activeProfile = profile; - this.sidebar.selectedItem = this.sidebar.getItemByProfile(profile); - this.recordingProfile = null; - this.emit("stopped"); - }); - }, - - /** - * Start collecting profile data. - * - * @param function onStart - * A function to call once we get the message - * that profiling had been successfuly started. - */ - startProfiling: function (name, onStart) { - this.controller.start(name, (err) => { - if (err) { - return void Cu.reportError("ProfilerController.start: " + err.message); - } - - onStart(); - this.emit("started"); - }); - }, - - /** - * Stop collecting profile data. - * - * @param function onStop - * A function to call once we get the message - * that profiling had been successfuly stopped. - */ - stopProfiling: function (name, onStop) { - this.controller.isActive((err, isActive) => { - if (err) { - Cu.reportError("ProfilerController.isActive: " + err.message); - return; - } - - if (!isActive) { - return; - } - - this.controller.stop(name, (err, data) => { - if (err) { - Cu.reportError("ProfilerController.stop: " + err.message); - return; - } - - onStop(data); - this.emit("stopped", data); - }); - }); - }, - - /** - * Lookup an individual profile by its name. - * - * @param string name name of the profile - * @return profile object or null - */ - getProfileByName: function PP_getProfileByName(name) { - if (!this.profiles) { - return null; - } - - for (let [ uid, profile ] of this.profiles) { - if (profile.name === name) { - return profile; - } - } - - return null; - }, - - /** - * Lookup an individual profile by its UID. - * - * @param number uid UID of the profile - * @return profile object or null - */ - getProfileByUID: function PP_getProfileByUID(uid) { - if (!this.profiles) { - return null; - } - - return this.profiles.get(uid) || null; - }, - - /** - * Iterates over each available profile and calls - * a callback with it as a parameter. - * - * @param function cb a callback to call - */ - eachProfile: function PP_eachProfile(cb) { - let uid = this._uid; - - if (!this.profiles) { - return; - } - - while (uid >= 0) { - if (this.profiles.has(uid)) { - cb(this.profiles.get(uid)); - } - - uid -= 1; - } - }, - - /** - * Broadcast messages to all Cleopatra instances. - * - * @param number target - * UID of the recepient profile. All profiles will receive the message - * but the profile specified by 'target' will have a special property, - * isCurrent, set to true. - * @param object data - * An object with a property 'task' that will be sent over to Cleopatra. - */ - broadcast: function PP_broadcast(target, data) { - if (!this.profiles) { - return; - } - - this.eachProfile((profile) => { - profile.message({ - uid: target, - isCurrent: target === profile.uid, - task: data.task - }); - }); - }, - - /** - * Open file specified in data in either a debugger or view-source. - * - * @param object data - * An object describing the file. It must have three properties: - * - uri - * - line - * - isChrome (chrome files are opened via view-source) - */ - displaySource: function PP_displaySource(data) { - let { browserWindow: win, document: doc } = this; - let { uri, line, isChrome } = data; - let deferred = promise.defer(); - - if (isChrome) { - return void win.gViewSourceUtils.viewSource(uri, null, doc, line); - } - - let showSource = ({ DebuggerView }) => { - if (DebuggerView.Sources.containsValue(uri)) { - DebuggerView.setEditorLocation(uri, line).then(deferred.resolve); - } - // XXX: What to do if the source isn't present in the Debugger? - // Switch back to the Profiler panel and viewSource()? - } - - // If the Debugger was already open, switch to it and try to show the - // source immediately. Otherwise, initialize it and wait for the sources - // to be added first. - let toolbox = gDevTools.getToolbox(this.target); - let debuggerAlreadyOpen = toolbox.getPanel("jsdebugger"); - toolbox.selectTool("jsdebugger").then(({ panelWin: dbg }) => { - if (debuggerAlreadyOpen) { - showSource(dbg); - } else { - dbg.once(dbg.EVENTS.SOURCES_ADDED, () => showSource(dbg)); - } - }); - - return deferred.promise; - }, - - /** - * Opens a normal file dialog. - * - * @params object opts, (optional) property 'mode' can be used to - * specify which dialog to open. Can be either - * 'save' or 'open' (default is 'open'). - * @return promise - */ - openFileDialog: function (opts={}) { - let deferred = promise.defer(); - - let picker = Ci.nsIFilePicker; - let fp = Cc["@mozilla.org/filepicker;1"].createInstance(picker); - let { name, mode } = opts; - let save = mode === "save"; - let title = L10N.getStr(save ? "profiler.saveFileAs" : "profiler.openFile"); - - fp.init(this.window, title, save ? picker.modeSave : picker.modeOpen); - fp.appendFilter("JSON", "*.json"); - fp.appendFilters(picker.filterText | picker.filterAll); - - if (save) - fp.defaultString = (name || "profile") + ".json"; - - fp.open((result) => { - deferred.resolve(result === picker.returnCancel ? null : fp.file); - }); - - return deferred.promise; - }, - - /** - * Saves profile data to disk - * - * @param File file - * @param object data - * - * @return promise - */ - saveProfile: function (file, data) { - let encoder = new TextEncoder(); - let buffer = encoder.encode(JSON.stringify({ profile: data }, null, " ")); - let opts = { tmpPath: file.path + ".tmp" }; - - return OS.File.writeAtomic(file.path, buffer, opts); - }, - - /** - * Reads profile data from disk - * - * @param File file - * @return promise - */ - loadProfile: function (file) { - let deferred = promise.defer(); - let ch = NetUtil.newChannel(file); - ch.contentType = "application/json"; - - NetUtil.asyncFetch(ch, (input, status) => { - if (!components.isSuccessCode(status)) throw new Error(status); - - let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"] - .createInstance(Ci.nsIScriptableUnicodeConverter); - conv.charset = "UTF-8"; - - let data = NetUtil.readInputStreamToString(input, input.available()); - data = conv.ConvertToUnicode(data); - this.importProfile(file.leafName, JSON.parse(data).profile, { external: true }); - - deferred.resolve(); - }); - - return deferred.promise; - }, - - /** - * Cleanup. - */ - destroy: function PP_destroy() { - if (this.profiles) { - let uid = this._uid; - - while (uid >= 0) { - if (this.profiles.has(uid)) { - this.profiles.get(uid).destroy(); - this.profiles.delete(uid); - } - uid -= 1; - } - } - - if (this.controller) { - this.controller.destroy(); - } - - this.isReady = null; - this.window = null; - this.document = null; - this.target = null; - this.controller = null; - this.profiles = null; - this._uid = null; - this._activeUid = null; - - this.emit("destroyed"); - } -}; - -module.exports = ProfilerPanel; diff --git a/browser/devtools/profiler/profiler.xul b/browser/devtools/profiler/profiler.xul index 30e630374575..c0fabe85bae3 100644 --- a/browser/devtools/profiler/profiler.xul +++ b/browser/devtools/profiler/profiler.xul @@ -1,52 +1,4 @@ - - - - - - - - - - %profilerDTD; -]> - - - - "; - let expl2 = ""; - - is(win.escapeHTML(expl), - "<script>function f() {}</script></textarea><img/src='about:logo'>"); - - is(win.escapeHTML(expl2), - "<script>function f() {}</script></pre><img/src='about:logo'>"); - - tearDown(gTab, () => { - gTab = null; - gPanel = null; - }); - }); - - setTimeout(() => { - record.click(); - }, 50); - }); - - record.click(); - }); -} \ No newline at end of file diff --git a/browser/devtools/profiler/test/browser_profiler_gecko_data.js b/browser/devtools/profiler/test/browser_profiler_gecko_data.js deleted file mode 100644 index aa4ecdbbc3d3..000000000000 --- a/browser/devtools/profiler/test/browser_profiler_gecko_data.js +++ /dev/null @@ -1,52 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -const URL = "data:text/html;charset=utf8,

JavaScript Profiler test

"; - -let gTab, gPanel; - -function test() { - waitForExplicitFinish(); - - setUp(URL, function onSetUp(tab, browser, panel) { - gTab = tab; - gPanel = panel; - - function done() { - tearDown(gTab, () => { gPanel = null; gTab = null; }); - } - - Services.prefs.setBoolPref(SHOW_PLATFORM_DATA, false); - recordProfile() - .then(toggleGeckoDataOption) - .then(recordProfile) - .then(done); - }); -} - -function recordProfile() { - let deferred = promise.defer(); - let record = gPanel.controls.record; - - gPanel.once("started", () => { - gPanel.once("parsed", () => { - // We cannot be sure which data is returned by - // the profiler within a test. Until we get rid - // of Cleopatra, at least. - deferred.resolve(); - }); - - record.click(); - }); - - record.click(); - return deferred.promise; -} - -function toggleGeckoDataOption() { - ok(!gPanel.showPlatformData, "showPlatformData is not set"); - - Services.prefs.setBoolPref(SHOW_PLATFORM_DATA, true); - - ok(gPanel.showPlatformData, "showPlatformData is set"); -} diff --git a/browser/devtools/profiler/test/browser_profiler_io.js b/browser/devtools/profiler/test/browser_profiler_io.js deleted file mode 100644 index 42cf1e4897ce..000000000000 --- a/browser/devtools/profiler/test/browser_profiler_io.js +++ /dev/null @@ -1,80 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -const URL = "data:text/html;charset=utf8,

browser_profiler_io

"; - -let temp = {}; -Cu.import("resource://gre/modules/FileUtils.jsm", temp); -let FileUtils = temp.FileUtils; -let gTab, gPanel; - -let gData = { - "libs": "[]", // This property is not important for this test. - "meta": { - "version": 2, - "interval": 1, - "stackwalk": 0, - "jank": 0, - "processType": 0, - "platform": "Macintosh", - "oscpu": "Intel Mac OS X 10.8", - "misc": "rv:25.0", - "abi": "x86_64-gcc3", - "toolkit": "cocoa", - "product": "Firefox" - }, - "threads": [ - { - "samples": [ - { - "name": "(root)", - "frames": [ - { - "location": "Startup::XRE_Main", - "line": 3871 - }, - { - "location": "Events::ProcessGeckoEvents", - "line": 355 - }, - { - "location": "Events::ProcessGeckoEvents", - "line": 355 - } - ], - "responsiveness": -0.002963, - "time": 8.120823 - } - ] - } - ] -}; - -function test() { - waitForExplicitFinish(); - - setUp(URL, function onSetUp(tab, browser, panel) { - gTab = tab; - gPanel = panel; - - let file = FileUtils.getFile("TmpD", ["tmpprofile.json"]); - file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8)); - - gPanel.saveProfile(file, gData) - .then(gPanel.loadProfile.bind(gPanel, file)) - .then(checkData); - }); -} - -function checkData() { - let profile = gPanel.activeProfile; - let item = gPanel.sidebar.getItemByProfile(profile); - let data = profile.data; - - is(item.attachment.state, PROFILE_COMPLETED, "Profile is COMPLETED"); - is(gData.meta.oscpu, data.meta.oscpu, "Meta data is correct"); - is(gData.threads[0].samples.length, 1, "There's one sample"); - is(gData.threads[0].samples[0].name, "(root)", "Sample is correct"); - - tearDown(gTab, () => { gPanel = null; gTab = null; }); -} diff --git a/browser/devtools/profiler/test/browser_profiler_remote.js b/browser/devtools/profiler/test/browser_profiler_remote.js deleted file mode 100644 index 0a7191058c92..000000000000 --- a/browser/devtools/profiler/test/browser_profiler_remote.js +++ /dev/null @@ -1,55 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -const URL = "data:text/html;charset=utf8,

JavaScript Profiler test

"; - -let temp = {}; - -Cu.import("resource://gre/modules/devtools/dbg-server.jsm", temp); -let DebuggerServer = temp.DebuggerServer; - -Cu.import("resource://gre/modules/devtools/dbg-client.jsm", temp); -let DebuggerClient = temp.DebuggerClient; -let debuggerSocketConnect = temp.debuggerSocketConnect; - -Cu.import("resource:///modules/devtools/profiler/controller.js", temp); -let ProfilerController = temp.ProfilerController; - -function test() { - waitForExplicitFinish(); - Services.prefs.setBoolPref(REMOTE_ENABLED, true); - - loadTab(URL, function onTabLoad(tab, browser) { - DebuggerServer.init(function () true); - DebuggerServer.addBrowserActors(); - is(DebuggerServer.listeningSockets, 0); - - DebuggerServer.openListener(2929); - is(DebuggerServer.listeningSockets, 1); - - let transport = debuggerSocketConnect("127.0.0.1", 2929); - let client = new DebuggerClient(transport); - client.connect(function onClientConnect() { - let target = { isRemote: true, client: client }; - let controller = new ProfilerController(target); - - controller.connect(function onControllerConnect() { - // If this callback is called, this means listTabs call worked. - // Which means that the transport worked. Time to finish up this - // test. - - function onShutdown() { - window.removeEventListener("Debugger:Shutdown", onShutdown, true); - transport = client = null; - finish(); - } - - window.addEventListener("Debugger:Shutdown", onShutdown, true); - - client.close(function () { - gBrowser.removeTab(tab); - }); - }); - }); - }); -} diff --git a/browser/devtools/profiler/test/browser_profiler_run.js b/browser/devtools/profiler/test/browser_profiler_run.js deleted file mode 100644 index 338432ff540f..000000000000 --- a/browser/devtools/profiler/test/browser_profiler_run.js +++ /dev/null @@ -1,119 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -const URL = "data:text/html;charset=utf8,

JavaScript Profiler test

"; - -let gTab, gPanel; - -function test() { - waitForExplicitFinish(); - - setUp(URL, function onSetUp(tab, browser, panel) { - gTab = tab; - gPanel = panel; - - function done() { - tearDown(gTab, () => { gPanel = null; gTab = null; }); - } - - startRecording() - .then(stopRecording) - .then(startRecordingAgain) - .then(stopRecording) - .then(switchBackToTheFirstOne) - .then(done); - }); -} - -function startRecording() { - let deferred = promise.defer(); - - ok(gPanel, "Profiler panel exists"); - ok(!gPanel.activeProfile, "Active profile doesn't exist"); - ok(!gPanel.recordingProfile, "Recording profile doesn't exist"); - - let record = gPanel.controls.record; - ok(record, "Record button exists."); - ok(!record.getAttribute("checked"), "Record button is unchecked"); - - gPanel.once("started", () => { - let item = gPanel.sidebar.getItemByProfile(gPanel.recordingProfile); - is(item.attachment.name, "Profile 1"); - is(item.attachment.state, PROFILE_RUNNING); - is(record.getAttribute("tooltiptext"), "Stop profiling"); - - gPanel.controller.isActive(function (err, isActive) { - ok(isActive, "Profiler is running"); - deferred.resolve(); - }); - }); - - record.click(); - return deferred.promise; -} - -function stopRecording() { - let deferred = promise.defer(); - let record = gPanel.controls.record; - - gPanel.once("parsed", () => { - let item = gPanel.sidebar.getItemByProfile(gPanel.activeProfile); - is(item.attachment.state, PROFILE_COMPLETED); - is(record.getAttribute("tooltiptext"), "Start profiling"); - - function assertSample() { - let [ win, doc ] = getProfileInternals(); - let sample = doc.getElementsByClassName("samplePercentage"); - - if (sample.length <= 0) { - return void setTimeout(assertSample, 100); - } - - ok(sample.length > 0, "We have some items displayed"); - is(sample[0].innerHTML, "100.0%", "First percentage is 100%"); - - deferred.resolve(); - } - - assertSample(); - }); - - setTimeout(function () gPanel.controls.record.click(), 100); - return deferred.promise; -} - -function startRecordingAgain() { - let deferred = promise.defer(); - - let record = gPanel.controls.record; - ok(!record.getAttribute("checked"), "Record button is unchecked"); - - gPanel.once("started", () => { - ok(gPanel.activeProfile !== gPanel.recordingProfile); - - let item = gPanel.sidebar.getItemByProfile(gPanel.recordingProfile); - is(item.attachment.name, "Profile 2"); - is(item.attachment.state, PROFILE_RUNNING); - is(record.getAttribute("tooltiptext"), "Stop profiling"); - - deferred.resolve(); - }); - - record.click(); - return deferred.promise; -} - -function switchBackToTheFirstOne() { - let deferred = promise.defer(); - let button = gPanel.sidebar.getElementByProfile({ uid: 1 }); - let item = gPanel.sidebar.getItemByProfile({ uid: 1 }); - - gPanel.once("profileSwitched", () => { - is(gPanel.activeProfile.uid, 1, "activeProfile is correct"); - is(gPanel.sidebar.selectedItem, item, "selectedItem is correct"); - deferred.resolve(); - }); - - button.click(); - return deferred.promise; -} diff --git a/browser/devtools/profiler/test/head.js b/browser/devtools/profiler/test/head.js index c27b9f687567..f02321a0daed 100644 --- a/browser/devtools/profiler/test/head.js +++ b/browser/devtools/profiler/test/head.js @@ -1,128 +1,3 @@ /* Any copyright is dedicated to the Public Domain. http://creativecommons.org/publicdomain/zero/1.0/ */ - -let temp = {}; - -const PROFILER_ENABLED = "devtools.profiler.enabled"; -const REMOTE_ENABLED = "devtools.debugger.remote-enabled"; -const SHOW_PLATFORM_DATA = "devtools.profiler.ui.show-platform-data"; -const PROFILE_IDLE = 0; -const PROFILE_RUNNING = 1; -const PROFILE_COMPLETED = 2; - -Cu.import("resource:///modules/devtools/gDevTools.jsm", temp); -let gDevTools = temp.gDevTools; - -Cu.import("resource://gre/modules/devtools/Loader.jsm", temp); -let TargetFactory = temp.devtools.TargetFactory; - -Cu.import("resource://gre/modules/devtools/dbg-server.jsm", temp); -let DebuggerServer = temp.DebuggerServer; - -// Import the GCLI test helper -let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/")); -Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this); - -gDevTools.testing = true; -SimpleTest.registerCleanupFunction(() => { - gDevTools.testing = false; -}); - -registerCleanupFunction(function () { - helpers = null; - Services.prefs.clearUserPref(PROFILER_ENABLED); - Services.prefs.clearUserPref(REMOTE_ENABLED); - Services.prefs.clearUserPref(SHOW_PLATFORM_DATA); - DebuggerServer.destroy(); - - // These tests use a lot of memory due to GL contexts, so force a GC to help - // fragmentation. - info("Forcing GC after profiler test."); - Cu.forceGC(); -}); - -function getProfileInternals(uid) { - let profile = (uid != null) ? gPanel.profiles.get(uid) : gPanel.activeProfile; - let win = profile.iframe.contentWindow; - let doc = win.document; - - return [win, doc]; -} - -function getSidebarItem(uid, panel=gPanel) { - let profile = panel.profiles.get(uid); - return panel.sidebar.getItemByProfile(profile); -} - -function sendFromProfile(uid, msg) { - let [win, doc] = getProfileInternals(uid); - win.parent.postMessage({ uid: uid, status: msg }, "*"); -} - -function loadTab(url, callback) { - let tab = gBrowser.addTab(); - gBrowser.selectedTab = tab; - loadUrl(url, tab, callback); -} - -function loadUrl(url, tab, callback) { - content.location.assign(url); - let browser = gBrowser.getBrowserForTab(tab); - if (browser.contentDocument.readyState === "complete") { - callback(tab, browser); - return; - } - - let onLoad = function onLoad() { - browser.removeEventListener("load", onLoad, true); - callback(tab, browser); - }; - - browser.addEventListener("load", onLoad, true); -} - -function openProfiler(tab, callback) { - let target = TargetFactory.forTab(tab); - gDevTools.showToolbox(target, "jsprofiler").then(callback); -} - -function openConsole(tab, cb=function(){}) { - // This function was borrowed from webconsole/test/head.js - let target = TargetFactory.forTab(tab); - - gDevTools.showToolbox(target, "webconsole").then(function (toolbox) { - let hud = toolbox.getCurrentPanel().hud; - hud.jsterm._lazyVariablesView = false; - cb(hud); - }); -} - -function closeProfiler(tab, callback) { - let target = TargetFactory.forTab(tab); - let toolbox = gDevTools.getToolbox(target); - toolbox.destroy().then(callback); -} - -function setUp(url, callback=function(){}) { - Services.prefs.setBoolPref(PROFILER_ENABLED, true); - - loadTab(url, function onTabLoad(tab, browser) { - openProfiler(tab, function onProfilerOpen() { - let target = TargetFactory.forTab(tab); - let panel = gDevTools.getToolbox(target).getPanel("jsprofiler"); - callback(tab, browser, panel); - }); - }); -} - -function tearDown(tab, callback=function(){}) { - closeProfiler(tab, function onProfilerClose() { - callback(); - - while (gBrowser.tabs.length > 1) { - gBrowser.removeCurrentTab(); - } - - finish(); - }); -} +"use strict"; diff --git a/browser/devtools/profiler/test/mock_console_api.html b/browser/devtools/profiler/test/mock_console_api.html deleted file mode 100644 index 2a626c9aac88..000000000000 --- a/browser/devtools/profiler/test/mock_console_api.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - console.profile from content - - - - - - \ No newline at end of file diff --git a/browser/devtools/profiler/test/mock_profiler_bug_834878_page.html b/browser/devtools/profiler/test/mock_profiler_bug_834878_page.html deleted file mode 100644 index ad150b98a358..000000000000 --- a/browser/devtools/profiler/test/mock_profiler_bug_834878_page.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - Profiler Script Linking Test - - - - - diff --git a/browser/devtools/profiler/test/mock_profiler_bug_834878_script.js b/browser/devtools/profiler/test/mock_profiler_bug_834878_script.js deleted file mode 100644 index 0ef5f3772c0a..000000000000 --- a/browser/devtools/profiler/test/mock_profiler_bug_834878_script.js +++ /dev/null @@ -1,7 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -function main() { - console.log("Hello, World!"); - return 0; -} \ No newline at end of file diff --git a/browser/locales/en-US/chrome/browser/devtools/profiler.dtd b/browser/locales/en-US/chrome/browser/devtools/profiler.dtd index e02a9df98197..efd0ddec6418 100644 --- a/browser/locales/en-US/chrome/browser/devtools/profiler.dtd +++ b/browser/locales/en-US/chrome/browser/devtools/profiler.dtd @@ -9,11 +9,3 @@ - You want to make that choice consistent across the developer tools. - A good criteria is the language in which you'd find the best - documentation on web development on the web. --> - - - - - - diff --git a/browser/locales/en-US/chrome/browser/devtools/profiler.properties b/browser/locales/en-US/chrome/browser/devtools/profiler.properties index f3ab03b40631..d6bbe2635771 100644 --- a/browser/locales/en-US/chrome/browser/devtools/profiler.properties +++ b/browser/locales/en-US/chrome/browser/devtools/profiler.properties @@ -2,8 +2,8 @@ # 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/. -# LOCALIZATION NOTE These strings are used inside the Debugger -# which is available from the Web Developer sub-menu -> 'Debugger'. +# LOCALIZATION NOTE These strings are used inside the Profiler +# which is available from the Web Developer sub-menu -> 'Profiler'. # The correct localization of this file might be to keep it in # English, or another language commonly spoken among web developers. # You want to make that choice consistent across the developer tools. @@ -21,97 +21,10 @@ profiler.panelLabel=Profiler Panel # LOCALIZATION NOTE (profiler2.commandkey, profiler.accesskey) # Used for the menuitem in the tool menu -profiler2.commandkey=VK_F5 +profiler.commandkey2=VK_F5 profiler.accesskey=P # LOCALIZATION NOTE (profiler.tooltip2): # This string is displayed in the tooltip of the tab when the profiler is # displayed inside the developer tools window. profiler.tooltip2=JavaScript Profiler - -# LOCALIZATION NOTE (profiler.profileName): -# This string is the default name for new profiles. Its parameter is a number. -# For example: "Profile 1", "Profile 2", etc. -profiler.profileName=Profile %S - -# LOCALIZATION NOTE (profiler.completeProfile): -# This string is displayed as a tab in the profiler UI. Clicking on it -# displays everything that the profiler has generated so far. -profiler.completeProfile=Complete Profile - -# LOCALIZATION NOTE (profiler.sampleRange): -# This string is displayed as a tab in the profiler UI. Clicking on it -# displays a sample range of data selected by user themselves. -profiler.sampleRange=Sample Range - -# LOCALIZATION NOTE (profiler.runningTime): -# This string is displayed as a table header in the profiler UI. -profiler.runningTime=Running Time - -# LOCALIZATION NOTE (profiler.self): -# This string is displayed as a table header in the profiler UI. -# "Self" is how much time was spent doing work directly in that function. -# As opposed to the total time which is how much time was spent in that -# function and in functions it called. -profiler.self=Self - -# LOCALIZATION NOTE (profiler.symbolName) -# This string is displayed as a table header in the profiler UI. -profiler.symbolName=Symbol Name - -# LOCALIZATION NOTE (profiler.start) -# This string is displayed on the button that starts the profiler. -profiler.start=Start - -# LOCALIZATION NOTE (profiler.stop) -# This string is displayed on the button that stops the profiler. -profiler.stop=Stop - -# LOCALIZATION NOTE (profiler.loading) -# This string is displayed above the progress bar when the profiler -# is busy loading and parsing the report. -profiler.loading=Loading profileā€¦ - -# LOCALIZATION NOTE (profiler.stateIdle) -# This string is used to show that the profile in question is in IDLE -# state meaning that it hasn't been started yet. -profiler.stateIdle=Idle - -# LOCALIZATION NOTE (profiler.stateRunning) -# This string is used to show that the profile in question is in RUNNING -# state meaning that it has been started and currently gathering profile data. -profiler.stateRunning=Running - -# LOCALIZATION NOTE (profiler.stateCompleted) -# This string is used to show that the profile in question is in COMPLETED -# state meaning that it has been started and stopped already. -profiler.stateCompleted=Completed - -# LOCALIZATION NOTE (profiler.sidebarNotice) -# This string is displayed in the profiler sidebar when there are no -# existing profiles to show (usually happens when the user opens the -# profiler for the first time). -profiler.sidebarNotice=There are no profiles yet. - -# LOCALIZATION NOTE (profiler.startProfilerString) -# This string is displayed on the profiler button when no active -# profiling sessions are running -profiler.startProfilerString=Start profiling - -# LOCALIZATION NOTE (profiler.stopProfilerString) -# This string is displayed on the profiler button when an active -# profiling session is running -profiler.stopProfilerString=Stop profiling - -# LOCALIZATION NOTE (profiler.save) -# This string is displayed as a label for a button that opens a Save File -# dialog where user can save generated profiler to a file. -profiler.save=Save - -# LOCALIZATION NOTE (profiler.saveFileAs) -# This string as a title for a Save File dialog. -profiler.saveFileAs=Save Profile As - -# LOCALIZATION NOTE (profiler.openFile) -# This string as a title for a Open File dialog. -profiler.openFile=Import Profile diff --git a/browser/themes/osx/devtools/profiler.css b/browser/themes/osx/devtools/profiler.css index 152678396c50..af0f0a5f0d7e 100644 --- a/browser/themes/osx/devtools/profiler.css +++ b/browser/themes/osx/devtools/profiler.css @@ -2,4 +2,4 @@ * 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/. */ -%include ../../shared/devtools/profiler.inc.css \ No newline at end of file +%include ../../shared/devtools/profiler.inc.css diff --git a/browser/themes/shared/devtools/profiler.inc.css b/browser/themes/shared/devtools/profiler.inc.css index b7e4eb5c41b1..96ec57eec269 100644 --- a/browser/themes/shared/devtools/profiler.inc.css +++ b/browser/themes/shared/devtools/profiler.inc.css @@ -1,78 +1,4 @@ -%if 0 +/* vim:set ts=2 sw=2 sts=2 et: */ /* 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/. */ -%endif - -.profiler-sidebar-empty-notice { - max-width: 176px; - padding: 10px; - background-color: rgb(61, 69, 76); - color: white; - font-weight: bold; -} - -.profiler-sidebar { - min-width: 196px; -} - -.profiler-sidebar + .devtools-side-splitter { - border-color: transparent; -} - -.profiler-sidebar .devtools-toolbar { - -moz-border-end: 1px solid; -} - -.theme-dark .profiler-sidebar .devtools-toolbar { - -moz-border-end-color: black; /* Match the splitter. */ -} - -.theme-light .profiler-sidebar .devtools-toolbar { - -moz-border-end-color: #aaa; /* Match the splitter color. */ -} - - -.profiler-sidebar-item { - padding: 3px 5px; -} - -.profiler-sidebar-item, .side-menu-widget-item-contents { - cursor: default; -} - -.profiler-sidebar-item > h3 { - font-size: 13px; - display: block; - cursor: pointer; -} - -.profiler-sidebar-item > hbox { - margin-top: 2px; -} - -.profiler-sidebar-item > hbox > a { - display: none; - text-decoration: underline; - cursor: pointer; -} - -.selected [state=completed] .profiler-sidebar-item > hbox > a { - display: block; -} - -.theme-dark .selected .profiler-sidebar-item > hbox { - color: #b6babf; -} - -.theme-light .selected .profiler-sidebar-item > hbox { - color: #ebeced; -} - -#profiler-start { - list-style-image: url("chrome://browser/skin/devtools/profiler-stopwatch.svg"); -} - -#profiler-start[checked] { - list-style-image: url("chrome://browser/skin/devtools/profiler-stopwatch-checked.svg"); -} \ No newline at end of file diff --git a/toolkit/devtools/server/actors/profiler.js b/toolkit/devtools/server/actors/profiler.js index 5824f3a969c1..c507c9624d10 100644 --- a/toolkit/devtools/server/actors/profiler.js +++ b/toolkit/devtools/server/actors/profiler.js @@ -1,245 +1,4 @@ /* 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"; - -var startedProfilers = 0; -var startTime = 0; - -function getCurrentTime() { - return (new Date()).getTime() - startTime; -} - -/** - * Creates a ProfilerActor. ProfilerActor provides remote access to the - * built-in profiler module. - * - * ProfilerActor.onGetProfile returns a JavaScript object with data - * generated by our built-in profiler moduele. It has the following - * format: - * - * { - * libs: string, - * meta: { - * interval: number, - * platform: string, - * (...) - * }, - * threads: [ - * { - * samples: [ - * { - * frames: [ - * { - * line: number, - * location: string - * } - * ], - * name: string - * responsiveness: number (in ms) - * time: number (nspr time) - * } - * ] - * } - * ] - * } - * - */ -function ProfilerActor(aConnection) -{ - this._profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); - this._started = false; - this._observedEvents = []; -} - -ProfilerActor.prototype = { - actorPrefix: "profiler", - - disconnect: function() { - for (var event of this._observedEvents) { - Services.obs.removeObserver(this, event); - } - - this.stopProfiler(); - this._profiler = null; - }, - - stopProfiler: function() { - // We stop the profiler only after the last client has - // stopped profiling. Otherwise there's a problem where - // we stop the profiler as soon as you close the devtools - // panel in one tab even though there might be other - // profiler instances running in other tabs. - if (!this._started) { - return; - } - this._started = false; - startedProfilers -= 1; - if (startedProfilers <= 0) { - this._profiler.StopProfiler(); - } - }, - - onStartProfiler: function(aRequest) { - this._profiler.StartProfiler(aRequest.entries, aRequest.interval, - aRequest.features, aRequest.features.length); - this._started = true; - startedProfilers += 1; - startTime = (new Date()).getTime(); - return { "msg": "profiler started" } - }, - onStopProfiler: function(aRequest) { - this.stopProfiler(); - return { "msg": "profiler stopped" } - }, - onGetProfileStr: function(aRequest) { - var profileStr = this._profiler.GetProfile(); - return { "profileStr": profileStr } - }, - onGetProfile: function(aRequest) { - var profile = this._profiler.getProfileData(); - return { "profile": profile, "currentTime": getCurrentTime() } - }, - onIsActive: function(aRequest) { - var isActive = this._profiler.IsActive(); - var currentTime = isActive ? getCurrentTime() : null; - return { "isActive": isActive, "currentTime": currentTime } - }, - onGetFeatures: function(aRequest) { - var features = this._profiler.GetFeatures([]); - return { "features": features } - }, - onGetSharedLibraryInformation: function(aRequest) { - var sharedLibraries = this._profiler.getSharedLibraryInformation(); - return { "sharedLibraryInformation": sharedLibraries } - }, - onRegisterEventNotifications: function(aRequest) { - let registered = []; - for (var event of aRequest.events) { - if (this._observedEvents.indexOf(event) != -1) - continue; - Services.obs.addObserver(this, event, false); - this._observedEvents.push(event); - registered.push(event); - } - return { registered: registered } - }, - onUnregisterEventNotifications: function(aRequest) { - let unregistered = []; - for (var event of aRequest.events) { - let idx = this._observedEvents.indexOf(event); - if (idx == -1) - continue; - Services.obs.removeObserver(this, event); - this._observedEvents.splice(idx, 1); - unregistered.push(event); - } - return { unregistered: unregistered } - }, - observe: DevToolsUtils.makeInfallible(function(aSubject, aTopic, aData) { - /* - * this.conn.send can only transmit acyclic values. However, it is - * idiomatic for wrapped JS objects like aSubject (and possibly aData?) - * to have a 'wrappedJSObject' property pointing to themselves. - * - * this.conn.send also assumes that it can retain the object it is - * passed to be handled on later event ticks; and that it's okay to - * freeze it. Since we don't really know what aSubject and aData are, - * we need to pass this.conn.send a copy of them, not the originals. - * - * We break the cycle and make the copy by JSON.stringifying those - * values with a replacer that omits properties known to introduce - * cycles, and then JSON.parsing the result. This spends processor - * time, but it's simple. - */ - function cycleBreaker(key, value) { - if (key === 'wrappedJSObject') { - return undefined; - } - return value; - } - - /* - * If these values are objects with a non-null 'wrappedJSObject' property - * and aren't Xrays, use their .wrappedJSObject. Otherwise, use the value - * unchanged. - */ - aSubject = (aSubject && !Cu.isXrayWrapper(aSubject) && aSubject.wrappedJSObject) || aSubject; - aData = (aData && !Cu.isXrayWrapper(aData) && aData.wrappedJSObject) || aData; - - let subj = JSON.parse(JSON.stringify(aSubject, cycleBreaker)); - let data = JSON.parse(JSON.stringify(aData, cycleBreaker)); - - let send = (extra) => { - data = data || {}; - - if (extra) - data.extra = extra; - - this.conn.send({ - from: this.actorID, - type: "eventNotification", - event: aTopic, - subject: subj, - data: data - }); - } - - if (aTopic !== "console-api-profiler") - return void send(); - - // If the event was generated from console.profile or - // console.profileEnd we need to start the profiler - // right away and only then notify our client. Otherwise, - // we'll lose precious samples. - - let name = subj.arguments[0]; - - if (subj.action === "profile") { - let resp = this.onIsActive(); - - if (resp.isActive) { - return void send({ - name: name, - currentTime: resp.currentTime, - action: "profile" - }); - } - - this.onStartProfiler({ - entries: 1000000, - interval: 1, - features: ["js"] - }); - - return void send({ currentTime: 0, action: "profile", name: name }); - } - - if (subj.action === "profileEnd") { - let resp = this.onGetProfile(); - resp.action = "profileEnd"; - resp.name = name; - send(resp); - } - - return undefined; // Otherwise xpcshell tests fail. - }, "ProfilerActor.prototype.observe"), -}; - -/** - * The request types this actor can handle. - */ -ProfilerActor.prototype.requestTypes = { - "startProfiler": ProfilerActor.prototype.onStartProfiler, - "stopProfiler": ProfilerActor.prototype.onStopProfiler, - "getProfileStr": ProfilerActor.prototype.onGetProfileStr, - "getProfile": ProfilerActor.prototype.onGetProfile, - "isActive": ProfilerActor.prototype.onIsActive, - "getFeatures": ProfilerActor.prototype.onGetFeatures, - "getSharedLibraryInformation": ProfilerActor.prototype.onGetSharedLibraryInformation, - "registerEventNotifications": ProfilerActor.prototype.onRegisterEventNotifications, - "unregisterEventNotifications": ProfilerActor.prototype.onUnregisterEventNotifications -}; - -DebuggerServer.addGlobalActor(ProfilerActor, "profilerActor"); diff --git a/toolkit/devtools/server/tests/unit/test_profiler_activation.js b/toolkit/devtools/server/tests/unit/test_profiler_activation.js deleted file mode 100644 index 3dcc41ed73a6..000000000000 --- a/toolkit/devtools/server/tests/unit/test_profiler_activation.js +++ /dev/null @@ -1,66 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); - -function connectClient(callback) { - let client = new DebuggerClient(DebuggerServer.connectPipe()); - client.connect(function () { - client.listTabs(function(response) { - callback(client, response.profilerActor); - }); - }); -} - -function run_test() -{ - // Ensure the profiler is not running when the test starts (it could - // happen if the MOZ_PROFILER_STARTUP environment variable is set) - Profiler.StopProfiler(); - - DebuggerServer.init(function () { return true; }); - DebuggerServer.addBrowserActors(); - - connectClient((client1, actor1) => { - connectClient((client2, actor2) => { - activate_first(client1, actor1, client2, actor2); - }); - }) - - do_test_pending(); -} - -function activate_first(client1, actor1, client2, actor2) { - // Start the profiler on the first connection.... - client1.request({ to: actor1, type: "startProfiler", features: ['js']}, startResponse => { - // Profiler should be active now. - do_check_true(Profiler.IsActive()); - - // But on the next connection just make sure the actor has been - // instantiated. - client2.request({ to: actor2, type: "getFeatures" }, featureResponse => { - - let connectionClosed = DebuggerServer._connectionClosed; - DebuggerServer._connectionClosed = function(conn) { - connectionClosed.call(this, conn); - - // Client1 is the only actor that started the profiler, - // it shouldn't be active anymore. - do_check_false(Profiler.IsActive()); - - DebuggerServer._connectionClosed = function(conn) { - connectionClosed.call(this, conn); - - // Now there are no open clients at all, it should *definitely* - // be deactivated by now. - do_check_false(Profiler.IsActive()); - do_test_finished(); - } - client2.close(); - }; - client1.close(); - }); - }); -} diff --git a/toolkit/devtools/server/tests/unit/test_profiler_actor.js b/toolkit/devtools/server/tests/unit/test_profiler_actor.js deleted file mode 100644 index 6e3fa74f9d6f..000000000000 --- a/toolkit/devtools/server/tests/unit/test_profiler_actor.js +++ /dev/null @@ -1,191 +0,0 @@ -/* Any copyright is dedicated to the Public Domain. - http://creativecommons.org/publicdomain/zero/1.0/ */ - -"use strict"; - -const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler); - -function run_test() -{ - // Ensure the profiler is not running when the test starts (it could - // happen if the MOZ_PROFILER_STARTUP environment variable is set) - Profiler.StopProfiler(); - DebuggerServer.init(function () { return true; }); - DebuggerServer.addBrowserActors(); - var client = new DebuggerClient(DebuggerServer.connectPipe()); - client.connect(function () { - client.listTabs(function(aResponse) { - test_profiler_actor(client, aResponse.profilerActor); - }); - }); - do_test_pending(); -} - -function test_profiler_actor(aClient, aProfiler) -{ - aClient.request({ to: aProfiler, type: "isActive" }, function (aResponse) { - do_check_false(aResponse.isActive); - - aClient.request({ to: aProfiler, type: "getFeatures" }, function (aResponse) { - var features = Profiler.GetFeatures([]); - do_check_eq(aResponse.features.length, features.length); - for (var i = 0; i < features.length; i++) - do_check_eq(aResponse.features[i], features[i]); - - aClient.request({ to: aProfiler, type: "startProfiler", features: ['js'] }, function (aResponse) { - do_check_eq(typeof aResponse.msg, "string"); - aClient.request({ to: aProfiler, type: "isActive" }, function (aResponse) { - do_check_true(aResponse.isActive); - - aClient.request({ to: aProfiler, type: "getSharedLibraryInformation" }, function (aResponse) { - do_check_eq(typeof aResponse.sharedLibraryInformation, "string"); - try { - JSON.parse(aResponse.sharedLibraryInformation); - } catch(e) { - do_throw(e.toString(), Components.stack.caller); - } - - test_event_notifications(aClient, aProfiler); - }); - }); - }); - }); - }); -} - -function test_event_notifications(aClient, aProfiler) -{ - aClient.request({ to: aProfiler, type: "registerEventNotifications", events: ["foo", "bar"] }, function (aResponse) { - do_check_eq(typeof aResponse.registered, "object"); - do_check_eq(aResponse.registered.length, 2); - do_check_eq(aResponse.registered[0], "foo"); - do_check_eq(aResponse.registered[1], "bar"); - - aClient.request({ to: aProfiler, type: "registerEventNotifications", events: ["foo"] }, function (aResponse) { - do_check_eq(typeof aResponse.registered, "object"); - do_check_eq(aResponse.registered.length, 0); - - aClient.addListener("eventNotification", function (aType, aData) { - do_check_eq(aType, "eventNotification"); - do_check_eq(aData.event, "foo"); - do_check_eq(typeof aData.subject, "object"); - do_check_eq(aData.subject.foo, "foo"); - do_check_eq(aData.data, "foo"); - }); - var subject = { foo: "foo" }; - subject.wrappedJSObject = subject; - Services.obs.notifyObservers(subject, "foo", "foo"); - - aClient.request({ to: aProfiler, type: "unregisterEventNotifications", events: ["foo", "bar", "qux"] }, function (aResponse) { - do_check_eq(typeof aResponse.unregistered, "object"); - do_check_eq(aResponse.unregistered.length, 2); - do_check_eq(aResponse.unregistered[0], "foo"); - do_check_eq(aResponse.unregistered[1], "bar"); - - // All events being now unregistered, sending an event shouldn't - // do anything. If it does, the eventNotification listener above - // will catch the event and fail on the aData.event test. - Services.obs.notifyObservers(null, "bar", null); - - test_profile(aClient, aProfiler); - }); - }); - }); -} - -function test_profile(aClient, aProfiler) -{ - function attempt(aDelayMS) - { - // No idea why, but Components.stack.sourceLine returns null. - var funcLine = Components.stack.lineNumber - 3; - // Spin for the requested time, then take a sample. - var start = Date.now(); - var stack; - do_print("attempt: delay = " + aDelayMS); - while (Date.now() - start < aDelayMS) { stack = Components.stack; } - do_print("attempt: before getProfile"); - - aClient.request({ to: aProfiler, type: "getProfile" }, function (aResponse) { - // Any valid getProfile response should have the following top - // level structure. - do_check_eq(typeof aResponse.profile, "object"); - do_check_eq(typeof aResponse.profile.meta, "object"); - do_check_eq(typeof aResponse.profile.meta.platform, "string"); - do_check_eq(typeof aResponse.profile.threads, "object"); - do_check_eq(typeof aResponse.profile.threads[0], "object"); - do_check_eq(typeof aResponse.profile.threads[0].samples, "object"); - - // At this point, we may or may not have samples, depending on - // whether the spin loop above has given the profiler enough time - // to get started. - if (aResponse.profile.threads[0].samples.length == 0) { - if (aDelayMS < 20000) { - // Double the spin-wait time and try again. - do_print("attempt: no samples, going around again"); - return attempt(aDelayMS * 2); - } else { - // We've waited long enough, so just fail. - do_print("attempt: waited 20000ms, but no samples were collected. Giving up."); - do_check_true(false); - return; - } - } - - // Now check the samples. At least one sample is expected to - // have been in the busy wait above. - do_print("attempt: got a profile that contains samples"); - let location = stack.name + " (" + stack.filename + ":" + funcLine + ")"; - do_check_true(aResponse.profile.threads[0].samples.some(function(sample) { - return typeof sample.frames == "object" && - sample.frames.length != 0 && - sample.frames.some(function(f) { - return (f.line == stack.lineNumber) && - (f.location == location); - }); - })); - - aClient.request({ to: aProfiler, type: "stopProfiler" }, function (aResponse) { - do_check_eq(typeof aResponse.msg, "string"); - aClient.request({ to: aProfiler, type: "isActive" }, function (aResponse) { - do_check_false(aResponse.isActive); - aClient.close(function() { - test_profiler_status(); - }); - }); - }); - }); - } - - // Start off with a 100 millisecond delay. - attempt(100); -} - -function test_profiler_status() -{ - var connectionClosed = DebuggerServer._connectionClosed; - var client = new DebuggerClient(DebuggerServer.connectPipe()); - - client.connect(() => { - client.listTabs((aResponse) => { - DebuggerServer._connectionClosed = function (conn) { - connectionClosed.call(this, conn); - - // Check that closing the last (only?) connection stops the profiler. - do_check_false(Profiler.IsActive()); - do_test_finished(); - } - - var profiler = aResponse.profilerActor; - do_check_false(Profiler.IsActive()); - client.request({ - to: profiler, - type: "startProfiler", - features: [] - }, function (aResponse) { - do_check_true(Profiler.IsActive()); - client.close(); - }); - }); - }); -} diff --git a/toolkit/devtools/server/tests/unit/xpcshell.ini b/toolkit/devtools/server/tests/unit/xpcshell.ini index 3eee53d9cb08..08e931295120 100644 --- a/toolkit/devtools/server/tests/unit/xpcshell.ini +++ b/toolkit/devtools/server/tests/unit/xpcshell.ini @@ -179,10 +179,6 @@ reason = bug 820380 skip-if = toolkit == "gonk" reason = bug 820380 [test_breakpointstore.js] -[test_profiler_actor.js] -[test_profiler_activation.js] -skip-if = toolkit == "gonk" -reason = bug 820380 [test_unsafeDereference.js] [test_add_actors.js] [test_trace_actor-01.js]