Roll src/third_party/WebKit bf18a82:a9cee16 (svn 185297:185304)
[chromium-blink-merge.git] / chrome / browser / resources / inspect / inspect.js
bloba99d3c44fd5e6330c5b3415d3914eabf48676426
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;
10 var queryParamsObject = {};
12 (function() {
13 var queryParams = window.location.search;
14 if (!queryParams)
15 return;
16 var params = queryParams.substring(1).split('&');
17 for (var i = 0; i < params.length; ++i) {
18 var pair = params[i].split('=');
19 queryParamsObject[pair[0]] = pair[1];
22 })();
24 function sendCommand(command, args) {
25 chrome.send(command, Array.prototype.slice.call(arguments, 1));
28 function sendTargetCommand(command, target) {
29 sendCommand(command, target.source, target.id);
32 function removeChildren(element_id) {
33 var element = $(element_id);
34 element.textContent = '';
37 function onload() {
38 var tabContents = document.querySelectorAll('#content > div');
39 for (var i = 0; i != tabContents.length; i++) {
40 var tabContent = tabContents[i];
41 var tabName = tabContent.querySelector('.content-header').textContent;
43 var tabHeader = document.createElement('div');
44 tabHeader.className = 'tab-header';
45 var button = document.createElement('button');
46 button.textContent = tabName;
47 tabHeader.appendChild(button);
48 tabHeader.addEventListener('click', selectTab.bind(null, tabContent.id));
49 $('navigation').appendChild(tabHeader);
51 onHashChange();
52 initSettings();
53 sendCommand('init-ui');
56 function onHashChange() {
57 var hash = window.location.hash.slice(1).toLowerCase();
58 if (!selectTab(hash))
59 selectTab('devices');
62 /**
63 * @param {string} id Tab id.
64 * @return {boolean} True if successful.
66 function selectTab(id) {
67 closePortForwardingConfig();
69 var tabContents = document.querySelectorAll('#content > div');
70 var tabHeaders = $('navigation').querySelectorAll('.tab-header');
71 var found = false;
72 for (var i = 0; i != tabContents.length; i++) {
73 var tabContent = tabContents[i];
74 var tabHeader = tabHeaders[i];
75 if (tabContent.id == id) {
76 tabContent.classList.add('selected');
77 tabHeader.classList.add('selected');
78 found = true;
79 } else {
80 tabContent.classList.remove('selected');
81 tabHeader.classList.remove('selected');
84 if (!found)
85 return false;
86 window.location.hash = id;
87 return true;
90 function populateTargets(source, data) {
91 if (source == 'local')
92 populateLocalTargets(data);
93 else if (source == 'remote')
94 populateRemoteTargets(data);
95 else
96 console.error('Unknown source type: ' + source);
99 function populateLocalTargets(data) {
100 removeChildren('pages-list');
101 removeChildren('extensions-list');
102 removeChildren('apps-list');
103 removeChildren('others-list');
104 removeChildren('workers-list');
105 removeChildren('service-workers-list');
107 for (var i = 0; i < data.length; i++) {
108 if (data[i].type === 'page')
109 addToPagesList(data[i]);
110 else if (data[i].type === 'background_page')
111 addToExtensionsList(data[i]);
112 else if (data[i].type === 'app')
113 addToAppsList(data[i]);
114 else if (data[i].type === 'worker')
115 addToWorkersList(data[i]);
116 else if (data[i].type === 'service_worker')
117 addToServiceWorkersList(data[i]);
118 else
119 addToOthersList(data[i]);
123 function showIncognitoWarning() {
124 $('devices-incognito').hidden = false;
127 function alreadyDisplayed(element, data) {
128 var json = JSON.stringify(data);
129 if (element.cachedJSON == json)
130 return true;
131 element.cachedJSON = json;
132 return false;
135 function populateRemoteTargets(devices) {
136 if (!devices)
137 return;
139 if (window.modal) {
140 window.holdDevices = devices;
141 return;
144 function insertChildSortedById(parent, child) {
145 for (var sibling = parent.firstElementChild;
146 sibling;
147 sibling = sibling.nextElementSibling) {
148 if (sibling.id > child.id) {
149 parent.insertBefore(child, sibling);
150 return;
153 parent.appendChild(child);
156 var deviceList = $('devices-list');
157 if (alreadyDisplayed(deviceList, devices))
158 return;
160 function removeObsolete(validIds, section) {
161 if (validIds.indexOf(section.id) < 0)
162 section.remove();
165 var newDeviceIds = devices.map(function(d) { return d.id });
166 Array.prototype.forEach.call(
167 deviceList.querySelectorAll('.device'),
168 removeObsolete.bind(null, newDeviceIds));
170 $('devices-help').hidden = !!devices.length;
172 for (var d = 0; d < devices.length; d++) {
173 var device = devices[d];
175 var deviceSection = $(device.id);
176 if (!deviceSection) {
177 deviceSection = document.createElement('div');
178 deviceSection.id = device.id;
179 deviceSection.className = 'device';
180 deviceList.appendChild(deviceSection);
182 var deviceHeader = document.createElement('div');
183 deviceHeader.className = 'device-header';
184 deviceSection.appendChild(deviceHeader);
186 var deviceName = document.createElement('div');
187 deviceName.className = 'device-name';
188 deviceHeader.appendChild(deviceName);
190 var deviceSerial = document.createElement('div');
191 deviceSerial.className = 'device-serial';
192 deviceSerial.textContent = '#' + device.adbSerial.toUpperCase();
193 deviceHeader.appendChild(deviceSerial);
195 var devicePorts = document.createElement('div');
196 devicePorts.className = 'device-ports';
197 deviceHeader.appendChild(devicePorts);
199 var browserList = document.createElement('div');
200 browserList.className = 'browsers';
201 deviceSection.appendChild(browserList);
203 var authenticating = document.createElement('div');
204 authenticating.className = 'device-auth';
205 deviceSection.appendChild(authenticating);
208 if (alreadyDisplayed(deviceSection, device))
209 continue;
211 deviceSection.querySelector('.device-name').textContent = device.adbModel;
212 deviceSection.querySelector('.device-auth').textContent =
213 device.adbConnected ? '' : 'Pending authentication: please accept ' +
214 'debugging session on the device.';
216 var browserList = deviceSection.querySelector('.browsers');
217 var newBrowserIds =
218 device.browsers.map(function(b) { return b.id });
219 Array.prototype.forEach.call(
220 browserList.querySelectorAll('.browser'),
221 removeObsolete.bind(null, newBrowserIds));
223 for (var b = 0; b < device.browsers.length; b++) {
224 var browser = device.browsers[b];
226 var majorChromeVersion = browser.adbBrowserChromeVersion;
228 var incompatibleVersion = browser.hasOwnProperty('compatibleVersion') &&
229 !browser.compatibleVersion;
230 var pageList;
231 var browserSection = $(browser.id);
232 if (browserSection) {
233 pageList = browserSection.querySelector('.pages');
234 } else {
235 browserSection = document.createElement('div');
236 browserSection.id = browser.id;
237 browserSection.className = 'browser';
238 insertChildSortedById(browserList, browserSection);
240 var browserHeader = document.createElement('div');
241 browserHeader.className = 'browser-header';
243 var browserName = document.createElement('div');
244 browserName.className = 'browser-name';
245 browserHeader.appendChild(browserName);
246 browserName.textContent = browser.adbBrowserName;
247 if (browser.adbBrowserVersion)
248 browserName.textContent += ' (' + browser.adbBrowserVersion + ')';
249 browserSection.appendChild(browserHeader);
251 if (!incompatibleVersion && majorChromeVersion >= MIN_VERSION_NEW_TAB) {
252 var newPage = document.createElement('div');
253 newPage.className = 'open';
255 var newPageUrl = document.createElement('input');
256 newPageUrl.type = 'text';
257 newPageUrl.placeholder = 'Open tab with url';
258 newPage.appendChild(newPageUrl);
260 var openHandler = function(sourceId, browserId, input) {
261 sendCommand(
262 'open', sourceId, browserId, input.value || 'about:blank');
263 input.value = '';
264 }.bind(null, browser.source, browser.id, newPageUrl);
265 newPageUrl.addEventListener('keyup', function(handler, event) {
266 if (event.keyIdentifier == 'Enter' && event.target.value)
267 handler();
268 }.bind(null, openHandler), true);
270 var newPageButton = document.createElement('button');
271 newPageButton.textContent = 'Open';
272 newPage.appendChild(newPageButton);
273 newPageButton.addEventListener('click', openHandler, true);
275 browserHeader.appendChild(newPage);
278 var portForwardingInfo = document.createElement('div');
279 portForwardingInfo.className = 'used-for-port-forwarding';
280 portForwardingInfo.hidden = true;
281 portForwardingInfo.title = 'This browser is used for port ' +
282 'forwarding. Closing it will drop current connections.';
283 browserHeader.appendChild(portForwardingInfo);
285 if (incompatibleVersion) {
286 var warningSection = document.createElement('div');
287 warningSection.className = 'warning';
288 warningSection.textContent =
289 'You may need a newer version of desktop Chrome. ' +
290 'Please try Chrome ' + browser.adbBrowserVersion + ' or later.';
291 browserSection.appendChild(warningSection);
294 var browserInspector;
295 var browserInspectorTitle;
296 if ('trace' in queryParamsObject || 'tracing' in queryParamsObject) {
297 browserInspector = 'chrome://tracing';
298 browserInspectorTitle = 'trace';
299 } else {
300 browserInspector = queryParamsObject['browser-inspector'];
301 browserInspectorTitle = 'inspect';
303 if (browserInspector) {
304 var link = document.createElement('span');
305 link.classList.add('action');
306 link.setAttribute('tabindex', 1);
307 link.textContent = browserInspectorTitle;
308 browserHeader.appendChild(link);
309 link.addEventListener(
310 'click',
311 sendCommand.bind(null, 'inspect-browser', browser.source,
312 browser.id, browserInspector), false);
315 pageList = document.createElement('div');
316 pageList.className = 'list pages';
317 browserSection.appendChild(pageList);
320 if (incompatibleVersion || alreadyDisplayed(browserSection, browser))
321 continue;
323 pageList.textContent = '';
324 for (var p = 0; p < browser.pages.length; p++) {
325 var page = browser.pages[p];
326 // Attached targets have no unique id until Chrome 26. For such targets
327 // it is impossible to activate existing DevTools window.
328 page.hasNoUniqueId = page.attached &&
329 (majorChromeVersion && majorChromeVersion < MIN_VERSION_TARGET_ID);
330 var row = addTargetToList(page, pageList, ['name', 'url']);
331 if (page['description'])
332 addWebViewDetails(row, page);
333 else
334 addFavicon(row, page);
335 if (majorChromeVersion >= MIN_VERSION_TAB_ACTIVATE) {
336 addActionLink(row, 'focus tab',
337 sendTargetCommand.bind(null, 'activate', page), false);
339 if (majorChromeVersion) {
340 addActionLink(row, 'reload',
341 sendTargetCommand.bind(null, 'reload', page), page.attached);
343 if (majorChromeVersion >= MIN_VERSION_TAB_CLOSE) {
344 addActionLink(row, 'close',
345 sendTargetCommand.bind(null, 'close', page), false);
352 function addToPagesList(data) {
353 var row = addTargetToList(data, $('pages-list'), ['name', 'url']);
354 addFavicon(row, data);
355 if (data.guests)
356 addGuestViews(row, data.guests);
359 function addToExtensionsList(data) {
360 var row = addTargetToList(data, $('extensions-list'), ['name', 'url']);
361 addFavicon(row, data);
362 if (data.guests)
363 addGuestViews(row, data.guests);
366 function addToAppsList(data) {
367 var row = addTargetToList(data, $('apps-list'), ['name', 'url']);
368 addFavicon(row, data);
369 if (data.guests)
370 addGuestViews(row, data.guests);
373 function addGuestViews(row, guests) {
374 Array.prototype.forEach.call(guests, function(guest) {
375 var guestRow = addTargetToList(guest, row, ['name', 'url']);
376 guestRow.classList.add('guest');
377 addFavicon(guestRow, guest);
381 function addToWorkersList(data) {
382 var row =
383 addTargetToList(data, $('workers-list'), ['name', 'description', 'url']);
384 addActionLink(row, 'terminate',
385 sendTargetCommand.bind(null, 'close', data), false);
388 function addToServiceWorkersList(data) {
389 var row = addTargetToList(
390 data, $('service-workers-list'), ['name', 'description', 'url']);
391 addActionLink(row, 'terminate',
392 sendTargetCommand.bind(null, 'close', data), false);
395 function addToOthersList(data) {
396 addTargetToList(data, $('others-list'), ['url']);
399 function formatValue(data, property) {
400 var value = data[property];
402 if (property == 'name' && value == '') {
403 value = 'untitled';
406 var text = value ? String(value) : '';
407 if (text.length > 100)
408 text = text.substring(0, 100) + '\u2026';
410 var div = document.createElement('div');
411 div.textContent = text;
412 div.className = property;
413 return div;
416 function addFavicon(row, data) {
417 var favicon = document.createElement('img');
418 if (data['faviconUrl'])
419 favicon.src = data['faviconUrl'];
420 var propertiesBox = row.querySelector('.properties-box');
421 propertiesBox.insertBefore(favicon, propertiesBox.firstChild);
424 function addWebViewDetails(row, data) {
425 var webview;
426 try {
427 webview = JSON.parse(data['description']);
428 } catch (e) {
429 return;
431 addWebViewDescription(row, webview);
432 if (data.adbScreenWidth && data.adbScreenHeight)
433 addWebViewThumbnail(
434 row, webview, data.adbScreenWidth, data.adbScreenHeight);
437 function addWebViewDescription(row, webview) {
438 var viewStatus = { visibility: '', position: '', size: '' };
439 if (!webview.empty) {
440 if (webview.attached && !webview.visible)
441 viewStatus.visibility = 'hidden';
442 else if (!webview.attached)
443 viewStatus.visibility = 'detached';
444 viewStatus.size = 'size ' + webview.width + ' \u00d7 ' + webview.height;
445 } else {
446 viewStatus.visibility = 'empty';
448 if (webview.attached) {
449 viewStatus.position =
450 'at (' + webview.screenX + ', ' + webview.screenY + ')';
453 var subRow = document.createElement('div');
454 subRow.className = 'subrow webview';
455 if (webview.empty || !webview.attached || !webview.visible)
456 subRow.className += ' invisible-view';
457 if (viewStatus.visibility)
458 subRow.appendChild(formatValue(viewStatus, 'visibility'));
459 if (viewStatus.position)
460 subRow.appendChild(formatValue(viewStatus, 'position'));
461 subRow.appendChild(formatValue(viewStatus, 'size'));
462 var subrowBox = row.querySelector('.subrow-box');
463 subrowBox.insertBefore(subRow, row.querySelector('.actions'));
466 function addWebViewThumbnail(row, webview, screenWidth, screenHeight) {
467 var maxScreenRectSize = 50;
468 var screenRectWidth;
469 var screenRectHeight;
471 var aspectRatio = screenWidth / screenHeight;
472 if (aspectRatio < 1) {
473 screenRectWidth = Math.round(maxScreenRectSize * aspectRatio);
474 screenRectHeight = maxScreenRectSize;
475 } else {
476 screenRectWidth = maxScreenRectSize;
477 screenRectHeight = Math.round(maxScreenRectSize / aspectRatio);
480 var thumbnail = document.createElement('div');
481 thumbnail.className = 'webview-thumbnail';
482 var thumbnailWidth = 3 * screenRectWidth;
483 var thumbnailHeight = 60;
484 thumbnail.style.width = thumbnailWidth + 'px';
485 thumbnail.style.height = thumbnailHeight + 'px';
487 var screenRect = document.createElement('div');
488 screenRect.className = 'screen-rect';
489 screenRect.style.left = screenRectWidth + 'px';
490 screenRect.style.top = (thumbnailHeight - screenRectHeight) / 2 + 'px';
491 screenRect.style.width = screenRectWidth + 'px';
492 screenRect.style.height = screenRectHeight + 'px';
493 thumbnail.appendChild(screenRect);
495 if (!webview.empty && webview.attached) {
496 var viewRect = document.createElement('div');
497 viewRect.className = 'view-rect';
498 if (!webview.visible)
499 viewRect.classList.add('hidden');
500 function percent(ratio) {
501 return ratio * 100 + '%';
503 viewRect.style.left = percent(webview.screenX / screenWidth);
504 viewRect.style.top = percent(webview.screenY / screenHeight);
505 viewRect.style.width = percent(webview.width / screenWidth);
506 viewRect.style.height = percent(webview.height / screenHeight);
507 screenRect.appendChild(viewRect);
510 var propertiesBox = row.querySelector('.properties-box');
511 propertiesBox.insertBefore(thumbnail, propertiesBox.firstChild);
514 function addTargetToList(data, list, properties) {
515 var row = document.createElement('div');
516 row.className = 'row';
517 row.targetId = data.id;
519 var propertiesBox = document.createElement('div');
520 propertiesBox.className = 'properties-box';
521 row.appendChild(propertiesBox);
523 var subrowBox = document.createElement('div');
524 subrowBox.className = 'subrow-box';
525 propertiesBox.appendChild(subrowBox);
527 var subrow = document.createElement('div');
528 subrow.className = 'subrow';
529 subrowBox.appendChild(subrow);
531 for (var j = 0; j < properties.length; j++)
532 subrow.appendChild(formatValue(data, properties[j]));
534 var actionBox = document.createElement('div');
535 actionBox.className = 'actions';
536 subrowBox.appendChild(actionBox);
538 if (!data.hasCustomInspectAction) {
539 addActionLink(row, 'inspect', sendTargetCommand.bind(null, 'inspect', data),
540 data.hasNoUniqueId || data.adbAttachedForeign);
543 list.appendChild(row);
544 return row;
547 function addActionLink(row, text, handler, opt_disabled) {
548 var link = document.createElement('span');
549 link.classList.add('action');
550 link.setAttribute('tabindex', 1);
551 if (opt_disabled)
552 link.classList.add('disabled');
553 else
554 link.classList.remove('disabled');
556 link.textContent = text;
557 link.addEventListener('click', handler, true);
558 function handleKey(e) {
559 if (e.keyIdentifier == 'Enter' || e.keyIdentifier == 'U+0020') {
560 e.preventDefault();
561 handler();
564 link.addEventListener('keydown', handleKey, true);
565 row.querySelector('.actions').appendChild(link);
569 function initSettings() {
570 $('discover-usb-devices-enable').addEventListener('change',
571 enableDiscoverUsbDevices);
573 $('port-forwarding-enable').addEventListener('change', enablePortForwarding);
574 $('port-forwarding-config-open').addEventListener(
575 'click', openPortForwardingConfig);
576 $('port-forwarding-config-close').addEventListener(
577 'click', closePortForwardingConfig);
578 $('port-forwarding-config-done').addEventListener(
579 'click', commitPortForwardingConfig.bind(true));
582 function enableDiscoverUsbDevices(event) {
583 sendCommand('set-discover-usb-devices-enabled', event.target.checked);
586 function enablePortForwarding(event) {
587 sendCommand('set-port-forwarding-enabled', event.target.checked);
590 function handleKey(event) {
591 switch (event.keyCode) {
592 case 13: // Enter
593 if (event.target.nodeName == 'INPUT') {
594 var line = event.target.parentNode;
595 if (!line.classList.contains('fresh') ||
596 line.classList.contains('empty')) {
597 commitPortForwardingConfig(true);
598 } else {
599 commitFreshLineIfValid(true /* select new line */);
600 commitPortForwardingConfig(false);
602 } else {
603 commitPortForwardingConfig(true);
605 break;
607 case 27:
608 commitPortForwardingConfig(true);
609 break;
613 function setModal(dialog) {
614 dialog.deactivatedNodes = Array.prototype.filter.call(
615 document.querySelectorAll('*'),
616 function(n) {
617 return n != dialog && !dialog.contains(n) && n.tabIndex >= 0;
620 dialog.tabIndexes = dialog.deactivatedNodes.map(
621 function(n) { return n.getAttribute('tabindex'); });
623 dialog.deactivatedNodes.forEach(function(n) { n.tabIndex = -1; });
624 window.modal = dialog;
627 function unsetModal(dialog) {
628 for (var i = 0; i < dialog.deactivatedNodes.length; i++) {
629 var node = dialog.deactivatedNodes[i];
630 if (dialog.tabIndexes[i] === null)
631 node.removeAttribute('tabindex');
632 else
633 node.setAttribute('tabindex', dialog.tabIndexes[i]);
636 if (window.holdDevices) {
637 populateRemoteTargets(window.holdDevices);
638 delete window.holdDevices;
641 delete dialog.deactivatedNodes;
642 delete dialog.tabIndexes;
643 delete window.modal;
646 function openPortForwardingConfig() {
647 loadPortForwardingConfig(window.portForwardingConfig);
649 $('port-forwarding-overlay').classList.add('open');
650 document.addEventListener('keyup', handleKey);
652 var freshPort = document.querySelector('.fresh .port');
653 if (freshPort)
654 freshPort.focus();
655 else
656 $('port-forwarding-config-done').focus();
658 setModal($('port-forwarding-overlay'));
661 function closePortForwardingConfig() {
662 if (!$('port-forwarding-overlay').classList.contains('open'))
663 return;
665 $('port-forwarding-overlay').classList.remove('open');
666 document.removeEventListener('keyup', handleKey);
667 unsetModal($('port-forwarding-overlay'));
670 function loadPortForwardingConfig(config) {
671 var list = $('port-forwarding-config-list');
672 list.textContent = '';
673 for (var port in config)
674 list.appendChild(createConfigLine(port, config[port]));
675 list.appendChild(createEmptyConfigLine());
678 function commitPortForwardingConfig(closeConfig) {
679 if (closeConfig)
680 closePortForwardingConfig();
682 commitFreshLineIfValid();
683 var lines = document.querySelectorAll('.port-forwarding-pair');
684 var config = {};
685 for (var i = 0; i != lines.length; i++) {
686 var line = lines[i];
687 var portInput = line.querySelector('.port');
688 var locationInput = line.querySelector('.location');
690 var port = portInput.classList.contains('invalid') ?
691 portInput.lastValidValue :
692 portInput.value;
694 var location = locationInput.classList.contains('invalid') ?
695 locationInput.lastValidValue :
696 locationInput.value;
698 if (port && location)
699 config[port] = location;
701 sendCommand('set-port-forwarding-config', config);
704 function updateDiscoverUsbDevicesEnabled(enabled) {
705 var checkbox = $('discover-usb-devices-enable');
706 checkbox.checked = !!enabled;
707 checkbox.disabled = false;
710 function updatePortForwardingEnabled(enabled) {
711 var checkbox = $('port-forwarding-enable');
712 checkbox.checked = !!enabled;
713 checkbox.disabled = false;
716 function updatePortForwardingConfig(config) {
717 window.portForwardingConfig = config;
718 $('port-forwarding-config-open').disabled = !config;
721 function createConfigLine(port, location) {
722 var line = document.createElement('div');
723 line.className = 'port-forwarding-pair';
725 var portInput = createConfigField(port, 'port', 'Port', validatePort);
726 line.appendChild(portInput);
728 var locationInput = createConfigField(
729 location, 'location', 'IP address and port', validateLocation);
730 line.appendChild(locationInput);
731 locationInput.addEventListener('keydown', function(e) {
732 if (e.keyIdentifier == 'U+0009' && // Tab
733 !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey &&
734 line.classList.contains('fresh') &&
735 !line.classList.contains('empty')) {
736 // Tabbing forward on the fresh line, try create a new empty one.
737 if (commitFreshLineIfValid(true))
738 e.preventDefault();
742 var lineDelete = document.createElement('div');
743 lineDelete.className = 'close-button';
744 lineDelete.addEventListener('click', function() {
745 var newSelection = line.nextElementSibling;
746 line.parentNode.removeChild(line);
747 selectLine(newSelection);
749 line.appendChild(lineDelete);
751 line.addEventListener('click', selectLine.bind(null, line));
752 line.addEventListener('focus', selectLine.bind(null, line));
754 checkEmptyLine(line);
756 return line;
759 function validatePort(input) {
760 var match = input.value.match(/^(\d+)$/);
761 if (!match)
762 return false;
763 var port = parseInt(match[1]);
764 if (port < 1024 || 65535 < port)
765 return false;
767 var inputs = document.querySelectorAll('input.port:not(.invalid)');
768 for (var i = 0; i != inputs.length; ++i) {
769 if (inputs[i] == input)
770 break;
771 if (parseInt(inputs[i].value) == port)
772 return false;
774 return true;
777 function validateLocation(input) {
778 var match = input.value.match(/^([a-zA-Z0-9\.\-_]+):(\d+)$/);
779 if (!match)
780 return false;
781 var port = parseInt(match[2]);
782 return port <= 65535;
785 function createEmptyConfigLine() {
786 var line = createConfigLine('', '');
787 line.classList.add('fresh');
788 return line;
791 function createConfigField(value, className, hint, validate) {
792 var input = document.createElement('input');
793 input.className = className;
794 input.type = 'text';
795 input.placeholder = hint;
796 input.value = value;
797 input.lastValidValue = value;
799 function checkInput() {
800 if (validate(input))
801 input.classList.remove('invalid');
802 else
803 input.classList.add('invalid');
804 if (input.parentNode)
805 checkEmptyLine(input.parentNode);
807 checkInput();
809 input.addEventListener('keyup', checkInput);
810 input.addEventListener('focus', function() {
811 selectLine(input.parentNode);
814 input.addEventListener('blur', function() {
815 if (validate(input))
816 input.lastValidValue = input.value;
819 return input;
822 function checkEmptyLine(line) {
823 var inputs = line.querySelectorAll('input');
824 var empty = true;
825 for (var i = 0; i != inputs.length; i++) {
826 if (inputs[i].value != '')
827 empty = false;
829 if (empty)
830 line.classList.add('empty');
831 else
832 line.classList.remove('empty');
835 function selectLine(line) {
836 if (line.classList.contains('selected'))
837 return;
838 unselectLine();
839 line.classList.add('selected');
842 function unselectLine() {
843 var line = document.querySelector('.port-forwarding-pair.selected');
844 if (!line)
845 return;
846 line.classList.remove('selected');
847 commitFreshLineIfValid();
850 function commitFreshLineIfValid(opt_selectNew) {
851 var line = document.querySelector('.port-forwarding-pair.fresh');
852 if (line.querySelector('.invalid'))
853 return false;
854 line.classList.remove('fresh');
855 var freshLine = createEmptyConfigLine();
856 line.parentNode.appendChild(freshLine);
857 if (opt_selectNew)
858 freshLine.querySelector('.port').focus();
859 return true;
862 function populatePortStatus(devicesStatusMap) {
863 for (var deviceId in devicesStatusMap) {
864 if (!devicesStatusMap.hasOwnProperty(deviceId))
865 continue;
866 var deviceStatus = devicesStatusMap[deviceId];
867 var deviceStatusMap = deviceStatus.ports;
869 var deviceSection = $(deviceId);
870 if (!deviceSection)
871 continue;
873 var devicePorts = deviceSection.querySelector('.device-ports');
874 if (alreadyDisplayed(devicePorts, deviceStatus))
875 continue;
877 devicePorts.textContent = '';
878 for (var port in deviceStatusMap) {
879 if (!deviceStatusMap.hasOwnProperty(port))
880 continue;
882 var status = deviceStatusMap[port];
883 var portIcon = document.createElement('div');
884 portIcon.className = 'port-icon';
885 // status === 0 is the default (connected) state.
886 // Positive values correspond to the tunnelling connection count
887 // (in DEBUG_DEVTOOLS mode).
888 if (status > 0)
889 portIcon.classList.add('connected');
890 else if (status === -1 || status === -2)
891 portIcon.classList.add('transient');
892 else if (status < 0)
893 portIcon.classList.add('error');
894 devicePorts.appendChild(portIcon);
896 var portNumber = document.createElement('div');
897 portNumber.className = 'port-number';
898 portNumber.textContent = ':' + port;
899 if (status > 0)
900 portNumber.textContent += '(' + status + ')';
901 devicePorts.appendChild(portNumber);
904 function updatePortForwardingInfo(browserSection) {
905 var icon = browserSection.querySelector('.used-for-port-forwarding');
906 if (icon)
907 icon.hidden = (browserSection.id !== deviceStatus.browserId);
910 Array.prototype.forEach.call(
911 deviceSection.querySelectorAll('.browser'), updatePortForwardingInfo);
914 function clearPorts(deviceSection) {
915 if (deviceSection.id in devicesStatusMap)
916 return;
917 deviceSection.querySelector('.device-ports').textContent = '';
920 Array.prototype.forEach.call(
921 document.querySelectorAll('.device'), clearPorts);
924 document.addEventListener('DOMContentLoaded', onload);
926 window.addEventListener('hashchange', onHashChange);