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