Home | History | Annotate | Download | only in js
      1 // Copyright 2013 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 /** @type {string}
      6  * @const
      7  */
      8 var FEEDBACK_LANDING_PAGE =
      9     'https://www.google.com/support/chrome/go/feedback_confirmation';
     10 /** @type {number}
     11  * @const
     12  */
     13 var MAX_ATTACH_FILE_SIZE = 3 * 1024 * 1024;
     14 
     15 /**
     16  * @type {number}
     17  * @const
     18  */
     19 var FEEDBACK_MIN_WIDTH = 500;
     20 /**
     21  * @type {number}
     22  * @const
     23  */
     24 var FEEDBACK_MIN_HEIGHT = 585;
     25 
     26 /** @type {number}
     27  * @const
     28  */
     29 var CONTENT_MARGIN_HEIGHT = 40;
     30 
     31 /** @type {number}
     32  * @const
     33  */
     34 var MAX_SCREENSHOT_WIDTH = 100;
     35 
     36 /** @type {string}
     37  * @const
     38  */
     39 var SYSINFO_WINDOW_ID = 'sysinfo_window';
     40 
     41 /** @type {string}
     42  * @const
     43  */
     44 var STATS_WINDOW_ID = 'stats_window';
     45 
     46 var attachedFileBlob = null;
     47 var lastReader = null;
     48 
     49 var feedbackInfo = null;
     50 var systemInfo = null;
     51 
     52 /**
     53  * Reads the selected file when the user selects a file.
     54  * @param {Event} fileSelectedEvent The onChanged event for the file input box.
     55  */
     56 function onFileSelected(fileSelectedEvent) {
     57   $('attach-error').hidden = true;
     58   var file = fileSelectedEvent.target.files[0];
     59   if (!file) {
     60     // User canceled file selection.
     61     attachedFileBlob = null;
     62     return;
     63   }
     64 
     65   if (file.size > MAX_ATTACH_FILE_SIZE) {
     66     $('attach-error').hidden = false;
     67 
     68     // Clear our selected file.
     69     $('attach-file').value = '';
     70     attachedFileBlob = null;
     71     return;
     72   }
     73 
     74   attachedFileBlob = file.slice();
     75 }
     76 
     77 /**
     78  * Clears the file that was attached to the report with the initial request.
     79  * Instead we will now show the attach file button in case the user wants to
     80  * attach another file.
     81  */
     82 function clearAttachedFile() {
     83   $('custom-file-container').hidden = true;
     84   attachedFileBlob = null;
     85   feedbackInfo.attachedFile = null;
     86   $('attach-file').hidden = false;
     87 }
     88 
     89 /**
     90  * Creates a closure that creates or shows a window with the given url.
     91  * @param {string} windowId A string with the ID of the window we are opening.
     92  * @param {string} url The destination URL of the new window.
     93  * @return {function()} A function to be called to open the window.
     94  */
     95 function windowOpener(windowId, url) {
     96   return function(e) {
     97     e.preventDefault();
     98     chrome.app.window.create(url, {id: windowId});
     99   };
    100 }
    101 
    102 /**
    103  * Opens a new window with chrome://slow_trace, downloading performance data.
    104  */
    105 function openSlowTraceWindow() {
    106   chrome.app.window.create(
    107       'chrome://slow_trace/tracing.zip#' + feedbackInfo.traceId);
    108 }
    109 
    110 /**
    111  * Sends the report; after the report is sent, we need to be redirected to
    112  * the landing page, but we shouldn't be able to navigate back, hence
    113  * we open the landing page in a new tab and sendReport closes this tab.
    114  * @return {boolean} True if the report was sent.
    115  */
    116 function sendReport() {
    117   if ($('description-text').value.length == 0) {
    118     var description = $('description-text');
    119     description.placeholder = loadTimeData.getString('no-description');
    120     description.focus();
    121     return false;
    122   }
    123 
    124   // Prevent double clicking from sending additional reports.
    125   $('send-report-button').disabled = true;
    126   console.log('Feedback: Sending report');
    127   if (!feedbackInfo.attachedFile && attachedFileBlob) {
    128     feedbackInfo.attachedFile = { name: $('attach-file').value,
    129                                   data: attachedFileBlob };
    130   }
    131 
    132   feedbackInfo.description = $('description-text').value;
    133   feedbackInfo.pageUrl = $('page-url-text').value;
    134   feedbackInfo.email = $('user-email-text').value;
    135 
    136   var useSystemInfo = false;
    137   var useHistograms = false;
    138   if ($('sys-info-checkbox') != null &&
    139       $('sys-info-checkbox').checked &&
    140       systemInfo != null) {
    141     // Send histograms along with system info.
    142     useSystemInfo = useHistograms = true;
    143   }
    144 <if expr="chromeos">
    145   if ($('performance-info-checkbox') == null ||
    146       !($('performance-info-checkbox').checked)) {
    147     feedbackInfo.traceId = null;
    148   }
    149 </if>
    150 
    151   if (useSystemInfo) {
    152     if (feedbackInfo.systemInformation != null) {
    153       // Concatenate sysinfo if we had any initial system information
    154       // sent with the feedback request event.
    155       feedbackInfo.systemInformation =
    156           feedbackInfo.systemInformation.concat(systemInfo);
    157     } else {
    158       feedbackInfo.systemInformation = systemInfo;
    159     }
    160   }
    161 
    162   feedbackInfo.sendHistograms = useHistograms;
    163 
    164   // If the user doesn't want to send the screenshot.
    165   if (!$('screenshot-checkbox').checked)
    166     feedbackInfo.screenshot = null;
    167 
    168   chrome.feedbackPrivate.sendFeedback(feedbackInfo, function(result) {
    169     window.open(FEEDBACK_LANDING_PAGE, '_blank');
    170     window.close();
    171   });
    172 
    173   return true;
    174 }
    175 
    176 /**
    177  * Click listener for the cancel button.
    178  * @param {Event} e The click event being handled.
    179  */
    180 function cancel(e) {
    181   e.preventDefault();
    182   window.close();
    183 }
    184 
    185 /**
    186  * Converts a blob data URL to a blob object.
    187  * @param {string} url The data URL to convert.
    188  * @return {Blob} Blob object containing the data.
    189  */
    190 function dataUrlToBlob(url) {
    191   var mimeString = url.split(',')[0].split(':')[1].split(';')[0];
    192   var data = atob(url.split(',')[1]);
    193   var dataArray = [];
    194   for (var i = 0; i < data.length; ++i)
    195     dataArray.push(data.charCodeAt(i));
    196 
    197   return new Blob([new Uint8Array(dataArray)], {type: mimeString});
    198 }
    199 
    200 <if expr="chromeos">
    201 /**
    202  * Update the page when performance feedback state is changed.
    203  */
    204 function performanceFeedbackChanged() {
    205   if ($('performance-info-checkbox').checked) {
    206     $('attach-file').disabled = true;
    207     $('attach-file').checked = false;
    208 
    209     $('screenshot-checkbox').disabled = true;
    210     $('screenshot-checkbox').checked = false;
    211   } else {
    212     $('attach-file').disabled = false;
    213     $('screenshot-checkbox').disabled = false;
    214   }
    215 }
    216 </if>
    217 
    218 function resizeAppWindow() {
    219   // We pick the width from the titlebar, which has no margins.
    220   var width = $('title-bar').scrollWidth;
    221   if (width < FEEDBACK_MIN_WIDTH)
    222     width = FEEDBACK_MIN_WIDTH;
    223 
    224   // We get the height by adding the titlebar height and the content height +
    225   // margins. We can't get the margins for the content-pane here by using
    226   // style.margin - the variable seems to not exist.
    227   var height = $('title-bar').scrollHeight +
    228       $('content-pane').scrollHeight + CONTENT_MARGIN_HEIGHT;
    229   if (height < FEEDBACK_MIN_HEIGHT)
    230     height = FEEDBACK_MIN_HEIGHT;
    231 
    232   chrome.app.window.current().resizeTo(width, height);
    233 }
    234 
    235 /**
    236  * Initializes our page.
    237  * Flow:
    238  * .) DOMContent Loaded        -> . Request feedbackInfo object
    239  *                                . Setup page event handlers
    240  * .) Feedback Object Received -> . take screenshot
    241  *                                . request email
    242  *                                . request System info
    243  *                                . request i18n strings
    244  * .) Screenshot taken         -> . Show Feedback window.
    245  */
    246 function initialize() {
    247   // TODO(rkc):  Remove logging once crbug.com/284662 is closed.
    248   console.log('FEEDBACK_DEBUG: feedback.js: initialize()');
    249 
    250   // Add listener to receive the feedback info object.
    251   chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    252     if (request.sentFromEventPage) {
    253       // TODO(rkc):  Remove logging once crbug.com/284662 is closed.
    254       console.log('FEEDBACK_DEBUG: Received feedbackInfo.');
    255       feedbackInfo = request.data;
    256       $('description-text').textContent = feedbackInfo.description;
    257       if (feedbackInfo.pageUrl)
    258         $('page-url-text').value = feedbackInfo.pageUrl;
    259 
    260       takeScreenshot(function(screenshotCanvas) {
    261         // TODO(rkc):  Remove logging once crbug.com/284662 is closed.
    262         console.log('FEEDBACK_DEBUG: Taken screenshot. Showing window.');
    263 
    264         // We've taken our screenshot, show the feedback page without any
    265         // further delay.
    266         window.webkitRequestAnimationFrame(function() {
    267           resizeAppWindow();
    268         });
    269         chrome.app.window.current().show();
    270 
    271         var screenshotDataUrl = screenshotCanvas.toDataURL('image/png');
    272         $('screenshot-image').src = screenshotDataUrl;
    273         $('screenshot-image').classList.toggle('wide-screen',
    274             $('screenshot-image').width > MAX_SCREENSHOT_WIDTH);
    275         feedbackInfo.screenshot = dataUrlToBlob(screenshotDataUrl);
    276       });
    277 
    278       chrome.feedbackPrivate.getUserEmail(function(email) {
    279         $('user-email-text').value = email;
    280       });
    281 
    282       chrome.feedbackPrivate.getSystemInformation(function(sysInfo) {
    283         systemInfo = sysInfo;
    284       });
    285 
    286       // An extension called us with an attached file.
    287       if (feedbackInfo.attachedFile) {
    288         $('attached-filename-text').textContent =
    289             feedbackInfo.attachedFile.name;
    290         attachedFileBlob = feedbackInfo.attachedFile.data;
    291         $('custom-file-container').hidden = false;
    292         $('attach-file').hidden = true;
    293       }
    294 
    295 <if expr="chromeos">
    296       if (feedbackInfo.traceId && ($('performance-info-area'))) {
    297         $('performance-info-area').hidden = false;
    298         $('performance-info-checkbox').checked = true;
    299         performanceFeedbackChanged();
    300         $('performance-info-link').onclick = openSlowTraceWindow;
    301       }
    302 </if>
    303 
    304       chrome.feedbackPrivate.getStrings(function(strings) {
    305         loadTimeData.data = strings;
    306         i18nTemplate.process(document, loadTimeData);
    307 
    308         if ($('sys-info-url')) {
    309           // Opens a new window showing the current system info.
    310           $('sys-info-url').onclick =
    311               windowOpener(SYSINFO_WINDOW_ID, 'chrome://system');
    312         }
    313         if ($('histograms-url')) {
    314           // Opens a new window showing the histogram metrics.
    315           $('histograms-url').onclick =
    316               windowOpener(STATS_WINDOW_ID, 'chrome://histograms');
    317         }
    318       });
    319     }
    320   });
    321 
    322   window.addEventListener('DOMContentLoaded', function() {
    323     // TODO(rkc):  Remove logging once crbug.com/284662 is closed.
    324     console.log('FEEDBACK_DEBUG: feedback.js: DOMContentLoaded');
    325     // Ready to receive the feedback object.
    326     chrome.runtime.sendMessage({ready: true});
    327 
    328     // Setup our event handlers.
    329     $('attach-file').addEventListener('change', onFileSelected);
    330     $('send-report-button').onclick = sendReport;
    331     $('cancel-button').onclick = cancel;
    332     $('remove-attached-file').onclick = clearAttachedFile;
    333 <if expr="chromeos">
    334     $('performance-info-checkbox').addEventListener(
    335         'change', performanceFeedbackChanged);
    336 </if>
    337   });
    338 }
    339 
    340 initialize();
    341