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 alreadyDisplayed(element
, data
) {
128 var json
= JSON
.stringify(data
);
129 if (element
.cachedJSON
== json
)
131 element
.cachedJSON
= json
;
135 function populateRemoteTargets(devices
) {
140 window
.holdDevices
= devices
;
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
&& 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
) {
262 'open', sourceId
, browserId
, input
.value
|| 'about:blank');
264 }.bind(null, browser
.source
, browser
.id
, newPageUrl
);
265 newPageUrl
.addEventListener('keyup', function(handler
, event
) {
266 if (event
.keyIdentifier
== 'Enter' && event
.target
.value
)
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';
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(
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
))
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
);
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
);
356 addGuestViews(row
, data
.guests
);
359 function addToExtensionsList(data
) {
360 var row
= addTargetToList(data
, $('extensions-list'), ['name', 'url']);
361 addFavicon(row
, data
);
363 addGuestViews(row
, data
.guests
);
366 function addToAppsList(data
) {
367 var row
= addTargetToList(data
, $('apps-list'), ['name', 'url']);
368 addFavicon(row
, data
);
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
) {
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
== '') {
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
;
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
) {
427 webview
= JSON
.parse(data
['description']);
431 addWebViewDescription(row
, webview
);
432 if (data
.adbScreenWidth
&& data
.adbScreenHeight
)
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
;
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;
469 var screenRectHeight
;
471 var aspectRatio
= screenWidth
/ screenHeight
;
472 if (aspectRatio
< 1) {
473 screenRectWidth
= Math
.round(maxScreenRectSize
* aspectRatio
);
474 screenRectHeight
= maxScreenRectSize
;
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
);
547 function addActionLink(row
, text
, handler
, opt_disabled
) {
548 var link
= document
.createElement('span');
549 link
.classList
.add('action');
550 link
.setAttribute('tabindex', 1);
552 link
.classList
.add('disabled');
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') {
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
) {
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);
599 commitFreshLineIfValid(true /* select new line */);
600 commitPortForwardingConfig(false);
603 commitPortForwardingConfig(true);
608 commitPortForwardingConfig(true);
613 function setModal(dialog
) {
614 dialog
.deactivatedNodes
= Array
.prototype.filter
.call(
615 document
.querySelectorAll('*'),
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');
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
;
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');
656 $('port-forwarding-config-done').focus();
658 setModal($('port-forwarding-overlay'));
661 function closePortForwardingConfig() {
662 if (!$('port-forwarding-overlay').classList
.contains('open'))
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
) {
680 closePortForwardingConfig();
682 commitFreshLineIfValid();
683 var lines
= document
.querySelectorAll('.port-forwarding-pair');
685 for (var i
= 0; i
!= lines
.length
; i
++) {
687 var portInput
= line
.querySelector('.port');
688 var locationInput
= line
.querySelector('.location');
690 var port
= portInput
.classList
.contains('invalid') ?
691 portInput
.lastValidValue
:
694 var location
= locationInput
.classList
.contains('invalid') ?
695 locationInput
.lastValidValue
:
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))
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
);
759 function validatePort(input
) {
760 var match
= input
.value
.match(/^(\d+)$/);
763 var port
= parseInt(match
[1]);
764 if (port
< 1024 || 65535 < port
)
767 var inputs
= document
.querySelectorAll('input.port:not(.invalid)');
768 for (var i
= 0; i
!= inputs
.length
; ++i
) {
769 if (inputs
[i
] == input
)
771 if (parseInt(inputs
[i
].value
) == port
)
777 function validateLocation(input
) {
778 var match
= input
.value
.match(/^([a-zA-Z0-9\.\-_]+):(\d+)$/);
781 var port
= parseInt(match
[2]);
782 return port
<= 65535;
785 function createEmptyConfigLine() {
786 var line
= createConfigLine('', '');
787 line
.classList
.add('fresh');
791 function createConfigField(value
, className
, hint
, validate
) {
792 var input
= document
.createElement('input');
793 input
.className
= className
;
795 input
.placeholder
= hint
;
797 input
.lastValidValue
= value
;
799 function checkInput() {
801 input
.classList
.remove('invalid');
803 input
.classList
.add('invalid');
804 if (input
.parentNode
)
805 checkEmptyLine(input
.parentNode
);
809 input
.addEventListener('keyup', checkInput
);
810 input
.addEventListener('focus', function() {
811 selectLine(input
.parentNode
);
814 input
.addEventListener('blur', function() {
816 input
.lastValidValue
= input
.value
;
822 function checkEmptyLine(line
) {
823 var inputs
= line
.querySelectorAll('input');
825 for (var i
= 0; i
!= inputs
.length
; i
++) {
826 if (inputs
[i
].value
!= '')
830 line
.classList
.add('empty');
832 line
.classList
.remove('empty');
835 function selectLine(line
) {
836 if (line
.classList
.contains('selected'))
839 line
.classList
.add('selected');
842 function unselectLine() {
843 var line
= document
.querySelector('.port-forwarding-pair.selected');
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'))
854 line
.classList
.remove('fresh');
855 var freshLine
= createEmptyConfigLine();
856 line
.parentNode
.appendChild(freshLine
);
858 freshLine
.querySelector('.port').focus();
862 function populatePortStatus(devicesStatusMap
) {
863 for (var deviceId
in devicesStatusMap
) {
864 if (!devicesStatusMap
.hasOwnProperty(deviceId
))
866 var deviceStatus
= devicesStatusMap
[deviceId
];
867 var deviceStatusMap
= deviceStatus
.ports
;
869 var deviceSection
= $(deviceId
);
873 var devicePorts
= deviceSection
.querySelector('.device-ports');
874 if (alreadyDisplayed(devicePorts
, deviceStatus
))
877 devicePorts
.textContent
= '';
878 for (var port
in deviceStatusMap
) {
879 if (!deviceStatusMap
.hasOwnProperty(port
))
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).
889 portIcon
.classList
.add('connected');
890 else if (status
=== -1 || status
=== -2)
891 portIcon
.classList
.add('transient');
893 portIcon
.classList
.add('error');
894 devicePorts
.appendChild(portIcon
);
896 var portNumber
= document
.createElement('div');
897 portNumber
.className
= 'port-number';
898 portNumber
.textContent
= ':' + port
;
900 portNumber
.textContent
+= '(' + status
+ ')';
901 devicePorts
.appendChild(portNumber
);
904 function updatePortForwardingInfo(browserSection
) {
905 var icon
= browserSection
.querySelector('.used-for-port-forwarding');
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
)
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
);