Home | History | Annotate | Download | only in proxy_configuration
      1 // Copyright (c) 2011 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 /**
      6  * @fileoverview This file implements the ProxyFormController class, which
      7  * wraps a form element with logic that enables implementation of proxy
      8  * settings.
      9  *
     10  * @author mkwst (a] google.com (Mike West)
     11  */
     12 
     13 /**
     14  * Wraps the proxy configuration form, binding proper handlers to its various
     15  * `change`, `click`, etc. events in order to take appropriate action in
     16  * response to user events.
     17  *
     18  * @param {string} id The form's DOM ID.
     19  * @constructor
     20  */
     21 var ProxyFormController = function(id) {
     22   /**
     23    * The wrapped form element
     24    * @type {Node}
     25    * @private
     26    */
     27   this.form_ = document.getElementById(id);
     28 
     29   // Throw an error if the element either doesn't exist, or isn't a form.
     30   if (!this.form_)
     31     throw chrome.i18n.getMessage('errorIdNotFound', id);
     32   else if (this.form_.nodeName !== 'FORM')
     33     throw chrome.i18n.getMessage('errorIdNotForm', id);
     34 
     35   /**
     36    * Cached references to the `fieldset` groups that define the configuration
     37    * options presented to the user.
     38    *
     39    * @type {NodeList}
     40    * @private
     41    */
     42   this.configGroups_ = document.querySelectorAll('#' + id + ' > fieldset');
     43 
     44   this.bindEventHandlers_();
     45   this.readCurrentState_();
     46 
     47   // Handle errors
     48   this.handleProxyErrors_();
     49 };
     50 
     51 ///////////////////////////////////////////////////////////////////////////////
     52 
     53 /**
     54  * The proxy types we're capable of handling.
     55  * @enum {string}
     56  */
     57 ProxyFormController.ProxyTypes = {
     58   AUTO: 'auto_detect',
     59   PAC: 'pac_script',
     60   DIRECT: 'direct',
     61   FIXED: 'fixed_servers',
     62   SYSTEM: 'system'
     63 };
     64 
     65 /**
     66  * The window types we're capable of handling.
     67  * @enum {int}
     68  */
     69 ProxyFormController.WindowTypes = {
     70   REGULAR: 1,
     71   INCOGNITO: 2
     72 };
     73 
     74 /**
     75  * The extension's level of control of Chrome's roxy setting
     76  * @enum {string}
     77  */
     78 ProxyFormController.LevelOfControl = {
     79   NOT_CONTROLLABLE: 'not_controllable',
     80   OTHER_EXTENSION: 'controlled_by_other_extension',
     81   AVAILABLE: 'controllable_by_this_extension',
     82   CONTROLLING: 'controlled_by_this_extension'
     83 };
     84 
     85 /**
     86  * The response type from 'proxy.settings.get'
     87  *
     88  * @typedef {{value: ProxyConfig,
     89  *     levelOfControl: ProxyFormController.LevelOfControl}}
     90  */
     91 ProxyFormController.WrappedProxyConfig;
     92 
     93 ///////////////////////////////////////////////////////////////////////////////
     94 
     95 /**
     96  * Retrieves proxy settings that have been persisted across restarts.
     97  *
     98  * @return {?ProxyConfig} The persisted proxy configuration, or null if no
     99  *     value has been persisted.
    100  * @static
    101  */
    102 ProxyFormController.getPersistedSettings = function() {
    103   var result = null;
    104   if (window.localStorage['proxyConfig'] !== undefined)
    105     result = JSON.parse(window.localStorage['proxyConfig']);
    106   return result ? result : null;
    107 };
    108 
    109 
    110 /**
    111  * Persists proxy settings across restarts.
    112  *
    113  * @param {!ProxyConfig} config The proxy config to persist.
    114  * @static
    115  */
    116 ProxyFormController.setPersistedSettings = function(config) {
    117   window.localStorage['proxyConfig'] = JSON.stringify(config);
    118 };
    119 
    120 ///////////////////////////////////////////////////////////////////////////////
    121 
    122 ProxyFormController.prototype = {
    123   /**
    124    * The form's current state.
    125    * @type {regular: ?ProxyConfig, incognito: ?ProxyConfig}
    126    * @private
    127    */
    128   config_: {regular: null, incognito: null},
    129 
    130   /**
    131    * Do we have access to incognito mode?
    132    * @type {boolean}
    133    * @private
    134    */
    135   isAllowedIncognitoAccess_: false,
    136 
    137   /**
    138    * @return {string} The PAC file URL (or an empty string).
    139    */
    140   get pacURL() {
    141     return document.getElementById('autoconfigURL').value;
    142   },
    143 
    144 
    145   /**
    146    * @param {!string} value The PAC file URL.
    147    */
    148   set pacURL(value) {
    149     document.getElementById('autoconfigURL').value = value;
    150   },
    151 
    152 
    153   /**
    154    * @return {string} The PAC file data (or an empty string).
    155    */
    156   get manualPac() {
    157     return document.getElementById('autoconfigData').value;
    158   },
    159 
    160 
    161   /**
    162    * @param {!string} value The PAC file data.
    163    */
    164   set manualPac(value) {
    165     document.getElementById('autoconfigData').value = value;
    166   },
    167 
    168 
    169   /**
    170    * @return {Array.<string>} A list of hostnames that should bypass the proxy.
    171    */
    172   get bypassList() {
    173     return document.getElementById('bypassList').value.split(/\s*(?:,|^)\s*/m);
    174   },
    175 
    176 
    177   /**
    178    * @param {?Array.<string>} data A list of hostnames that should bypass
    179    *     the proxy. If empty, the bypass list is emptied.
    180    */
    181   set bypassList(data) {
    182     if (!data)
    183       data = [];
    184     document.getElementById('bypassList').value = data.join(', ');
    185   },
    186 
    187 
    188   /**
    189    * @see http://code.google.com/chrome/extensions/trunk/proxy.html
    190    * @return {?ProxyServer} An object containing the proxy server host, port,
    191    *     and scheme. If null, there is no single proxy.
    192    */
    193   get singleProxy() {
    194     var checkbox = document.getElementById('singleProxyForEverything');
    195     return checkbox.checked ? this.httpProxy : null;
    196   },
    197 
    198 
    199   /**
    200    * @see http://code.google.com/chrome/extensions/trunk/proxy.html
    201    * @param {?ProxyServer} data An object containing the proxy server host,
    202    *     port, and scheme. If null, the single proxy checkbox will be unchecked.
    203    */
    204   set singleProxy(data) {
    205     var checkbox = document.getElementById('singleProxyForEverything');
    206     checkbox.checked = !!data;
    207 
    208     if (data)
    209       this.httpProxy = data;
    210 
    211     if (checkbox.checked)
    212       checkbox.parentNode.parentNode.classList.add('single');
    213     else
    214       checkbox.parentNode.parentNode.classList.remove('single');
    215   },
    216 
    217   /**
    218    * @return {?ProxyServer} An object containing the proxy server host, port
    219    *     and scheme.
    220    */
    221   get httpProxy() {
    222     return this.getProxyImpl_('Http');
    223   },
    224 
    225 
    226   /**
    227    * @param {?ProxyServer} data An object containing the proxy server host,
    228    *     port, and scheme. If empty, empties the proxy setting.
    229    */
    230   set httpProxy(data) {
    231     this.setProxyImpl_('Http', data);
    232   },
    233 
    234 
    235   /**
    236    * @return {?ProxyServer} An object containing the proxy server host, port
    237    *     and scheme.
    238    */
    239   get httpsProxy() {
    240     return this.getProxyImpl_('Https');
    241   },
    242 
    243 
    244   /**
    245    * @param {?ProxyServer} data An object containing the proxy server host,
    246    *     port, and scheme. If empty, empties the proxy setting.
    247    */
    248   set httpsProxy(data) {
    249     this.setProxyImpl_('Https', data);
    250   },
    251 
    252 
    253   /**
    254    * @return {?ProxyServer} An object containing the proxy server host, port
    255    *     and scheme.
    256    */
    257   get ftpProxy() {
    258     return this.getProxyImpl_('Ftp');
    259   },
    260 
    261 
    262   /**
    263    * @param {?ProxyServer} data An object containing the proxy server host,
    264    *     port, and scheme. If empty, empties the proxy setting.
    265    */
    266   set ftpProxy(data) {
    267     this.setProxyImpl_('Ftp', data);
    268   },
    269 
    270 
    271   /**
    272    * @return {?ProxyServer} An object containing the proxy server host, port
    273    *     and scheme.
    274    */
    275   get fallbackProxy() {
    276     return this.getProxyImpl_('Fallback');
    277   },
    278 
    279 
    280   /**
    281    * @param {?ProxyServer} data An object containing the proxy server host,
    282    *     port, and scheme. If empty, empties the proxy setting.
    283    */
    284   set fallbackProxy(data) {
    285     this.setProxyImpl_('Fallback', data);
    286   },
    287 
    288 
    289   /**
    290    * @param {string} type The type of proxy that's being set ("Http",
    291    *     "Https", etc.).
    292    * @return {?ProxyServer} An object containing the proxy server host,
    293    *     port, and scheme.
    294    * @private
    295    */
    296   getProxyImpl_: function(type) {
    297     var result = {
    298       scheme: document.getElementById('proxyScheme' + type).value,
    299       host: document.getElementById('proxyHost' + type).value,
    300       port: parseInt(document.getElementById('proxyPort' + type).value, 10)
    301     };
    302     return (result.scheme && result.host && result.port) ? result : undefined;
    303   },
    304 
    305 
    306   /**
    307    * A generic mechanism for setting proxy data.
    308    *
    309    * @see http://code.google.com/chrome/extensions/trunk/proxy.html
    310    * @param {string} type The type of proxy that's being set ("Http",
    311    *     "Https", etc.).
    312    * @param {?ProxyServer} data An object containing the proxy server host,
    313    *     port, and scheme. If empty, empties the proxy setting.
    314    * @private
    315    */
    316   setProxyImpl_: function(type, data) {
    317     if (!data)
    318       data = {scheme: 'http', host: '', port: ''};
    319 
    320     document.getElementById('proxyScheme' + type).value = data.scheme;
    321     document.getElementById('proxyHost' + type).value = data.host;
    322     document.getElementById('proxyPort' + type).value = data.port;
    323   },
    324 
    325 ///////////////////////////////////////////////////////////////////////////////
    326 
    327   /**
    328    * Calls the proxy API to read the current settings, and populates the form
    329    * accordingly.
    330    *
    331    * @private
    332    */
    333   readCurrentState_: function() {
    334     chrome.extension.isAllowedIncognitoAccess(
    335         this.handleIncognitoAccessResponse_.bind(this));
    336   },
    337 
    338   /**
    339    * Handles the respnse from `chrome.extension.isAllowedIncognitoAccess`
    340    * We can't render the form until we know what our access level is, so
    341    * we wait until we have confirmed incognito access levels before
    342    * asking for the proxy state.
    343    *
    344    * @param {boolean} state The state of incognito access.
    345    * @private
    346    */
    347   handleIncognitoAccessResponse_: function(state) {
    348     this.isAllowedIncognitoAccess_ = state;
    349     chrome.proxy.settings.get({incognito: false},
    350         this.handleRegularState_.bind(this));
    351     if (this.isAllowedIncognitoAccess_) {
    352       chrome.proxy.settings.get({incognito: true},
    353           this.handleIncognitoState_.bind(this));
    354     }
    355   },
    356 
    357   /**
    358    * Handles the response from 'proxy.settings.get' for regular
    359    * settings.
    360    *
    361    * @param {ProxyFormController.WrappedProxyConfig} c The proxy data and
    362    *     extension's level of control thereof.
    363    * @private
    364    */
    365   handleRegularState_: function(c) {
    366     if (c.levelOfControl === ProxyFormController.LevelOfControl.AVAILABLE ||
    367         c.levelOfControl === ProxyFormController.LevelOfControl.CONTROLLING) {
    368       this.recalcFormValues_(c.value);
    369       this.config_.regular = c.value;
    370     } else {
    371       this.handleLackOfControl_(c.levelOfControl);
    372     }
    373   },
    374 
    375   /**
    376    * Handles the response from 'proxy.settings.get' for incognito
    377    * settings.
    378    *
    379    * @param {ProxyFormController.WrappedProxyConfig} c The proxy data and
    380    *     extension's level of control thereof.
    381    * @private
    382    */
    383   handleIncognitoState_: function(c) {
    384     if (c.levelOfControl === ProxyFormController.LevelOfControl.AVAILABLE ||
    385         c.levelOfControl === ProxyFormController.LevelOfControl.CONTROLLING) {
    386       if (this.isIncognitoMode_())
    387         this.recalcFormValues_(c.value);
    388 
    389       this.config_.incognito = c.value;
    390     } else {
    391       this.handleLackOfControl_(c.levelOfControl);
    392     }
    393   },
    394 
    395   /**
    396    * Binds event handlers for the various bits and pieces of the form that
    397    * are interesting to the controller.
    398    *
    399    * @private
    400    */
    401   bindEventHandlers_: function() {
    402     this.form_.addEventListener('click', this.dispatchFormClick_.bind(this));
    403   },
    404 
    405 
    406   /**
    407    * When a `click` event is triggered on the form, this function handles it by
    408    * analyzing the context, and dispatching the click to the correct handler.
    409    *
    410    * @param {Event} e The event to be handled.
    411    * @private
    412    * @return {boolean} True if the event should bubble, false otherwise.
    413    */
    414   dispatchFormClick_: function(e) {
    415     var t = e.target;
    416 
    417     // Case 1: "Apply"
    418     if (t.nodeName === 'INPUT' && t.getAttribute('type') === 'submit') {
    419       return this.applyChanges_(e);
    420 
    421     // Case 2: "Use the same proxy for all protocols" in an active section
    422     } else if (t.nodeName === 'INPUT' &&
    423                t.getAttribute('type') === 'checkbox' &&
    424                t.parentNode.parentNode.parentNode.classList.contains('active')
    425               ) {
    426       return this.toggleSingleProxyConfig_(e);
    427 
    428     // Case 3: "Flip to incognito mode."
    429     } else if (t.nodeName === 'BUTTON') {
    430       return this.toggleIncognitoMode_(e);
    431 
    432     // Case 4: Click on something random: maybe changing active config group?
    433     } else {
    434       // Walk up the tree until we hit `form > fieldset` or fall off the top
    435       while (t && (t.nodeName !== 'FIELDSET' ||
    436              t.parentNode.nodeName !== 'FORM')) {
    437         t = t.parentNode;
    438       }
    439       if (t) {
    440         this.changeActive_(t);
    441         return false;
    442       }
    443     }
    444     return true;
    445   },
    446 
    447 
    448   /**
    449    * Sets the form's active config group.
    450    *
    451    * @param {DOMElement} fieldset The configuration group to activate.
    452    * @private
    453    */
    454   changeActive_: function(fieldset) {
    455     for (var i = 0; i < this.configGroups_.length; i++) {
    456       var el = this.configGroups_[i];
    457       var radio = el.querySelector("input[type='radio']");
    458       if (el === fieldset) {
    459         el.classList.add('active');
    460         radio.checked = true;
    461       } else {
    462         el.classList.remove('active');
    463       }
    464     }
    465     this.recalcDisabledInputs_();
    466   },
    467 
    468 
    469   /**
    470    * Recalculates the `disabled` state of the form's input elements, based
    471    * on the currently active group, and that group's contents.
    472    *
    473    * @private
    474    */
    475   recalcDisabledInputs_: function() {
    476     var i, j;
    477     for (i = 0; i < this.configGroups_.length; i++) {
    478       var el = this.configGroups_[i];
    479       var inputs = el.querySelectorAll(
    480           "input:not([type='radio']), select, textarea");
    481       if (el.classList.contains('active')) {
    482         for (j = 0; j < inputs.length; j++) {
    483           inputs[j].removeAttribute('disabled');
    484         }
    485       } else {
    486         for (j = 0; j < inputs.length; j++) {
    487           inputs[j].setAttribute('disabled', 'disabled');
    488         }
    489       }
    490     }
    491   },
    492 
    493 
    494   /**
    495    * Handler called in response to click on form's submission button. Generates
    496    * the proxy configuration and passes it to `useCustomProxySettings`, or
    497    * handles errors in user input.
    498    *
    499    * Proxy errors (and the browser action's badge) are cleared upon setting new
    500    * values.
    501    *
    502    * @param {Event} e DOM event generated by the user's click.
    503    * @private
    504    */
    505   applyChanges_: function(e) {
    506     e.preventDefault();
    507     e.stopPropagation();
    508 
    509     if (this.isIncognitoMode_())
    510       this.config_.incognito = this.generateProxyConfig_();
    511     else
    512       this.config_.regular = this.generateProxyConfig_();
    513 
    514     chrome.proxy.settings.set(
    515         {value: this.config_.regular, scope: 'regular'},
    516         this.callbackForRegularSettings_.bind(this));
    517     chrome.extension.sendRequest({type: 'clearError'});
    518   },
    519 
    520   /**
    521    * Called in response to setting a regular window's proxy settings: checks
    522    * for `lastError`, and then sets incognito settings (if they exist).
    523    *
    524    * @private
    525    */
    526   callbackForRegularSettings_: function() {
    527     if (chrome.runtime.lastError) {
    528       this.generateAlert_(chrome.i18n.getMessage('errorSettingRegularProxy'));
    529       return;
    530     }
    531     if (this.config_.incognito) {
    532       chrome.proxy.settings.set(
    533           {value: this.config_.incognito, scope: 'incognito_persistent'},
    534           this.callbackForIncognitoSettings_.bind(this));
    535     } else {
    536       ProxyFormController.setPersistedSettings(this.config_);
    537       this.generateAlert_(chrome.i18n.getMessage('successfullySetProxy'));
    538     }
    539   },
    540 
    541   /**
    542    * Called in response to setting an incognito window's proxy settings: checks
    543    * for `lastError` and sets a success message.
    544    *
    545    * @private
    546    */
    547   callbackForIncognitoSettings_: function() {
    548     if (chrome.runtime.lastError) {
    549       this.generateAlert_(chrome.i18n.getMessage('errorSettingIncognitoProxy'));
    550       return;
    551     }
    552     ProxyFormController.setPersistedSettings(this.config_);
    553     this.generateAlert_(
    554         chrome.i18n.getMessage('successfullySetProxy'));
    555   },
    556 
    557   /**
    558    * Generates an alert overlay inside the proxy's popup, then closes the popup
    559    * after a short delay.
    560    *
    561    * @param {string} msg The message to be displayed in the overlay.
    562    * @param {?boolean} close Should the window be closed?  Defaults to true.
    563    * @private
    564    */
    565   generateAlert_: function(msg, close) {
    566     var success = document.createElement('div');
    567     success.classList.add('overlay');
    568     success.setAttribute('role', 'alert');
    569     success.textContent = msg;
    570     document.body.appendChild(success);
    571 
    572     setTimeout(function() { success.classList.add('visible'); }, 10);
    573     setTimeout(function() {
    574       if (close === false)
    575         success.classList.remove('visible');
    576       else
    577         window.close();
    578     }, 4000);
    579   },
    580 
    581 
    582   /**
    583    * Parses the proxy configuration form, and generates a ProxyConfig object
    584    * that can be passed to `useCustomProxyConfig`.
    585    *
    586    * @see http://code.google.com/chrome/extensions/trunk/proxy.html
    587    * @return {ProxyConfig} The proxy configuration represented by the form.
    588    * @private
    589    */
    590   generateProxyConfig_: function() {
    591     var active = document.getElementsByClassName('active')[0];
    592     switch (active.id) {
    593       case ProxyFormController.ProxyTypes.SYSTEM:
    594         return {mode: 'system'};
    595       case ProxyFormController.ProxyTypes.DIRECT:
    596         return {mode: 'direct'};
    597       case ProxyFormController.ProxyTypes.PAC:
    598         var pacScriptURL = this.pacURL;
    599         var pacManual = this.manualPac;
    600         if (pacScriptURL)
    601           return {mode: 'pac_script',
    602                   pacScript: {url: pacScriptURL, mandatory: true}};
    603         else if (pacManual)
    604           return {mode: 'pac_script',
    605                   pacScript: {data: pacManual, mandatory: true}};
    606         else
    607           return {mode: 'auto_detect'};
    608       case ProxyFormController.ProxyTypes.FIXED:
    609         var config = {mode: 'fixed_servers'};
    610         if (this.singleProxy) {
    611           config.rules = {
    612             singleProxy: this.singleProxy,
    613             bypassList: this.bypassList
    614           };
    615         } else {
    616           config.rules = {
    617             proxyForHttp: this.httpProxy,
    618             proxyForHttps: this.httpsProxy,
    619             proxyForFtp: this.ftpProxy,
    620             fallbackProxy: this.fallbackProxy,
    621             bypassList: this.bypassList
    622           };
    623         }
    624         return config;
    625     }
    626   },
    627 
    628 
    629   /**
    630    * Sets the proper display classes based on the "Use the same proxy server
    631    * for all protocols" checkbox. Expects to be called as an event handler
    632    * when that field is clicked.
    633    *
    634    * @param {Event} e The `click` event to respond to.
    635    * @private
    636    */
    637   toggleSingleProxyConfig_: function(e) {
    638     var checkbox = e.target;
    639     if (checkbox.nodeName === 'INPUT' &&
    640         checkbox.getAttribute('type') === 'checkbox') {
    641       if (checkbox.checked)
    642         checkbox.parentNode.parentNode.classList.add('single');
    643       else
    644         checkbox.parentNode.parentNode.classList.remove('single');
    645     }
    646   },
    647 
    648 
    649   /**
    650    * Returns the form's current incognito status.
    651    *
    652    * @return {boolean} True if the form is in incognito mode, false otherwise.
    653    * @private
    654    */
    655   isIncognitoMode_: function(e) {
    656     return this.form_.parentNode.classList.contains('incognito');
    657   },
    658 
    659 
    660   /**
    661    * Toggles the form's incognito mode. Saves the current state to an object
    662    * property for later use, clears the form, and toggles the appropriate state.
    663    *
    664    * @param {Event} e The `click` event to respond to.
    665    * @private
    666    */
    667   toggleIncognitoMode_: function(e) {
    668     var div = this.form_.parentNode;
    669     var button = document.getElementsByTagName('button')[0];
    670 
    671     // Cancel the button click.
    672     e.preventDefault();
    673     e.stopPropagation();
    674 
    675     // If we can't access Incognito settings, throw a message and return.
    676     if (!this.isAllowedIncognitoAccess_) {
    677       var msg = "I'm sorry, Dave, I'm afraid I can't do that. Give me access " +
    678                 "to Incognito settings by checking the checkbox labeled " +
    679                 "'Allow in Incognito mode', which is visible at " +
    680                 "chrome://extensions.";
    681       this.generateAlert_(msg, false);
    682       return;
    683     }
    684 
    685     if (this.isIncognitoMode_()) {
    686       // In incognito mode, switching to cognito.
    687       this.config_.incognito = this.generateProxyConfig_();
    688       div.classList.remove('incognito');
    689       this.recalcFormValues_(this.config_.regular);
    690       button.innerText = 'Configure incognito window settings.';
    691     } else {
    692       // In cognito mode, switching to incognito.
    693       this.config_.regular = this.generateProxyConfig_();
    694       div.classList.add('incognito');
    695       this.recalcFormValues_(this.config_.incognito);
    696       button.innerText = 'Configure regular window settings.';
    697     }
    698   },
    699 
    700 
    701   /**
    702    * Sets the form's values based on a ProxyConfig.
    703    *
    704    * @param {!ProxyConfig} c The ProxyConfig object.
    705    * @private
    706    */
    707   recalcFormValues_: function(c) {
    708     // Normalize `auto_detect`
    709     if (c.mode === 'auto_detect')
    710       c.mode = 'pac_script';
    711     // Activate one of the groups, based on `mode`.
    712     this.changeActive_(document.getElementById(c.mode));
    713     // Populate the PAC script
    714     if (c.pacScript) {
    715       if (c.pacScript.url)
    716         this.pacURL = c.pacScript.url;
    717     } else {
    718       this.pacURL = '';
    719     }
    720     // Evaluate the `rules`
    721     if (c.rules) {
    722       var rules = c.rules;
    723       if (rules.singleProxy) {
    724         this.singleProxy = rules.singleProxy;
    725       } else {
    726         this.singleProxy = null;
    727         this.httpProxy = rules.proxyForHttp;
    728         this.httpsProxy = rules.proxyForHttps;
    729         this.ftpProxy = rules.proxyForFtp;
    730         this.fallbackProxy = rules.fallbackProxy;
    731       }
    732       this.bypassList = rules.bypassList;
    733     } else {
    734       this.singleProxy = null;
    735       this.httpProxy = null;
    736       this.httpsProxy = null;
    737       this.ftpProxy = null;
    738       this.fallbackProxy = null;
    739       this.bypassList = '';
    740     }
    741   },
    742 
    743 
    744   /**
    745    * Handles the case in which this extension doesn't have the ability to
    746    * control the Proxy settings, either because of an overriding policy
    747    * or an extension with higher priority.
    748    *
    749    * @param {ProxyFormController.LevelOfControl} l The level of control this
    750    *     extension has over the proxy settings.
    751    * @private
    752    */
    753   handleLackOfControl_: function(l) {
    754     var msg;
    755     if (l === ProxyFormController.LevelOfControl.NO_ACCESS)
    756       msg = chrome.i18n.getMessage('errorNoExtensionAccess');
    757     else if (l === ProxyFormController.LevelOfControl.OTHER_EXTENSION)
    758       msg = chrome.i18n.getMessage('errorOtherExtensionControls');
    759     this.generateAlert_(msg);
    760   },
    761 
    762 
    763   /**
    764    * Handle the case in which errors have been generated outside the context
    765    * of this popup.
    766    *
    767    * @private
    768    */
    769   handleProxyErrors_: function() {
    770     chrome.extension.sendRequest(
    771         {type: 'getError'},
    772         this.handleProxyErrorHandlerResponse_.bind(this));
    773   },
    774 
    775   /**
    776    * Handles response from ProxyErrorHandler
    777    *
    778    * @param {{result: !string}} response The message sent in response to this
    779    *     popup's request.
    780    */
    781   handleProxyErrorHandlerResponse_: function(response) {
    782     if (response.result !== null) {
    783       var error = JSON.parse(response.result);
    784       console.error(error);
    785       // TODO(mkwst): Do something more interesting
    786       this.generateAlert_(
    787           chrome.i18n.getMessage(
    788               error.details ? 'errorProxyDetailedError' : 'errorProxyError',
    789               [error.error, error.details]),
    790           false);
    791     }
    792   }
    793 };
    794