Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / tools / memory_inspector / chrome_app / template / main_window.js
blob024c143d47dc13555875d074b1a4a5e9547b1c20
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.
5 'use strict';
7 /**
8  * Singleton object representing the main window of the Memory Inspector Chrome
9  * App.
10  * @constructor
11  */
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;
23   // Dots animation.
24   this.loadDotsAnimationIntervalId_ = undefined;
26   // Process manager.
27   this.processManager_ = undefined;
29   // Terminal.
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;
45 /**
46  * Initialize the main window. It will be shown once settings are retrieved
47  * (asynchronous request).
48  * @private
49  */
50 MemoryInspectorWindow.prototype.initialize = function() {
51   this.setUpGui_();
52   this.startServerProcess_();
53   this.retrieveSettings_();
54   this.pollServer_();
57 /**
58  * Set up the main window. This method retrieves relevant HTML elements, adds
59  * corresponding event handlers, and starts animations.
60  * @private
61  */
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);
87 /**
88  * Toggle the terminal.
89  * @private
90  */
91 MemoryInspectorWindow.prototype.toggleTerminal_ = function() {
92   if (this.terminalVisible_) {
93     this.hideTerminal_();
94   } else {
95     this.showTerminal_();
96   }
97   this.storeSettings_();
101  * Show the terminal.
102  * @private
103  */
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;
112  * Hide the terminal.
113  * @private
114  */
115 MemoryInspectorWindow.prototype.hideTerminal_ = function() {
116   document.body.classList.remove('terminal_visible');
117   this.terminalVisible_ = false;
121  * Stop the loading dots animation.
122  * @private
123  */
124 MemoryInspectorWindow.prototype.stopLoadDotsAnimation_ = function() {
125   window.clearInterval(this.loadDotsAnimationIntervalId_);
126   this.loadDotsAnimationIntervalId_ = undefined;
130  * Animate the loading dots.
131  * @private
132  */
133 MemoryInspectorWindow.prototype.animateLoadDots_ = function() {
134   if (this.loadDotsElement_.innerText.length >=
135       MemoryInspectorWindow.MAX_LOAD_DOTS_COUNT) {
136     this.loadDotsElement_.innerText = '.';
137   } else {
138     this.loadDotsElement_.innerText += '.';
139   }
143  * Start the server process inside PNaCl.
144  * @private
145  */
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');
160   mgr.spawn(
161       MemoryInspectorConfig.NMF,
162       MemoryInspectorConfig.ARGV,
163       MemoryInspectorConfig.ENV,
164       MemoryInspectorConfig.CWD,
165       'pnacl',
166       null /* parent */,
167       this.onServerProcessSpawned_.bind(this));
171  * Listener called when the server process is spawned.
172  * @private
173  * @param {number} pid The PID of the spawned server process or error code if
174  *     negative.
175  */
176 MemoryInspectorWindow.prototype.onServerProcessSpawned_ = function(pid) {
177   this.processManager.waitpid(pid, 0, this.onServerProcessExit_.bind(this));
181  * Listener called when the server process exits.
182  * @private
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.
185  */
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.
192  * @private
193  * @param {string} msg The string sent to stdout.
194  */
195 MemoryInspectorWindow.prototype.onServerStdout_ = function(msg) {
196   this.printOutput_(msg);
200  * Listener called when an error event is received from the server process.
201  * @private
202  * @param {string} cmd The name of the process with the error.
203  * @param {string} err The error message.
204  */
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.
211  * @private
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.
216  */
217 MemoryInspectorWindow.prototype.onServerProgress_ = function(url,
218     lengthComputable, loaded, total) {
219   if (url === undefined) {
220     return;
221   }
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 + '%]';
229   }
230   this.printInfo_(message + '\n');
234  * Listener called when the server NaCl module has been successfully loaded.
235  * @private
236  */
237 MemoryInspectorWindow.prototype.onServerLoad_ = function() {
238   this.printInfo_('NaCl module loaded\n');
242  * Print an output message in the terminal.
243  * @private
244  * @param {string} msg The text of the message.
245  */
246 MemoryInspectorWindow.prototype.printOutput_ = function(msg) {
247   this.printMessage_(msg, 'terminal_message_output');
251  * Print an info message in the terminal.
252  * @private
253  * @param {string} msg The text of the message.
254  */
255 MemoryInspectorWindow.prototype.printInfo_ = function(msg) {
256   this.printMessage_(msg, 'terminal_message_info');
260  * Print an error message in the terminal.
261  * @private
262  * @param {string} msg The text of the message.
263  */
264 MemoryInspectorWindow.prototype.printError_ = function(msg) {
265   this.printMessage_(msg, 'terminal_message_error');
269  * Print a message of a given type in the terminal.
270  * @private
271  * @param {string} msg The text of the message.
272  * @param {string} cls The CSS class of the message.
273  */
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.
289   if (shouldScroll) {
290     this.terminalElement_.scrollTop = this.terminalElement_.scrollHeight;
291   }
295  * Asynchronously retrieve main window settings from local storage.
296  * @private
297  */
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.
305  * @private
306  */
307 MemoryInspectorWindow.prototype.storeSettings_ = function() {
308   var settings = {};
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.
316  * @private
317  * @param {Object<*>} settings The retrieved settings.
318  */
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';
325   }
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
333  * asynchronous.
334  * @private
335  */
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);
343   request.send();
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.
350  * @private
351  * @param {XMLHttpRequest} request The request.
352  */
353 MemoryInspectorWindow.prototype.onPollRequestStateChange_ = function(request) {
354   // We wait until the request is complete.
355   if (request.readyState !== XMLHttpRequest.DONE) {
356     return;
357   }
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);
364     return;
365   }
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.
380  * @private
381  */
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.
389  * @private
390  */
391 MemoryInspectorWindow.prototype.onInspectorNavigated_ = function() {
392   this.inspectorViewElement_.executeScript({
393     'file': 'inject.js',
394     'runAt': 'document_start'
395   });
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;