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 5var $console = window.console; 6 7/** 8 * Returns a function that logs a 'not available' error to the console and 9 * returns undefined. 10 * 11 * @param {string} messagePrefix text to prepend to the exception message. 12 */ 13function generateDisabledMethodStub(messagePrefix, opt_messageSuffix) { 14 var message = messagePrefix + ' is not available in packaged apps.'; 15 if (opt_messageSuffix) message = message + ' ' + opt_messageSuffix; 16 return function() { 17 $console.error(message); 18 return; 19 }; 20} 21 22/** 23 * Returns a function that throws a 'not available' error. 24 * 25 * @param {string} messagePrefix text to prepend to the exception message. 26 */ 27function generateThrowingMethodStub(messagePrefix, opt_messageSuffix) { 28 var message = messagePrefix + ' is not available in packaged apps.'; 29 if (opt_messageSuffix) message = message + ' ' + opt_messageSuffix; 30 return function() { 31 throw new Error(message); 32 }; 33} 34 35/** 36 * Replaces the given methods of the passed in object with stubs that log 37 * 'not available' errors to the console and return undefined. 38 * 39 * This should be used on methods attached via non-configurable properties, 40 * such as window.alert. disableGetters should be used when possible, because 41 * it is friendlier towards feature detection. 42 * 43 * In most cases, the useThrowingStubs should be false, so the stubs used to 44 * replace the methods log an error to the console, but allow the calling code 45 * to continue. We shouldn't break library code that uses feature detection 46 * responsibly, such as: 47 * if(window.confirm) { 48 * var result = window.confirm('Are you sure you want to delete ...?'); 49 * ... 50 * } 51 * 52 * useThrowingStubs should only be true for methods that are deprecated in the 53 * Web platform, and should not be used by a responsible library, even in 54 * conjunction with feature detection. A great example is document.write(), as 55 * the HTML5 specification recommends against using it, and says that its 56 * behavior is unreliable. No reasonable library code should ever use it. 57 * HTML5 spec: http://www.w3.org/TR/html5/dom.html#dom-document-write 58 * 59 * @param {Object} object The object with methods to disable. The prototype is 60 * preferred. 61 * @param {string} objectName The display name to use in the error message 62 * thrown by the stub (this is the name that the object is commonly referred 63 * to by web developers, e.g. "document" instead of "HTMLDocument"). 64 * @param {Array.<string>} methodNames names of methods to disable. 65 * @param {Boolean} useThrowingStubs if true, the replaced methods will throw 66 * an error instead of silently returning undefined 67 */ 68function disableMethods(object, objectName, methodNames, useThrowingStubs) { 69 $Array.forEach(methodNames, function(methodName) { 70 var messagePrefix = objectName + '.' + methodName + '()'; 71 object[methodName] = useThrowingStubs ? 72 generateThrowingMethodStub(messagePrefix) : 73 generateDisabledMethodStub(messagePrefix); 74 }); 75} 76 77/** 78 * Replaces the given properties of the passed in object with stubs that log 79 * 'not available' warnings to the console and return undefined when gotten. If 80 * a property's setter is later invoked, the getter and setter are restored to 81 * default behaviors. 82 * 83 * @param {Object} object The object with properties to disable. The prototype 84 * is preferred. 85 * @param {string} objectName The display name to use in the error message 86 * thrown by the getter stub (this is the name that the object is commonly 87 * referred to by web developers, e.g. "document" instead of 88 * "HTMLDocument"). 89 * @param {Array.<string>} propertyNames names of properties to disable. 90 */ 91function disableGetters(object, objectName, propertyNames, opt_messageSuffix) { 92 $Array.forEach(propertyNames, function(propertyName) { 93 var stub = generateDisabledMethodStub(objectName + '.' + propertyName, 94 opt_messageSuffix); 95 stub._is_platform_app_disabled_getter = true; 96 $Object.defineProperty(object, propertyName, { 97 configurable: true, 98 enumerable: false, 99 get: stub, 100 set: function(value) { 101 var descriptor = $Object.getOwnPropertyDescriptor(this, propertyName); 102 if (!descriptor || !descriptor.get || 103 descriptor.get._is_platform_app_disabled_getter) { 104 // The stub getter is still defined. Blow-away the property to 105 // restore default getter/setter behaviors and re-create it with the 106 // given value. 107 delete this[propertyName]; 108 this[propertyName] = value; 109 } else { 110 // Do nothing. If some custom getter (not ours) has been defined, 111 // there would be no way to read back the value stored by a default 112 // setter. Also, the only way to clear a custom getter is to first 113 // delete the property. Therefore, the value we have here should 114 // just go into a black hole. 115 } 116 } 117 }); 118 }); 119} 120 121/** 122 * Replaces the given properties of the passed in object with stubs that log 123 * 'not available' warnings to the console when set. 124 * 125 * @param {Object} object The object with properties to disable. The prototype 126 * is preferred. 127 * @param {string} objectName The display name to use in the error message 128 * thrown by the setter stub (this is the name that the object is commonly 129 * referred to by web developers, e.g. "document" instead of 130 * "HTMLDocument"). 131 * @param {Array.<string>} propertyNames names of properties to disable. 132 */ 133function disableSetters(object, objectName, propertyNames, opt_messageSuffix) { 134 $Array.forEach(propertyNames, function(propertyName) { 135 var stub = generateDisabledMethodStub(objectName + '.' + propertyName, 136 opt_messageSuffix); 137 $Object.defineProperty(object, propertyName, { 138 configurable: true, 139 enumerable: false, 140 get: function() { 141 return; 142 }, 143 set: stub 144 }); 145 }); 146} 147 148// Disable benign Document methods. 149disableMethods(HTMLDocument.prototype, 'document', ['open', 'clear', 'close']); 150 151// Replace evil Document methods with exception-throwing stubs. 152disableMethods(HTMLDocument.prototype, 'document', ['write', 'writeln'], true); 153 154// Disable history. 155Object.defineProperty(window, "history", { value: {} }); 156disableGetters(window.history, 'history', ['back', 'forward', 'go', 'length']); 157 158// Disable find. 159disableMethods(Window.prototype, 'window', ['find']); 160 161// Disable modal dialogs. Shell windows disable these anyway, but it's nice to 162// warn. 163disableMethods(Window.prototype, 'window', ['alert', 'confirm', 'prompt']); 164 165// Disable window.*bar. 166disableGetters(window, 'window', 167 ['locationbar', 'menubar', 'personalbar', 'scrollbars', 'statusbar', 168 'toolbar']); 169 170// Disable window.localStorage. 171// Sometimes DOM security policy prevents us from doing this (e.g. for data: 172// URLs) so wrap in try-catch. 173try { 174 disableGetters(window, 'window', 175 ['localStorage'], 176 'Use chrome.storage.local instead.'); 177} catch (e) {} 178 179// Document instance properties that we wish to disable need to be set when 180// the document begins loading, since only then will the "document" reference 181// point to the page's document (it will be reset between now and then). 182// We can't listen for the "readystatechange" event on the document (because 183// the object that it's dispatched on doesn't exist yet), but we can instead 184// do it at the window level in the capturing phase. 185window.addEventListener('readystatechange', function(event) { 186 if (document.readyState != 'loading') 187 return; 188 189 // Deprecated document properties from 190 // https://developer.mozilla.org/en/DOM/document. 191 // To deprecate document.all, simply changing its getter and setter would 192 // activate its cache mechanism, and degrade the performance. Here we assign 193 // it first to 'undefined' to avoid this. 194 document.all = undefined; 195 disableGetters(document, 'document', 196 ['alinkColor', 'all', 'bgColor', 'fgColor', 'linkColor', 'vlinkColor']); 197}, true); 198 199// Disable onunload, onbeforeunload. 200disableSetters(Window.prototype, 'window', ['onbeforeunload', 'onunload']); 201var windowAddEventListener = Window.prototype.addEventListener; 202Window.prototype.addEventListener = function(type) { 203 if (type === 'unload' || type === 'beforeunload') 204 generateDisabledMethodStub(type)(); 205 else 206 return $Function.apply(windowAddEventListener, window, arguments); 207}; 208