15f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)// Copyright 2014 The Chromium Authors. All rights reserved.
25f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be
35f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)// found in the LICENSE file.
45f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
55f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)/**
65f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @fileoverview Provides a system for memoizing computations applied to
75f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * DOM nodes within the same call stack.
85f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *
95f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * To make a function memoizable - suppose you have a function
105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * isAccessible that takes a node and returns a boolean:
115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *
125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * function isAccessible(node) {
135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *   return expensiveComputation(node);
145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * }
155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *
165f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * Make it memoizable like this:
175f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *
185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * function isAccessible(node) {
195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *   return cvox.Memoize.memoize(computeIsAccessible_, 'isAccessible', node);
205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * }
215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *
225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * function computeIsAccessible_(node) {
235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *   return expensiveComputation(node);
245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * }
255f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *
265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * To take advantage of memoization, you need to wrap a sequence of
275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * computations in a call to memoize.scope() - memoization is only
285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * enabled while in that scope, and all cached data is thrown away at
295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * the end. You should use this only when you're sure the computation
305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * being memoized will not change within the scope.
315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *
325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * cvox.Memoize.scope(function() {
335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *   console.log(isAccessible(document.body));
345f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * });
355f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *
365f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) */
375f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
385f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
395f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)goog.provide('cvox.Memoize');
405f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
415f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
425f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)/**
435f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * Create the namespace.
445f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @constructor
455f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) */
465f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)cvox.Memoize = function() {
475f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)};
485f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
495f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)/**
505f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * The cache: a map from string function name to a WeakMap from DOM node
515f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * to function result. This variable is null when we're out of scope, and it's
525f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * a map from string to WeakMap to result when we're in scope.
535f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *
545f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @type {?Object.<string, WeakMap.<Node, *> >}
555f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @private
565f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) */
575f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)cvox.Memoize.nodeMap_ = null;
585f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
595f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)/**
605f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * Keeps track of how many nested times scope() has been called.
615f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @type {number}
625f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @private
635f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) */
645f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)cvox.Memoize.scopeCount_ = 0;
655f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
665f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
675f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)/**
685f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * Enables memoization within the scope of the given function. You should
695f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * ensure that the DOM is not modified within this scope.
705f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *
715f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * It's safe to nest calls to scope. The nested calls have
725f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * no effect, only the outermost one.
735f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *
745f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @param {Function} functionScope The function to call with memoization
755f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *     enabled.
765f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @return {*} The value returned by |functionScope|.
775f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) */
785f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)cvox.Memoize.scope = function(functionScope) {
795f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  var result;
805f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  try {
815f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    cvox.Memoize.scopeCount_++;
825f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if (cvox.Memoize.scopeCount_ == 1) {
835f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      cvox.Memoize.nodeMap_ = {};
845f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    }
855f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    result = functionScope();
865f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  } finally {
875f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    cvox.Memoize.scopeCount_--;
885f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if (cvox.Memoize.scopeCount_ == 0) {
895f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      cvox.Memoize.nodeMap_ = null;
905f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    }
915f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  }
925f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  return result;
935f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)};
945f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
955f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)/**
965f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * Memoizes the result of a function call, so if you call this again
975f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * with the same exact parameters and memoization is currently enabled
985f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * (via a call to scope()), the second time the cached result
995f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * will just be returned directly.
1005f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *
1015f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @param {Function} functionClosure The function to call and cache the
1025f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *     result of.
1035f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @param {string} functionName The name of the function you're calling.
1045f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *     This string is used to store and retrieve the cached result, so
1055f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *     it should be unique. If the function to be memoized takes simple
1065f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *     arguments in addition to a DOM node, you can incorporate those
1075f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *     arguments into the function name.
1085f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @param {Node} node The DOM node that should be passed as the argument
1095f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) *     to the function.
1105f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) * @return {*} The return value of |functionClosure|.
1115f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles) */
1125f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)cvox.Memoize.memoize = function(functionClosure, functionName, node) {
1135f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  if (cvox.Memoize.nodeMap_ &&
1145f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      cvox.Memoize.nodeMap_[functionName] === undefined) {
1155f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    cvox.Memoize.nodeMap_[functionName] = new WeakMap();
1165f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  }
1175f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1185f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  // If we're not in scope, just call the function directly.
1195f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  if (!cvox.Memoize.nodeMap_) {
1205f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    return functionClosure(node);
1215f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  }
1225f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1235f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  var result = cvox.Memoize.nodeMap_[functionName].get(node);
1245f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  if (result === undefined) {
1255f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    result = functionClosure(node);
1265f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    if (result === undefined) {
1275f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)      throw 'A memoized function cannot return undefined.';
1285f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    }
1295f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)    cvox.Memoize.nodeMap_[functionName].set(node, result);
1305f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  }
1315f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)
1325f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)  return result;
1335f1c94371a64b3196d4be9466099bb892df9b88eTorne (Richard Coles)};
134