1// Copyright (c) 2012 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
5/**
6 * Returns a function that throws a 'not available' exception when called.
7 *
8 * @param {string} messagePrefix text to prepend to the exception message.
9 */
10function generateDisabledMethodStub(messagePrefix, opt_messageSuffix) {
11  return function() {
12    var message = messagePrefix + ' is not available in packaged apps.';
13    if (opt_messageSuffix) message = message + ' ' + opt_messageSuffix;
14    throw message;
15  };
16}
17
18/**
19 * Replaces the given methods of the passed in object with stubs that throw
20 * 'not available' exceptions when called.
21 *
22 * @param {Object} object The object with methods to disable. The prototype is
23 *     preferred.
24 * @param {string} objectName The display name to use in the error message
25 *     thrown by the stub (this is the name that the object is commonly referred
26 *     to by web developers, e.g. "document" instead of "HTMLDocument").
27 * @param {Array.<string>} methodNames names of methods to disable.
28 */
29function disableMethods(object, objectName, methodNames) {
30  $Array.forEach(methodNames, function(methodName) {
31    object[methodName] =
32        generateDisabledMethodStub(objectName + '.' + methodName + '()');
33  });
34}
35
36/**
37 * Replaces the given properties of the passed in object with stubs that throw
38 * 'not available' exceptions when gotten.  If a property's setter is later
39 * invoked, the getter and setter are restored to default behaviors.
40 *
41 * @param {Object} object The object with properties to disable. The prototype
42 *     is preferred.
43 * @param {string} objectName The display name to use in the error message
44 *     thrown by the getter stub (this is the name that the object is commonly
45 *     referred to by web developers, e.g. "document" instead of
46 *     "HTMLDocument").
47 * @param {Array.<string>} propertyNames names of properties to disable.
48 */
49function disableGetters(object, objectName, propertyNames, opt_messageSuffix) {
50  $Array.forEach(propertyNames, function(propertyName) {
51    var stub = generateDisabledMethodStub(objectName + '.' + propertyName,
52                                          opt_messageSuffix);
53    stub._is_platform_app_disabled_getter = true;
54    object.__defineGetter__(propertyName, stub);
55
56    object.__defineSetter__(propertyName, function(value) {
57      var getter = this.__lookupGetter__(propertyName);
58      if (!getter || getter._is_platform_app_disabled_getter) {
59        // The stub getter is still defined.  Blow-away the property to restore
60        // default getter/setter behaviors and re-create it with the given
61        // value.
62        delete this[propertyName];
63        this[propertyName] = value;
64      } else {
65        // Do nothing.  If some custom getter (not ours) has been defined, there
66        // would be no way to read back the value stored by a default setter.
67        // Also, the only way to clear a custom getter is to first delete the
68        // property.  Therefore, the value we have here should just go into a
69        // black hole.
70      }
71    });
72  });
73}
74
75// Disable document.open|close|write|etc.
76disableMethods(HTMLDocument.prototype, 'document',
77    ['open', 'clear', 'close', 'write', 'writeln']);
78
79// Disable history.
80window.history = {};
81disableMethods(window.history, 'history',
82    ['back', 'forward', 'go']);
83disableGetters(window.history, 'history', ['length']);
84
85// Disable find.
86disableMethods(Window.prototype, 'window', ['find']);
87
88// Disable modal dialogs. Shell windows disable these anyway, but it's nice to
89// warn.
90disableMethods(Window.prototype, 'window', ['alert', 'confirm', 'prompt']);
91
92// Disable window.*bar.
93disableGetters(window, 'window',
94    ['locationbar', 'menubar', 'personalbar', 'scrollbars', 'statusbar',
95     'toolbar']);
96
97// Disable window.localStorage.
98// Sometimes DOM security policy prevents us from doing this (e.g. for data:
99// URLs) so wrap in try-catch.
100try {
101  disableGetters(window, 'window',
102      ['localStorage'],
103      'Use chrome.storage.local instead.');
104} catch (e) {}
105
106// Document instance properties that we wish to disable need to be set when
107// the document begins loading, since only then will the "document" reference
108// point to the page's document (it will be reset between now and then).
109// We can't listen for the "readystatechange" event on the document (because
110// the object that it's dispatched on doesn't exist yet), but we can instead
111// do it at the window level in the capturing phase.
112window.addEventListener('readystatechange', function(event) {
113  if (document.readyState != 'loading')
114    return;
115
116  // Deprecated document properties from
117  // https://developer.mozilla.org/en/DOM/document.
118  // To deprecate document.all, simply changing its getter and setter would
119  // activate its cache mechanism, and degrade the performance. Here we assign
120  // it first to 'undefined' to avoid this.
121  document.all = undefined;
122  disableGetters(document, 'document',
123      ['alinkColor', 'all', 'bgColor', 'fgColor', 'linkColor',
124       'vlinkColor']);
125}, true);
126
127// Disable onunload, onbeforeunload.
128Window.prototype.__defineSetter__(
129    'onbeforeunload', generateDisabledMethodStub('onbeforeunload'));
130Window.prototype.__defineSetter__(
131    'onunload', generateDisabledMethodStub('onunload'));
132var windowAddEventListener = Window.prototype.addEventListener;
133Window.prototype.addEventListener = function(type) {
134  if (type === 'unload' || type === 'beforeunload')
135    generateDisabledMethodStub(type)();
136  else
137    return $Function.apply(windowAddEventListener, window, arguments);
138};
139