1// Copyright (c) 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/**
6 * @fileoverview Implementation of ScreenContext class: key-value storage for
7 * values that are shared between C++ and JS sides.
8 */
9cr.define('login', function() {
10  'use strict';
11
12  function require(condition, message) {
13    if (!condition) {
14      throw Error(message);
15    }
16  }
17
18  function checkKeyIsValid(key) {
19    var keyType = typeof key;
20    require(keyType === 'string', 'Invalid type of key: "' + keyType + '".');
21  }
22
23  function checkValueIsValid(value) {
24    var valueType = typeof value;
25    require(['string', 'boolean', 'number'].indexOf(valueType) != -1,
26        'Invalid type of value: "' + valueType + '".');
27  }
28
29  function ScreenContext() {
30    this.storage_ = {};
31    this.changes_ = {};
32    this.observers_ = {};
33  }
34
35  ScreenContext.prototype = {
36    /**
37     * Returns stored value for |key| or |defaultValue| if key not found in
38     * storage. Throws Error if key not found and |defaultValue| omitted.
39     */
40    get: function(key, defaultValue) {
41      checkKeyIsValid(key);
42      if (this.hasKey(key)) {
43        return this.storage_[key];
44      } else if (typeof defaultValue !== 'undefined') {
45        return defaultValue;
46      } else {
47        throw Error('Key "' + key + '" not found.');
48      }
49    },
50
51    /**
52     * Sets |value| for |key|. Returns true if call changes state of context,
53     * false otherwise.
54     */
55    set: function(key, value) {
56      checkKeyIsValid(key);
57      checkValueIsValid(value);
58      if (this.hasKey(key) && this.storage_[key] === value)
59        return false;
60      this.changes_[key] = value;
61      this.storage_[key] = value;
62      return true;
63    },
64
65    hasKey: function(key) {
66      checkKeyIsValid(key);
67      return this.storage_.hasOwnProperty(key);
68    },
69
70    hasChanges: function() {
71      return Object.keys(this.changes_).length > 0;
72    },
73
74    /**
75     * Applies |changes| to context. Returns Array of changed keys' names.
76     */
77    applyChanges: function(changes) {
78      require(!this.hasChanges(), 'Context has changes.');
79      var oldValues = {};
80      for (var key in changes) {
81        checkKeyIsValid(key);
82        checkValueIsValid(changes[key]);
83        oldValues[key] = this.storage_[key];
84        this.storage_[key] = changes[key];
85      }
86      var observers = this.cloneObservers_();
87      for (var key in changes) {
88        if (observers.hasOwnProperty(key)) {
89          var keyObservers = observers[key];
90          for (var observerIndex in keyObservers)
91            keyObservers[observerIndex](changes[key], oldValues[key], key);
92        }
93      }
94      return Object.keys(changes);
95    },
96
97    /**
98     * Returns changes made on context since previous call.
99     */
100    getChangesAndReset: function() {
101      var result = this.changes_;
102      this.changes_ = {};
103      return result;
104    },
105
106    addObserver: function(key, observer) {
107      if (!this.observers_.hasOwnProperty(key))
108        this.observers_[key] = [];
109      if (this.observers_[key].indexOf(observer) !== -1) {
110        console.warn('Observer already registered.');
111        return;
112      }
113      this.observers_[key].push(observer);
114    },
115
116    removeObserver: function(observer) {
117      for (var key in this.observers_) {
118        var observerIndex = this.observers_[key].indexOf(observer);
119        if (observerIndex != -1)
120          this.observers_[key].splice(observerIndex, 1);
121      }
122    },
123
124    /**
125     * Creates deep copy of observers lists.
126     * @private
127     */
128    cloneObservers_: function() {
129      var result = {};
130      for (var key in this.observers_)
131        result[key] = this.observers_[key].slice();
132      return result;
133    }
134  };
135
136  return {
137    ScreenContext: ScreenContext
138  };
139});
140