1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
|
// 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.
/**
* 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', 'pushState', 'replaceState', 'state']);
// Disable find.
disableMethods(window, 'window', ['find']);
disableMethods(Window.prototype, 'window', ['find']);
// Disable modal dialogs. Shell windows disable these anyway, but it's nice to
// warn.
disableMethods(window, 'window', ['alert', 'confirm', 'prompt']);
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, 'window', ['onbeforeunload', 'onunload']);
disableSetters(Window.prototype, 'window', ['onbeforeunload', 'onunload']);
var eventTargetAddEventListener = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function(type) {
if (type === 'unload' || type === 'beforeunload')
generateDisabledMethodStub(type)();
else
return $Function.apply(eventTargetAddEventListener, this, arguments);
};
|