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