Sat, 03 Jan 2015 20:18:00 +0100
Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | "use strict"; |
michael@0 | 6 | |
michael@0 | 7 | module.metadata = { |
michael@0 | 8 | "stability": "deprecated" |
michael@0 | 9 | }; |
michael@0 | 10 | |
michael@0 | 11 | // Design inspired by: http://www.traitsjs.org/ |
michael@0 | 12 | |
michael@0 | 13 | // shortcuts |
michael@0 | 14 | const getOwnPropertyNames = Object.getOwnPropertyNames, |
michael@0 | 15 | getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor, |
michael@0 | 16 | hasOwn = Object.prototype.hasOwnProperty, |
michael@0 | 17 | _create = Object.create; |
michael@0 | 18 | |
michael@0 | 19 | function doPropertiesMatch(object1, object2, name) { |
michael@0 | 20 | // If `object1` has property with the given `name` |
michael@0 | 21 | return name in object1 ? |
michael@0 | 22 | // then `object2` should have it with the same value. |
michael@0 | 23 | name in object2 && object1[name] === object2[name] : |
michael@0 | 24 | // otherwise `object2` should not have property with the given `name`. |
michael@0 | 25 | !(name in object2); |
michael@0 | 26 | } |
michael@0 | 27 | |
michael@0 | 28 | /** |
michael@0 | 29 | * Compares two trait custom property descriptors if they are the same. If |
michael@0 | 30 | * both are `conflict` or all the properties of descriptor are equal returned |
michael@0 | 31 | * value will be `true`, otherwise it will be `false`. |
michael@0 | 32 | * @param {Object} desc1 |
michael@0 | 33 | * @param {Object} desc2 |
michael@0 | 34 | */ |
michael@0 | 35 | function areSame(desc1, desc2) { |
michael@0 | 36 | return ('conflict' in desc1 && desc1.conflict && |
michael@0 | 37 | 'conflict' in desc2 && desc2.conflict) || |
michael@0 | 38 | (doPropertiesMatch(desc1, desc2, 'get') && |
michael@0 | 39 | doPropertiesMatch(desc1, desc2, 'set') && |
michael@0 | 40 | doPropertiesMatch(desc1, desc2, 'value') && |
michael@0 | 41 | doPropertiesMatch(desc1, desc2, 'enumerable') && |
michael@0 | 42 | doPropertiesMatch(desc1, desc2, 'required') && |
michael@0 | 43 | doPropertiesMatch(desc1, desc2, 'conflict')); |
michael@0 | 44 | } |
michael@0 | 45 | |
michael@0 | 46 | /** |
michael@0 | 47 | * Converts array to an object whose own property names represent |
michael@0 | 48 | * values of array. |
michael@0 | 49 | * @param {String[]} names |
michael@0 | 50 | * @returns {Object} |
michael@0 | 51 | * @example |
michael@0 | 52 | * Map(['foo', ...]) => { foo: true, ...} |
michael@0 | 53 | */ |
michael@0 | 54 | function Map(names) { |
michael@0 | 55 | let map = {}; |
michael@0 | 56 | for each (let name in names) |
michael@0 | 57 | map[name] = true; |
michael@0 | 58 | return map; |
michael@0 | 59 | } |
michael@0 | 60 | |
michael@0 | 61 | |
michael@0 | 62 | const ERR_CONFLICT = 'Remaining conflicting property: ', |
michael@0 | 63 | ERR_REQUIRED = 'Missing required property: '; |
michael@0 | 64 | /** |
michael@0 | 65 | * Constant singleton, representing placeholder for required properties. |
michael@0 | 66 | * @type {Object} |
michael@0 | 67 | */ |
michael@0 | 68 | const required = { toString: function()'<Trait.required>' }; |
michael@0 | 69 | exports.required = required; |
michael@0 | 70 | |
michael@0 | 71 | /** |
michael@0 | 72 | * Generates custom **required** property descriptor. Descriptor contains |
michael@0 | 73 | * non-standard property `required` that is equal to `true`. |
michael@0 | 74 | * @param {String} name |
michael@0 | 75 | * property name to generate descriptor for. |
michael@0 | 76 | * @returns {Object} |
michael@0 | 77 | * custom property descriptor |
michael@0 | 78 | */ |
michael@0 | 79 | function Required(name) { |
michael@0 | 80 | function required() { throw new Error(ERR_REQUIRED + name) } |
michael@0 | 81 | return { |
michael@0 | 82 | get: required, |
michael@0 | 83 | set: required, |
michael@0 | 84 | required: true |
michael@0 | 85 | }; |
michael@0 | 86 | } |
michael@0 | 87 | |
michael@0 | 88 | /** |
michael@0 | 89 | * Generates custom **conflicting** property descriptor. Descriptor contains |
michael@0 | 90 | * non-standard property `conflict` that is equal to `true`. |
michael@0 | 91 | * @param {String} name |
michael@0 | 92 | * property name to generate descriptor for. |
michael@0 | 93 | * @returns {Object} |
michael@0 | 94 | * custom property descriptor |
michael@0 | 95 | */ |
michael@0 | 96 | function Conflict(name) { |
michael@0 | 97 | function conflict() { throw new Error(ERR_CONFLICT + name) } |
michael@0 | 98 | return { |
michael@0 | 99 | get: conflict, |
michael@0 | 100 | set: conflict, |
michael@0 | 101 | conflict: true |
michael@0 | 102 | }; |
michael@0 | 103 | } |
michael@0 | 104 | |
michael@0 | 105 | /** |
michael@0 | 106 | * Function generates custom properties descriptor of the `object`s own |
michael@0 | 107 | * properties. All the inherited properties are going to be ignored. |
michael@0 | 108 | * Properties with values matching `required` singleton will be marked as |
michael@0 | 109 | * 'required' properties. |
michael@0 | 110 | * @param {Object} object |
michael@0 | 111 | * Set of properties to generate trait from. |
michael@0 | 112 | * @returns {Object} |
michael@0 | 113 | * Properties descriptor of all of the `object`'s own properties. |
michael@0 | 114 | */ |
michael@0 | 115 | function trait(properties) { |
michael@0 | 116 | let result = {}, |
michael@0 | 117 | keys = getOwnPropertyNames(properties); |
michael@0 | 118 | for each (let key in keys) { |
michael@0 | 119 | let descriptor = getOwnPropertyDescriptor(properties, key); |
michael@0 | 120 | result[key] = (required === descriptor.value) ? Required(key) : descriptor; |
michael@0 | 121 | } |
michael@0 | 122 | return result; |
michael@0 | 123 | } |
michael@0 | 124 | exports.Trait = exports.trait = trait; |
michael@0 | 125 | |
michael@0 | 126 | /** |
michael@0 | 127 | * Composes new trait. If two or more traits have own properties with the |
michael@0 | 128 | * same name, the new trait will contain a 'conflict' property for that name. |
michael@0 | 129 | * 'compose' is a commutative and associative operation, and the order of its |
michael@0 | 130 | * arguments is not significant. |
michael@0 | 131 | * |
michael@0 | 132 | * @params {Object} trait |
michael@0 | 133 | * Takes traits as an arguments |
michael@0 | 134 | * @returns {Object} |
michael@0 | 135 | * New trait containing the combined own properties of all the traits. |
michael@0 | 136 | * @example |
michael@0 | 137 | * var newTrait = compose(trait_1, trait_2, ..., trait_N); |
michael@0 | 138 | */ |
michael@0 | 139 | function compose(trait1, trait2) { |
michael@0 | 140 | let traits = Array.slice(arguments, 0), |
michael@0 | 141 | result = {}; |
michael@0 | 142 | for each (let trait in traits) { |
michael@0 | 143 | let keys = getOwnPropertyNames(trait); |
michael@0 | 144 | for each (let key in keys) { |
michael@0 | 145 | let descriptor = trait[key]; |
michael@0 | 146 | // if property already exists and it's not a requirement |
michael@0 | 147 | if (hasOwn.call(result, key) && !result[key].required) { |
michael@0 | 148 | if (descriptor.required) |
michael@0 | 149 | continue; |
michael@0 | 150 | if (!areSame(descriptor, result[key])) |
michael@0 | 151 | result[key] = Conflict(key); |
michael@0 | 152 | } else { |
michael@0 | 153 | result[key] = descriptor; |
michael@0 | 154 | } |
michael@0 | 155 | } |
michael@0 | 156 | } |
michael@0 | 157 | return result; |
michael@0 | 158 | } |
michael@0 | 159 | exports.compose = compose; |
michael@0 | 160 | |
michael@0 | 161 | /** |
michael@0 | 162 | * Composes new trait with the same own properties as the original trait, |
michael@0 | 163 | * except that all property names appearing in the first argument are replaced |
michael@0 | 164 | * by 'required' property descriptors. |
michael@0 | 165 | * @param {String[]} keys |
michael@0 | 166 | * Array of strings property names. |
michael@0 | 167 | * @param {Object} trait |
michael@0 | 168 | * A trait some properties of which should be excluded. |
michael@0 | 169 | * @returns {Object} |
michael@0 | 170 | * @example |
michael@0 | 171 | * var newTrait = exclude(['name', ...], trait) |
michael@0 | 172 | */ |
michael@0 | 173 | function exclude(keys, trait) { |
michael@0 | 174 | let exclusions = Map(keys), |
michael@0 | 175 | result = {}; |
michael@0 | 176 | |
michael@0 | 177 | keys = getOwnPropertyNames(trait); |
michael@0 | 178 | |
michael@0 | 179 | for each (let key in keys) { |
michael@0 | 180 | if (!hasOwn.call(exclusions, key) || trait[key].required) |
michael@0 | 181 | result[key] = trait[key]; |
michael@0 | 182 | else |
michael@0 | 183 | result[key] = Required(key); |
michael@0 | 184 | } |
michael@0 | 185 | return result; |
michael@0 | 186 | } |
michael@0 | 187 | |
michael@0 | 188 | /** |
michael@0 | 189 | * Composes a new trait with all of the combined properties of the argument |
michael@0 | 190 | * traits. In contrast to `compose`, `override` immediately resolves all |
michael@0 | 191 | * conflicts resulting from this composition by overriding the properties of |
michael@0 | 192 | * later traits. Trait priority is from left to right. I.e. the properties of |
michael@0 | 193 | * the leftmost trait are never overridden. |
michael@0 | 194 | * @params {Object} trait |
michael@0 | 195 | * @returns {Object} |
michael@0 | 196 | * @examples |
michael@0 | 197 | * // override is associative: |
michael@0 | 198 | * override(t1,t2,t3) |
michael@0 | 199 | * // is equivalent to |
michael@0 | 200 | * override(t1, override(t2, t3)) |
michael@0 | 201 | * // or |
michael@0 | 202 | * to override(override(t1, t2), t3) |
michael@0 | 203 | * |
michael@0 | 204 | * // override is not commutative: |
michael@0 | 205 | * override(t1,t2) |
michael@0 | 206 | * // is not equivalent to |
michael@0 | 207 | * override(t2,t1) |
michael@0 | 208 | */ |
michael@0 | 209 | function override() { |
michael@0 | 210 | let traits = Array.slice(arguments, 0), |
michael@0 | 211 | result = {}; |
michael@0 | 212 | for each (let trait in traits) { |
michael@0 | 213 | let keys = getOwnPropertyNames(trait); |
michael@0 | 214 | for each(let key in keys) { |
michael@0 | 215 | let descriptor = trait[key]; |
michael@0 | 216 | if (!hasOwn.call(result, key) || result[key].required) |
michael@0 | 217 | result[key] = descriptor; |
michael@0 | 218 | } |
michael@0 | 219 | } |
michael@0 | 220 | return result; |
michael@0 | 221 | } |
michael@0 | 222 | exports.override = override; |
michael@0 | 223 | |
michael@0 | 224 | /** |
michael@0 | 225 | * Composes a new trait with the same properties as the original trait, except |
michael@0 | 226 | * that all properties whose name is an own property of map will be renamed to |
michael@0 | 227 | * map[name], and a 'required' property for name will be added instead. |
michael@0 | 228 | * @param {Object} map |
michael@0 | 229 | * An object whose own properties serve as a mapping from old names to new |
michael@0 | 230 | * names. |
michael@0 | 231 | * @param {Object} trait |
michael@0 | 232 | * A trait object |
michael@0 | 233 | * @returns {Object} |
michael@0 | 234 | * @example |
michael@0 | 235 | * var newTrait = rename(map, trait); |
michael@0 | 236 | */ |
michael@0 | 237 | function rename(map, trait) { |
michael@0 | 238 | let result = {}, |
michael@0 | 239 | keys = getOwnPropertyNames(trait); |
michael@0 | 240 | for each(let key in keys) { |
michael@0 | 241 | // must be renamed & it's not requirement |
michael@0 | 242 | if (hasOwn.call(map, key) && !trait[key].required) { |
michael@0 | 243 | let alias = map[key]; |
michael@0 | 244 | if (hasOwn.call(result, alias) && !result[alias].required) |
michael@0 | 245 | result[alias] = Conflict(alias); |
michael@0 | 246 | else |
michael@0 | 247 | result[alias] = trait[key]; |
michael@0 | 248 | if (!hasOwn.call(result, key)) |
michael@0 | 249 | result[key] = Required(key); |
michael@0 | 250 | } else { // must not be renamed or its a requirement |
michael@0 | 251 | // property is not in result trait yet |
michael@0 | 252 | if (!hasOwn.call(result, key)) |
michael@0 | 253 | result[key] = trait[key]; |
michael@0 | 254 | // property is already in resulted trait & it's not requirement |
michael@0 | 255 | else if (!trait[key].required) |
michael@0 | 256 | result[key] = Conflict(key); |
michael@0 | 257 | } |
michael@0 | 258 | } |
michael@0 | 259 | return result; |
michael@0 | 260 | } |
michael@0 | 261 | |
michael@0 | 262 | /** |
michael@0 | 263 | * Composes new resolved trait, with all the same properties as the original |
michael@0 | 264 | * trait, except that all properties whose name is an own property of |
michael@0 | 265 | * resolutions will be renamed to `resolutions[name]`. If it is |
michael@0 | 266 | * `resolutions[name]` is `null` value is changed into a required property |
michael@0 | 267 | * descriptor. |
michael@0 | 268 | * function can be implemented as `rename(map,exclude(exclusions, trait))` |
michael@0 | 269 | * where map is the subset of mappings from oldName to newName and exclusions |
michael@0 | 270 | * is an array of all the keys that map to `null`. |
michael@0 | 271 | * Note: it's important to **first** `exclude`, **then** `rename`, since |
michael@0 | 272 | * `exclude` and rename are not associative. |
michael@0 | 273 | * @param {Object} resolutions |
michael@0 | 274 | * An object whose own properties serve as a mapping from old names to new |
michael@0 | 275 | * names, or to `null` if the property should be excluded. |
michael@0 | 276 | * @param {Object} trait |
michael@0 | 277 | * A trait object |
michael@0 | 278 | * @returns {Object} |
michael@0 | 279 | * Resolved trait with the same own properties as the original trait. |
michael@0 | 280 | */ |
michael@0 | 281 | function resolve(resolutions, trait) { |
michael@0 | 282 | let renames = {}, |
michael@0 | 283 | exclusions = [], |
michael@0 | 284 | keys = getOwnPropertyNames(resolutions); |
michael@0 | 285 | for each (let key in keys) { // pre-process renamed and excluded properties |
michael@0 | 286 | if (resolutions[key]) // old name -> new name |
michael@0 | 287 | renames[key] = resolutions[key]; |
michael@0 | 288 | else // name -> undefined |
michael@0 | 289 | exclusions.push(key); |
michael@0 | 290 | } |
michael@0 | 291 | return rename(renames, exclude(exclusions, trait)); |
michael@0 | 292 | } |
michael@0 | 293 | exports.resolve = resolve; |
michael@0 | 294 | |
michael@0 | 295 | /** |
michael@0 | 296 | * `create` is like `Object.create`, except that it ensures that: |
michael@0 | 297 | * - an exception is thrown if 'trait' still contains required properties |
michael@0 | 298 | * - an exception is thrown if 'trait' still contains conflicting |
michael@0 | 299 | * properties |
michael@0 | 300 | * @param {Object} |
michael@0 | 301 | * prototype of the completed object |
michael@0 | 302 | * @param {Object} trait |
michael@0 | 303 | * trait object to be turned into a complete object |
michael@0 | 304 | * @returns {Object} |
michael@0 | 305 | * An object with all of the properties described by the trait. |
michael@0 | 306 | */ |
michael@0 | 307 | function create(proto, trait) { |
michael@0 | 308 | let properties = {}, |
michael@0 | 309 | keys = getOwnPropertyNames(trait); |
michael@0 | 310 | for each(let key in keys) { |
michael@0 | 311 | let descriptor = trait[key]; |
michael@0 | 312 | if (descriptor.required && !hasOwn.call(proto, key)) |
michael@0 | 313 | throw new Error(ERR_REQUIRED + key); |
michael@0 | 314 | else if (descriptor.conflict) |
michael@0 | 315 | throw new Error(ERR_CONFLICT + key); |
michael@0 | 316 | else |
michael@0 | 317 | properties[key] = descriptor; |
michael@0 | 318 | } |
michael@0 | 319 | return _create(proto, properties); |
michael@0 | 320 | } |
michael@0 | 321 | exports.create = create; |
michael@0 | 322 |