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
= {};
13 var queryParams
= window
.location
.search
;
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];
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
= '';
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
);
53 sendCommand('init-ui');
56 function onHashChange() {
57 var hash
= window
.location
.hash
.slice(1).toLowerCase();
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');
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');
80 tabContent
.classList
.remove('selected');
81 tabHeader
.classList
.remove('selected');
86 window
.location
.hash
= id
;
90 function populateTargets(source
, data
) {
91 if (source
== 'local')
92 populateLocalTargets(data
);
93 else if (source
== 'remote')
94 populateRemoteTargets(data
);
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
]);
119 addToOthersList(data
[i
]);
123 function showIncognitoWarning() {
124 $('devices-incognito').hidden
= false;
127 function populateRemoteTargets(devices
) {
132 window
.holdDevices
= devices
;
136 function alreadyDisplayed(element
, data
) {
137 var json
= JSON
.stringify(data
);
138 if (element
.cachedJSON
== json
)
140 element
.cachedJSON
= json
;
144 function insertChildSortedById(parent
, child
) {
145 for (var sibling
= parent
.firstElementChild
;
147 sibling
= sibling
.nextElementSibling
) {
148 if (sibling
.id
> child
.id
) {
149 parent
.insertBefore(child
, sibling
);
153 parent
.appendChild(child
);
156 var deviceList
= $('devices-list');
157 if (alreadyDisplayed(deviceList
, devices
))
160 function removeObsolete(validIds
, section
) {
161 if (validIds
.indexOf(section
.id
) < 0)
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
))
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');
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
;
231 var browserSection
= $(browser
.id
);
232 if (browserSection
) {
233 pageList
= browserSection
.querySelector('.pages');
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
) {
252 var warningSection
= document
.createElement('div');
253 warningSection
.className
= 'warning';
254 warningSection
.textContent
=
255 'You may need a newer version of desktop Chrome. ' +
256 'Please try Chrome ' + browser
.adbBrowserVersion
+ ' or later.';
257 browserHeader
.appendChild(warningSection
);
258 } else if (majorChromeVersion
>= MIN_VERSION_NEW_TAB
) {
259 var newPage
= document
.createElement('div');
260 newPage
.className
= 'open';
262 var newPageUrl
= document
.createElement('input');
263 newPageUrl
.type
= 'text';
264 newPageUrl
.placeholder
= 'Open tab with url';
265 newPage
.appendChild(newPageUrl
);
267 var openHandler = function(sourceId
, browserId
, input
) {
269 'open', sourceId
, browserId
, input
.value
|| 'about:blank');
271 }.bind(null, browser
.source
, browser
.id
, newPageUrl
);
272 newPageUrl
.addEventListener('keyup', function(handler
, event
) {
273 if (event
.keyIdentifier
== 'Enter' && event
.target
.value
)
275 }.bind(null, openHandler
), true);
277 var newPageButton
= document
.createElement('button');
278 newPageButton
.textContent
= 'Open';
279 newPage
.appendChild(newPageButton
);
280 newPageButton
.addEventListener('click', openHandler
, true);
282 browserHeader
.appendChild(newPage
);
285 var browserInspector
;
286 var browserInspectorTitle
;
287 if ('trace' in queryParamsObject
|| 'tracing' in queryParamsObject
) {
288 browserInspector
= 'chrome://tracing';
289 browserInspectorTitle
= 'trace';
291 browserInspector
= queryParamsObject
['browser-inspector'];
292 browserInspectorTitle
= 'inspect';
294 if (browserInspector
) {
295 var link
= document
.createElement('span');
296 link
.classList
.add('action');
297 link
.setAttribute('tabindex', 1);
298 link
.textContent
= browserInspectorTitle
;
299 browserHeader
.appendChild(link
);
300 link
.addEventListener(
302 sendCommand
.bind(null, 'inspect-browser', browser
.source
,
303 browser
.id
, browserInspector
), false);
306 pageList
= document
.createElement('div');
307 pageList
.className
= 'list pages';
308 browserSection
.appendChild(pageList
);
311 if (incompatibleVersion
|| alreadyDisplayed(browserSection
, browser
))
314 pageList
.textContent
= '';
315 for (var p
= 0; p
< browser
.pages
.length
; p
++) {
316 var page
= browser
.pages
[p
];
317 // Attached targets have no unique id until Chrome 26. For such targets
318 // it is impossible to activate existing DevTools window.
319 page
.hasNoUniqueId
= page
.attached
&&
320 (majorChromeVersion
&& majorChromeVersion
< MIN_VERSION_TARGET_ID
);
321 var row
= addTargetToList(page
, pageList
, ['name', 'url']);
322 if (page
['description'])
323 addWebViewDetails(row
, page
);
325 addFavicon(row
, page
);
326 if (majorChromeVersion
>= MIN_VERSION_TAB_ACTIVATE
) {
327 addActionLink(row
, 'focus tab',
328 sendTargetCommand
.bind(null, 'activate', page
), false);
330 if (majorChromeVersion
) {
331 addActionLink(row
, 'reload',
332 sendTargetCommand
.bind(null, 'reload', page
), page
.attached
);
334 if (majorChromeVersion
>= MIN_VERSION_TAB_CLOSE
) {
335 addActionLink(row
, 'close',
336 sendTargetCommand
.bind(null, 'close', page
), false);
343 function addToPagesList(data
) {
344 var row
= addTargetToList(data
, $('pages-list'), ['name', 'url']);
345 addFavicon(row
, data
);
347 addGuestViews(row
, data
.guests
);
350 function addToExtensionsList(data
) {
351 var row
= addTargetToList(data
, $('extensions-list'), ['name', 'url']);
352 addFavicon(row
, data
);
354 addGuestViews(row
, data
.guests
);
357 function addToAppsList(data
) {
358 var row
= addTargetToList(data
, $('apps-list'), ['name', 'url']);
359 addFavicon(row
, data
);
361 addGuestViews(row
, data
.guests
);
364 function addGuestViews(row
, guests
) {
365 Array
.prototype.forEach
.call(guests
, function(guest
) {
366 var guestRow
= addTargetToList(guest
, row
, ['name', 'url']);
367 guestRow
.classList
.add('guest');
368 addFavicon(guestRow
, guest
);
372 function addToWorkersList(data
) {
374 addTargetToList(data
, $('workers-list'), ['name', 'description', 'url']);
375 addActionLink(row
, 'terminate',
376 sendTargetCommand
.bind(null, 'close', data
), false);
379 function addToServiceWorkersList(data
) {
380 var row
= addTargetToList(
381 data
, $('service-workers-list'), ['name', 'description', 'url']);
382 addActionLink(row
, 'terminate',
383 sendTargetCommand
.bind(null, 'close', data
), false);
386 function addToOthersList(data
) {
387 addTargetToList(data
, $('others-list'), ['url']);
390 function formatValue(data
, property
) {
391 var value
= data
[property
];
393 if (property
== 'name' && value
== '') {
397 var text
= value
? String(value
) : '';
398 if (text
.length
> 100)
399 text
= text
.substring(0, 100) + '\u2026';
401 var div
= document
.createElement('div');
402 div
.textContent
= text
;
403 div
.className
= property
;
407 function addFavicon(row
, data
) {
408 var favicon
= document
.createElement('img');
409 if (data
['faviconUrl'])
410 favicon
.src
= data
['faviconUrl'];
411 var propertiesBox
= row
.querySelector('.properties-box');
412 propertiesBox
.insertBefore(favicon
, propertiesBox
.firstChild
);
415 function addWebViewDetails(row
, data
) {
418 webview
= JSON
.parse(data
['description']);
422 addWebViewDescription(row
, webview
);
423 if (data
.adbScreenWidth
&& data
.adbScreenHeight
)
425 row
, webview
, data
.adbScreenWidth
, data
.adbScreenHeight
);
428 function addWebViewDescription(row
, webview
) {
429 var viewStatus
= { visibility
: '', position
: '', size
: '' };
430 if (!webview
.empty
) {
431 if (webview
.attached
&& !webview
.visible
)
432 viewStatus
.visibility
= 'hidden';
433 else if (!webview
.attached
)
434 viewStatus
.visibility
= 'detached';
435 viewStatus
.size
= 'size ' + webview
.width
+ ' \u00d7 ' + webview
.height
;
437 viewStatus
.visibility
= 'empty';
439 if (webview
.attached
) {
440 viewStatus
.position
=
441 'at (' + webview
.screenX
+ ', ' + webview
.screenY
+ ')';
444 var subRow
= document
.createElement('div');
445 subRow
.className
= 'subrow webview';
446 if (webview
.empty
|| !webview
.attached
|| !webview
.visible
)
447 subRow
.className
+= ' invisible-view';
448 if (viewStatus
.visibility
)
449 subRow
.appendChild(formatValue(viewStatus
, 'visibility'));
450 if (viewStatus
.position
)
451 subRow
.appendChild(formatValue(viewStatus
, 'position'));
452 subRow
.appendChild(formatValue(viewStatus
, 'size'));
453 var subrowBox
= row
.querySelector('.subrow-box');
454 subrowBox
.insertBefore(subRow
, row
.querySelector('.actions'));
457 function addWebViewThumbnail(row
, webview
, screenWidth
, screenHeight
) {
458 var maxScreenRectSize
= 50;
460 var screenRectHeight
;
462 var aspectRatio
= screenWidth
/ screenHeight
;
463 if (aspectRatio
< 1) {
464 screenRectWidth
= Math
.round(maxScreenRectSize
* aspectRatio
);
465 screenRectHeight
= maxScreenRectSize
;
467 screenRectWidth
= maxScreenRectSize
;
468 screenRectHeight
= Math
.round(maxScreenRectSize
/ aspectRatio
);
471 var thumbnail
= document
.createElement('div');
472 thumbnail
.className
= 'webview-thumbnail';
473 var thumbnailWidth
= 3 * screenRectWidth
;
474 var thumbnailHeight
= 60;
475 thumbnail
.style
.width
= thumbnailWidth
+ 'px';
476 thumbnail
.style
.height
= thumbnailHeight
+ 'px';
478 var screenRect
= document
.createElement('div');
479 screenRect
.className
= 'screen-rect';
480 screenRect
.style
.left
= screenRectWidth
+ 'px';
481 screenRect
.style
.top
= (thumbnailHeight
- screenRectHeight
) / 2 + 'px';
482 screenRect
.style
.width
= screenRectWidth
+ 'px';
483 screenRect
.style
.height
= screenRectHeight
+ 'px';
484 thumbnail
.appendChild(screenRect
);
486 if (!webview
.empty
&& webview
.attached
) {
487 var viewRect
= document
.createElement('div');
488 viewRect
.className
= 'view-rect';
489 if (!webview
.visible
)
490 viewRect
.classList
.add('hidden');
491 function percent(ratio
) {
492 return ratio
* 100 + '%';
494 viewRect
.style
.left
= percent(webview
.screenX
/ screenWidth
);
495 viewRect
.style
.top
= percent(webview
.screenY
/ screenHeight
);
496 viewRect
.style
.width
= percent(webview
.width
/ screenWidth
);
497 viewRect
.style
.height
= percent(webview
.height
/ screenHeight
);
498 screenRect
.appendChild(viewRect
);
501 var propertiesBox
= row
.querySelector('.properties-box');
502 propertiesBox
.insertBefore(thumbnail
, propertiesBox
.firstChild
);
505 function addTargetToList(data
, list
, properties
) {
506 var row
= document
.createElement('div');
507 row
.className
= 'row';
509 var propertiesBox
= document
.createElement('div');
510 propertiesBox
.className
= 'properties-box';
511 row
.appendChild(propertiesBox
);
513 var subrowBox
= document
.createElement('div');
514 subrowBox
.className
= 'subrow-box';
515 propertiesBox
.appendChild(subrowBox
);
517 var subrow
= document
.createElement('div');
518 subrow
.className
= 'subrow';
519 subrowBox
.appendChild(subrow
);
521 for (var j
= 0; j
< properties
.length
; j
++)
522 subrow
.appendChild(formatValue(data
, properties
[j
]));
524 var actionBox
= document
.createElement('div');
525 actionBox
.className
= 'actions';
526 subrowBox
.appendChild(actionBox
);
528 if (!data
.hasCustomInspectAction
) {
529 addActionLink(row
, 'inspect', sendTargetCommand
.bind(null, 'inspect', data
),
530 data
.hasNoUniqueId
|| data
.adbAttachedForeign
);
533 list
.appendChild(row
);
537 function addActionLink(row
, text
, handler
, opt_disabled
) {
538 var link
= document
.createElement('span');
539 link
.classList
.add('action');
540 link
.setAttribute('tabindex', 1);
542 link
.classList
.add('disabled');
544 link
.classList
.remove('disabled');
546 link
.textContent
= text
;
547 link
.addEventListener('click', handler
, true);
548 function handleKey(e
) {
549 if (e
.keyIdentifier
== 'Enter' || e
.keyIdentifier
== 'U+0020') {
554 link
.addEventListener('keydown', handleKey
, true);
555 row
.querySelector('.actions').appendChild(link
);
559 function initSettings() {
560 $('discover-usb-devices-enable').addEventListener('change',
561 enableDiscoverUsbDevices
);
563 $('port-forwarding-enable').addEventListener('change', enablePortForwarding
);
564 $('port-forwarding-config-open').addEventListener(
565 'click', openPortForwardingConfig
);
566 $('port-forwarding-config-close').addEventListener(
567 'click', closePortForwardingConfig
);
568 $('port-forwarding-config-done').addEventListener(
569 'click', commitPortForwardingConfig
.bind(true));
572 function enableDiscoverUsbDevices(event
) {
573 sendCommand('set-discover-usb-devices-enabled', event
.target
.checked
);
576 function enablePortForwarding(event
) {
577 sendCommand('set-port-forwarding-enabled', event
.target
.checked
);
580 function handleKey(event
) {
581 switch (event
.keyCode
) {
583 if (event
.target
.nodeName
== 'INPUT') {
584 var line
= event
.target
.parentNode
;
585 if (!line
.classList
.contains('fresh') ||
586 line
.classList
.contains('empty')) {
587 commitPortForwardingConfig(true);
589 commitFreshLineIfValid(true /* select new line */);
590 commitPortForwardingConfig(false);
593 commitPortForwardingConfig(true);
598 commitPortForwardingConfig(true);
603 function setModal(dialog
) {
604 dialog
.deactivatedNodes
= Array
.prototype.filter
.call(
605 document
.querySelectorAll('*'),
607 return n
!= dialog
&& !dialog
.contains(n
) && n
.tabIndex
>= 0;
610 dialog
.tabIndexes
= dialog
.deactivatedNodes
.map(
611 function(n
) { return n
.getAttribute('tabindex'); });
613 dialog
.deactivatedNodes
.forEach(function(n
) { n
.tabIndex
= -1; });
614 window
.modal
= dialog
;
617 function unsetModal(dialog
) {
618 for (var i
= 0; i
< dialog
.deactivatedNodes
.length
; i
++) {
619 var node
= dialog
.deactivatedNodes
[i
];
620 if (dialog
.tabIndexes
[i
] === null)
621 node
.removeAttribute('tabindex');
623 node
.setAttribute('tabindex', dialog
.tabIndexes
[i
]);
626 if (window
.holdDevices
) {
627 populateRemoteTargets(window
.holdDevices
);
628 delete window
.holdDevices
;
631 delete dialog
.deactivatedNodes
;
632 delete dialog
.tabIndexes
;
636 function openPortForwardingConfig() {
637 loadPortForwardingConfig(window
.portForwardingConfig
);
639 $('port-forwarding-overlay').classList
.add('open');
640 document
.addEventListener('keyup', handleKey
);
642 var freshPort
= document
.querySelector('.fresh .port');
646 $('port-forwarding-config-done').focus();
648 setModal($('port-forwarding-overlay'));
651 function closePortForwardingConfig() {
652 if (!$('port-forwarding-overlay').classList
.contains('open'))
655 $('port-forwarding-overlay').classList
.remove('open');
656 document
.removeEventListener('keyup', handleKey
);
657 unsetModal($('port-forwarding-overlay'));
660 function loadPortForwardingConfig(config
) {
661 var list
= $('port-forwarding-config-list');
662 list
.textContent
= '';
663 for (var port
in config
)
664 list
.appendChild(createConfigLine(port
, config
[port
]));
665 list
.appendChild(createEmptyConfigLine());
668 function commitPortForwardingConfig(closeConfig
) {
670 closePortForwardingConfig();
672 commitFreshLineIfValid();
673 var lines
= document
.querySelectorAll('.port-forwarding-pair');
675 for (var i
= 0; i
!= lines
.length
; i
++) {
677 var portInput
= line
.querySelector('.port');
678 var locationInput
= line
.querySelector('.location');
680 var port
= portInput
.classList
.contains('invalid') ?
681 portInput
.lastValidValue
:
684 var location
= locationInput
.classList
.contains('invalid') ?
685 locationInput
.lastValidValue
:
688 if (port
&& location
)
689 config
[port
] = location
;
691 sendCommand('set-port-forwarding-config', config
);
694 function updateDiscoverUsbDevicesEnabled(enabled
) {
695 var checkbox
= $('discover-usb-devices-enable');
696 checkbox
.checked
= !!enabled
;
697 checkbox
.disabled
= false;
700 function updatePortForwardingEnabled(enabled
) {
701 var checkbox
= $('port-forwarding-enable');
702 checkbox
.checked
= !!enabled
;
703 checkbox
.disabled
= false;
706 function updatePortForwardingConfig(config
) {
707 window
.portForwardingConfig
= config
;
708 $('port-forwarding-config-open').disabled
= !config
;
711 function createConfigLine(port
, location
) {
712 var line
= document
.createElement('div');
713 line
.className
= 'port-forwarding-pair';
715 var portInput
= createConfigField(port
, 'port', 'Port', validatePort
);
716 line
.appendChild(portInput
);
718 var locationInput
= createConfigField(
719 location
, 'location', 'IP address and port', validateLocation
);
720 line
.appendChild(locationInput
);
721 locationInput
.addEventListener('keydown', function(e
) {
722 if (e
.keyIdentifier
== 'U+0009' && // Tab
723 !e
.ctrlKey
&& !e
.altKey
&& !e
.shiftKey
&& !e
.metaKey
&&
724 line
.classList
.contains('fresh') &&
725 !line
.classList
.contains('empty')) {
726 // Tabbing forward on the fresh line, try create a new empty one.
727 if (commitFreshLineIfValid(true))
732 var lineDelete
= document
.createElement('div');
733 lineDelete
.className
= 'close-button';
734 lineDelete
.addEventListener('click', function() {
735 var newSelection
= line
.nextElementSibling
;
736 line
.parentNode
.removeChild(line
);
737 selectLine(newSelection
);
739 line
.appendChild(lineDelete
);
741 line
.addEventListener('click', selectLine
.bind(null, line
));
742 line
.addEventListener('focus', selectLine
.bind(null, line
));
744 checkEmptyLine(line
);
749 function validatePort(input
) {
750 var match
= input
.value
.match(/^(\d+)$/);
753 var port
= parseInt(match
[1]);
754 if (port
< 1024 || 65535 < port
)
757 var inputs
= document
.querySelectorAll('input.port:not(.invalid)');
758 for (var i
= 0; i
!= inputs
.length
; ++i
) {
759 if (inputs
[i
] == input
)
761 if (parseInt(inputs
[i
].value
) == port
)
767 function validateLocation(input
) {
768 var match
= input
.value
.match(/^([a-zA-Z0-9\.\-_]+):(\d+)$/);
771 var port
= parseInt(match
[2]);
772 return port
<= 65535;
775 function createEmptyConfigLine() {
776 var line
= createConfigLine('', '');
777 line
.classList
.add('fresh');
781 function createConfigField(value
, className
, hint
, validate
) {
782 var input
= document
.createElement('input');
783 input
.className
= className
;
785 input
.placeholder
= hint
;
787 input
.lastValidValue
= value
;
789 function checkInput() {
791 input
.classList
.remove('invalid');
793 input
.classList
.add('invalid');
794 if (input
.parentNode
)
795 checkEmptyLine(input
.parentNode
);
799 input
.addEventListener('keyup', checkInput
);
800 input
.addEventListener('focus', function() {
801 selectLine(input
.parentNode
);
804 input
.addEventListener('blur', function() {
806 input
.lastValidValue
= input
.value
;
812 function checkEmptyLine(line
) {
813 var inputs
= line
.querySelectorAll('input');
815 for (var i
= 0; i
!= inputs
.length
; i
++) {
816 if (inputs
[i
].value
!= '')
820 line
.classList
.add('empty');
822 line
.classList
.remove('empty');
825 function selectLine(line
) {
826 if (line
.classList
.contains('selected'))
829 line
.classList
.add('selected');
832 function unselectLine() {
833 var line
= document
.querySelector('.port-forwarding-pair.selected');
836 line
.classList
.remove('selected');
837 commitFreshLineIfValid();
840 function commitFreshLineIfValid(opt_selectNew
) {
841 var line
= document
.querySelector('.port-forwarding-pair.fresh');
842 if (line
.querySelector('.invalid'))
844 line
.classList
.remove('fresh');
845 var freshLine
= createEmptyConfigLine();
846 line
.parentNode
.appendChild(freshLine
);
848 freshLine
.querySelector('.port').focus();
852 function populatePortStatus(devicesStatusMap
) {
853 for (var deviceId
in devicesStatusMap
) {
854 if (!devicesStatusMap
.hasOwnProperty(deviceId
))
856 var deviceStatusMap
= devicesStatusMap
[deviceId
];
858 var deviceSection
= $(deviceId
);
862 var devicePorts
= deviceSection
.querySelector('.device-ports');
863 devicePorts
.textContent
= '';
864 for (var port
in deviceStatusMap
) {
865 if (!deviceStatusMap
.hasOwnProperty(port
))
868 var status
= deviceStatusMap
[port
];
869 var portIcon
= document
.createElement('div');
870 portIcon
.className
= 'port-icon';
871 // status === 0 is the default (connected) state.
872 // Positive values correspond to the tunnelling connection count
873 // (in DEBUG_DEVTOOLS mode).
875 portIcon
.classList
.add('connected');
876 else if (status
=== -1 || status
=== -2)
877 portIcon
.classList
.add('transient');
879 portIcon
.classList
.add('error');
880 devicePorts
.appendChild(portIcon
);
882 var portNumber
= document
.createElement('div');
883 portNumber
.className
= 'port-number';
884 portNumber
.textContent
= ':' + port
;
886 portNumber
.textContent
+= '(' + status
+ ')';
887 devicePorts
.appendChild(portNumber
);
891 function clearPorts(deviceSection
) {
892 if (deviceSection
.id
in devicesStatusMap
)
894 deviceSection
.querySelector('.device-ports').textContent
= '';
897 Array
.prototype.forEach
.call(
898 document
.querySelectorAll('.device'), clearPorts
);
901 document
.addEventListener('DOMContentLoaded', onload
);
903 window
.addEventListener('hashchange', onHashChange
);