Thu, 15 Jan 2015 15:59:08 +0100
Implement a real Private Browsing Mode condition by changing the API/ABI;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
michael@0 | 1 | "use strict"; |
michael@0 | 2 | |
michael@0 | 3 | var defineProperty = Object.defineProperty || function(object, name, property) { |
michael@0 | 4 | object[name] = property.value |
michael@0 | 5 | return object |
michael@0 | 6 | } |
michael@0 | 7 | |
michael@0 | 8 | // Shortcut for `Object.prototype.toString` for faster access. |
michael@0 | 9 | var typefy = Object.prototype.toString |
michael@0 | 10 | |
michael@0 | 11 | // Map to for jumping from typeof(value) to associated type prefix used |
michael@0 | 12 | // as a hash in the map of builtin implementations. |
michael@0 | 13 | var types = { "function": "Object", "object": "Object" } |
michael@0 | 14 | |
michael@0 | 15 | // Array is used to save method implementations for the host objects in order |
michael@0 | 16 | // to avoid extending them with non-primitive values that could cause leaks. |
michael@0 | 17 | var host = [] |
michael@0 | 18 | // Hash map is used to save method implementations for builtin types in order |
michael@0 | 19 | // to avoid extending their prototypes. This also allows to share method |
michael@0 | 20 | // implementations for types across diff contexts / frames / compartments. |
michael@0 | 21 | var builtin = {} |
michael@0 | 22 | |
michael@0 | 23 | function Primitive() {} |
michael@0 | 24 | function ObjectType() {} |
michael@0 | 25 | ObjectType.prototype = new Primitive() |
michael@0 | 26 | function ErrorType() {} |
michael@0 | 27 | ErrorType.prototype = new ObjectType() |
michael@0 | 28 | |
michael@0 | 29 | var Default = builtin.Default = Primitive.prototype |
michael@0 | 30 | var Null = builtin.Null = new Primitive() |
michael@0 | 31 | var Void = builtin.Void = new Primitive() |
michael@0 | 32 | builtin.String = new Primitive() |
michael@0 | 33 | builtin.Number = new Primitive() |
michael@0 | 34 | builtin.Boolean = new Primitive() |
michael@0 | 35 | |
michael@0 | 36 | builtin.Object = ObjectType.prototype |
michael@0 | 37 | builtin.Error = ErrorType.prototype |
michael@0 | 38 | |
michael@0 | 39 | builtin.EvalError = new ErrorType() |
michael@0 | 40 | builtin.InternalError = new ErrorType() |
michael@0 | 41 | builtin.RangeError = new ErrorType() |
michael@0 | 42 | builtin.ReferenceError = new ErrorType() |
michael@0 | 43 | builtin.StopIteration = new ErrorType() |
michael@0 | 44 | builtin.SyntaxError = new ErrorType() |
michael@0 | 45 | builtin.TypeError = new ErrorType() |
michael@0 | 46 | builtin.URIError = new ErrorType() |
michael@0 | 47 | |
michael@0 | 48 | |
michael@0 | 49 | function Method(hint) { |
michael@0 | 50 | /** |
michael@0 | 51 | Private Method is a callable private name that dispatches on the first |
michael@0 | 52 | arguments same named Method: |
michael@0 | 53 | |
michael@0 | 54 | method(object, ...rest) => object[method](...rest) |
michael@0 | 55 | |
michael@0 | 56 | Optionally hint string may be provided that will be used in generated names |
michael@0 | 57 | to ease debugging. |
michael@0 | 58 | |
michael@0 | 59 | ## Example |
michael@0 | 60 | |
michael@0 | 61 | var foo = Method() |
michael@0 | 62 | |
michael@0 | 63 | // Implementation for any types |
michael@0 | 64 | foo.define(function(value, arg1, arg2) { |
michael@0 | 65 | // ... |
michael@0 | 66 | }) |
michael@0 | 67 | |
michael@0 | 68 | // Implementation for a specific type |
michael@0 | 69 | foo.define(BarType, function(bar, arg1, arg2) { |
michael@0 | 70 | // ... |
michael@0 | 71 | }) |
michael@0 | 72 | **/ |
michael@0 | 73 | |
michael@0 | 74 | // Create an internal unique name if `hint` is provided it is used to |
michael@0 | 75 | // prefix name to ease debugging. |
michael@0 | 76 | var name = (hint || "") + "#" + Math.random().toString(32).substr(2) |
michael@0 | 77 | |
michael@0 | 78 | function dispatch(value) { |
michael@0 | 79 | // Method dispatches on type of the first argument. |
michael@0 | 80 | // If first argument is `null` or `void` associated implementation is |
michael@0 | 81 | // looked up in the `builtin` hash where implementations for built-ins |
michael@0 | 82 | // are stored. |
michael@0 | 83 | var type = null |
michael@0 | 84 | var method = value === null ? Null[name] : |
michael@0 | 85 | value === void(0) ? Void[name] : |
michael@0 | 86 | // Otherwise attempt to use method with a generated private |
michael@0 | 87 | // `name` that is supposedly in the prototype chain of the |
michael@0 | 88 | // `target`. |
michael@0 | 89 | value[name] || |
michael@0 | 90 | // Otherwise assume it's one of the built-in type instances, |
michael@0 | 91 | // in which case implementation is stored in a `builtin` hash. |
michael@0 | 92 | // Attempt to find a implementation for the given built-in |
michael@0 | 93 | // via constructor name and method name. |
michael@0 | 94 | ((type = builtin[(value.constructor || "").name]) && |
michael@0 | 95 | type[name]) || |
michael@0 | 96 | // Otherwise assume it's a host object. For host objects |
michael@0 | 97 | // actual method implementations are stored in the `host` |
michael@0 | 98 | // array and only index for the implementation is stored |
michael@0 | 99 | // in the host object's prototype chain. This avoids memory |
michael@0 | 100 | // leaks that otherwise could happen when saving JS objects |
michael@0 | 101 | // on host object. |
michael@0 | 102 | host[value["!" + name] || void(0)] || |
michael@0 | 103 | // Otherwise attempt to lookup implementation for builtins by |
michael@0 | 104 | // a type of the value. This basically makes sure that all |
michael@0 | 105 | // non primitive values will delegate to an `Object`. |
michael@0 | 106 | ((type = builtin[types[typeof(value)]]) && type[name]) |
michael@0 | 107 | |
michael@0 | 108 | |
michael@0 | 109 | // If method implementation for the type is still not found then |
michael@0 | 110 | // just fallback for default implementation. |
michael@0 | 111 | method = method || Default[name] |
michael@0 | 112 | |
michael@0 | 113 | |
michael@0 | 114 | // If implementation is still not found (which also means there is no |
michael@0 | 115 | // default) just throw an error with a descriptive message. |
michael@0 | 116 | if (!method) throw TypeError("Type does not implements method: " + name) |
michael@0 | 117 | |
michael@0 | 118 | // If implementation was found then just delegate. |
michael@0 | 119 | return method.apply(method, arguments) |
michael@0 | 120 | } |
michael@0 | 121 | |
michael@0 | 122 | // Make `toString` of the dispatch return a private name, this enables |
michael@0 | 123 | // method definition without sugar: |
michael@0 | 124 | // |
michael@0 | 125 | // var method = Method() |
michael@0 | 126 | // object[method] = function() { /***/ } |
michael@0 | 127 | dispatch.toString = function toString() { return name } |
michael@0 | 128 | |
michael@0 | 129 | // Copy utility methods for convenient API. |
michael@0 | 130 | dispatch.implement = implementMethod |
michael@0 | 131 | dispatch.define = defineMethod |
michael@0 | 132 | |
michael@0 | 133 | return dispatch |
michael@0 | 134 | } |
michael@0 | 135 | |
michael@0 | 136 | // Create method shortcuts form functions. |
michael@0 | 137 | var defineMethod = function defineMethod(Type, lambda) { |
michael@0 | 138 | return define(this, Type, lambda) |
michael@0 | 139 | } |
michael@0 | 140 | var implementMethod = function implementMethod(object, lambda) { |
michael@0 | 141 | return implement(this, object, lambda) |
michael@0 | 142 | } |
michael@0 | 143 | |
michael@0 | 144 | // Define `implement` and `define` polymorphic methods to allow definitions |
michael@0 | 145 | // and implementations through them. |
michael@0 | 146 | var implement = Method("implement") |
michael@0 | 147 | var define = Method("define") |
michael@0 | 148 | |
michael@0 | 149 | |
michael@0 | 150 | function _implement(method, object, lambda) { |
michael@0 | 151 | /** |
michael@0 | 152 | Implements `Method` for the given `object` with a provided `implementation`. |
michael@0 | 153 | Calling `Method` with `object` as a first argument will dispatch on provided |
michael@0 | 154 | implementation. |
michael@0 | 155 | **/ |
michael@0 | 156 | return defineProperty(object, method.toString(), { |
michael@0 | 157 | enumerable: false, |
michael@0 | 158 | configurable: false, |
michael@0 | 159 | writable: false, |
michael@0 | 160 | value: lambda |
michael@0 | 161 | }) |
michael@0 | 162 | } |
michael@0 | 163 | |
michael@0 | 164 | function _define(method, Type, lambda) { |
michael@0 | 165 | /** |
michael@0 | 166 | Defines `Method` for the given `Type` with a provided `implementation`. |
michael@0 | 167 | Calling `Method` with a first argument of this `Type` will dispatch on |
michael@0 | 168 | provided `implementation`. If `Type` is a `Method` default implementation |
michael@0 | 169 | is defined. If `Type` is a `null` or `undefined` `Method` is implemented |
michael@0 | 170 | for that value type. |
michael@0 | 171 | **/ |
michael@0 | 172 | |
michael@0 | 173 | // Attempt to guess a type via `Object.prototype.toString.call` hack. |
michael@0 | 174 | var type = Type && typefy.call(Type.prototype) |
michael@0 | 175 | |
michael@0 | 176 | // If only two arguments are passed then `Type` is actually an implementation |
michael@0 | 177 | // for a default type. |
michael@0 | 178 | if (!lambda) Default[method] = Type |
michael@0 | 179 | // If `Type` is `null` or `void` store implementation accordingly. |
michael@0 | 180 | else if (Type === null) Null[method] = lambda |
michael@0 | 181 | else if (Type === void(0)) Void[method] = lambda |
michael@0 | 182 | // If `type` hack indicates built-in type and type has a name us it to |
michael@0 | 183 | // store a implementation into associated hash. If hash for this type does |
michael@0 | 184 | // not exists yet create one. |
michael@0 | 185 | else if (type !== "[object Object]" && Type.name) { |
michael@0 | 186 | var Bulitin = builtin[Type.name] || (builtin[Type.name] = new ObjectType()) |
michael@0 | 187 | Bulitin[method] = lambda |
michael@0 | 188 | } |
michael@0 | 189 | // If `type` hack indicates an object, that may be either object or any |
michael@0 | 190 | // JS defined "Class". If name of the constructor is `Object`, assume it's |
michael@0 | 191 | // built-in `Object` and store implementation accordingly. |
michael@0 | 192 | else if (Type.name === "Object") |
michael@0 | 193 | builtin.Object[method] = lambda |
michael@0 | 194 | // Host objects are pain!!! Every browser does some crazy stuff for them |
michael@0 | 195 | // So far all browser seem to not implement `call` method for host object |
michael@0 | 196 | // constructors. If that is a case here, assume it's a host object and |
michael@0 | 197 | // store implementation in a `host` array and store `index` in the array |
michael@0 | 198 | // in a `Type.prototype` itself. This avoids memory leaks that could be |
michael@0 | 199 | // caused by storing JS objects on a host objects. |
michael@0 | 200 | else if (Type.call === void(0)) { |
michael@0 | 201 | var index = host.indexOf(lambda) |
michael@0 | 202 | if (index < 0) index = host.push(lambda) - 1 |
michael@0 | 203 | // Prefix private name with `!` so it can be dispatched from the method |
michael@0 | 204 | // without type checks. |
michael@0 | 205 | implement("!" + method, Type.prototype, index) |
michael@0 | 206 | } |
michael@0 | 207 | // If Got that far `Type` is user defined JS `Class`. Define private name |
michael@0 | 208 | // as hidden property on it's prototype. |
michael@0 | 209 | else |
michael@0 | 210 | implement(method, Type.prototype, lambda) |
michael@0 | 211 | } |
michael@0 | 212 | |
michael@0 | 213 | // And provided implementations for a polymorphic equivalents. |
michael@0 | 214 | _define(define, _define) |
michael@0 | 215 | _define(implement, _implement) |
michael@0 | 216 | |
michael@0 | 217 | // Define exports on `Method` as it's only thing being exported. |
michael@0 | 218 | Method.implement = implement |
michael@0 | 219 | Method.define = define |
michael@0 | 220 | Method.Method = Method |
michael@0 | 221 | Method.method = Method |
michael@0 | 222 | Method.builtin = builtin |
michael@0 | 223 | Method.host = host |
michael@0 | 224 | |
michael@0 | 225 | module.exports = Method |