| /* |
| * Copyright 2012 Google Inc. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /** |
| * @fileoverview Code for caching inlined resources in local storage on first |
| * view and fetching them therefrom on repeat view. |
| * |
| * @author matterbury@google.com (Matt Atterbury) |
| */ |
| |
| goog.require('pagespeedutils'); |
| |
| // Exporting functions using quoted attributes to prevent js compiler from |
| // renaming them. |
| // See http://code.google.com/closure/compiler/docs/api-tutorial3.html#dangers |
| window['pagespeed'] = window['pagespeed'] || {}; |
| var pagespeed = window['pagespeed']; |
| |
| |
| |
| /** |
| * @constructor |
| */ |
| pagespeed.LocalStorageCache = function() { |
| /** |
| * Flag indicating that the cookie needs to be regenerated. |
| * @type {boolean} |
| * @private |
| */ |
| this.regenerate_cookie_ = true; |
| }; |
| |
| |
| /** |
| * @return {boolean} True if the given object has expired. |
| * @param {*} obj is an object of the form '<expiry> <hash> <inline-data>'. |
| */ |
| pagespeed.LocalStorageCache.prototype.hasExpired = function(obj) { |
| var expiry = parseInt(obj.substring(0, obj.indexOf(' ')), 10); |
| return (!isNaN(expiry) && expiry <= pagespeedutils.now()); |
| }; |
| |
| pagespeed.LocalStorageCache.prototype['hasExpired'] = |
| pagespeed.LocalStorageCache.prototype.hasExpired; |
| |
| |
| /** |
| * @return {string} The data part of the given object. |
| * @param {*} obj is an object of the form '<expiry> <hash> <inline-data>'. |
| */ |
| pagespeed.LocalStorageCache.prototype.getData = function(obj) { |
| var pos1 = obj.indexOf(' '); |
| var pos2 = obj.indexOf(' ', pos1 + 1); |
| return obj.substring(pos2 + 1); |
| }; |
| |
| pagespeed.LocalStorageCache.prototype['getData'] = |
| pagespeed.LocalStorageCache.prototype.getData; |
| |
| |
| /** |
| * Replaces the last script element in the DOM with the given element. The |
| * script element was added by our C++ code, replacing the element that was |
| * there, either an img or a style element. The script itself kicked off the |
| * call to this method to replace itself with the original img/style element |
| * using the inlined data from local storage. |
| * @param {Element} newElement The element to replace with. |
| */ |
| pagespeed.LocalStorageCache.prototype.replaceLastScript = function(newElement) { |
| var scripts = document.getElementsByTagName('script'); |
| var lastElement = scripts[scripts.length - 1]; |
| lastElement.parentNode.replaceChild(newElement, lastElement); |
| }; |
| |
| pagespeed.LocalStorageCache.prototype['replaceLastScript'] = |
| pagespeed.LocalStorageCache.prototype.replaceLastScript; |
| |
| |
| /** |
| * Inline the CSS with the given URL. |
| * @param {string} url is the URL of the CSS to inline. |
| */ |
| pagespeed.LocalStorageCache.prototype.inlineCss = function(url) { |
| var obj = window.localStorage.getItem('pagespeed_lsc_url:' + url); |
| var newNode = document.createElement(obj ? 'style' : 'link'); |
| if (obj && !this.hasExpired(obj)) { |
| newNode.type = 'text/css'; |
| newNode.appendChild(document.createTextNode(this.getData(obj))); |
| } else { |
| newNode.rel = 'stylesheet'; |
| newNode.href = url; |
| this.regenerate_cookie_ = true; |
| } |
| this.replaceLastScript(newNode); |
| }; |
| |
| pagespeed.LocalStorageCache.prototype['inlineCss'] = |
| pagespeed.LocalStorageCache.prototype.inlineCss; |
| |
| |
| /** |
| * Inline the IMG with the given URL. |
| * @param {string} url is the URL of the image to inline. |
| * @param {string} hash is the hash of the image to inline. |
| */ |
| pagespeed.LocalStorageCache.prototype.inlineImg = function(url, hash) { |
| var obj = window.localStorage.getItem('pagespeed_lsc_url:' + url + ' ' + |
| 'pagespeed_lsc_hash:' + hash); |
| var newNode = document.createElement('img'); |
| if (obj && !this.hasExpired(obj)) { |
| newNode.src = this.getData(obj); |
| } else { |
| newNode.src = url; |
| this.regenerate_cookie_ = true; |
| } |
| // Copy over any other original attributes. |
| for (var i = 2, n = arguments.length; i < n; ++i) { |
| var pos = arguments[i].indexOf('='); |
| newNode.setAttribute(arguments[i].substring(0, pos), |
| arguments[i].substring(pos + 1)); |
| } |
| this.replaceLastScript(newNode); |
| }; |
| |
| pagespeed.LocalStorageCache.prototype['inlineImg'] = |
| pagespeed.LocalStorageCache.prototype.inlineImg; |
| |
| |
| /** |
| * For each element of the given tag name, check if it has |
| * data-pagespeed-lsc-url and data-pagespeed-lsc-hash attributes and, if so, |
| * save the element's hash, expiry, and data in local |
| * storage. regenerate_cookie_ is set to true if any elements are saved, which |
| * later triggers regeneration of the cookie. |
| * @param {string} tagName Tag Name of elements to process. |
| * @param {boolean} isHashInKey True iff the hash is part of the lookup key. |
| * @param {(function ({Element})|function ({Image}))} dataFunc Function to get |
| * an element's data. |
| * @private |
| */ |
| pagespeed.LocalStorageCache.prototype.processTags_ = function(tagName, |
| isHashInKey, |
| dataFunc) { |
| var elements = document.getElementsByTagName(tagName); |
| for (var i = 0, n = elements.length; i < n; ++i) { |
| var element = elements[i]; |
| var hash = element.getAttribute('data-pagespeed-lsc-hash'); |
| var url = element.getAttribute('data-pagespeed-lsc-url'); |
| if (hash && url) { |
| var urlkey = 'pagespeed_lsc_url:' + url; |
| if (isHashInKey) { |
| urlkey += ' pagespeed_lsc_hash:' + hash; |
| } |
| var expiry = element.getAttribute('data-pagespeed-lsc-expiry'); |
| var millis = (expiry ? (new Date(expiry)).getTime() : ''); |
| var data = dataFunc(element); |
| if (!data) { |
| // img.src is set to a data URI on the repeat view but is missing |
| // thereafter, and we must not forget it once we have it. |
| var obj = window.localStorage.getItem(urlkey); |
| if (obj) { |
| data = this.getData(obj); |
| } |
| } |
| if (data) { |
| window.localStorage.setItem(urlkey, millis + ' ' + hash + ' ' + data); |
| this.regenerate_cookie_ = true; |
| } |
| } |
| } |
| }; |
| |
| |
| /** |
| * Save any inlined data to local storage. |
| * @private |
| */ |
| pagespeed.LocalStorageCache.prototype.saveInlinedData_ = function() { |
| this.processTags_('img', true /* isHashInKey */, |
| function(e) { return e.src; }); |
| this.processTags_('style', false /* isHashInKey */, |
| function(e) { |
| return (e.firstChild ? e.firstChild.nodeValue : null); |
| }); |
| }; |
| |
| |
| /** |
| * Regenerate the cookie from the hash's of unexpired objects in local storage |
| * and remove all expired objects. |
| * @private |
| */ |
| pagespeed.LocalStorageCache.prototype.generateCookie_ = function() { |
| if (this.regenerate_cookie_) { |
| var deadUns = []; |
| var goodUns = []; |
| var minExpiry = 0; |
| var currentTime = pagespeedutils.now(); |
| // Process every local storage object of ours. |
| for (var i = 0, n = window.localStorage.length; i < n; ++i) { |
| var key = window.localStorage.key(i); |
| if (key.indexOf('pagespeed_lsc_url:')) continue; // Not one of ours. |
| var obj = window.localStorage.getItem(key); |
| var pos1 = obj.indexOf(' '); |
| var expiry = parseInt(obj.substring(0, pos1), 10); |
| if (!isNaN(expiry)) { |
| if (expiry <= currentTime) { |
| deadUns.push(key); |
| continue; |
| } else if (expiry < minExpiry || minExpiry == 0) { |
| minExpiry = expiry; |
| } |
| } |
| var pos2 = obj.indexOf(' ', pos1 + 1); |
| var hash = obj.substring(pos1 + 1, pos2); |
| goodUns.push(hash); |
| } |
| // Set the cookie. |
| var expires = ''; |
| if (minExpiry) expires = '; expires=' + (new Date(minExpiry)).toUTCString(); |
| document.cookie = '_GPSLSC=' + goodUns.join('!') + expires; |
| // Remove all expired objects. |
| for (var i = 0, n = deadUns.length; i < n; ++i) { |
| window.localStorage.removeItem(deadUns[i]); |
| } |
| this.regenerate_cookie_ = false; |
| } |
| }; |
| |
| |
| /** |
| * Initializes the local storage cache module. |
| */ |
| pagespeed.localStorageCacheInit = function() { |
| // Do nothing if any required API is missing. |
| if (window.localStorage) { |
| var temp = new pagespeed.LocalStorageCache(); |
| pagespeed['localStorageCache'] = temp; |
| pagespeedutils.addHandler(window, 'load', |
| function() { |
| temp.saveInlinedData_(); |
| }); |
| pagespeedutils.addHandler(window, 'load', |
| function() { |
| temp.generateCookie_(); |
| }); |
| } |
| }; |
| |
| pagespeed['localStorageCacheInit'] = pagespeed.localStorageCacheInit; |