// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. var $console = window.console; /** * Returns a function that logs a 'not available' error to the console and * returns undefined. * * @param {string} messagePrefix text to prepend to the exception message. */ function generateDisabledMethodStub(messagePrefix, opt_messageSuffix) { var message = messagePrefix + ' is not available in packaged apps.'; if (opt_messageSuffix) message = message + ' ' + opt_messageSuffix; return function() { $console.error(message); return; }; } /** * Returns a function that throws a 'not available' error. * * @param {string} messagePrefix text to prepend to the exception message. */ function generateThrowingMethodStub(messagePrefix, opt_messageSuffix) { var message = messagePrefix + ' is not available in packaged apps.'; if (opt_messageSuffix) message = message + ' ' + opt_messageSuffix; return function() { throw new Error(message); }; } /** * Replaces the given methods of the passed in object with stubs that log * 'not available' errors to the console and return undefined. * * This should be used on methods attached via non-configurable properties, * such as window.alert. disableGetters should be used when possible, because * it is friendlier towards feature detection. * * In most cases, the useThrowingStubs should be false, so the stubs used to * replace the methods log an error to the console, but allow the calling code * to continue. We shouldn't break library code that uses feature detection * responsibly, such as: * if(window.confirm) { * var result = window.confirm('Are you sure you want to delete ...?'); * ... * } * * useThrowingStubs should only be true for methods that are deprecated in the * Web platform, and should not be used by a responsible library, even in * conjunction with feature detection. A great example is document.write(), as * the HTML5 specification recommends against using it, and says that its * behavior is unreliable. No reasonable library code should ever use it. * HTML5 spec: http://www.w3.org/TR/html5/dom.html#dom-document-write * * @param {Object} object The object with methods to disable. The prototype is * preferred. * @param {string} objectName The display name to use in the error message * thrown by the stub (this is the name that the object is commonly referred * to by web developers, e.g. "document" instead of "HTMLDocument"). * @param {Array.<string>} methodNames names of methods to disable. * @param {Boolean} useThrowingStubs if true, the replaced methods will throw * an error instead of silently returning undefined */ function disableMethods(object, objectName, methodNames, useThrowingStubs) { $Array.forEach(methodNames, function(methodName) { var messagePrefix = objectName + '.' + methodName + '()'; object[methodName] = useThrowingStubs ? generateThrowingMethodStub(messagePrefix) : generateDisabledMethodStub(messagePrefix); }); } /** * Replaces the given properties of the passed in object with stubs that log * 'not available' warnings to the console and return undefined when gotten. If * a property's setter is later invoked, the getter and setter are restored to * default behaviors. * * @param {Object} object The object with properties to disable. The prototype * is preferred. * @param {string} objectName The display name to use in the error message * thrown by the getter stub (this is the name that the object is commonly * referred to by web developers, e.g. "document" instead of * "HTMLDocument"). * @param {Array.<string>} propertyNames names of properties to disable. */ function disableGetters(object, objectName, propertyNames, opt_messageSuffix) { $Array.forEach(propertyNames, function(propertyName) { var stub = generateDisabledMethodStub(objectName + '.' + propertyName, opt_messageSuffix); stub._is_platform_app_disabled_getter = true; $Object.defineProperty(object, propertyName, { configurable: true, enumerable: false, get: stub, set: function(value) { var descriptor = $Object.getOwnPropertyDescriptor(this, propertyName); if (!descriptor || !descriptor.get || descriptor.get._is_platform_app_disabled_getter) { // The stub getter is still defined. Blow-away the property to // restore default getter/setter behaviors and re-create it with the // given value. delete this[propertyName]; this[propertyName] = value; } else { // Do nothing. If some custom getter (not ours) has been defined, // there would be no way to read back the value stored by a default // setter. Also, the only way to clear a custom getter is to first // delete the property. Therefore, the value we have here should // just go into a black hole. } } }); }); } /** * Replaces the given properties of the passed in object with stubs that log * 'not available' warnings to the console when set. * * @param {Object} object The object with properties to disable. The prototype * is preferred. * @param {string} objectName The display name to use in the error message * thrown by the setter stub (this is the name that the object is commonly * referred to by web developers, e.g. "document" instead of * "HTMLDocument"). * @param {Array.<string>} propertyNames names of properties to disable. */ function disableSetters(object, objectName, propertyNames, opt_messageSuffix) { $Array.forEach(propertyNames, function(propertyName) { var stub = generateDisabledMethodStub(objectName + '.' + propertyName, opt_messageSuffix); $Object.defineProperty(object, propertyName, { configurable: true, enumerable: false, get: function() { return; }, set: stub }); }); } // Disable benign Document methods. disableMethods(HTMLDocument.prototype, 'document', ['open', 'clear', 'close']); // Replace evil Document methods with exception-throwing stubs. disableMethods(HTMLDocument.prototype, 'document', ['write', 'writeln'], true); // Disable history. Object.defineProperty(window, "history", { value: {} }); disableGetters(window.history, 'history', ['back', 'forward', 'go', 'length']); // Disable find. disableMethods(Window.prototype, 'window', ['find']); // Disable modal dialogs. Shell windows disable these anyway, but it's nice to // warn. disableMethods(Window.prototype, 'window', ['alert', 'confirm', 'prompt']); // Disable window.*bar. disableGetters(window, 'window', ['locationbar', 'menubar', 'personalbar', 'scrollbars', 'statusbar', 'toolbar']); // Disable window.localStorage. // Sometimes DOM security policy prevents us from doing this (e.g. for data: // URLs) so wrap in try-catch. try { disableGetters(window, 'window', ['localStorage'], 'Use chrome.storage.local instead.'); } catch (e) {} // Document instance properties that we wish to disable need to be set when // the document begins loading, since only then will the "document" reference // point to the page's document (it will be reset between now and then). // We can't listen for the "readystatechange" event on the document (because // the object that it's dispatched on doesn't exist yet), but we can instead // do it at the window level in the capturing phase. window.addEventListener('readystatechange', function(event) { if (document.readyState != 'loading') return; // Deprecated document properties from // https://developer.mozilla.org/en/DOM/document. // To deprecate document.all, simply changing its getter and setter would // activate its cache mechanism, and degrade the performance. Here we assign // it first to 'undefined' to avoid this. document.all = undefined; disableGetters(document, 'document', ['alinkColor', 'all', 'bgColor', 'fgColor', 'linkColor', 'vlinkColor']); }, true); // Disable onunload, onbeforeunload. disableSetters(Window.prototype, 'window', ['onbeforeunload', 'onunload']); var windowAddEventListener = Window.prototype.addEventListener; Window.prototype.addEventListener = function(type) { if (type === 'unload' || type === 'beforeunload') generateDisabledMethodStub(type)(); else return $Function.apply(windowAddEventListener, window, arguments); };