From 4cd728553dd66659d76d652e5710eb656b89ba14 Mon Sep 17 00:00:00 2001 From: "dzhioev@chromium.org" Date: Fri, 20 Jun 2014 16:57:48 +0000 Subject: [PATCH] Added usefull utility methods to Screen and ScreenContext. List of changes: * Added ability to annotate HTML elements (see login.Screen.initialize method comment). * Prefix is added to messages sent from JS to C++ (login.Screen.send method). * Added ability to observe changes in specific keys of context (on JS side). * And more. BUG=NONE Review URL: https://codereview.chromium.org/328763003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@278724 0039d316-1c4b-4281-b951-d872f2087c98 --- .../chromeos/login/screens/screen_context.cc | 10 +- .../chromeos/login/screens/screen_context.h | 2 +- .../resources/chromeos/login/login_common.js | 1 + .../resources/chromeos/login/screen_context.js | 46 ++++++- chrome/browser/resources/login/screen.js | 150 +++++++++++++++++++++ 5 files changed, 202 insertions(+), 7 deletions(-) diff --git a/chrome/browser/chromeos/login/screens/screen_context.cc b/chrome/browser/chromeos/login/screens/screen_context.cc index 658bd53675b8..e613d31fff4d 100644 --- a/chrome/browser/chromeos/login/screens/screen_context.cc +++ b/chrome/browser/chromeos/login/screens/screen_context.cc @@ -98,13 +98,15 @@ void ScreenContext::ApplyChanges(const base::DictionaryValue& diff, std::vector* keys) { DCHECK(CalledOnValidThread()); DCHECK(!HasChanges()); - DCHECK(keys); - keys->clear(); - keys->reserve(diff.size()); + if (keys) { + keys->clear(); + keys->reserve(diff.size()); + } base::DictionaryValue::Iterator it(diff); while (!it.IsAtEnd()) { Set(it.key(), it.value().DeepCopy()); - keys->push_back(it.key()); + if (keys) + keys->push_back(it.key()); it.Advance(); } changes_.Clear(); diff --git a/chrome/browser/chromeos/login/screens/screen_context.h b/chrome/browser/chromeos/login/screens/screen_context.h index c0dc4460c833..c92a13758ae1 100644 --- a/chrome/browser/chromeos/login/screens/screen_context.h +++ b/chrome/browser/chromeos/login/screens/screen_context.h @@ -71,7 +71,7 @@ class ScreenContext : public base::NonThreadSafe { void GetChangesAndReset(base::DictionaryValue* diff); // Applies changes from |diff| to the context. All keys from |diff| - // are stored in |keys|. + // are stored in |keys|. |keys| argument is optional and can be NULL. void ApplyChanges(const base::DictionaryValue& diff, std::vector* keys); diff --git a/chrome/browser/resources/chromeos/login/login_common.js b/chrome/browser/resources/chromeos/login/login_common.js index 5a18de23e0fd..e8ea73dacc08 100644 --- a/chrome/browser/resources/chromeos/login/login_common.js +++ b/chrome/browser/resources/chromeos/login/login_common.js @@ -7,6 +7,7 @@ */ + diff --git a/chrome/browser/resources/chromeos/login/screen_context.js b/chrome/browser/resources/chromeos/login/screen_context.js index 6a014a482836..083b90c68258 100644 --- a/chrome/browser/resources/chromeos/login/screen_context.js +++ b/chrome/browser/resources/chromeos/login/screen_context.js @@ -7,6 +7,8 @@ * values that are shared between C++ and JS sides. */ cr.define('login', function() { + 'use strict'; + function require(condition, message) { if (!condition) { throw Error(message); @@ -27,6 +29,7 @@ cr.define('login', function() { function ScreenContext() { this.storage_ = {}; this.changes_ = {}; + this.observers_ = {}; } ScreenContext.prototype = { @@ -73,11 +76,21 @@ cr.define('login', function() { */ applyChanges: function(changes) { require(!this.hasChanges(), 'Context has changes.'); - Object.keys(changes).forEach(function(key) { + var oldValues = {}; + for (var key in changes) { checkKeyIsValid(key); checkValueIsValid(changes[key]); + oldValues[key] = this.storage_[key]; this.storage_[key] = changes[key]; - }, this); + } + var observers = this.cloneObservers_(); + for (var key in changes) { + if (observers.hasOwnProperty(key)) { + var keyObservers = observers[key]; + for (var observerIndex in keyObservers) + keyObservers[observerIndex](changes[key], oldValues[key], key); + } + } return Object.keys(changes); }, @@ -88,6 +101,35 @@ cr.define('login', function() { var result = this.changes_; this.changes_ = {}; return result; + }, + + addObserver: function(key, observer) { + if (!this.observers_.hasOwnProperty(key)) + this.observers_[key] = []; + if (this.observers_[key].indexOf(observer) !== -1) { + console.warn('Observer already registered.'); + return; + } + this.observers_[key].push(observer); + }, + + removeObserver: function(observer) { + for (var key in this.observers_) { + var observerIndex = this.observers_[key].indexOf(observer); + if (observerIndex != -1) + this.observers_[key].splice(observerIndex, 1); + } + }, + + /** + * Creates deep copy of observers lists. + * @private + */ + cloneObservers_: function() { + var result = {}; + for (var key in this.observers_) + result[key] = this.observers_[key].slice(); + return result; } }; diff --git a/chrome/browser/resources/login/screen.js b/chrome/browser/resources/login/screen.js index fa03371f6a3d..f557417861f6 100644 --- a/chrome/browser/resources/login/screen.js +++ b/chrome/browser/resources/login/screen.js @@ -8,11 +8,60 @@ cr.define('login', function() { var Screen = cr.ui.define('div'); + /** @const */ var CALLBACK_USER_ACTED = 'userActed'; + /** @const */ var CALLBACK_CONTEXT_CHANGED = 'contextChanged'; + function doNothing() {}; + var querySelectorAll = HTMLDivElement.prototype.querySelectorAll; + Screen.prototype = { __proto__: HTMLDivElement.prototype, + /** + * Prefix added to sent to Chrome messages' names. + */ + sendPrefix_: '', + + /** + * Context used by this screen. + */ + context_: null, + + /** + * Dictionary of context observers that are methods of |this| bound to + * |this|. + */ + contextObservers_: {}, + + get context() { + return this.context_; + }, + + /** + * Sends recent context changes to C++ handler. + */ + commitContextChanges: function() { + if (!this.context_.hasChanges()) + return; + this.send(CALLBACK_CONTEXT_CHANGED, this.context_.getChangesAndReset()); + }, + + /** + * Sends message to Chrome, adding needed prefix to message name. All + * arguments after |messageName| are packed into message parameters list. + * + * @param {string} messageName Name of message without a prefix. + * @param {...*} varArgs parameters for message. + */ + send: function(messageName, varArgs) { + if (arguments.length == 0) + throw Error('Message name is not provided.'); + var fullMessageName = this.sendPrefix_ + messageName; + var payload = Array.prototype.slice.call(arguments, 1); + chrome.send(fullMessageName, payload); + }, + decorate: doNothing, /** @@ -28,8 +77,104 @@ cr.define('login', function() { * Called for currently active screen when screen size changed. */ onWindowResize: doNothing, + + /** + * Does the following things: + * * Creates screen context. + * * Looks for elements having "alias" property and adds them as the + * proprties of the screen with name equal to value of "alias", i.e. HTML + * element
will be stored in this.myDiv. + * * Looks for buttons having "action" properties and adds click handlers + * to them. These handlers send |CALLBACK_USER_ACTED| messages to + * C++ with "action" property's value as payload. + */ + initialize: function() { + this.context_ = new login.ScreenContext(); + this.querySelectorAll('[alias]').forEach(function(element) { + this[element.getAttribute('alias')] = element; + }, this); + var self = this; + this.querySelectorAll('button[action]').forEach(function(button) { + button.addEventListener('click', function(e) { + var action = this.getAttribute('action'); + self.send(CALLBACK_USER_ACTED, action); + e.stopPropagation(); + }); + }); + }, + + /** + * Starts observation of property with |key| of the context attached to + * current screen. This method differs from "login.ScreenContext" in that + * it automatically detects if observer is method of |this| and make + * all needed actions to make it work correctly. So it's no need for client + * to bind methods to |this| and keep resulting callback for + * |removeObserver| call: + * + * this.addContextObserver('key', this.onKeyChanged_); + * ... + * this.removeContextObserver('key', this.onKeyChanged_); + */ + addContextObserver: function(key, observer) { + var realObserver = observer; + var propertyName = this.getPropertyNameOf_(observer); + if (propertyName) { + if (!this.contextObservers_.hasOwnProperty(propertyName)) + this.contextObservers_[propertyName] = observer.bind(this); + realObserver = this.contextObservers_[propertyName]; + } + this.context.addObserver(key, realObserver); + }, + + /** + * Removes |observer| from the list of context observers. Supports not only + * regular functions but also screen methods (see comment to + * |addContextObserver|). + */ + removeContextObserver: function(observer) { + var realObserver = observer; + var propertyName = this.getPropertyNameOf_(observer); + if (propertyName) { + if (!this.contextObservers_.hasOwnProperty(propertyName)) + return; + realObserver = this.contextObservers_[propertyName]; + delete this.contextObservers_[propertyName]; + } + this.context.removeObserver(realObserver); + }, + + /** + * Calls standart |querySelectorAll| method and returns its result converted + * to Array. + */ + querySelectorAll: function(selector) { + var list = querySelectorAll.call(this, selector); + return Array.prototype.slice.call(list); + }, + + /** + * Called when context changes are recieved from C++. + * @private + */ + contextChanged_: function(diff) { + this.context.applyChanges(diff); + }, + + /** + * If |value| is the value of some property of |this| returns property's + * name. Otherwise returns empty string. + * @private + */ + getPropertyNameOf_: function(value) { + for (var key in this) + if (this[key] === value) + return key; + return ''; + } }; + Screen.CALLBACK_USER_ACTED = CALLBACK_USER_ACTED; + return { Screen: Screen }; @@ -87,7 +232,12 @@ cr.define('login', function() { })(propertyName); } }); + constructor.contextChanged = function() { + var screen = $(id); + screen.contextChanged_.apply(screen, arguments); + } constructor.prototype.name = function() { return id; }; + constructor.prototype.sendPrefix_ = 'login.' + name + '.'; constructor.register = function() { var screen = $(id); -- 2.11.4.GIT