1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
8 * Singleton object representing the main window of the Memory Inspector Chrome
12 var MemoryInspectorWindow = function() {
13 // HTML elements in the window.
14 this.terminalElement_
= undefined;
15 this.terminalButtonElement_
= undefined;
16 this.contentsElement_
= undefined;
17 this.inspectorViewElement_
= undefined;
18 this.loadOverlayElement_
= undefined;
19 this.loadMessageElement_
= undefined;
20 this.loadDotsElement_
= undefined;
21 this.phantomLoadDots_
= undefined;
24 this.loadDotsAnimationIntervalId_
= undefined;
27 this.processManager_
= undefined;
30 this.terminalVisible_
= false;
33 /** The threshold for terminal auto-scroll. */
34 MemoryInspectorWindow
.AUTOSCROLL_THRESHOLD
= 10;
36 /** The interval between steps of the loading dots animation (milliseconds). */
37 MemoryInspectorWindow
.LOAD_DOTS_ANIMATION_INTERVAL
= 500;
39 /** The maximum number of dots in the loading dots animation. */
40 MemoryInspectorWindow
.MAX_LOAD_DOTS_COUNT
= 3;
42 /** The interval between polls to the backend server (milliseconds). */
43 MemoryInspectorWindow
.POLL_INTERVALL
= 500;
46 * Initialize the main window. It will be shown once settings are retrieved
47 * (asynchronous request).
50 MemoryInspectorWindow
.prototype.initialize = function() {
52 this.startServerProcess_();
53 this.retrieveSettings_();
58 * Set up the main window. This method retrieves relevant HTML elements, adds
59 * corresponding event handlers, and starts animations.
62 MemoryInspectorWindow
.prototype.setUpGui_ = function() {
63 // Retrieve HTML elements in the window.
64 this.terminalElement_
= document
.getElementById('terminal');
65 this.terminalButtonElement_
= document
.getElementById('terminal_button');
66 this.contentsElement_
= document
.getElementById('contents');
67 this.inspectorViewElement_
= document
.getElementById('inspector_view');
68 this.loadOverlayElement_
= document
.getElementById('load_overlay');
69 this.loadMessageElement_
= document
.getElementById('load_message');
70 this.loadDotsElement_
= document
.getElementById('load_dots');
71 this.phantomLoadDotsElement_
= document
.getElementById('phantom_load_dots');
73 // Hook up the terminal toggle button.
74 this.terminalButtonElement_
.addEventListener('click',
75 this.toggleTerminal_
.bind(this));
77 // Print app name and version in the terminal.
78 var manifest
= chrome
.runtime
.getManifest();
79 this.printInfo_(manifest
.name
+ ' (version ' + manifest
.version
+ ')\n');
81 // Start the loading dots animation.
82 this.loadDotsAnimationIntervalId_
= window
.setInterval(
83 this.animateLoadDots_
.bind(this),
84 MemoryInspectorWindow
.LOAD_DOTS_ANIMATION_INTERVAL
);
88 * Toggle the terminal.
91 MemoryInspectorWindow
.prototype.toggleTerminal_ = function() {
92 if (this.terminalVisible_
) {
97 this.storeSettings_();
104 MemoryInspectorWindow
.prototype.showTerminal_ = function() {
105 // Scroll to the bottom.
106 this.terminalElement_
.scrollTop
= terminal
.scrollHeight
;
107 document
.body
.classList
.add('terminal_visible');
108 this.terminalVisible_
= true;
115 MemoryInspectorWindow
.prototype.hideTerminal_ = function() {
116 document
.body
.classList
.remove('terminal_visible');
117 this.terminalVisible_
= false;
121 * Stop the loading dots animation.
124 MemoryInspectorWindow
.prototype.stopLoadDotsAnimation_ = function() {
125 window
.clearInterval(this.loadDotsAnimationIntervalId_
);
126 this.loadDotsAnimationIntervalId_
= undefined;
130 * Animate the loading dots.
133 MemoryInspectorWindow
.prototype.animateLoadDots_ = function() {
134 if (this.loadDotsElement_
.innerText
.length
>=
135 MemoryInspectorWindow
.MAX_LOAD_DOTS_COUNT
) {
136 this.loadDotsElement_
.innerText
= '.';
138 this.loadDotsElement_
.innerText
+= '.';
143 * Start the server process inside PNaCl.
146 MemoryInspectorWindow
.prototype.startServerProcess_ = function() {
147 // Create and hook up a NaCl process manager.
148 var mgr
= this.processManager
= new NaClProcessManager();
149 mgr
.setStdoutListener(this.onServerStdout_
.bind(this));
150 mgr
.setErrorListener(this.onServerError_
.bind(this));
151 mgr
.setRootProgressListener(this.onServerProgress_
.bind(this));
152 mgr
.setRootLoadListener(this.onServerLoad_
.bind(this));
154 // Set dummy terminal size.
155 // (see https://code.google.com/p/naclports/issues/detail?id=186)
156 mgr
.onTerminalResize(200, 200);
158 // Spawn the server process.
159 this.printInfo_('Spawning ' + MemoryInspectorConfig
.ARGV
.join(' ') + '\n');
161 MemoryInspectorConfig
.NMF
,
162 MemoryInspectorConfig
.ARGV
,
163 MemoryInspectorConfig
.ENV
,
164 MemoryInspectorConfig
.CWD
,
167 this.onServerProcessSpawned_
.bind(this));
171 * Listener called when the server process is spawned.
173 * @param {number} pid The PID of the spawned server process or error code if
176 MemoryInspectorWindow
.prototype.onServerProcessSpawned_ = function(pid
) {
177 this.processManager
.waitpid(pid
, 0, this.onServerProcessExit_
.bind(this));
181 * Listener called when the server process exits.
183 * @param {number} pid The PID of the server process or an error code on error.
184 * @param {number} status The exit code of the server process.
186 MemoryInspectorWindow
.prototype.onServerProcessExit_ = function(pid
, status
) {
187 this.printInfo_('NaCl module exited with status ' + status
+ '\n');
191 * Listener called when an stdout event is received from the server process.
193 * @param {string} msg The string sent to stdout.
195 MemoryInspectorWindow
.prototype.onServerStdout_ = function(msg
) {
196 this.printOutput_(msg
);
200 * Listener called when an error event is received from the server process.
202 * @param {string} cmd The name of the process with the error.
203 * @param {string} err The error message.
205 MemoryInspectorWindow
.prototype.onServerError_ = function(cmd
, err
) {
206 this.printError_(cmd
+ ': ' + err
+ '\n');
210 * Listener called when a part of the server NaCl module has been loaded.
212 * @param {string} url The URL that is being loaded.
213 * @param {boolean} lengthComputable Is our progress quantitatively measurable?
214 * @param {number} loaded The number of bytes that have been loaded.
215 * @param {number} total The total number of bytes to be loaded.
217 MemoryInspectorWindow
.prototype.onServerProgress_ = function(url
,
218 lengthComputable
, loaded
, total
) {
219 if (url
=== undefined) {
223 var message
= 'Loading ' + url
.substring(url
.lastIndexOf('/') + 1);
224 if (lengthComputable
&& total
> 0) {
225 var percentLoaded
= Math
.round(loaded
/ total
* 100);
226 var kbLoaded
= Math
.round(loaded
/ 1024);
227 var kbTotal
= Math
.round(total
/ 1024);
228 message
+= ' [' + kbLoaded
+ '/' + kbTotal
+ ' KiB ' + percentLoaded
+ '%]';
230 this.printInfo_(message
+ '\n');
234 * Listener called when the server NaCl module has been successfully loaded.
237 MemoryInspectorWindow
.prototype.onServerLoad_ = function() {
238 this.printInfo_('NaCl module loaded\n');
242 * Print an output message in the terminal.
244 * @param {string} msg The text of the message.
246 MemoryInspectorWindow
.prototype.printOutput_ = function(msg
) {
247 this.printMessage_(msg
, 'terminal_message_output');
251 * Print an info message in the terminal.
253 * @param {string} msg The text of the message.
255 MemoryInspectorWindow
.prototype.printInfo_ = function(msg
) {
256 this.printMessage_(msg
, 'terminal_message_info');
260 * Print an error message in the terminal.
262 * @param {string} msg The text of the message.
264 MemoryInspectorWindow
.prototype.printError_ = function(msg
) {
265 this.printMessage_(msg
, 'terminal_message_error');
269 * Print a message of a given type in the terminal.
271 * @param {string} msg The text of the message.
272 * @param {string} cls The CSS class of the message.
274 MemoryInspectorWindow
.prototype.printMessage_ = function(msg
, cls
) {
275 // Determine whether we are at the bottom of the terminal.
276 var scrollBottom
= this.terminalElement_
.scrollTop
+
277 this.terminalElement_
.clientHeight
;
278 var autoscrollBottomMin
= this.terminalElement_
.scrollHeight
-
279 MemoryInspectorWindow
.AUTOSCROLL_THRESHOLD
;
280 var shouldScroll
= scrollBottom
>= autoscrollBottomMin
;
282 // Append the message to the terminal.
283 var messageElement
= document
.createElement('span');
284 messageElement
.innerText
= msg
;
285 messageElement
.classList
.add(cls
);
286 this.terminalElement_
.appendChild(messageElement
);
288 // If we were at the bottom of the terminal, scroll to the bottom again.
290 this.terminalElement_
.scrollTop
= this.terminalElement_
.scrollHeight
;
295 * Asynchronously retrieve main window settings from local storage.
298 MemoryInspectorWindow
.prototype.retrieveSettings_ = function() {
299 chrome
.storage
.local
.get('terminal_visible',
300 this.onSettingsRetrieved_
.bind(this));
304 * Asynchronously store main window settings in local storage.
307 MemoryInspectorWindow
.prototype.storeSettings_ = function() {
309 settings
['terminal_visible'] = this.terminalVisible_
;
310 chrome
.storage
.local
.set(settings
);
314 * Listener called when main window settings were retrieved from local storage.
315 * It saves them and finally shows the app window.
317 * @param {Object<*>} settings The retrieved settings.
319 MemoryInspectorWindow
.prototype.onSettingsRetrieved_ = function(settings
) {
320 if (chrome
.runtime
.lastError
=== undefined && settings
['terminal_visible']) {
321 // Skip CSS animations by temporarily hiding everything.
322 document
.body
.style
.display
= 'none';
323 this.showTerminal_();
324 document
.body
.style
.display
= 'block';
327 // We are finally ready to show the window.
328 chrome
.app
.window
.current().show();
332 * Keep polling the backend server until it is reachable. The requests are
336 MemoryInspectorWindow
.prototype.pollServer_ = function() {
337 var requestUrl
= 'http://127.0.0.1:' + MemoryInspectorConfig
.PORT
+
338 '?timestamp=' + new Date().getTime();
339 var request
= new XMLHttpRequest();
340 request
.addEventListener('readystatechange',
341 this.onPollRequestStateChange_
.bind(this, request
));
342 request
.open('GET', requestUrl
);
347 * Listener called when the state of the server request changes. If the request
348 * is complete and successful, the inspector view is navigated to the server.
349 * If unsuccessful, the request is repeated. Incomplete requests are ignored.
351 * @param {XMLHttpRequest} request The request.
353 MemoryInspectorWindow
.prototype.onPollRequestStateChange_ = function(request
) {
354 // We wait until the request is complete.
355 if (request
.readyState
!== XMLHttpRequest
.DONE
) {
359 // If the request was not successful, try again.
360 if (request
.status
!== 200) {
361 console
.log('Inspector server unreachable. Trying again...');
362 setTimeout(this.pollServer_
.bind(this),
363 MemoryInspectorWindow
.POLL_INTERVALL
);
367 // Stop the animation, load the inspector, and display it.
368 console
.log('Inspector server ready. Loading the inspector view...');
369 this.inspectorViewElement_
.addEventListener('loadstop',
370 this.onInspectorLoaded_
.bind(this));
371 this.inspectorViewElement_
.addEventListener('loadcommit',
372 this.onInspectorNavigated_
.bind(this));
373 this.inspectorViewElement_
.src
= 'http://127.0.0.1:' +
374 MemoryInspectorConfig
.PORT
;
378 * Listener called when the inspector is loaded. It stops the loading dots
379 * animation, shows the inspector view, and fades out the loading overlay.
382 MemoryInspectorWindow
.prototype.onInspectorLoaded_ = function() {
383 this.stopLoadDotsAnimation_();
384 document
.body
.classList
.add('inspector_view_visible');
388 * Listener called when the inspector view navigates to the inspector website.
391 MemoryInspectorWindow
.prototype.onInspectorNavigated_ = function() {
392 this.inspectorViewElement_
.executeScript({
394 'runAt': 'document_start'
398 window
.addEventListener('load', function() {
399 // Create the singleton MemoryInspectorWindow instance and initialize it.
400 var mainWindow
= new MemoryInspectorWindow();
401 mainWindow
.initialize();
403 // Make the instance global for debugging purposes.
404 window
.mainWindow
= mainWindow
;