michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: let { Cu } = require("chrome"); michael@0: let { defer } = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise; michael@0: let EventEmitter = require("devtools/toolkit/event-emitter"); michael@0: michael@0: const { PROFILE_IDLE, PROFILE_COMPLETED, PROFILE_RUNNING } = require("devtools/profiler/consts"); michael@0: michael@0: /** michael@0: * An implementation of a profile visualization that uses Cleopatra. michael@0: * It consists of an iframe with Cleopatra loaded in it and some michael@0: * surrounding meta-data (such as UIDs). michael@0: * michael@0: * Cleopatra is also an event emitter. It emits the following events: michael@0: * - ready, when Cleopatra is done loading (you can also check the isReady michael@0: * property to see if a particular instance has been loaded yet. michael@0: * michael@0: * @param number uid michael@0: * Unique ID for this profile. michael@0: * @param ProfilerPanel panel michael@0: * A reference to the container panel. michael@0: */ michael@0: function Cleopatra(panel, opts) { michael@0: let doc = panel.document; michael@0: let win = panel.window; michael@0: let { uid, name } = opts; michael@0: let spd = opts.showPlatformData; michael@0: let ext = opts.external; michael@0: michael@0: EventEmitter.decorate(this); michael@0: michael@0: this.isReady = false; michael@0: this.isStarted = false; michael@0: this.isFinished = false; michael@0: michael@0: this.panel = panel; michael@0: this.uid = uid; michael@0: this.name = name; michael@0: michael@0: this.iframe = doc.createElement("iframe"); michael@0: this.iframe.setAttribute("flex", "1"); michael@0: this.iframe.setAttribute("id", "profiler-cleo-" + uid); michael@0: this.iframe.setAttribute("src", "cleopatra.html?uid=" + uid + "&spd=" + spd + "&ext=" + ext); michael@0: this.iframe.setAttribute("hidden", "true"); michael@0: michael@0: // Append our iframe and subscribe to postMessage events. michael@0: // They'll tell us when the underlying page is done loading michael@0: // or when user clicks on start/stop buttons. michael@0: michael@0: doc.getElementById("profiler-report").appendChild(this.iframe); michael@0: win.addEventListener("message", function (event) { michael@0: if (parseInt(event.data.uid, 10) !== parseInt(this.uid, 10)) { michael@0: return; michael@0: } michael@0: michael@0: switch (event.data.status) { michael@0: case "loaded": michael@0: this.isReady = true; michael@0: this.emit("ready"); michael@0: break; michael@0: case "displaysource": michael@0: this.panel.displaySource(event.data.data); michael@0: } michael@0: }.bind(this)); michael@0: } michael@0: michael@0: Cleopatra.prototype = { michael@0: /** michael@0: * Returns a contentWindow of the iframe pointing to Cleopatra michael@0: * if it exists and can be accessed. Otherwise returns null. michael@0: */ michael@0: get contentWindow() { michael@0: if (!this.iframe) { michael@0: return null; michael@0: } michael@0: michael@0: try { michael@0: return this.iframe.contentWindow; michael@0: } catch (err) { michael@0: return null; michael@0: } michael@0: }, michael@0: michael@0: show: function () { michael@0: this.iframe.removeAttribute("hidden"); michael@0: }, michael@0: michael@0: hide: function () { michael@0: this.iframe.setAttribute("hidden", true); michael@0: }, michael@0: michael@0: /** michael@0: * Send raw profiling data to Cleopatra for parsing. michael@0: * michael@0: * @param object data michael@0: * Raw profiling data from the SPS Profiler. michael@0: * @param function onParsed michael@0: * A callback to be called when Cleopatra finishes michael@0: * parsing and displaying results. michael@0: * michael@0: */ michael@0: parse: function (data, onParsed) { michael@0: if (!this.isReady) { michael@0: return void this.on("ready", this.parse.bind(this, data, onParsed)); michael@0: } michael@0: michael@0: this.message({ task: "receiveProfileData", rawProfile: data }).then(() => { michael@0: let poll = () => { michael@0: let wait = this.panel.window.setTimeout.bind(null, poll, 100); michael@0: let trail = this.contentWindow.gBreadcrumbTrail; michael@0: michael@0: if (!trail) { michael@0: return wait(); michael@0: } michael@0: michael@0: if (!trail._breadcrumbs || !trail._breadcrumbs.length) { michael@0: return wait(); michael@0: } michael@0: michael@0: onParsed(); michael@0: }; michael@0: michael@0: poll(); michael@0: }); michael@0: }, michael@0: michael@0: /** michael@0: * Send a message to Cleopatra instance. If a message cannot be michael@0: * sent, this method queues it for later. michael@0: * michael@0: * @param object data JSON data to send (must be serializable) michael@0: * @return promise michael@0: */ michael@0: message: function (data) { michael@0: let deferred = defer(); michael@0: data = JSON.stringify(data); michael@0: michael@0: let send = () => { michael@0: if (!this.contentWindow) michael@0: setTimeout(send, 50); michael@0: michael@0: this.contentWindow.postMessage(data, "*"); michael@0: deferred.resolve(); michael@0: }; michael@0: michael@0: send(); michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: /** michael@0: * Destroys the ProfileUI instance. michael@0: */ michael@0: destroy: function () { michael@0: this.isReady = null; michael@0: this.panel = null; michael@0: this.uid = null; michael@0: this.iframe = null; michael@0: this.messages = null; michael@0: } michael@0: }; michael@0: michael@0: module.exports = Cleopatra;