Home | History | Annotate | Download | only in login
      1 // Copyright 2014 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 Base class for all login WebUI screens.
      7  */
      8 cr.define('login', function() {
      9   var Screen = cr.ui.define('div');
     10 
     11   /** @const */ var CALLBACK_USER_ACTED = 'userActed';
     12   /** @const */ var CALLBACK_CONTEXT_CHANGED = 'contextChanged';
     13 
     14   function doNothing() {};
     15 
     16   var querySelectorAll = HTMLDivElement.prototype.querySelectorAll;
     17 
     18   Screen.prototype = {
     19     __proto__: HTMLDivElement.prototype,
     20 
     21     /**
     22      * Prefix added to sent to Chrome messages' names.
     23      */
     24     sendPrefix_: '',
     25 
     26     /**
     27      * Context used by this screen.
     28      */
     29     context_: null,
     30 
     31     /**
     32      * Dictionary of context observers that are methods of |this| bound to
     33      * |this|.
     34      */
     35     contextObservers_: {},
     36 
     37     get context() {
     38       return this.context_;
     39     },
     40 
     41     /**
     42      * Sends recent context changes to C++ handler.
     43      */
     44     commitContextChanges: function() {
     45       if (!this.context_.hasChanges())
     46         return;
     47       this.send(CALLBACK_CONTEXT_CHANGED, this.context_.getChangesAndReset());
     48     },
     49 
     50     /**
     51      * Sends message to Chrome, adding needed prefix to message name. All
     52      * arguments after |messageName| are packed into message parameters list.
     53      *
     54      * @param {string} messageName Name of message without a prefix.
     55      * @param {...*} varArgs parameters for message.
     56      */
     57     send: function(messageName, varArgs) {
     58       if (arguments.length == 0)
     59         throw Error('Message name is not provided.');
     60       var fullMessageName = this.sendPrefix_ + messageName;
     61       var payload = Array.prototype.slice.call(arguments, 1);
     62       chrome.send(fullMessageName, payload);
     63     },
     64 
     65     decorate: doNothing,
     66 
     67     /**
     68      * Returns minimal size that screen prefers to have. Default implementation
     69      * returns current screen size.
     70      * @return {{width: number, height: number}}
     71      */
     72     getPreferredSize: function() {
     73       return {width: this.offsetWidth, height: this.offsetHeight};
     74     },
     75 
     76     /**
     77      * Called for currently active screen when screen size changed.
     78      */
     79     onWindowResize: doNothing,
     80 
     81     /**
     82      * Does the following things:
     83      *  * Creates screen context.
     84      *  * Looks for elements having "alias" property and adds them as the
     85      *    proprties of the screen with name equal to value of "alias", i.e. HTML
     86      *    element <div alias="myDiv"></div> will be stored in this.myDiv.
     87      *  * Looks for buttons having "action" properties and adds click handlers
     88      *    to them. These handlers send |CALLBACK_USER_ACTED| messages to
     89      *    C++ with "action" property's value as payload.
     90      */
     91     initialize: function() {
     92       this.context_ = new login.ScreenContext();
     93       this.querySelectorAll('[alias]').forEach(function(element) {
     94         this[element.getAttribute('alias')] = element;
     95       }, this);
     96       var self = this;
     97       this.querySelectorAll('button[action]').forEach(function(button) {
     98         button.addEventListener('click', function(e) {
     99           var action = this.getAttribute('action');
    100           self.send(CALLBACK_USER_ACTED, action);
    101           e.stopPropagation();
    102         });
    103       });
    104     },
    105 
    106     /**
    107      * Starts observation of property with |key| of the context attached to
    108      * current screen. This method differs from "login.ScreenContext" in that
    109      * it automatically detects if observer is method of |this| and make
    110      * all needed actions to make it work correctly. So it's no need for client
    111      * to bind methods to |this| and keep resulting callback for
    112      * |removeObserver| call:
    113      *
    114      *   this.addContextObserver('key', this.onKeyChanged_);
    115      *   ...
    116      *   this.removeContextObserver('key', this.onKeyChanged_);
    117      */
    118     addContextObserver: function(key, observer) {
    119       var realObserver = observer;
    120       var propertyName = this.getPropertyNameOf_(observer);
    121       if (propertyName) {
    122         if (!this.contextObservers_.hasOwnProperty(propertyName))
    123           this.contextObservers_[propertyName] = observer.bind(this);
    124         realObserver = this.contextObservers_[propertyName];
    125       }
    126       this.context.addObserver(key, realObserver);
    127     },
    128 
    129     /**
    130      * Removes |observer| from the list of context observers. Supports not only
    131      * regular functions but also screen methods (see comment to
    132      * |addContextObserver|).
    133      */
    134     removeContextObserver: function(observer) {
    135       var realObserver = observer;
    136       var propertyName = this.getPropertyNameOf_(observer);
    137       if (propertyName) {
    138         if (!this.contextObservers_.hasOwnProperty(propertyName))
    139           return;
    140         realObserver = this.contextObservers_[propertyName];
    141         delete this.contextObservers_[propertyName];
    142       }
    143       this.context.removeObserver(realObserver);
    144     },
    145 
    146     /**
    147      * Calls standart |querySelectorAll| method and returns its result converted
    148      * to Array.
    149      */
    150     querySelectorAll: function(selector) {
    151       var list = querySelectorAll.call(this, selector);
    152       return Array.prototype.slice.call(list);
    153     },
    154 
    155     /**
    156      * Called when context changes are recieved from C++.
    157      * @private
    158      */
    159     contextChanged_: function(diff) {
    160       this.context.applyChanges(diff);
    161     },
    162 
    163     /**
    164      * If |value| is the value of some property of |this| returns property's
    165      * name. Otherwise returns empty string.
    166      * @private
    167      */
    168     getPropertyNameOf_: function(value) {
    169       for (var key in this)
    170         if (this[key] === value)
    171           return key;
    172       return '';
    173     }
    174   };
    175 
    176   Screen.CALLBACK_USER_ACTED = CALLBACK_USER_ACTED;
    177 
    178   return {
    179     Screen: Screen
    180   };
    181 });
    182 
    183 cr.define('login', function() {
    184   return {
    185     /**
    186      * Creates class and object for screen.
    187      * Methods specified in EXTERNAL_API array of prototype
    188      * will be available from C++ part.
    189      * Example:
    190      *     login.createScreen('ScreenName', 'screen-id', {
    191      *       foo: function() { console.log('foo'); },
    192      *       bar: function() { console.log('bar'); }
    193      *       EXTERNAL_API: ['foo'];
    194      *     });
    195      *     login.ScreenName.register();
    196      *     var screen = $('screen-id');
    197      *     screen.foo(); // valid
    198      *     login.ScreenName.foo(); // valid
    199      *     screen.bar(); // valid
    200      *     login.ScreenName.bar(); // invalid
    201      *
    202      * @param {string} name Name of created class.
    203      * @param {string} id Id of div representing screen.
    204      * @param {(function()|Object)} proto Prototype of object or function that
    205      *     returns prototype.
    206      */
    207     createScreen: function(name, id, proto) {
    208       if (typeof proto == 'function')
    209         proto = proto();
    210       cr.define('login', function() {
    211         var api = proto.EXTERNAL_API || [];
    212         for (var i = 0; i < api.length; ++i) {
    213           var methodName = api[i];
    214           if (typeof proto[methodName] !== 'function')
    215             throw Error('External method "' + methodName + '" for screen "' +
    216                 name + '" not a function or undefined.');
    217         }
    218 
    219         var constructor = cr.ui.define(login.Screen);
    220         constructor.prototype = Object.create(login.Screen.prototype);
    221         Object.getOwnPropertyNames(proto).forEach(function(propertyName) {
    222           var descriptor =
    223               Object.getOwnPropertyDescriptor(proto, propertyName);
    224           Object.defineProperty(constructor.prototype,
    225               propertyName, descriptor);
    226           if (api.indexOf(propertyName) >= 0) {
    227             constructor[propertyName] = (function(x) {
    228               return function() {
    229                 var screen = $(id);
    230                 return screen[x].apply(screen, arguments);
    231               };
    232             })(propertyName);
    233           }
    234         });
    235         constructor.contextChanged = function() {
    236           var screen = $(id);
    237           screen.contextChanged_.apply(screen, arguments);
    238         }
    239         constructor.prototype.name = function() { return id; };
    240         constructor.prototype.sendPrefix_ = 'login.' + name + '.';
    241 
    242         constructor.register = function() {
    243           var screen = $(id);
    244           constructor.decorate(screen);
    245           Oobe.getInstance().registerScreen(screen);
    246         };
    247 
    248         var result = {};
    249         result[name] = constructor;
    250         return result;
    251       });
    252     }
    253   };
    254 });
    255 
    256