1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/profiler/cleopatra.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,166 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +let { Cu } = require("chrome"); 1.11 +let { defer } = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise; 1.12 +let EventEmitter = require("devtools/toolkit/event-emitter"); 1.13 + 1.14 +const { PROFILE_IDLE, PROFILE_COMPLETED, PROFILE_RUNNING } = require("devtools/profiler/consts"); 1.15 + 1.16 +/** 1.17 + * An implementation of a profile visualization that uses Cleopatra. 1.18 + * It consists of an iframe with Cleopatra loaded in it and some 1.19 + * surrounding meta-data (such as UIDs). 1.20 + * 1.21 + * Cleopatra is also an event emitter. It emits the following events: 1.22 + * - ready, when Cleopatra is done loading (you can also check the isReady 1.23 + * property to see if a particular instance has been loaded yet. 1.24 + * 1.25 + * @param number uid 1.26 + * Unique ID for this profile. 1.27 + * @param ProfilerPanel panel 1.28 + * A reference to the container panel. 1.29 + */ 1.30 +function Cleopatra(panel, opts) { 1.31 + let doc = panel.document; 1.32 + let win = panel.window; 1.33 + let { uid, name } = opts; 1.34 + let spd = opts.showPlatformData; 1.35 + let ext = opts.external; 1.36 + 1.37 + EventEmitter.decorate(this); 1.38 + 1.39 + this.isReady = false; 1.40 + this.isStarted = false; 1.41 + this.isFinished = false; 1.42 + 1.43 + this.panel = panel; 1.44 + this.uid = uid; 1.45 + this.name = name; 1.46 + 1.47 + this.iframe = doc.createElement("iframe"); 1.48 + this.iframe.setAttribute("flex", "1"); 1.49 + this.iframe.setAttribute("id", "profiler-cleo-" + uid); 1.50 + this.iframe.setAttribute("src", "cleopatra.html?uid=" + uid + "&spd=" + spd + "&ext=" + ext); 1.51 + this.iframe.setAttribute("hidden", "true"); 1.52 + 1.53 + // Append our iframe and subscribe to postMessage events. 1.54 + // They'll tell us when the underlying page is done loading 1.55 + // or when user clicks on start/stop buttons. 1.56 + 1.57 + doc.getElementById("profiler-report").appendChild(this.iframe); 1.58 + win.addEventListener("message", function (event) { 1.59 + if (parseInt(event.data.uid, 10) !== parseInt(this.uid, 10)) { 1.60 + return; 1.61 + } 1.62 + 1.63 + switch (event.data.status) { 1.64 + case "loaded": 1.65 + this.isReady = true; 1.66 + this.emit("ready"); 1.67 + break; 1.68 + case "displaysource": 1.69 + this.panel.displaySource(event.data.data); 1.70 + } 1.71 + }.bind(this)); 1.72 +} 1.73 + 1.74 +Cleopatra.prototype = { 1.75 + /** 1.76 + * Returns a contentWindow of the iframe pointing to Cleopatra 1.77 + * if it exists and can be accessed. Otherwise returns null. 1.78 + */ 1.79 + get contentWindow() { 1.80 + if (!this.iframe) { 1.81 + return null; 1.82 + } 1.83 + 1.84 + try { 1.85 + return this.iframe.contentWindow; 1.86 + } catch (err) { 1.87 + return null; 1.88 + } 1.89 + }, 1.90 + 1.91 + show: function () { 1.92 + this.iframe.removeAttribute("hidden"); 1.93 + }, 1.94 + 1.95 + hide: function () { 1.96 + this.iframe.setAttribute("hidden", true); 1.97 + }, 1.98 + 1.99 + /** 1.100 + * Send raw profiling data to Cleopatra for parsing. 1.101 + * 1.102 + * @param object data 1.103 + * Raw profiling data from the SPS Profiler. 1.104 + * @param function onParsed 1.105 + * A callback to be called when Cleopatra finishes 1.106 + * parsing and displaying results. 1.107 + * 1.108 + */ 1.109 + parse: function (data, onParsed) { 1.110 + if (!this.isReady) { 1.111 + return void this.on("ready", this.parse.bind(this, data, onParsed)); 1.112 + } 1.113 + 1.114 + this.message({ task: "receiveProfileData", rawProfile: data }).then(() => { 1.115 + let poll = () => { 1.116 + let wait = this.panel.window.setTimeout.bind(null, poll, 100); 1.117 + let trail = this.contentWindow.gBreadcrumbTrail; 1.118 + 1.119 + if (!trail) { 1.120 + return wait(); 1.121 + } 1.122 + 1.123 + if (!trail._breadcrumbs || !trail._breadcrumbs.length) { 1.124 + return wait(); 1.125 + } 1.126 + 1.127 + onParsed(); 1.128 + }; 1.129 + 1.130 + poll(); 1.131 + }); 1.132 + }, 1.133 + 1.134 + /** 1.135 + * Send a message to Cleopatra instance. If a message cannot be 1.136 + * sent, this method queues it for later. 1.137 + * 1.138 + * @param object data JSON data to send (must be serializable) 1.139 + * @return promise 1.140 + */ 1.141 + message: function (data) { 1.142 + let deferred = defer(); 1.143 + data = JSON.stringify(data); 1.144 + 1.145 + let send = () => { 1.146 + if (!this.contentWindow) 1.147 + setTimeout(send, 50); 1.148 + 1.149 + this.contentWindow.postMessage(data, "*"); 1.150 + deferred.resolve(); 1.151 + }; 1.152 + 1.153 + send(); 1.154 + return deferred.promise; 1.155 + }, 1.156 + 1.157 + /** 1.158 + * Destroys the ProfileUI instance. 1.159 + */ 1.160 + destroy: function () { 1.161 + this.isReady = null; 1.162 + this.panel = null; 1.163 + this.uid = null; 1.164 + this.iframe = null; 1.165 + this.messages = null; 1.166 + } 1.167 +}; 1.168 + 1.169 +module.exports = Cleopatra; 1.170 \ No newline at end of file