Home | History | Annotate | Download | only in common
      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 Provides a system for memoizing computations applied to
      7  * DOM nodes within the same call stack.
      8  *
      9  * To make a function memoizable - suppose you have a function
     10  * isAccessible that takes a node and returns a boolean:
     11  *
     12  * function isAccessible(node) {
     13  *   return expensiveComputation(node);
     14  * }
     15  *
     16  * Make it memoizable like this:
     17  *
     18  * function isAccessible(node) {
     19  *   return cvox.Memoize.memoize(computeIsAccessible_, 'isAccessible', node);
     20  * }
     21  *
     22  * function computeIsAccessible_(node) {
     23  *   return expensiveComputation(node);
     24  * }
     25  *
     26  * To take advantage of memoization, you need to wrap a sequence of
     27  * computations in a call to memoize.scope() - memoization is only
     28  * enabled while in that scope, and all cached data is thrown away at
     29  * the end. You should use this only when you're sure the computation
     30  * being memoized will not change within the scope.
     31  *
     32  * cvox.Memoize.scope(function() {
     33  *   console.log(isAccessible(document.body));
     34  * });
     35  *
     36  */
     37 
     38 
     39 goog.provide('cvox.Memoize');
     40 
     41 
     42 /**
     43  * Create the namespace.
     44  * @constructor
     45  */
     46 cvox.Memoize = function() {
     47 };
     48 
     49 /**
     50  * The cache: a map from string function name to a WeakMap from DOM node
     51  * to function result. This variable is null when we're out of scope, and it's
     52  * a map from string to WeakMap to result when we're in scope.
     53  *
     54  * @type {?Object.<string, WeakMap.<Node, *> >}
     55  * @private
     56  */
     57 cvox.Memoize.nodeMap_ = null;
     58 
     59 /**
     60  * Keeps track of how many nested times scope() has been called.
     61  * @type {number}
     62  * @private
     63  */
     64 cvox.Memoize.scopeCount_ = 0;
     65 
     66 
     67 /**
     68  * Enables memoization within the scope of the given function. You should
     69  * ensure that the DOM is not modified within this scope.
     70  *
     71  * It's safe to nest calls to scope. The nested calls have
     72  * no effect, only the outermost one.
     73  *
     74  * @param {Function} functionScope The function to call with memoization
     75  *     enabled.
     76  * @return {*} The value returned by |functionScope|.
     77  */
     78 cvox.Memoize.scope = function(functionScope) {
     79   var result;
     80   try {
     81     cvox.Memoize.scopeCount_++;
     82     if (cvox.Memoize.scopeCount_ == 1) {
     83       cvox.Memoize.nodeMap_ = {};
     84     }
     85     result = functionScope();
     86   } finally {
     87     cvox.Memoize.scopeCount_--;
     88     if (cvox.Memoize.scopeCount_ == 0) {
     89       cvox.Memoize.nodeMap_ = null;
     90     }
     91   }
     92   return result;
     93 };
     94 
     95 /**
     96  * Memoizes the result of a function call, so if you call this again
     97  * with the same exact parameters and memoization is currently enabled
     98  * (via a call to scope()), the second time the cached result
     99  * will just be returned directly.
    100  *
    101  * @param {Function} functionClosure The function to call and cache the
    102  *     result of.
    103  * @param {string} functionName The name of the function you're calling.
    104  *     This string is used to store and retrieve the cached result, so
    105  *     it should be unique. If the function to be memoized takes simple
    106  *     arguments in addition to a DOM node, you can incorporate those
    107  *     arguments into the function name.
    108  * @param {Node} node The DOM node that should be passed as the argument
    109  *     to the function.
    110  * @return {*} The return value of |functionClosure|.
    111  */
    112 cvox.Memoize.memoize = function(functionClosure, functionName, node) {
    113   if (cvox.Memoize.nodeMap_ &&
    114       cvox.Memoize.nodeMap_[functionName] === undefined) {
    115     cvox.Memoize.nodeMap_[functionName] = new WeakMap();
    116   }
    117 
    118   // If we're not in scope, just call the function directly.
    119   if (!cvox.Memoize.nodeMap_) {
    120     return functionClosure(node);
    121   }
    122 
    123   var result = cvox.Memoize.nodeMap_[functionName].get(node);
    124   if (result === undefined) {
    125     result = functionClosure(node);
    126     if (result === undefined) {
    127       throw 'A memoized function cannot return undefined.';
    128     }
    129     cvox.Memoize.nodeMap_[functionName].set(node, result);
    130   }
    131 
    132   return result;
    133 };
    134