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