Revert of Add button to add new FSP services to Files app. (patchset #8 id:140001...
[chromium-blink-merge.git] / chrome / browser / resources / inspect / inspect.js
blob78f117dac1b4573979faa5a8e922286be91f3129
1 // Copyright (c) 2012 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 var MIN_VERSION_TAB_CLOSE = 25;
6 var MIN_VERSION_TARGET_ID = 26;
7 var MIN_VERSION_NEW_TAB = 29;
8 var MIN_VERSION_TAB_ACTIVATE = 30;
9 var WEBRTC_SERIAL = 'WEBRTC';
11 var queryParamsObject = {};
12 var browserInspector;
13 var browserInspectorTitle;
15 (function() {
16 var queryParams = window.location.search;
17 if (!queryParams)
18     return;
19 var params = queryParams.substring(1).split('&');
20 for (var i = 0; i < params.length; ++i) {
21     var pair = params[i].split('=');
22     queryParamsObject[pair[0]] = pair[1];
25 if ('trace' in queryParamsObject || 'tracing' in queryParamsObject) {
26   browserInspector = 'chrome://tracing';
27   browserInspectorTitle = 'trace';
28 } else {
29   browserInspector = queryParamsObject['browser-inspector'];
30   browserInspectorTitle = 'inspect';
32 })();
34 function sendCommand(command, args) {
35   chrome.send(command, Array.prototype.slice.call(arguments, 1));
38 function sendTargetCommand(command, target) {
39   sendCommand(command, target.source, target.id);
42 function removeChildren(element_id) {
43   var element = $(element_id);
44   element.textContent = '';
47 function onload() {
48   var tabContents = document.querySelectorAll('#content > div');
49   for (var i = 0; i != tabContents.length; i++) {
50     var tabContent = tabContents[i];
51     var tabName = tabContent.querySelector('.content-header').textContent;
53     var tabHeader = document.createElement('div');
54     tabHeader.className = 'tab-header';
55     var button = document.createElement('button');
56     button.textContent = tabName;
57     tabHeader.appendChild(button);
58     tabHeader.addEventListener('click', selectTab.bind(null, tabContent.id));
59     $('navigation').appendChild(tabHeader);
60   }
61   onHashChange();
62   initSettings();
63   sendCommand('init-ui');
66 function onHashChange() {
67   var hash = window.location.hash.slice(1).toLowerCase();
68   if (!selectTab(hash))
69     selectTab('devices');
72 /**
73  * @param {string} id Tab id.
74  * @return {boolean} True if successful.
75  */
76 function selectTab(id) {
77   closePortForwardingConfig();
79   var tabContents = document.querySelectorAll('#content > div');
80   var tabHeaders = $('navigation').querySelectorAll('.tab-header');
81   var found = false;
82   for (var i = 0; i != tabContents.length; i++) {
83     var tabContent = tabContents[i];
84     var tabHeader = tabHeaders[i];
85     if (tabContent.id == id) {
86       tabContent.classList.add('selected');
87       tabHeader.classList.add('selected');
88       found = true;
89     } else {
90       tabContent.classList.remove('selected');
91       tabHeader.classList.remove('selected');
92     }
93   }
94   if (!found)
95     return false;
96   window.location.hash = id;
97   return true;
100 function populateTargets(source, data) {
101   if (source == 'local')
102     populateLocalTargets(data);
103   else if (source == 'remote')
104     populateRemoteTargets(data);
105   else
106     console.error('Unknown source type: ' + source);
109 function populateLocalTargets(data) {
110   removeChildren('pages-list');
111   removeChildren('extensions-list');
112   removeChildren('apps-list');
113   removeChildren('others-list');
114   removeChildren('workers-list');
115   removeChildren('service-workers-list');
117     for (var i = 0; i < data.length; i++) {
118     if (data[i].type === 'page')
119       addToPagesList(data[i]);
120     else if (data[i].type === 'background_page')
121       addToExtensionsList(data[i]);
122     else if (data[i].type === 'app')
123       addToAppsList(data[i]);
124     else if (data[i].type === 'worker')
125       addToWorkersList(data[i]);
126     else if (data[i].type === 'service_worker')
127       addToServiceWorkersList(data[i]);
128     else
129       addToOthersList(data[i]);
130   }
133 function showIncognitoWarning() {
134   $('devices-incognito').hidden = false;
137 function alreadyDisplayed(element, data) {
138   var json = JSON.stringify(data);
139   if (element.cachedJSON == json)
140     return true;
141   element.cachedJSON = json;
142   return false;
145 function updateBrowserVisibility(browserSection) {
146   var icon = browserSection.querySelector('.used-for-port-forwarding');
147   browserSection.hidden = !browserSection.querySelector('.open') &&
148                           !browserSection.querySelector('.row') &&
149                           !browserInspector &&
150                           (!icon || icon.hidden);
153 function updateUsernameVisibility(deviceSection) {
154   var users = new Set();
155   var browsers = deviceSection.querySelectorAll('.browser');
157   Array.prototype.forEach.call(browsers, function(browserSection) {
158     if (!browserSection.hidden) {
159       var browserUser = browserSection.querySelector('.browser-user');
160       if (browserUser)
161         users.add(browserUser.textContent);
162     }
163   });
164   var hasSingleUser = users.size <= 1;
166   Array.prototype.forEach.call(browsers, function(browserSection) {
167     var browserUser = browserSection.querySelector('.browser-user');
168     if (browserUser)
169       browserUser.hidden = hasSingleUser;
170   });
173 function populateRemoteTargets(devices) {
174   if (!devices)
175     return;
177   if (window.modal) {
178     window.holdDevices = devices;
179     return;
180   }
182   function browserCompare(a, b) {
183     if (a.adbBrowserName != b.adbBrowserName)
184       return a.adbBrowserName < b.adbBrowserName;
185     if (a.adbBrowserVersion != b.adbBrowserVersion)
186       return a.adbBrowserVersion < b.adbBrowserVersion;
187     return a.id < b.id;
188   }
190   function insertBrowser(browserList, browser) {
191     for (var sibling = browserList.firstElementChild; sibling;
192         sibling = sibling.nextElementSibling) {
193       if (browserCompare(browser, sibling)) {
194         browserList.insertBefore(browser, sibling);
195         return;
196       }
197     }
198     browserList.appendChild(browser);
199   }
201   var deviceList = $('devices-list');
202   if (alreadyDisplayed(deviceList, devices))
203     return;
205   function removeObsolete(validIds, section) {
206     if (validIds.indexOf(section.id) < 0)
207       section.remove();
208   }
210   var newDeviceIds = devices.map(function(d) { return d.id });
211   Array.prototype.forEach.call(
212       deviceList.querySelectorAll('.device'),
213       removeObsolete.bind(null, newDeviceIds));
215   $('devices-help').hidden = !!devices.length;
217   for (var d = 0; d < devices.length; d++) {
218     var device = devices[d];
220     var deviceSection = $(device.id);
221     if (!deviceSection) {
222       deviceSection = document.createElement('div');
223       deviceSection.id = device.id;
224       deviceSection.className = 'device';
225       deviceList.appendChild(deviceSection);
227       var deviceHeader = document.createElement('div');
228       deviceHeader.className = 'device-header';
229       deviceSection.appendChild(deviceHeader);
231       var deviceName = document.createElement('div');
232       deviceName.className = 'device-name';
233       deviceHeader.appendChild(deviceName);
235       var deviceSerial = document.createElement('div');
236       deviceSerial.className = 'device-serial';
237       var serial = device.adbSerial.toUpperCase();
238       deviceSerial.textContent = '#' + serial;
239       deviceHeader.appendChild(deviceSerial);
241       if (serial === WEBRTC_SERIAL)
242         deviceHeader.classList.add('hidden');
244       var devicePorts = document.createElement('div');
245       devicePorts.className = 'device-ports';
246       deviceHeader.appendChild(devicePorts);
248       var browserList = document.createElement('div');
249       browserList.className = 'browsers';
250       deviceSection.appendChild(browserList);
252       var authenticating = document.createElement('div');
253       authenticating.className = 'device-auth';
254       deviceSection.appendChild(authenticating);
255     }
257     if (alreadyDisplayed(deviceSection, device))
258       continue;
260     deviceSection.querySelector('.device-name').textContent = device.adbModel;
261     deviceSection.querySelector('.device-auth').textContent =
262         device.adbConnected ? '' : 'Pending authentication: please accept ' +
263           'debugging session on the device.';
265     var browserList = deviceSection.querySelector('.browsers');
266     var newBrowserIds =
267         device.browsers.map(function(b) { return b.id });
268     Array.prototype.forEach.call(
269         browserList.querySelectorAll('.browser'),
270         removeObsolete.bind(null, newBrowserIds));
272     for (var b = 0; b < device.browsers.length; b++) {
273       var browser = device.browsers[b];
274       var majorChromeVersion = browser.adbBrowserChromeVersion;
275       var pageList;
276       var browserSection = $(browser.id);
277       if (browserSection) {
278         pageList = browserSection.querySelector('.pages');
279       } else {
280         browserSection = document.createElement('div');
281         browserSection.id = browser.id;
282         browserSection.className = 'browser';
283         insertBrowser(browserList, browserSection);
285         var browserHeader = document.createElement('div');
286         browserHeader.className = 'browser-header';
288         var browserName = document.createElement('div');
289         browserName.className = 'browser-name';
290         browserHeader.appendChild(browserName);
291         browserName.textContent = browser.adbBrowserName;
292         if (browser.adbBrowserVersion)
293           browserName.textContent += ' (' + browser.adbBrowserVersion + ')';
294         if (browser.adbBrowserUser) {
295           var browserUser = document.createElement('div');
296           browserUser.className = 'browser-user';
297           browserUser.textContent = browser.adbBrowserUser;
298           browserHeader.appendChild(browserUser);
299         }
300         browserSection.appendChild(browserHeader);
302         if (majorChromeVersion >= MIN_VERSION_NEW_TAB) {
303           var newPage = document.createElement('div');
304           newPage.className = 'open';
306           var newPageUrl = document.createElement('input');
307           newPageUrl.type = 'text';
308           newPageUrl.placeholder = 'Open tab with url';
309           newPage.appendChild(newPageUrl);
311           var openHandler = function(sourceId, browserId, input) {
312             sendCommand(
313                 'open', sourceId, browserId, input.value || 'about:blank');
314             input.value = '';
315           }.bind(null, browser.source, browser.id, newPageUrl);
316           newPageUrl.addEventListener('keyup', function(handler, event) {
317             if (event.keyIdentifier == 'Enter' && event.target.value)
318               handler();
319           }.bind(null, openHandler), true);
321           var newPageButton = document.createElement('button');
322           newPageButton.textContent = 'Open';
323           newPage.appendChild(newPageButton);
324           newPageButton.addEventListener('click', openHandler, true);
326           browserHeader.appendChild(newPage);
327         }
329         var portForwardingInfo = document.createElement('div');
330         portForwardingInfo.className = 'used-for-port-forwarding';
331         portForwardingInfo.hidden = true;
332         portForwardingInfo.title = 'This browser is used for port ' +
333             'forwarding. Closing it will drop current connections.';
334         browserHeader.appendChild(portForwardingInfo);
336         if (browserInspector) {
337           var link = document.createElement('span');
338           link.classList.add('action');
339           link.setAttribute('tabindex', 1);
340           link.textContent = browserInspectorTitle;
341           browserHeader.appendChild(link);
342           link.addEventListener(
343               'click',
344               sendCommand.bind(null, 'inspect-browser', browser.source,
345                   browser.id, browserInspector), false);
346         }
348         pageList = document.createElement('div');
349         pageList.className = 'list pages';
350         browserSection.appendChild(pageList);
351       }
353       if (!alreadyDisplayed(browserSection, browser)) {
354         pageList.textContent = '';
355         for (var p = 0; p < browser.pages.length; p++) {
356           var page = browser.pages[p];
357           // Attached targets have no unique id until Chrome 26. For such
358           // targets it is impossible to activate existing DevTools window.
359           page.hasNoUniqueId = page.attached &&
360               majorChromeVersion && majorChromeVersion < MIN_VERSION_TARGET_ID;
361           var row = addTargetToList(page, pageList, ['name', 'url']);
362           if (page['description'])
363             addWebViewDetails(row, page);
364           else
365             addFavicon(row, page);
366           if (majorChromeVersion >= MIN_VERSION_TAB_ACTIVATE) {
367             addActionLink(row, 'focus tab',
368                 sendTargetCommand.bind(null, 'activate', page), false);
369           }
370           if (majorChromeVersion) {
371             addActionLink(row, 'reload',
372                 sendTargetCommand.bind(null, 'reload', page), page.attached);
373           }
374           if (majorChromeVersion >= MIN_VERSION_TAB_CLOSE) {
375             addActionLink(row, 'close',
376                 sendTargetCommand.bind(null, 'close', page), false);
377           }
378         }
379       }
380       updateBrowserVisibility(browserSection);
381     }
382     updateUsernameVisibility(deviceSection);
383   }
386 function addToPagesList(data) {
387   var row = addTargetToList(data, $('pages-list'), ['name', 'url']);
388   addFavicon(row, data);
389   if (data.guests)
390     addGuestViews(row, data.guests);
393 function addToExtensionsList(data) {
394   var row = addTargetToList(data, $('extensions-list'), ['name', 'url']);
395   addFavicon(row, data);
396   if (data.guests)
397     addGuestViews(row, data.guests);
400 function addToAppsList(data) {
401   var row = addTargetToList(data, $('apps-list'), ['name', 'url']);
402   addFavicon(row, data);
403   if (data.guests)
404     addGuestViews(row, data.guests);
407 function addGuestViews(row, guests) {
408   Array.prototype.forEach.call(guests, function(guest) {
409     var guestRow = addTargetToList(guest, row, ['name', 'url']);
410     guestRow.classList.add('guest');
411     addFavicon(guestRow, guest);
412   });
415 function addToWorkersList(data) {
416   var row =
417       addTargetToList(data, $('workers-list'), ['name', 'description', 'url']);
418   addActionLink(row, 'terminate',
419       sendTargetCommand.bind(null, 'close', data), false);
422 function addToServiceWorkersList(data) {
423     var row = addTargetToList(
424         data, $('service-workers-list'), ['name', 'description', 'url']);
425     addActionLink(row, 'terminate',
426         sendTargetCommand.bind(null, 'close', data), false);
429 function addToOthersList(data) {
430   addTargetToList(data, $('others-list'), ['url']);
433 function formatValue(data, property) {
434   var value = data[property];
436   if (property == 'name' && value == '') {
437     value = 'untitled';
438   }
440   var text = value ? String(value) : '';
441   if (text.length > 100)
442     text = text.substring(0, 100) + '\u2026';
444   var div = document.createElement('div');
445   div.textContent = text;
446   div.className = property;
447   return div;
450 function addFavicon(row, data) {
451   var favicon = document.createElement('img');
452   if (data['faviconUrl'])
453     favicon.src = data['faviconUrl'];
454   var propertiesBox = row.querySelector('.properties-box');
455   propertiesBox.insertBefore(favicon, propertiesBox.firstChild);
458 function addWebViewDetails(row, data) {
459   var webview;
460   try {
461     webview = JSON.parse(data['description']);
462   } catch (e) {
463     return;
464   }
465   addWebViewDescription(row, webview);
466   if (data.adbScreenWidth && data.adbScreenHeight)
467     addWebViewThumbnail(
468         row, webview, data.adbScreenWidth, data.adbScreenHeight);
471 function addWebViewDescription(row, webview) {
472   var viewStatus = { visibility: '', position: '', size: '' };
473   if (!webview.empty) {
474     if (webview.attached && !webview.visible)
475       viewStatus.visibility = 'hidden';
476     else if (!webview.attached)
477       viewStatus.visibility = 'detached';
478     viewStatus.size = 'size ' + webview.width + ' \u00d7 ' + webview.height;
479   } else {
480     viewStatus.visibility = 'empty';
481   }
482   if (webview.attached) {
483       viewStatus.position =
484         'at (' + webview.screenX + ', ' + webview.screenY + ')';
485   }
487   var subRow = document.createElement('div');
488   subRow.className = 'subrow webview';
489   if (webview.empty || !webview.attached || !webview.visible)
490     subRow.className += ' invisible-view';
491   if (viewStatus.visibility)
492     subRow.appendChild(formatValue(viewStatus, 'visibility'));
493   if (viewStatus.position)
494     subRow.appendChild(formatValue(viewStatus, 'position'));
495   subRow.appendChild(formatValue(viewStatus, 'size'));
496   var subrowBox = row.querySelector('.subrow-box');
497   subrowBox.insertBefore(subRow, row.querySelector('.actions'));
500 function addWebViewThumbnail(row, webview, screenWidth, screenHeight) {
501   var maxScreenRectSize = 50;
502   var screenRectWidth;
503   var screenRectHeight;
505   var aspectRatio = screenWidth / screenHeight;
506   if (aspectRatio < 1) {
507     screenRectWidth = Math.round(maxScreenRectSize * aspectRatio);
508     screenRectHeight = maxScreenRectSize;
509   } else {
510     screenRectWidth = maxScreenRectSize;
511     screenRectHeight = Math.round(maxScreenRectSize / aspectRatio);
512   }
514   var thumbnail = document.createElement('div');
515   thumbnail.className = 'webview-thumbnail';
516   var thumbnailWidth = 3 * screenRectWidth;
517   var thumbnailHeight = 60;
518   thumbnail.style.width = thumbnailWidth + 'px';
519   thumbnail.style.height = thumbnailHeight + 'px';
521   var screenRect = document.createElement('div');
522   screenRect.className = 'screen-rect';
523   screenRect.style.left = screenRectWidth + 'px';
524   screenRect.style.top = (thumbnailHeight - screenRectHeight) / 2 + 'px';
525   screenRect.style.width = screenRectWidth + 'px';
526   screenRect.style.height = screenRectHeight + 'px';
527   thumbnail.appendChild(screenRect);
529   if (!webview.empty && webview.attached) {
530     var viewRect = document.createElement('div');
531     viewRect.className = 'view-rect';
532     if (!webview.visible)
533       viewRect.classList.add('hidden');
534     function percent(ratio) {
535       return ratio * 100 + '%';
536     }
537     viewRect.style.left = percent(webview.screenX / screenWidth);
538     viewRect.style.top = percent(webview.screenY / screenHeight);
539     viewRect.style.width = percent(webview.width / screenWidth);
540     viewRect.style.height = percent(webview.height / screenHeight);
541     screenRect.appendChild(viewRect);
542   }
544   var propertiesBox = row.querySelector('.properties-box');
545   propertiesBox.insertBefore(thumbnail, propertiesBox.firstChild);
548 function addTargetToList(data, list, properties) {
549   var row = document.createElement('div');
550   row.className = 'row';
551   row.targetId = data.id;
553   var propertiesBox = document.createElement('div');
554   propertiesBox.className = 'properties-box';
555   row.appendChild(propertiesBox);
557   var subrowBox = document.createElement('div');
558   subrowBox.className = 'subrow-box';
559   propertiesBox.appendChild(subrowBox);
561   var subrow = document.createElement('div');
562   subrow.className = 'subrow';
563   subrowBox.appendChild(subrow);
565   for (var j = 0; j < properties.length; j++)
566     subrow.appendChild(formatValue(data, properties[j]));
568   var actionBox = document.createElement('div');
569   actionBox.className = 'actions';
570   subrowBox.appendChild(actionBox);
572   if (!data.hasCustomInspectAction) {
573     addActionLink(row, 'inspect', sendTargetCommand.bind(null, 'inspect', data),
574         data.hasNoUniqueId || data.adbAttachedForeign);
575   }
577   list.appendChild(row);
578   return row;
581 function addActionLink(row, text, handler, opt_disabled) {
582   var link = document.createElement('span');
583   link.classList.add('action');
584   link.setAttribute('tabindex', 1);
585   if (opt_disabled)
586     link.classList.add('disabled');
587   else
588     link.classList.remove('disabled');
590   link.textContent = text;
591   link.addEventListener('click', handler, true);
592   function handleKey(e) {
593     if (e.keyIdentifier == 'Enter' || e.keyIdentifier == 'U+0020') {
594       e.preventDefault();
595       handler();
596     }
597   }
598   link.addEventListener('keydown', handleKey, true);
599   row.querySelector('.actions').appendChild(link);
603 function initSettings() {
604   $('discover-usb-devices-enable').addEventListener('change',
605                                                     enableDiscoverUsbDevices);
607   $('port-forwarding-enable').addEventListener('change', enablePortForwarding);
608   $('port-forwarding-config-open').addEventListener(
609       'click', openPortForwardingConfig);
610   $('port-forwarding-config-close').addEventListener(
611       'click', closePortForwardingConfig);
612   $('port-forwarding-config-done').addEventListener(
613       'click', commitPortForwardingConfig.bind(true));
616 function enableDiscoverUsbDevices(event) {
617   sendCommand('set-discover-usb-devices-enabled', event.target.checked);
620 function enablePortForwarding(event) {
621   sendCommand('set-port-forwarding-enabled', event.target.checked);
624 function handleKey(event) {
625   switch (event.keyCode) {
626     case 13:  // Enter
627       if (event.target.nodeName == 'INPUT') {
628         var line = event.target.parentNode;
629         if (!line.classList.contains('fresh') ||
630             line.classList.contains('empty')) {
631           commitPortForwardingConfig(true);
632         } else {
633           commitFreshLineIfValid(true /* select new line */);
634           commitPortForwardingConfig(false);
635         }
636       } else {
637         commitPortForwardingConfig(true);
638       }
639       break;
641     case 27:
642       commitPortForwardingConfig(true);
643       break;
644   }
647 function setModal(dialog) {
648   dialog.deactivatedNodes = Array.prototype.filter.call(
649       document.querySelectorAll('*'),
650       function(n) {
651         return n != dialog && !dialog.contains(n) && n.tabIndex >= 0;
652       });
654   dialog.tabIndexes = dialog.deactivatedNodes.map(
655     function(n) { return n.getAttribute('tabindex'); });
657   dialog.deactivatedNodes.forEach(function(n) { n.tabIndex = -1; });
658   window.modal = dialog;
661 function unsetModal(dialog) {
662   for (var i = 0; i < dialog.deactivatedNodes.length; i++) {
663     var node = dialog.deactivatedNodes[i];
664     if (dialog.tabIndexes[i] === null)
665       node.removeAttribute('tabindex');
666     else
667       node.setAttribute('tabindex', dialog.tabIndexes[i]);
668   }
670   if (window.holdDevices) {
671     populateRemoteTargets(window.holdDevices);
672     delete window.holdDevices;
673   }
675   delete dialog.deactivatedNodes;
676   delete dialog.tabIndexes;
677   delete window.modal;
680 function openPortForwardingConfig() {
681   loadPortForwardingConfig(window.portForwardingConfig);
683   $('port-forwarding-overlay').classList.add('open');
684   document.addEventListener('keyup', handleKey);
686   var freshPort = document.querySelector('.fresh .port');
687   if (freshPort)
688     freshPort.focus();
689   else
690     $('port-forwarding-config-done').focus();
692   setModal($('port-forwarding-overlay'));
695 function closePortForwardingConfig() {
696   if (!$('port-forwarding-overlay').classList.contains('open'))
697     return;
699   $('port-forwarding-overlay').classList.remove('open');
700   document.removeEventListener('keyup', handleKey);
701   unsetModal($('port-forwarding-overlay'));
704 function loadPortForwardingConfig(config) {
705   var list = $('port-forwarding-config-list');
706   list.textContent = '';
707   for (var port in config)
708     list.appendChild(createConfigLine(port, config[port]));
709   list.appendChild(createEmptyConfigLine());
712 function commitPortForwardingConfig(closeConfig) {
713   if (closeConfig)
714     closePortForwardingConfig();
716   commitFreshLineIfValid();
717   var lines = document.querySelectorAll('.port-forwarding-pair');
718   var config = {};
719   for (var i = 0; i != lines.length; i++) {
720     var line = lines[i];
721     var portInput = line.querySelector('.port');
722     var locationInput = line.querySelector('.location');
724     var port = portInput.classList.contains('invalid') ?
725                portInput.lastValidValue :
726                portInput.value;
728     var location = locationInput.classList.contains('invalid') ?
729                    locationInput.lastValidValue :
730                    locationInput.value;
732     if (port && location)
733       config[port] = location;
734   }
735   sendCommand('set-port-forwarding-config', config);
738 function updateDiscoverUsbDevicesEnabled(enabled) {
739   var checkbox = $('discover-usb-devices-enable');
740   checkbox.checked = !!enabled;
741   checkbox.disabled = false;
744 function updatePortForwardingEnabled(enabled) {
745   var checkbox = $('port-forwarding-enable');
746   checkbox.checked = !!enabled;
747   checkbox.disabled = false;
750 function updatePortForwardingConfig(config) {
751   window.portForwardingConfig = config;
752   $('port-forwarding-config-open').disabled = !config;
755 function createConfigLine(port, location) {
756   var line = document.createElement('div');
757   line.className = 'port-forwarding-pair';
759   var portInput = createConfigField(port, 'port', 'Port', validatePort);
760   line.appendChild(portInput);
762   var locationInput = createConfigField(
763       location, 'location', 'IP address and port', validateLocation);
764   line.appendChild(locationInput);
765   locationInput.addEventListener('keydown', function(e) {
766     if (e.keyIdentifier == 'U+0009' &&  // Tab
767         !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey &&
768         line.classList.contains('fresh') &&
769         !line.classList.contains('empty')) {
770       // Tabbing forward on the fresh line, try create a new empty one.
771       if (commitFreshLineIfValid(true))
772         e.preventDefault();
773     }
774   });
776   var lineDelete = document.createElement('div');
777   lineDelete.className = 'close-button';
778   lineDelete.addEventListener('click', function() {
779     var newSelection = line.nextElementSibling;
780     line.parentNode.removeChild(line);
781     selectLine(newSelection);
782   });
783   line.appendChild(lineDelete);
785   line.addEventListener('click', selectLine.bind(null, line));
786   line.addEventListener('focus', selectLine.bind(null, line));
788   checkEmptyLine(line);
790   return line;
793 function validatePort(input) {
794   var match = input.value.match(/^(\d+)$/);
795   if (!match)
796     return false;
797   var port = parseInt(match[1]);
798   if (port < 1024 || 65535 < port)
799     return false;
801   var inputs = document.querySelectorAll('input.port:not(.invalid)');
802   for (var i = 0; i != inputs.length; ++i) {
803     if (inputs[i] == input)
804       break;
805     if (parseInt(inputs[i].value) == port)
806       return false;
807   }
808   return true;
811 function validateLocation(input) {
812   var match = input.value.match(/^([a-zA-Z0-9\.\-_]+):(\d+)$/);
813   if (!match)
814     return false;
815   var port = parseInt(match[2]);
816   return port <= 65535;
819 function createEmptyConfigLine() {
820   var line = createConfigLine('', '');
821   line.classList.add('fresh');
822   return line;
825 function createConfigField(value, className, hint, validate) {
826   var input = document.createElement('input');
827   input.className = className;
828   input.type = 'text';
829   input.placeholder = hint;
830   input.value = value;
831   input.lastValidValue = value;
833   function checkInput() {
834     if (validate(input))
835       input.classList.remove('invalid');
836     else
837       input.classList.add('invalid');
838     if (input.parentNode)
839       checkEmptyLine(input.parentNode);
840   }
841   checkInput();
843   input.addEventListener('keyup', checkInput);
844   input.addEventListener('focus', function() {
845     selectLine(input.parentNode);
846   });
848   input.addEventListener('blur', function() {
849     if (validate(input))
850       input.lastValidValue = input.value;
851   });
853   return input;
856 function checkEmptyLine(line) {
857   var inputs = line.querySelectorAll('input');
858   var empty = true;
859   for (var i = 0; i != inputs.length; i++) {
860     if (inputs[i].value != '')
861       empty = false;
862   }
863   if (empty)
864     line.classList.add('empty');
865   else
866     line.classList.remove('empty');
869 function selectLine(line) {
870   if (line.classList.contains('selected'))
871     return;
872   unselectLine();
873   line.classList.add('selected');
876 function unselectLine() {
877   var line = document.querySelector('.port-forwarding-pair.selected');
878   if (!line)
879     return;
880   line.classList.remove('selected');
881   commitFreshLineIfValid();
884 function commitFreshLineIfValid(opt_selectNew) {
885   var line = document.querySelector('.port-forwarding-pair.fresh');
886   if (line.querySelector('.invalid'))
887     return false;
888   line.classList.remove('fresh');
889   var freshLine = createEmptyConfigLine();
890   line.parentNode.appendChild(freshLine);
891   if (opt_selectNew)
892     freshLine.querySelector('.port').focus();
893   return true;
896 function populatePortStatus(devicesStatusMap) {
897   for (var deviceId in devicesStatusMap) {
898     if (!devicesStatusMap.hasOwnProperty(deviceId))
899       continue;
900     var deviceStatus = devicesStatusMap[deviceId];
901     var deviceStatusMap = deviceStatus.ports;
903     var deviceSection = $(deviceId);
904     if (!deviceSection)
905       continue;
907     var devicePorts = deviceSection.querySelector('.device-ports');
908     if (alreadyDisplayed(devicePorts, deviceStatus))
909       continue;
911     devicePorts.textContent = '';
912     for (var port in deviceStatusMap) {
913       if (!deviceStatusMap.hasOwnProperty(port))
914         continue;
916       var status = deviceStatusMap[port];
917       var portIcon = document.createElement('div');
918       portIcon.className = 'port-icon';
919       // status === 0 is the default (connected) state.
920       // Positive values correspond to the tunnelling connection count
921       // (in DEBUG_DEVTOOLS mode).
922       if (status > 0)
923         portIcon.classList.add('connected');
924       else if (status === -1 || status === -2)
925         portIcon.classList.add('transient');
926       else if (status < 0)
927         portIcon.classList.add('error');
928       devicePorts.appendChild(portIcon);
930       var portNumber = document.createElement('div');
931       portNumber.className = 'port-number';
932       portNumber.textContent = ':' + port;
933       if (status > 0)
934         portNumber.textContent += '(' + status + ')';
935       devicePorts.appendChild(portNumber);
936     }
938     function updatePortForwardingInfo(browserSection) {
939       var icon = browserSection.querySelector('.used-for-port-forwarding');
940       if (icon)
941         icon.hidden = (browserSection.id !== deviceStatus.browserId);
942       updateBrowserVisibility(browserSection);
943     }
945     Array.prototype.forEach.call(
946         deviceSection.querySelectorAll('.browser'), updatePortForwardingInfo);
948     updateUsernameVisibility(deviceSection);
949   }
951   function clearPorts(deviceSection) {
952     if (deviceSection.id in devicesStatusMap)
953       return;
954     deviceSection.querySelector('.device-ports').textContent = '';
955   }
957   Array.prototype.forEach.call(
958       document.querySelectorAll('.device'), clearPorts);
961 document.addEventListener('DOMContentLoaded', onload);
963 window.addEventListener('hashchange', onHashChange);