Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | "use strict"; |
michael@0 | 6 | |
michael@0 | 7 | let { Cu } = require("chrome"); |
michael@0 | 8 | let { defer } = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise; |
michael@0 | 9 | let EventEmitter = require("devtools/toolkit/event-emitter"); |
michael@0 | 10 | |
michael@0 | 11 | const { PROFILE_IDLE, PROFILE_COMPLETED, PROFILE_RUNNING } = require("devtools/profiler/consts"); |
michael@0 | 12 | |
michael@0 | 13 | /** |
michael@0 | 14 | * An implementation of a profile visualization that uses Cleopatra. |
michael@0 | 15 | * It consists of an iframe with Cleopatra loaded in it and some |
michael@0 | 16 | * surrounding meta-data (such as UIDs). |
michael@0 | 17 | * |
michael@0 | 18 | * Cleopatra is also an event emitter. It emits the following events: |
michael@0 | 19 | * - ready, when Cleopatra is done loading (you can also check the isReady |
michael@0 | 20 | * property to see if a particular instance has been loaded yet. |
michael@0 | 21 | * |
michael@0 | 22 | * @param number uid |
michael@0 | 23 | * Unique ID for this profile. |
michael@0 | 24 | * @param ProfilerPanel panel |
michael@0 | 25 | * A reference to the container panel. |
michael@0 | 26 | */ |
michael@0 | 27 | function Cleopatra(panel, opts) { |
michael@0 | 28 | let doc = panel.document; |
michael@0 | 29 | let win = panel.window; |
michael@0 | 30 | let { uid, name } = opts; |
michael@0 | 31 | let spd = opts.showPlatformData; |
michael@0 | 32 | let ext = opts.external; |
michael@0 | 33 | |
michael@0 | 34 | EventEmitter.decorate(this); |
michael@0 | 35 | |
michael@0 | 36 | this.isReady = false; |
michael@0 | 37 | this.isStarted = false; |
michael@0 | 38 | this.isFinished = false; |
michael@0 | 39 | |
michael@0 | 40 | this.panel = panel; |
michael@0 | 41 | this.uid = uid; |
michael@0 | 42 | this.name = name; |
michael@0 | 43 | |
michael@0 | 44 | this.iframe = doc.createElement("iframe"); |
michael@0 | 45 | this.iframe.setAttribute("flex", "1"); |
michael@0 | 46 | this.iframe.setAttribute("id", "profiler-cleo-" + uid); |
michael@0 | 47 | this.iframe.setAttribute("src", "cleopatra.html?uid=" + uid + "&spd=" + spd + "&ext=" + ext); |
michael@0 | 48 | this.iframe.setAttribute("hidden", "true"); |
michael@0 | 49 | |
michael@0 | 50 | // Append our iframe and subscribe to postMessage events. |
michael@0 | 51 | // They'll tell us when the underlying page is done loading |
michael@0 | 52 | // or when user clicks on start/stop buttons. |
michael@0 | 53 | |
michael@0 | 54 | doc.getElementById("profiler-report").appendChild(this.iframe); |
michael@0 | 55 | win.addEventListener("message", function (event) { |
michael@0 | 56 | if (parseInt(event.data.uid, 10) !== parseInt(this.uid, 10)) { |
michael@0 | 57 | return; |
michael@0 | 58 | } |
michael@0 | 59 | |
michael@0 | 60 | switch (event.data.status) { |
michael@0 | 61 | case "loaded": |
michael@0 | 62 | this.isReady = true; |
michael@0 | 63 | this.emit("ready"); |
michael@0 | 64 | break; |
michael@0 | 65 | case "displaysource": |
michael@0 | 66 | this.panel.displaySource(event.data.data); |
michael@0 | 67 | } |
michael@0 | 68 | }.bind(this)); |
michael@0 | 69 | } |
michael@0 | 70 | |
michael@0 | 71 | Cleopatra.prototype = { |
michael@0 | 72 | /** |
michael@0 | 73 | * Returns a contentWindow of the iframe pointing to Cleopatra |
michael@0 | 74 | * if it exists and can be accessed. Otherwise returns null. |
michael@0 | 75 | */ |
michael@0 | 76 | get contentWindow() { |
michael@0 | 77 | if (!this.iframe) { |
michael@0 | 78 | return null; |
michael@0 | 79 | } |
michael@0 | 80 | |
michael@0 | 81 | try { |
michael@0 | 82 | return this.iframe.contentWindow; |
michael@0 | 83 | } catch (err) { |
michael@0 | 84 | return null; |
michael@0 | 85 | } |
michael@0 | 86 | }, |
michael@0 | 87 | |
michael@0 | 88 | show: function () { |
michael@0 | 89 | this.iframe.removeAttribute("hidden"); |
michael@0 | 90 | }, |
michael@0 | 91 | |
michael@0 | 92 | hide: function () { |
michael@0 | 93 | this.iframe.setAttribute("hidden", true); |
michael@0 | 94 | }, |
michael@0 | 95 | |
michael@0 | 96 | /** |
michael@0 | 97 | * Send raw profiling data to Cleopatra for parsing. |
michael@0 | 98 | * |
michael@0 | 99 | * @param object data |
michael@0 | 100 | * Raw profiling data from the SPS Profiler. |
michael@0 | 101 | * @param function onParsed |
michael@0 | 102 | * A callback to be called when Cleopatra finishes |
michael@0 | 103 | * parsing and displaying results. |
michael@0 | 104 | * |
michael@0 | 105 | */ |
michael@0 | 106 | parse: function (data, onParsed) { |
michael@0 | 107 | if (!this.isReady) { |
michael@0 | 108 | return void this.on("ready", this.parse.bind(this, data, onParsed)); |
michael@0 | 109 | } |
michael@0 | 110 | |
michael@0 | 111 | this.message({ task: "receiveProfileData", rawProfile: data }).then(() => { |
michael@0 | 112 | let poll = () => { |
michael@0 | 113 | let wait = this.panel.window.setTimeout.bind(null, poll, 100); |
michael@0 | 114 | let trail = this.contentWindow.gBreadcrumbTrail; |
michael@0 | 115 | |
michael@0 | 116 | if (!trail) { |
michael@0 | 117 | return wait(); |
michael@0 | 118 | } |
michael@0 | 119 | |
michael@0 | 120 | if (!trail._breadcrumbs || !trail._breadcrumbs.length) { |
michael@0 | 121 | return wait(); |
michael@0 | 122 | } |
michael@0 | 123 | |
michael@0 | 124 | onParsed(); |
michael@0 | 125 | }; |
michael@0 | 126 | |
michael@0 | 127 | poll(); |
michael@0 | 128 | }); |
michael@0 | 129 | }, |
michael@0 | 130 | |
michael@0 | 131 | /** |
michael@0 | 132 | * Send a message to Cleopatra instance. If a message cannot be |
michael@0 | 133 | * sent, this method queues it for later. |
michael@0 | 134 | * |
michael@0 | 135 | * @param object data JSON data to send (must be serializable) |
michael@0 | 136 | * @return promise |
michael@0 | 137 | */ |
michael@0 | 138 | message: function (data) { |
michael@0 | 139 | let deferred = defer(); |
michael@0 | 140 | data = JSON.stringify(data); |
michael@0 | 141 | |
michael@0 | 142 | let send = () => { |
michael@0 | 143 | if (!this.contentWindow) |
michael@0 | 144 | setTimeout(send, 50); |
michael@0 | 145 | |
michael@0 | 146 | this.contentWindow.postMessage(data, "*"); |
michael@0 | 147 | deferred.resolve(); |
michael@0 | 148 | }; |
michael@0 | 149 | |
michael@0 | 150 | send(); |
michael@0 | 151 | return deferred.promise; |
michael@0 | 152 | }, |
michael@0 | 153 | |
michael@0 | 154 | /** |
michael@0 | 155 | * Destroys the ProfileUI instance. |
michael@0 | 156 | */ |
michael@0 | 157 | destroy: function () { |
michael@0 | 158 | this.isReady = null; |
michael@0 | 159 | this.panel = null; |
michael@0 | 160 | this.uid = null; |
michael@0 | 161 | this.iframe = null; |
michael@0 | 162 | this.messages = null; |
michael@0 | 163 | } |
michael@0 | 164 | }; |
michael@0 | 165 | |
michael@0 | 166 | module.exports = Cleopatra; |