Home | History | Annotate | Download | only in inspect
      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.
      4 
      5 function inspect(data) {
      6   chrome.send('inspect', [data]);
      7 }
      8 
      9 function terminate(data) {
     10   chrome.send('terminate', [data]);
     11 }
     12 
     13 function reload(data) {
     14   chrome.send('reload', [data]);
     15 }
     16 
     17 function open(browserId, url) {
     18   chrome.send('open', [browserId, url]);
     19 }
     20 
     21 function removeChildren(element_id) {
     22   var element = $(element_id);
     23   element.textContent = '';
     24 }
     25 
     26 function onload() {
     27   var tabContents = document.querySelectorAll('#content > div');
     28   for (var i = 0; i != tabContents.length; i++) {
     29     var tabContent = tabContents[i];
     30     var tabName = tabContent.querySelector('.content-header').textContent;
     31 
     32     var tabHeader = document.createElement('div');
     33     tabHeader.className = 'tab-header';
     34     var button = document.createElement('button');
     35     button.textContent = tabName;
     36     tabHeader.appendChild(button);
     37     tabHeader.addEventListener('click', selectTab.bind(null, tabContent.id));
     38     $('navigation').appendChild(tabHeader);
     39   }
     40   var selectedTabName = window.location.hash.slice(1) || 'devices';
     41   selectTab(selectedTabName + '-tab');
     42   initPortForwarding();
     43   chrome.send('init-ui');
     44 }
     45 
     46 function selectTab(id) {
     47   var tabContents = document.querySelectorAll('#content > div');
     48   var tabHeaders = $('navigation').querySelectorAll('.tab-header');
     49   for (var i = 0; i != tabContents.length; i++) {
     50     var tabContent = tabContents[i];
     51     var tabHeader = tabHeaders[i];
     52     if (tabContent.id == id) {
     53       tabContent.classList.add('selected');
     54       tabHeader.classList.add('selected');
     55     } else {
     56       tabContent.classList.remove('selected');
     57       tabHeader.classList.remove('selected');
     58     }
     59   }
     60 }
     61 
     62 function populateLists(data) {
     63   removeChildren('pages');
     64   removeChildren('extensions');
     65   removeChildren('apps');
     66   removeChildren('others');
     67 
     68   for (var i = 0; i < data.length; i++) {
     69     if (data[i].type === 'page')
     70       addToPagesList(data[i]);
     71     else if (data[i].type === 'extension')
     72       addToExtensionsList(data[i]);
     73     else if (data[i].type === 'app')
     74       addToAppsList(data[i]);
     75     else
     76       addToOthersList(data[i]);
     77   }
     78 }
     79 
     80 function populateWorkersList(data) {
     81   removeChildren('workers');
     82 
     83   for (var i = 0; i < data.length; i++)
     84     addToWorkersList(data[i]);
     85 }
     86 
     87 function populateDeviceLists(devices) {
     88   if (!devices)
     89     return;
     90 
     91   function alreadyDisplayed(element, data) {
     92     var json = JSON.stringify(data);
     93     if (element.cachedJSON == json)
     94       return true;
     95     element.cachedJSON = json;
     96     return false;
     97   }
     98 
     99   function insertChildSortedById(parent, child) {
    100     for (var sibling = parent.firstElementChild;
    101                      sibling;
    102                      sibling = sibling.nextElementSibling) {
    103       if (sibling.id > child.id) {
    104         parent.insertBefore(child, sibling);
    105         return;
    106       }
    107     }
    108     parent.appendChild(child);
    109   }
    110 
    111   var deviceList = $('devices');
    112   if (alreadyDisplayed(deviceList, devices))
    113     return;
    114 
    115   function removeObsolete(validIds, section) {
    116     if (validIds.indexOf(section.id) < 0)
    117       section.remove();
    118   }
    119 
    120   var newDeviceIds = devices.map(function(d) { return d.adbGlobalId });
    121   Array.prototype.forEach.call(
    122       deviceList.querySelectorAll('.device'),
    123       removeObsolete.bind(null, newDeviceIds));
    124 
    125   for (var d = 0; d < devices.length; d++) {
    126     var device = devices[d];
    127 
    128     var devicePorts;
    129     var browserList;
    130     var deviceSection = $(device.adbGlobalId);
    131     if (deviceSection) {
    132       devicePorts = deviceSection.querySelector('.device-ports');
    133       browserList = deviceSection.querySelector('.browsers');
    134     } else {
    135       deviceSection = document.createElement('div');
    136       deviceSection.id = device.adbGlobalId;
    137       deviceSection.className = 'device list';
    138       deviceList.appendChild(deviceSection);
    139 
    140       var deviceHeader = document.createElement('div');
    141       deviceHeader.className = 'device-header';
    142       deviceSection.appendChild(deviceHeader);
    143 
    144       var deviceName = document.createElement('div');
    145       deviceName.className = 'device-name';
    146       deviceName.textContent = device.adbModel;
    147       deviceHeader.appendChild(deviceName);
    148 
    149       if (device.adbSerial) {
    150         var deviceSerial = document.createElement('div');
    151         deviceSerial.className = 'device-serial';
    152         deviceSerial.textContent = '#' + device.adbSerial.toUpperCase();
    153         deviceHeader.appendChild(deviceSerial);
    154       }
    155 
    156       devicePorts = document.createElement('div');
    157       devicePorts.className = 'device-ports';
    158       deviceHeader.appendChild(devicePorts);
    159 
    160       browserList = document.createElement('div');
    161       browserList.className = 'browsers';
    162       deviceSection.appendChild(browserList);
    163     }
    164 
    165     if (alreadyDisplayed(deviceSection, device))
    166       continue;
    167 
    168     devicePorts.textContent = '';
    169     if (device.adbPortStatus) {
    170       for (var port in device.adbPortStatus) {
    171         var status = device.adbPortStatus[port];
    172         var portIcon = document.createElement('div');
    173         portIcon.className = 'port-icon';
    174         if (status > 0)
    175           portIcon.classList.add('connected');
    176         else if (status == -1 || status == -2)
    177           portIcon.classList.add('transient');
    178         else if (status < 0)
    179           portIcon.classList.add('error');
    180         devicePorts.appendChild(portIcon);
    181 
    182         var portNumber = document.createElement('div');
    183         portNumber.className = 'port-number';
    184         portNumber.textContent = ':' + port;
    185         if (status > 0)
    186           portNumber.textContent += '(' + status + ')';
    187         devicePorts.appendChild(portNumber);
    188       }
    189     }
    190 
    191     var newBrowserIds =
    192         device.browsers.map(function(b) { return b.adbGlobalId });
    193     Array.prototype.forEach.call(
    194         browserList.querySelectorAll('.browser'),
    195         removeObsolete.bind(null, newBrowserIds));
    196 
    197     for (var b = 0; b < device.browsers.length; b++) {
    198       var browser = device.browsers[b];
    199 
    200       var isChrome = browser.adbBrowserProduct &&
    201           browser.adbBrowserProduct.match(/^Chrome/);
    202 
    203       var pageList;
    204       var browserSection = $(browser.adbGlobalId);
    205       if (browserSection) {
    206         pageList = browserSection.querySelector('.pages');
    207       } else {
    208         browserSection = document.createElement('div');
    209         browserSection.id = browser.adbGlobalId;
    210         browserSection.className = 'browser';
    211         insertChildSortedById(browserList, browserSection);
    212 
    213         var browserHeader = document.createElement('div');
    214         browserHeader.className = 'browser-header';
    215         browserHeader.textContent = browser.adbBrowserProduct;
    216         var majorChromeVersion = 0;
    217         if (browser.adbBrowserVersion) {
    218           browserHeader.textContent += ' (' + browser.adbBrowserVersion + ')';
    219           if (isChrome) {
    220             var match = browser.adbBrowserVersion.match(/^(\d+)/);
    221             if (match)
    222               majorChromeVersion = parseInt(match[1]);
    223           }
    224         }
    225         browserSection.appendChild(browserHeader);
    226 
    227         if (majorChromeVersion >= 29) {
    228           var newPage = document.createElement('div');
    229           newPage.className = 'open';
    230 
    231           var newPageUrl = document.createElement('input');
    232           newPageUrl.type = 'text';
    233           newPageUrl.placeholder = 'Open tab with url';
    234           newPage.appendChild(newPageUrl);
    235 
    236           var openHandler = function(browserId, input) {
    237             open(browserId, input.value || 'about:blank');
    238             input.value = '';
    239           }.bind(null, browser.adbGlobalId, newPageUrl);
    240           newPageUrl.addEventListener('keyup', function(handler, event) {
    241             if (event.keyIdentifier == 'Enter' && event.target.value)
    242               handler();
    243           }.bind(null, openHandler), true);
    244 
    245           var newPageButton = document.createElement('button');
    246           newPageButton.textContent = 'Open';
    247           newPage.appendChild(newPageButton);
    248           newPageButton.addEventListener('click', openHandler, true);
    249 
    250           browserSection.appendChild(newPage);
    251         }
    252 
    253         pageList = document.createElement('div');
    254         pageList.className = 'list pages';
    255         browserSection.appendChild(pageList);
    256       }
    257 
    258       if (alreadyDisplayed(browserSection, browser))
    259         continue;
    260 
    261       pageList.textContent = '';
    262       for (var p = 0; p < browser.pages.length; p++) {
    263         var page = browser.pages[p];
    264         var row = addTargetToList(
    265             page, pageList, ['faviconUrl', 'name', 'url']);
    266         if (isChrome) {
    267           row.appendChild(createActionLink(
    268               'reload', reload.bind(null, page), page.attached));
    269           row.appendChild(createActionLink(
    270               'close', terminate.bind(null, page), page.attached));
    271         }
    272       }
    273     }
    274   }
    275 }
    276 
    277 function addToPagesList(data) {
    278   addTargetToList(data, $('pages'), ['faviconUrl', 'name', 'url']);
    279 }
    280 
    281 function addToExtensionsList(data) {
    282   addTargetToList(data, $('extensions'), ['name', 'url']);
    283 }
    284 
    285 function addToAppsList(data) {
    286   addTargetToList(data, $('apps'), ['name', 'url']);
    287 }
    288 
    289 function addToWorkersList(data) {
    290   var row = addTargetToList(data, $('workers'), ['name', 'url', 'pid']);
    291   row.appendChild(createActionLink(
    292       'terminate', terminate.bind(null, data), data.attached));
    293 }
    294 
    295 function addToOthersList(data) {
    296   addTargetToList(data, $('others'), ['url']);
    297 }
    298 
    299 function formatValue(data, property) {
    300   var value = data[property];
    301 
    302   if (property == 'name' && value == '') {
    303     value = 'untitled';
    304   }
    305 
    306   if (property == 'faviconUrl') {
    307     var faviconElement = document.createElement('img');
    308     if (value)
    309       faviconElement.src = value;
    310     return faviconElement;
    311   }
    312 
    313   var text = value ? String(value) : '';
    314   if (text.length > 100)
    315     text = text.substring(0, 100) + '\u2026';
    316 
    317   if (property == 'pid')
    318     text = 'Pid:' + text;
    319 
    320   var span = document.createElement('span');
    321   span.textContent = ' ' + text + ' ';
    322   span.className = property;
    323   return span;
    324 }
    325 
    326 function addTargetToList(data, list, properties) {
    327   var row = document.createElement('div');
    328   row.className = 'row';
    329   for (var j = 0; j < properties.length; j++)
    330     row.appendChild(formatValue(data, properties[j]));
    331 
    332   row.appendChild(createActionLink('inspect', inspect.bind(null, data)));
    333 
    334   row.processId = data.processId;
    335   row.routeId = data.routeId;
    336 
    337   list.appendChild(row);
    338   return row;
    339 }
    340 
    341 function createActionLink(text, handler, opt_disabled) {
    342   var link = document.createElement('a');
    343   if (opt_disabled)
    344     link.classList.add('disabled');
    345   else
    346     link.classList.remove('disabled');
    347 
    348   link.setAttribute('href', '#');
    349   link.textContent = text;
    350   link.addEventListener('click', handler, true);
    351   return link;
    352 }
    353 
    354 
    355 function initPortForwarding() {
    356   $('port-forwarding-enable').addEventListener('change', enablePortForwarding);
    357 
    358   $('port-forwarding-config-open').addEventListener(
    359       'click', openPortForwardingConfig);
    360   $('port-forwarding-config-close').addEventListener(
    361       'click', closePortForwardingConfig);
    362   $('port-forwarding-config-done').addEventListener(
    363       'click', commitPortForwardingConfig);
    364 }
    365 
    366 function enablePortForwarding(event) {
    367   chrome.send('set-port-forwarding-enabled', [event.target.checked]);
    368 }
    369 
    370 function handleKey(event) {
    371   switch (event.keyCode) {
    372     case 13:  // Enter
    373       if (event.target.nodeName == 'INPUT') {
    374         var line = event.target.parentNode;
    375         if (!line.classList.contains('fresh') ||
    376             line.classList.contains('empty'))
    377           commitPortForwardingConfig();
    378         else
    379           commitFreshLineIfValid(true /* select new line */);
    380       } else {
    381         commitPortForwardingConfig();
    382       }
    383       break;
    384 
    385     case 27:
    386       closePortForwardingConfig();
    387       break;
    388   }
    389 }
    390 
    391 function openPortForwardingConfig() {
    392   loadPortForwardingConfig(window.portForwardingConfig);
    393 
    394   $('port-forwarding-overlay').classList.add('open');
    395   document.addEventListener('keyup', handleKey);
    396 
    397   var freshPort = document.querySelector('.fresh .port');
    398   if (freshPort)
    399     freshPort.focus();
    400   else
    401     $('port-forwarding-config-done').focus();
    402 }
    403 
    404 function closePortForwardingConfig() {
    405   $('port-forwarding-overlay').classList.remove('open');
    406   document.removeEventListener('keyup', handleKey);
    407 }
    408 
    409 function loadPortForwardingConfig(config) {
    410   var list = $('port-forwarding-config-list');
    411   list.textContent = '';
    412   for (var port in config)
    413     list.appendChild(createConfigLine(port, config[port]));
    414   list.appendChild(createEmptyConfigLine());
    415 }
    416 
    417 function commitPortForwardingConfig() {
    418   if (document.querySelector(
    419       '.port-forwarding-pair:not(.fresh) input.invalid'))
    420     return;
    421 
    422   if (document.querySelector(
    423       '.port-forwarding-pair.fresh:not(.empty) input.invalid'))
    424     return;
    425 
    426   closePortForwardingConfig();
    427   commitFreshLineIfValid();
    428   var lines = document.querySelectorAll('.port-forwarding-pair');
    429   var config = {};
    430   for (var i = 0; i != lines.length; i++) {
    431     var line = lines[i];
    432     var portInput = line.querySelector('.port:not(.invalid)');
    433     var locationInput = line.querySelector('.location:not(.invalid)');
    434     if (portInput && locationInput)
    435       config[portInput.value] = locationInput.value;
    436   }
    437   chrome.send('set-port-forwarding-config', [config]);
    438 }
    439 
    440 function updatePortForwardingEnabled(enabled) {
    441   var checkbox = $('port-forwarding-enable');
    442   checkbox.checked = !!enabled;
    443   checkbox.disabled = false;
    444 }
    445 
    446 function updatePortForwardingConfig(config) {
    447   window.portForwardingConfig = config;
    448   $('port-forwarding-config-open').disabled = !config;
    449 }
    450 
    451 function createConfigLine(port, location) {
    452   var line = document.createElement('div');
    453   line.className = 'port-forwarding-pair';
    454 
    455   var portInput = createConfigField(port, 'port', 'Port', validatePort);
    456   line.appendChild(portInput);
    457 
    458   var locationInput = createConfigField(
    459       location, 'location', 'IP address and port', validateLocation);
    460   line.appendChild(locationInput);
    461   locationInput.addEventListener('keydown', function(e) {
    462     if (e.keyIdentifier == 'U+0009' &&  // Tab
    463         !e.ctrlKey && !e.altKey && !e.shiftKey && !e.metaKey &&
    464         line.classList.contains('fresh') &&
    465         !line.classList.contains('empty')) {
    466       // Tabbing forward on the fresh line, try create a new empty one.
    467       commitFreshLineIfValid(true);
    468       e.preventDefault();
    469     }
    470   });
    471 
    472   var lineDelete = document.createElement('div');
    473   lineDelete.className = 'close-button';
    474   lineDelete.addEventListener('click', function() {
    475     var newSelection = line.nextElementSibling;
    476     line.parentNode.removeChild(line);
    477     selectLine(newSelection);
    478   });
    479   line.appendChild(lineDelete);
    480 
    481   line.addEventListener('click', selectLine.bind(null, line));
    482   line.addEventListener('focus', selectLine.bind(null, line));
    483 
    484   checkEmptyLine(line);
    485 
    486   return line;
    487 }
    488 
    489 function validatePort(input) {
    490   var match = input.value.match(/^(\d+)$/);
    491   if (!match)
    492     return false;
    493   var port = parseInt(match[1]);
    494   if (port < 5000 || 10000 < port)
    495     return false;
    496 
    497   var inputs = document.querySelectorAll('input.port:not(.invalid)');
    498   for (var i = 0; i != inputs.length; ++i) {
    499     if (inputs[i] == input)
    500       break;
    501     if (parseInt(inputs[i].value) == port)
    502       return false;
    503   }
    504   return true;
    505 }
    506 
    507 function validateLocation(input) {
    508   var match = input.value.match(/^([a-zA-Z0-9\.]+):(\d+)$/);
    509   if (!match)
    510     return false;
    511   var port = parseInt(match[2]);
    512   return port <= 10000;
    513 }
    514 
    515 function createEmptyConfigLine() {
    516   var line = createConfigLine('', '');
    517   line.classList.add('fresh');
    518   return line;
    519 }
    520 
    521 function createConfigField(value, className, hint, validate) {
    522   var input = document.createElement('input');
    523   input.className = className;
    524   input.type = 'text';
    525   input.placeholder = hint;
    526   input.value = value;
    527 
    528   function checkInput() {
    529     if (validate(input))
    530       input.classList.remove('invalid');
    531     else
    532       input.classList.add('invalid');
    533     if (input.parentNode)
    534       checkEmptyLine(input.parentNode);
    535   }
    536   checkInput();
    537 
    538   input.addEventListener('keyup', checkInput);
    539   input.addEventListener('focus', function() {
    540     selectLine(input.parentNode);
    541   });
    542 
    543   return input;
    544 }
    545 
    546 function checkEmptyLine(line) {
    547   var inputs = line.querySelectorAll('input');
    548   var empty = true;
    549   for (var i = 0; i != inputs.length; i++) {
    550     if (inputs[i].value != '')
    551       empty = false;
    552   }
    553   if (empty)
    554     line.classList.add('empty');
    555   else
    556     line.classList.remove('empty');
    557 }
    558 
    559 function selectLine(line) {
    560   if (line.classList.contains('selected'))
    561     return;
    562   unselectLine();
    563   line.classList.add('selected');
    564 }
    565 
    566 function unselectLine() {
    567   var line = document.querySelector('.port-forwarding-pair.selected');
    568   if (!line)
    569     return;
    570   line.classList.remove('selected');
    571   commitFreshLineIfValid();
    572 }
    573 
    574 function commitFreshLineIfValid(opt_selectNew) {
    575   var line = document.querySelector('.port-forwarding-pair.fresh');
    576   if (line.querySelector('.invalid'))
    577     return;
    578   line.classList.remove('fresh');
    579   var freshLine = createEmptyConfigLine();
    580   line.parentNode.appendChild(freshLine);
    581   if (opt_selectNew)
    582     freshLine.querySelector('.port').focus();
    583 }
    584 
    585 document.addEventListener('DOMContentLoaded', onload);
    586