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