Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 | /* This code is loaded in every child process that is started by mochitest in |
michael@0 | 5 | * order to be used as a replacement for UniversalXPConnect |
michael@0 | 6 | */ |
michael@0 | 7 | |
michael@0 | 8 | var Ci = Components.interfaces; |
michael@0 | 9 | var Cc = Components.classes; |
michael@0 | 10 | var Cu = Components.utils; |
michael@0 | 11 | |
michael@0 | 12 | Cu.import("resource://specialpowers/MockFilePicker.jsm"); |
michael@0 | 13 | Cu.import("resource://specialpowers/MockColorPicker.jsm"); |
michael@0 | 14 | Cu.import("resource://specialpowers/MockPermissionPrompt.jsm"); |
michael@0 | 15 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 16 | Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm"); |
michael@0 | 17 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 18 | |
michael@0 | 19 | function SpecialPowersAPI() { |
michael@0 | 20 | this._consoleListeners = []; |
michael@0 | 21 | this._encounteredCrashDumpFiles = []; |
michael@0 | 22 | this._unexpectedCrashDumpFiles = { }; |
michael@0 | 23 | this._crashDumpDir = null; |
michael@0 | 24 | this._mfl = null; |
michael@0 | 25 | this._prefEnvUndoStack = []; |
michael@0 | 26 | this._pendingPrefs = []; |
michael@0 | 27 | this._applyingPrefs = false; |
michael@0 | 28 | this._permissionsUndoStack = []; |
michael@0 | 29 | this._pendingPermissions = []; |
michael@0 | 30 | this._applyingPermissions = false; |
michael@0 | 31 | this._fm = null; |
michael@0 | 32 | this._cb = null; |
michael@0 | 33 | } |
michael@0 | 34 | |
michael@0 | 35 | function bindDOMWindowUtils(aWindow) { |
michael@0 | 36 | if (!aWindow) |
michael@0 | 37 | return |
michael@0 | 38 | |
michael@0 | 39 | var util = aWindow.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 40 | .getInterface(Ci.nsIDOMWindowUtils); |
michael@0 | 41 | return wrapPrivileged(util); |
michael@0 | 42 | } |
michael@0 | 43 | |
michael@0 | 44 | function getRawComponents(aWindow) { |
michael@0 | 45 | // If we're running in automation that supports enablePrivilege, then we also |
michael@0 | 46 | // provided access to the privileged Components. |
michael@0 | 47 | try { |
michael@0 | 48 | let win = Cu.waiveXrays(aWindow); |
michael@0 | 49 | if (typeof win.netscape.security.PrivilegeManager == 'object') |
michael@0 | 50 | Cu.forcePrivilegedComponentsForScope(aWindow); |
michael@0 | 51 | } catch (e) {} |
michael@0 | 52 | return Cu.getComponentsForScope(aWindow); |
michael@0 | 53 | } |
michael@0 | 54 | |
michael@0 | 55 | function isWrappable(x) { |
michael@0 | 56 | if (typeof x === "object") |
michael@0 | 57 | return x !== null; |
michael@0 | 58 | return typeof x === "function"; |
michael@0 | 59 | }; |
michael@0 | 60 | |
michael@0 | 61 | function isWrapper(x) { |
michael@0 | 62 | return isWrappable(x) && (typeof x.SpecialPowers_wrappedObject !== "undefined"); |
michael@0 | 63 | }; |
michael@0 | 64 | |
michael@0 | 65 | function unwrapIfWrapped(x) { |
michael@0 | 66 | return isWrapper(x) ? unwrapPrivileged(x) : x; |
michael@0 | 67 | }; |
michael@0 | 68 | |
michael@0 | 69 | function wrapIfUnwrapped(x) { |
michael@0 | 70 | return isWrapper(x) ? x : wrapPrivileged(x); |
michael@0 | 71 | } |
michael@0 | 72 | |
michael@0 | 73 | function isXrayWrapper(x) { |
michael@0 | 74 | return Cu.isXrayWrapper(x); |
michael@0 | 75 | } |
michael@0 | 76 | |
michael@0 | 77 | function callGetOwnPropertyDescriptor(obj, name) { |
michael@0 | 78 | // Quickstubbed getters and setters are propertyOps, and don't get reified |
michael@0 | 79 | // until someone calls __lookupGetter__ or __lookupSetter__ on them (note |
michael@0 | 80 | // that there are special version of those functions for quickstubs, so |
michael@0 | 81 | // apply()ing Object.prototype.__lookupGetter__ isn't good enough). Try to |
michael@0 | 82 | // trigger reification before calling Object.getOwnPropertyDescriptor. |
michael@0 | 83 | // |
michael@0 | 84 | // See bug 764315. |
michael@0 | 85 | try { |
michael@0 | 86 | obj.__lookupGetter__(name); |
michael@0 | 87 | obj.__lookupSetter__(name); |
michael@0 | 88 | } catch(e) { } |
michael@0 | 89 | return Object.getOwnPropertyDescriptor(obj, name); |
michael@0 | 90 | } |
michael@0 | 91 | |
michael@0 | 92 | // We can't call apply() directy on Xray-wrapped functions, so we have to be |
michael@0 | 93 | // clever. |
michael@0 | 94 | function doApply(fun, invocant, args) { |
michael@0 | 95 | return Function.prototype.apply.call(fun, invocant, args); |
michael@0 | 96 | } |
michael@0 | 97 | |
michael@0 | 98 | function wrapPrivileged(obj) { |
michael@0 | 99 | |
michael@0 | 100 | // Primitives pass straight through. |
michael@0 | 101 | if (!isWrappable(obj)) |
michael@0 | 102 | return obj; |
michael@0 | 103 | |
michael@0 | 104 | // No double wrapping. |
michael@0 | 105 | if (isWrapper(obj)) |
michael@0 | 106 | throw "Trying to double-wrap object!"; |
michael@0 | 107 | |
michael@0 | 108 | // Make our core wrapper object. |
michael@0 | 109 | var handler = new SpecialPowersHandler(obj); |
michael@0 | 110 | |
michael@0 | 111 | // If the object is callable, make a function proxy. |
michael@0 | 112 | if (typeof obj === "function") { |
michael@0 | 113 | var callTrap = function() { |
michael@0 | 114 | // The invocant and arguments may or may not be wrappers. Unwrap them if necessary. |
michael@0 | 115 | var invocant = unwrapIfWrapped(this); |
michael@0 | 116 | var unwrappedArgs = Array.prototype.slice.call(arguments).map(unwrapIfWrapped); |
michael@0 | 117 | |
michael@0 | 118 | try { |
michael@0 | 119 | return wrapPrivileged(doApply(obj, invocant, unwrappedArgs)); |
michael@0 | 120 | } catch (e) { |
michael@0 | 121 | // Wrap exceptions and re-throw them. |
michael@0 | 122 | throw wrapIfUnwrapped(e); |
michael@0 | 123 | } |
michael@0 | 124 | }; |
michael@0 | 125 | var constructTrap = function() { |
michael@0 | 126 | // The arguments may or may not be wrappers. Unwrap them if necessary. |
michael@0 | 127 | var unwrappedArgs = Array.prototype.slice.call(arguments).map(unwrapIfWrapped); |
michael@0 | 128 | |
michael@0 | 129 | // We want to invoke "obj" as a constructor, but using unwrappedArgs as |
michael@0 | 130 | // the arguments. Make sure to wrap and re-throw exceptions! |
michael@0 | 131 | try { |
michael@0 | 132 | return wrapPrivileged(new obj(...unwrappedArgs)); |
michael@0 | 133 | } catch (e) { |
michael@0 | 134 | throw wrapIfUnwrapped(e); |
michael@0 | 135 | } |
michael@0 | 136 | }; |
michael@0 | 137 | |
michael@0 | 138 | return Proxy.createFunction(handler, callTrap, constructTrap); |
michael@0 | 139 | } |
michael@0 | 140 | |
michael@0 | 141 | // Otherwise, just make a regular object proxy. |
michael@0 | 142 | return Proxy.create(handler); |
michael@0 | 143 | }; |
michael@0 | 144 | |
michael@0 | 145 | function unwrapPrivileged(x) { |
michael@0 | 146 | |
michael@0 | 147 | // We don't wrap primitives, so sometimes we have a primitive where we'd |
michael@0 | 148 | // expect to have a wrapper. The proxy pretends to be the type that it's |
michael@0 | 149 | // emulating, so we can just as easily check isWrappable() on a proxy as |
michael@0 | 150 | // we can on an unwrapped object. |
michael@0 | 151 | if (!isWrappable(x)) |
michael@0 | 152 | return x; |
michael@0 | 153 | |
michael@0 | 154 | // If we have a wrappable type, make sure it's wrapped. |
michael@0 | 155 | if (!isWrapper(x)) |
michael@0 | 156 | throw "Trying to unwrap a non-wrapped object!"; |
michael@0 | 157 | |
michael@0 | 158 | // Unwrap. |
michael@0 | 159 | return x.SpecialPowers_wrappedObject; |
michael@0 | 160 | }; |
michael@0 | 161 | |
michael@0 | 162 | function crawlProtoChain(obj, fn) { |
michael@0 | 163 | var rv = fn(obj); |
michael@0 | 164 | if (rv !== undefined) |
michael@0 | 165 | return rv; |
michael@0 | 166 | if (Object.getPrototypeOf(obj)) |
michael@0 | 167 | return crawlProtoChain(Object.getPrototypeOf(obj), fn); |
michael@0 | 168 | }; |
michael@0 | 169 | |
michael@0 | 170 | /* |
michael@0 | 171 | * We want to waive the __exposedProps__ security check for SpecialPowers-wrapped |
michael@0 | 172 | * objects. We do this by creating a proxy singleton that just always returns 'rw' |
michael@0 | 173 | * for any property name. |
michael@0 | 174 | */ |
michael@0 | 175 | function ExposedPropsWaiverHandler() { |
michael@0 | 176 | // NB: XPConnect denies access if the relevant member of __exposedProps__ is not |
michael@0 | 177 | // enumerable. |
michael@0 | 178 | var _permit = { value: 'rw', writable: false, configurable: false, enumerable: true }; |
michael@0 | 179 | return { |
michael@0 | 180 | getOwnPropertyDescriptor: function(name) { return _permit; }, |
michael@0 | 181 | getPropertyDescriptor: function(name) { return _permit; }, |
michael@0 | 182 | getOwnPropertyNames: function() { throw Error("Can't enumerate ExposedPropsWaiver"); }, |
michael@0 | 183 | getPropertyNames: function() { throw Error("Can't enumerate ExposedPropsWaiver"); }, |
michael@0 | 184 | enumerate: function() { throw Error("Can't enumerate ExposedPropsWaiver"); }, |
michael@0 | 185 | defineProperty: function(name) { throw Error("Can't define props on ExposedPropsWaiver"); }, |
michael@0 | 186 | delete: function(name) { throw Error("Can't delete props from ExposedPropsWaiver"); } |
michael@0 | 187 | }; |
michael@0 | 188 | }; |
michael@0 | 189 | ExposedPropsWaiver = Proxy.create(ExposedPropsWaiverHandler()); |
michael@0 | 190 | |
michael@0 | 191 | function SpecialPowersHandler(obj) { |
michael@0 | 192 | this.wrappedObject = obj; |
michael@0 | 193 | }; |
michael@0 | 194 | |
michael@0 | 195 | // Allow us to transitively maintain the membrane by wrapping descriptors |
michael@0 | 196 | // we return. |
michael@0 | 197 | SpecialPowersHandler.prototype.doGetPropertyDescriptor = function(name, own) { |
michael@0 | 198 | |
michael@0 | 199 | // Handle our special API. |
michael@0 | 200 | if (name == "SpecialPowers_wrappedObject") |
michael@0 | 201 | return { value: this.wrappedObject, writeable: false, configurable: false, enumerable: false }; |
michael@0 | 202 | |
michael@0 | 203 | // Handle __exposedProps__. |
michael@0 | 204 | if (name == "__exposedProps__") |
michael@0 | 205 | return { value: ExposedPropsWaiver, writable: false, configurable: false, enumerable: false }; |
michael@0 | 206 | |
michael@0 | 207 | // In general, we want Xray wrappers for content DOM objects, because waiving |
michael@0 | 208 | // Xray gives us Xray waiver wrappers that clamp the principal when we cross |
michael@0 | 209 | // compartment boundaries. However, Xray adds some gunk to toString(), which |
michael@0 | 210 | // has the potential to confuse consumers that aren't expecting Xray wrappers. |
michael@0 | 211 | // Since toString() is a non-privileged method that returns only strings, we |
michael@0 | 212 | // can just waive Xray for that case. |
michael@0 | 213 | var obj = name == 'toString' ? XPCNativeWrapper.unwrap(this.wrappedObject) |
michael@0 | 214 | : this.wrappedObject; |
michael@0 | 215 | |
michael@0 | 216 | // |
michael@0 | 217 | // Call through to the wrapped object. |
michael@0 | 218 | // |
michael@0 | 219 | // Note that we have several cases here, each of which requires special handling. |
michael@0 | 220 | // |
michael@0 | 221 | var desc; |
michael@0 | 222 | |
michael@0 | 223 | // Case 1: Own Properties. |
michael@0 | 224 | // |
michael@0 | 225 | // This one is easy, thanks to Object.getOwnPropertyDescriptor(). |
michael@0 | 226 | if (own) |
michael@0 | 227 | desc = callGetOwnPropertyDescriptor(obj, name); |
michael@0 | 228 | |
michael@0 | 229 | // Case 2: Not own, not Xray-wrapped. |
michael@0 | 230 | // |
michael@0 | 231 | // Here, we can just crawl the prototype chain, calling |
michael@0 | 232 | // Object.getOwnPropertyDescriptor until we find what we want. |
michael@0 | 233 | // |
michael@0 | 234 | // NB: Make sure to check this.wrappedObject here, rather than obj, because |
michael@0 | 235 | // we may have waived Xray on obj above. |
michael@0 | 236 | else if (!isXrayWrapper(this.wrappedObject)) |
michael@0 | 237 | desc = crawlProtoChain(obj, function(o) {return callGetOwnPropertyDescriptor(o, name);}); |
michael@0 | 238 | |
michael@0 | 239 | // Case 3: Not own, Xray-wrapped. |
michael@0 | 240 | // |
michael@0 | 241 | // This one is harder, because we Xray wrappers are flattened and don't have |
michael@0 | 242 | // a prototype. Xray wrappers are proxies themselves, so we'd love to just call |
michael@0 | 243 | // through to XrayWrapper<Base>::getPropertyDescriptor(). Unfortunately though, |
michael@0 | 244 | // we don't have any way to do that. :-( |
michael@0 | 245 | // |
michael@0 | 246 | // So we first try with a call to getOwnPropertyDescriptor(). If that fails, |
michael@0 | 247 | // we make up a descriptor, using some assumptions about what kinds of things |
michael@0 | 248 | // tend to live on the prototypes of Xray-wrapped objects. |
michael@0 | 249 | else { |
michael@0 | 250 | desc = Object.getOwnPropertyDescriptor(obj, name); |
michael@0 | 251 | if (!desc) { |
michael@0 | 252 | var getter = Object.prototype.__lookupGetter__.call(obj, name); |
michael@0 | 253 | var setter = Object.prototype.__lookupSetter__.call(obj, name); |
michael@0 | 254 | if (getter || setter) |
michael@0 | 255 | desc = {get: getter, set: setter, configurable: true, enumerable: true}; |
michael@0 | 256 | else if (name in obj) |
michael@0 | 257 | desc = {value: obj[name], writable: false, configurable: true, enumerable: true}; |
michael@0 | 258 | } |
michael@0 | 259 | } |
michael@0 | 260 | |
michael@0 | 261 | // Bail if we've got nothing. |
michael@0 | 262 | if (typeof desc === 'undefined') |
michael@0 | 263 | return undefined; |
michael@0 | 264 | |
michael@0 | 265 | // When accessors are implemented as JSPropertyOps rather than JSNatives (ie, |
michael@0 | 266 | // QuickStubs), the js engine does the wrong thing and treats it as a value |
michael@0 | 267 | // descriptor rather than an accessor descriptor. Jorendorff suggested this |
michael@0 | 268 | // little hack to work around it. See bug 520882. |
michael@0 | 269 | if (desc && 'value' in desc && desc.value === undefined) |
michael@0 | 270 | desc.value = obj[name]; |
michael@0 | 271 | |
michael@0 | 272 | // A trapping proxy's properties must always be configurable, but sometimes |
michael@0 | 273 | // this we get non-configurable properties from Object.getOwnPropertyDescriptor(). |
michael@0 | 274 | // Tell a white lie. |
michael@0 | 275 | desc.configurable = true; |
michael@0 | 276 | |
michael@0 | 277 | // Transitively maintain the wrapper membrane. |
michael@0 | 278 | function wrapIfExists(key) { if (key in desc) desc[key] = wrapPrivileged(desc[key]); }; |
michael@0 | 279 | wrapIfExists('value'); |
michael@0 | 280 | wrapIfExists('get'); |
michael@0 | 281 | wrapIfExists('set'); |
michael@0 | 282 | |
michael@0 | 283 | return desc; |
michael@0 | 284 | }; |
michael@0 | 285 | |
michael@0 | 286 | SpecialPowersHandler.prototype.getOwnPropertyDescriptor = function(name) { |
michael@0 | 287 | return this.doGetPropertyDescriptor(name, true); |
michael@0 | 288 | }; |
michael@0 | 289 | |
michael@0 | 290 | SpecialPowersHandler.prototype.getPropertyDescriptor = function(name) { |
michael@0 | 291 | return this.doGetPropertyDescriptor(name, false); |
michael@0 | 292 | }; |
michael@0 | 293 | |
michael@0 | 294 | function doGetOwnPropertyNames(obj, props) { |
michael@0 | 295 | |
michael@0 | 296 | // Insert our special API. It's not enumerable, but getPropertyNames() |
michael@0 | 297 | // includes non-enumerable properties. |
michael@0 | 298 | var specialAPI = 'SpecialPowers_wrappedObject'; |
michael@0 | 299 | if (props.indexOf(specialAPI) == -1) |
michael@0 | 300 | props.push(specialAPI); |
michael@0 | 301 | |
michael@0 | 302 | // Do the normal thing. |
michael@0 | 303 | var flt = function(a) { return props.indexOf(a) == -1; }; |
michael@0 | 304 | props = props.concat(Object.getOwnPropertyNames(obj).filter(flt)); |
michael@0 | 305 | |
michael@0 | 306 | // If we've got an Xray wrapper, include the expandos as well. |
michael@0 | 307 | if ('wrappedJSObject' in obj) |
michael@0 | 308 | props = props.concat(Object.getOwnPropertyNames(obj.wrappedJSObject) |
michael@0 | 309 | .filter(flt)); |
michael@0 | 310 | |
michael@0 | 311 | return props; |
michael@0 | 312 | } |
michael@0 | 313 | |
michael@0 | 314 | SpecialPowersHandler.prototype.getOwnPropertyNames = function() { |
michael@0 | 315 | return doGetOwnPropertyNames(this.wrappedObject, []); |
michael@0 | 316 | }; |
michael@0 | 317 | |
michael@0 | 318 | SpecialPowersHandler.prototype.getPropertyNames = function() { |
michael@0 | 319 | |
michael@0 | 320 | // Manually walk the prototype chain, making sure to add only property names |
michael@0 | 321 | // that haven't been overridden. |
michael@0 | 322 | // |
michael@0 | 323 | // There's some trickiness here with Xray wrappers. Xray wrappers don't have |
michael@0 | 324 | // a prototype, so we need to unwrap them if we want to get all of the names |
michael@0 | 325 | // with Object.getOwnPropertyNames(). But we don't really want to unwrap the |
michael@0 | 326 | // base object, because that will include expandos that are inaccessible via |
michael@0 | 327 | // our implementation of get{,Own}PropertyDescriptor(). So we unwrap just |
michael@0 | 328 | // before accessing the prototype. This ensures that we get Xray vision on |
michael@0 | 329 | // the base object, and no Xray vision for the rest of the way up. |
michael@0 | 330 | var obj = this.wrappedObject; |
michael@0 | 331 | var props = []; |
michael@0 | 332 | while (obj) { |
michael@0 | 333 | props = doGetOwnPropertyNames(obj, props); |
michael@0 | 334 | obj = Object.getPrototypeOf(XPCNativeWrapper.unwrap(obj)); |
michael@0 | 335 | } |
michael@0 | 336 | return props; |
michael@0 | 337 | }; |
michael@0 | 338 | |
michael@0 | 339 | SpecialPowersHandler.prototype.defineProperty = function(name, desc) { |
michael@0 | 340 | return Object.defineProperty(this.wrappedObject, name, desc); |
michael@0 | 341 | }; |
michael@0 | 342 | |
michael@0 | 343 | SpecialPowersHandler.prototype.delete = function(name) { |
michael@0 | 344 | return delete this.wrappedObject[name]; |
michael@0 | 345 | }; |
michael@0 | 346 | |
michael@0 | 347 | SpecialPowersHandler.prototype.fix = function() { return undefined; /* Throws a TypeError. */ }; |
michael@0 | 348 | |
michael@0 | 349 | // Per the ES5 spec this is a derived trap, but it's fundamental in spidermonkey |
michael@0 | 350 | // for some reason. See bug 665198. |
michael@0 | 351 | SpecialPowersHandler.prototype.enumerate = function() { |
michael@0 | 352 | var t = this; |
michael@0 | 353 | var filt = function(name) { return t.getPropertyDescriptor(name).enumerable; }; |
michael@0 | 354 | return this.getPropertyNames().filter(filt); |
michael@0 | 355 | }; |
michael@0 | 356 | |
michael@0 | 357 | // SPConsoleListener reflects nsIConsoleMessage objects into JS in a |
michael@0 | 358 | // tidy, XPCOM-hiding way. Messages that are nsIScriptError objects |
michael@0 | 359 | // have their properties exposed in detail. It also auto-unregisters |
michael@0 | 360 | // itself when it receives a "sentinel" message. |
michael@0 | 361 | function SPConsoleListener(callback) { |
michael@0 | 362 | this.callback = callback; |
michael@0 | 363 | } |
michael@0 | 364 | |
michael@0 | 365 | SPConsoleListener.prototype = { |
michael@0 | 366 | observe: function(msg) { |
michael@0 | 367 | let m = { message: msg.message, |
michael@0 | 368 | errorMessage: null, |
michael@0 | 369 | sourceName: null, |
michael@0 | 370 | sourceLine: null, |
michael@0 | 371 | lineNumber: null, |
michael@0 | 372 | columnNumber: null, |
michael@0 | 373 | category: null, |
michael@0 | 374 | windowID: null, |
michael@0 | 375 | isScriptError: false, |
michael@0 | 376 | isWarning: false, |
michael@0 | 377 | isException: false, |
michael@0 | 378 | isStrict: false }; |
michael@0 | 379 | if (msg instanceof Ci.nsIScriptError) { |
michael@0 | 380 | m.errorMessage = msg.errorMessage; |
michael@0 | 381 | m.sourceName = msg.sourceName; |
michael@0 | 382 | m.sourceLine = msg.sourceLine; |
michael@0 | 383 | m.lineNumber = msg.lineNumber; |
michael@0 | 384 | m.columnNumber = msg.columnNumber; |
michael@0 | 385 | m.category = msg.category; |
michael@0 | 386 | m.windowID = msg.outerWindowID; |
michael@0 | 387 | m.isScriptError = true; |
michael@0 | 388 | m.isWarning = ((msg.flags & Ci.nsIScriptError.warningFlag) === 1); |
michael@0 | 389 | m.isException = ((msg.flags & Ci.nsIScriptError.exceptionFlag) === 1); |
michael@0 | 390 | m.isStrict = ((msg.flags & Ci.nsIScriptError.strictFlag) === 1); |
michael@0 | 391 | } |
michael@0 | 392 | |
michael@0 | 393 | // expose all props of 'm' as read-only |
michael@0 | 394 | let expose = {}; |
michael@0 | 395 | for (let prop in m) |
michael@0 | 396 | expose[prop] = 'r'; |
michael@0 | 397 | m.__exposedProps__ = expose; |
michael@0 | 398 | Object.freeze(m); |
michael@0 | 399 | |
michael@0 | 400 | this.callback.call(undefined, m); |
michael@0 | 401 | |
michael@0 | 402 | if (!m.isScriptError && m.message === "SENTINEL") |
michael@0 | 403 | Services.console.unregisterListener(this); |
michael@0 | 404 | }, |
michael@0 | 405 | |
michael@0 | 406 | QueryInterface: XPCOMUtils.generateQI([Ci.nsIConsoleListener]) |
michael@0 | 407 | }; |
michael@0 | 408 | |
michael@0 | 409 | function wrapCallback(cb) { |
michael@0 | 410 | return function SpecialPowersCallbackWrapper() { |
michael@0 | 411 | args = Array.prototype.map.call(arguments, wrapIfUnwrapped); |
michael@0 | 412 | return cb.apply(this, args); |
michael@0 | 413 | } |
michael@0 | 414 | } |
michael@0 | 415 | |
michael@0 | 416 | function wrapCallbackObject(obj) { |
michael@0 | 417 | wrapper = { __exposedProps__: ExposedPropsWaiver }; |
michael@0 | 418 | for (var i in obj) { |
michael@0 | 419 | if (typeof obj[i] == 'function') |
michael@0 | 420 | wrapper[i] = wrapCallback(obj[i]); |
michael@0 | 421 | else |
michael@0 | 422 | wrapper[i] = obj[i]; |
michael@0 | 423 | } |
michael@0 | 424 | return wrapper; |
michael@0 | 425 | } |
michael@0 | 426 | |
michael@0 | 427 | SpecialPowersAPI.prototype = { |
michael@0 | 428 | |
michael@0 | 429 | /* |
michael@0 | 430 | * Privileged object wrapping API |
michael@0 | 431 | * |
michael@0 | 432 | * Usage: |
michael@0 | 433 | * var wrapper = SpecialPowers.wrap(obj); |
michael@0 | 434 | * wrapper.privilegedMethod(); wrapper.privilegedProperty; |
michael@0 | 435 | * obj === SpecialPowers.unwrap(wrapper); |
michael@0 | 436 | * |
michael@0 | 437 | * These functions provide transparent access to privileged objects using |
michael@0 | 438 | * various pieces of deep SpiderMagic. Conceptually, a wrapper is just an |
michael@0 | 439 | * object containing a reference to the underlying object, where all method |
michael@0 | 440 | * calls and property accesses are transparently performed with the System |
michael@0 | 441 | * Principal. Moreover, objects obtained from the wrapper (including properties |
michael@0 | 442 | * and method return values) are wrapped automatically. Thus, after a single |
michael@0 | 443 | * call to SpecialPowers.wrap(), the wrapper layer is transitively maintained. |
michael@0 | 444 | * |
michael@0 | 445 | * Known Issues: |
michael@0 | 446 | * |
michael@0 | 447 | * - The wrapping function does not preserve identity, so |
michael@0 | 448 | * SpecialPowers.wrap(foo) !== SpecialPowers.wrap(foo). See bug 718543. |
michael@0 | 449 | * |
michael@0 | 450 | * - The wrapper cannot see expando properties on unprivileged DOM objects. |
michael@0 | 451 | * That is to say, the wrapper uses Xray delegation. |
michael@0 | 452 | * |
michael@0 | 453 | * - The wrapper sometimes guesses certain ES5 attributes for returned |
michael@0 | 454 | * properties. This is explained in a comment in the wrapper code above, |
michael@0 | 455 | * and shouldn't be a problem. |
michael@0 | 456 | */ |
michael@0 | 457 | wrap: wrapIfUnwrapped, |
michael@0 | 458 | unwrap: unwrapIfWrapped, |
michael@0 | 459 | isWrapper: isWrapper, |
michael@0 | 460 | |
michael@0 | 461 | /* |
michael@0 | 462 | * When content needs to pass a callback or a callback object to an API |
michael@0 | 463 | * accessed over SpecialPowers, that API may sometimes receive arguments for |
michael@0 | 464 | * whom it is forbidden to create a wrapper in content scopes. As such, we |
michael@0 | 465 | * need a layer to wrap the values in SpecialPowers wrappers before they ever |
michael@0 | 466 | * reach content. |
michael@0 | 467 | */ |
michael@0 | 468 | wrapCallback: wrapCallback, |
michael@0 | 469 | wrapCallbackObject: wrapCallbackObject, |
michael@0 | 470 | |
michael@0 | 471 | /* |
michael@0 | 472 | * Create blank privileged objects to use as out-params for privileged functions. |
michael@0 | 473 | */ |
michael@0 | 474 | createBlankObject: function () { |
michael@0 | 475 | var obj = new Object; |
michael@0 | 476 | obj.__exposedProps__ = ExposedPropsWaiver; |
michael@0 | 477 | return obj; |
michael@0 | 478 | }, |
michael@0 | 479 | |
michael@0 | 480 | /* |
michael@0 | 481 | * Because SpecialPowers wrappers don't preserve identity, comparing with == |
michael@0 | 482 | * can be hazardous. Sometimes we can just unwrap to compare, but sometimes |
michael@0 | 483 | * wrapping the underlying object into a content scope is forbidden. This |
michael@0 | 484 | * function strips any wrappers if they exist and compare the underlying |
michael@0 | 485 | * values. |
michael@0 | 486 | */ |
michael@0 | 487 | compare: function(a, b) { |
michael@0 | 488 | return unwrapIfWrapped(a) === unwrapIfWrapped(b); |
michael@0 | 489 | }, |
michael@0 | 490 | |
michael@0 | 491 | get MockFilePicker() { |
michael@0 | 492 | return MockFilePicker |
michael@0 | 493 | }, |
michael@0 | 494 | |
michael@0 | 495 | get MockColorPicker() { |
michael@0 | 496 | return MockColorPicker |
michael@0 | 497 | }, |
michael@0 | 498 | |
michael@0 | 499 | get MockPermissionPrompt() { |
michael@0 | 500 | return MockPermissionPrompt |
michael@0 | 501 | }, |
michael@0 | 502 | |
michael@0 | 503 | loadChromeScript: function (url) { |
michael@0 | 504 | // Create a unique id for this chrome script |
michael@0 | 505 | let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"] |
michael@0 | 506 | .getService(Ci.nsIUUIDGenerator); |
michael@0 | 507 | let id = uuidGenerator.generateUUID().toString(); |
michael@0 | 508 | |
michael@0 | 509 | // Tells chrome code to evaluate this chrome script |
michael@0 | 510 | this._sendSyncMessage("SPLoadChromeScript", |
michael@0 | 511 | { url: url, id: id }); |
michael@0 | 512 | |
michael@0 | 513 | // Returns a MessageManager like API in order to be |
michael@0 | 514 | // able to communicate with this chrome script |
michael@0 | 515 | let listeners = []; |
michael@0 | 516 | let chromeScript = { |
michael@0 | 517 | addMessageListener: (name, listener) => { |
michael@0 | 518 | listeners.push({ name: name, listener: listener }); |
michael@0 | 519 | }, |
michael@0 | 520 | |
michael@0 | 521 | removeMessageListener: (name, listener) => { |
michael@0 | 522 | listeners = listeners.filter( |
michael@0 | 523 | o => (o.name != name || o.listener != listener) |
michael@0 | 524 | ); |
michael@0 | 525 | }, |
michael@0 | 526 | |
michael@0 | 527 | sendAsyncMessage: (name, message) => { |
michael@0 | 528 | this._sendSyncMessage("SPChromeScriptMessage", |
michael@0 | 529 | { id: id, name: name, message: message }); |
michael@0 | 530 | }, |
michael@0 | 531 | |
michael@0 | 532 | destroy: () => { |
michael@0 | 533 | listeners = []; |
michael@0 | 534 | this._removeMessageListener("SPChromeScriptMessage", chromeScript); |
michael@0 | 535 | this._removeMessageListener("SPChromeScriptAssert", chromeScript); |
michael@0 | 536 | }, |
michael@0 | 537 | |
michael@0 | 538 | receiveMessage: (aMessage) => { |
michael@0 | 539 | let messageId = aMessage.json.id; |
michael@0 | 540 | let name = aMessage.json.name; |
michael@0 | 541 | let message = aMessage.json.message; |
michael@0 | 542 | // Ignore message from other chrome script |
michael@0 | 543 | if (messageId != id) |
michael@0 | 544 | return; |
michael@0 | 545 | |
michael@0 | 546 | if (aMessage.name == "SPChromeScriptMessage") { |
michael@0 | 547 | listeners.filter(o => (o.name == name)) |
michael@0 | 548 | .forEach(o => o.listener(this.wrap(message))); |
michael@0 | 549 | } else if (aMessage.name == "SPChromeScriptAssert") { |
michael@0 | 550 | assert(aMessage.json); |
michael@0 | 551 | } |
michael@0 | 552 | } |
michael@0 | 553 | }; |
michael@0 | 554 | this._addMessageListener("SPChromeScriptMessage", chromeScript); |
michael@0 | 555 | this._addMessageListener("SPChromeScriptAssert", chromeScript); |
michael@0 | 556 | |
michael@0 | 557 | let assert = json => { |
michael@0 | 558 | // An assertion has been done in a mochitest chrome script |
michael@0 | 559 | let {url, err, message, stack} = json; |
michael@0 | 560 | |
michael@0 | 561 | // Try to fetch a test runner from the mochitest |
michael@0 | 562 | // in order to properly log these assertions and notify |
michael@0 | 563 | // all usefull log observers |
michael@0 | 564 | let window = this.window.get(); |
michael@0 | 565 | let parentRunner, repr = function (o) o; |
michael@0 | 566 | if (window) { |
michael@0 | 567 | window = window.wrappedJSObject; |
michael@0 | 568 | parentRunner = window.TestRunner; |
michael@0 | 569 | if (window.repr) { |
michael@0 | 570 | repr = window.repr; |
michael@0 | 571 | } |
michael@0 | 572 | } |
michael@0 | 573 | |
michael@0 | 574 | // Craft a mochitest-like report string |
michael@0 | 575 | var resultString = err ? "TEST-UNEXPECTED-FAIL" : "TEST-PASS"; |
michael@0 | 576 | var diagnostic = |
michael@0 | 577 | message ? message : |
michael@0 | 578 | ("assertion @ " + stack.filename + ":" + stack.lineNumber); |
michael@0 | 579 | if (err) { |
michael@0 | 580 | diagnostic += |
michael@0 | 581 | " - got " + repr(err.actual) + |
michael@0 | 582 | ", expected " + repr(err.expected) + |
michael@0 | 583 | " (operator " + err.operator + ")"; |
michael@0 | 584 | } |
michael@0 | 585 | var msg = [resultString, url, diagnostic].join(" | "); |
michael@0 | 586 | if (parentRunner) { |
michael@0 | 587 | if (err) { |
michael@0 | 588 | parentRunner.addFailedTest(url); |
michael@0 | 589 | parentRunner.error(msg); |
michael@0 | 590 | } else { |
michael@0 | 591 | parentRunner.log(msg); |
michael@0 | 592 | } |
michael@0 | 593 | } else { |
michael@0 | 594 | // When we are running only a single mochitest, there is no test runner |
michael@0 | 595 | dump(msg + "\n"); |
michael@0 | 596 | } |
michael@0 | 597 | }; |
michael@0 | 598 | |
michael@0 | 599 | return this.wrap(chromeScript); |
michael@0 | 600 | }, |
michael@0 | 601 | |
michael@0 | 602 | get Services() { |
michael@0 | 603 | return wrapPrivileged(Services); |
michael@0 | 604 | }, |
michael@0 | 605 | |
michael@0 | 606 | /* |
michael@0 | 607 | * In general, any Components object created for unprivileged scopes is |
michael@0 | 608 | * neutered (it implements nsIXPCComponentsBase, but not nsIXPCComponents). |
michael@0 | 609 | * We override this in certain legacy automation configurations (see the |
michael@0 | 610 | * implementation of getRawComponents() above), but don't want to support |
michael@0 | 611 | * it in cases where it isn't already required. |
michael@0 | 612 | * |
michael@0 | 613 | * In scopes with neutered Components, we don't have a natural referent for |
michael@0 | 614 | * things like SpecialPowers.Cc. So in those cases, we fall back to the |
michael@0 | 615 | * Components object from the SpecialPowers scope. This doesn't quite behave |
michael@0 | 616 | * the same way (in particular, SpecialPowers.Cc[foo].createInstance() will |
michael@0 | 617 | * create an instance in the SpecialPowers scope), but SpecialPowers wrapping |
michael@0 | 618 | * is already a YMMV / Whatever-It-Takes-To-Get-TBPL-Green sort of thing. |
michael@0 | 619 | * |
michael@0 | 620 | * It probably wouldn't be too much work to just make SpecialPowers.Components |
michael@0 | 621 | * unconditionally point to the Components object in the SpecialPowers scope. |
michael@0 | 622 | * Try will tell what needs to be fixed up. |
michael@0 | 623 | */ |
michael@0 | 624 | getFullComponents: function() { |
michael@0 | 625 | return typeof this.Components.classes == 'object' ? this.Components |
michael@0 | 626 | : Components; |
michael@0 | 627 | }, |
michael@0 | 628 | |
michael@0 | 629 | /* |
michael@0 | 630 | * Convenient shortcuts to the standard Components abbreviations. Note that |
michael@0 | 631 | * we don't SpecialPowers-wrap Components.interfaces, because it's available |
michael@0 | 632 | * to untrusted content, and wrapping it confuses QI and identity checks. |
michael@0 | 633 | */ |
michael@0 | 634 | get Cc() { return wrapPrivileged(this.getFullComponents()).classes; }, |
michael@0 | 635 | get Ci() { return this.Components.interfaces; }, |
michael@0 | 636 | get Cu() { return wrapPrivileged(this.getFullComponents()).utils; }, |
michael@0 | 637 | get Cr() { return wrapPrivileged(this.Components).results; }, |
michael@0 | 638 | |
michael@0 | 639 | /* |
michael@0 | 640 | * SpecialPowers.getRawComponents() allows content to get a reference to a |
michael@0 | 641 | * naked (and, in certain automation configurations, privileged) Components |
michael@0 | 642 | * object for its scope. |
michael@0 | 643 | * |
michael@0 | 644 | * SpecialPowers.getRawComponents(window) is defined as the global property |
michael@0 | 645 | * window.SpecialPowers.Components for convenience. |
michael@0 | 646 | */ |
michael@0 | 647 | getRawComponents: getRawComponents, |
michael@0 | 648 | |
michael@0 | 649 | getDOMWindowUtils: function(aWindow) { |
michael@0 | 650 | if (aWindow == this.window.get() && this.DOMWindowUtils != null) |
michael@0 | 651 | return this.DOMWindowUtils; |
michael@0 | 652 | |
michael@0 | 653 | return bindDOMWindowUtils(aWindow); |
michael@0 | 654 | }, |
michael@0 | 655 | |
michael@0 | 656 | removeExpectedCrashDumpFiles: function(aExpectingProcessCrash) { |
michael@0 | 657 | var success = true; |
michael@0 | 658 | if (aExpectingProcessCrash) { |
michael@0 | 659 | var message = { |
michael@0 | 660 | op: "delete-crash-dump-files", |
michael@0 | 661 | filenames: this._encounteredCrashDumpFiles |
michael@0 | 662 | }; |
michael@0 | 663 | if (!this._sendSyncMessage("SPProcessCrashService", message)[0]) { |
michael@0 | 664 | success = false; |
michael@0 | 665 | } |
michael@0 | 666 | } |
michael@0 | 667 | this._encounteredCrashDumpFiles.length = 0; |
michael@0 | 668 | return success; |
michael@0 | 669 | }, |
michael@0 | 670 | |
michael@0 | 671 | findUnexpectedCrashDumpFiles: function() { |
michael@0 | 672 | var self = this; |
michael@0 | 673 | var message = { |
michael@0 | 674 | op: "find-crash-dump-files", |
michael@0 | 675 | crashDumpFilesToIgnore: this._unexpectedCrashDumpFiles |
michael@0 | 676 | }; |
michael@0 | 677 | var crashDumpFiles = this._sendSyncMessage("SPProcessCrashService", message)[0]; |
michael@0 | 678 | crashDumpFiles.forEach(function(aFilename) { |
michael@0 | 679 | self._unexpectedCrashDumpFiles[aFilename] = true; |
michael@0 | 680 | }); |
michael@0 | 681 | return crashDumpFiles; |
michael@0 | 682 | }, |
michael@0 | 683 | |
michael@0 | 684 | _delayCallbackTwice: function(callback) { |
michael@0 | 685 | function delayedCallback() { |
michael@0 | 686 | function delayAgain() { |
michael@0 | 687 | content.window.setTimeout(callback, 0); |
michael@0 | 688 | } |
michael@0 | 689 | content.window.setTimeout(delayAgain, 0); |
michael@0 | 690 | } |
michael@0 | 691 | return delayedCallback; |
michael@0 | 692 | }, |
michael@0 | 693 | |
michael@0 | 694 | /* apply permissions to the system and when the test case is finished (SimpleTest.finish()) |
michael@0 | 695 | we will revert the permission back to the original. |
michael@0 | 696 | |
michael@0 | 697 | inPermissions is an array of objects where each object has a type, action, context, ex: |
michael@0 | 698 | [{'type': 'SystemXHR', 'allow': 1, 'context': document}, |
michael@0 | 699 | {'type': 'SystemXHR', 'allow': Ci.nsIPermissionManager.PROMPT_ACTION, 'context': document}] |
michael@0 | 700 | |
michael@0 | 701 | Allow can be a boolean value of true/false or ALLOW_ACTION/DENY_ACTION/PROMPT_ACTION/UNKNOWN_ACTION |
michael@0 | 702 | */ |
michael@0 | 703 | pushPermissions: function(inPermissions, callback) { |
michael@0 | 704 | var pendingPermissions = []; |
michael@0 | 705 | var cleanupPermissions = []; |
michael@0 | 706 | |
michael@0 | 707 | for (var p in inPermissions) { |
michael@0 | 708 | var permission = inPermissions[p]; |
michael@0 | 709 | var originalValue = Ci.nsIPermissionManager.UNKNOWN_ACTION; |
michael@0 | 710 | if (this.testPermission(permission.type, Ci.nsIPermissionManager.ALLOW_ACTION, permission.context)) { |
michael@0 | 711 | originalValue = Ci.nsIPermissionManager.ALLOW_ACTION; |
michael@0 | 712 | } else if (this.testPermission(permission.type, Ci.nsIPermissionManager.DENY_ACTION, permission.context)) { |
michael@0 | 713 | originalValue = Ci.nsIPermissionManager.DENY_ACTION; |
michael@0 | 714 | } else if (this.testPermission(permission.type, Ci.nsIPermissionManager.PROMPT_ACTION, permission.context)) { |
michael@0 | 715 | originalValue = Ci.nsIPermissionManager.PROMPT_ACTION; |
michael@0 | 716 | } else if (this.testPermission(permission.type, Ci.nsICookiePermission.ACCESS_SESSION, permission.context)) { |
michael@0 | 717 | originalValue = Ci.nsICookiePermission.ACCESS_SESSION; |
michael@0 | 718 | } else if (this.testPermission(permission.type, Ci.nsICookiePermission.ACCESS_ALLOW_FIRST_PARTY_ONLY, permission.context)) { |
michael@0 | 719 | originalValue = Ci.nsICookiePermission.ACCESS_ALLOW_FIRST_PARTY_ONLY; |
michael@0 | 720 | } else if (this.testPermission(permission.type, Ci.nsICookiePermission.ACCESS_LIMIT_THIRD_PARTY, permission.context)) { |
michael@0 | 721 | originalValue = Ci.nsICookiePermission.ACCESS_LIMIT_THIRD_PARTY; |
michael@0 | 722 | } |
michael@0 | 723 | |
michael@0 | 724 | let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(permission.context); |
michael@0 | 725 | |
michael@0 | 726 | let perm; |
michael@0 | 727 | if (typeof permission.allow !== 'boolean') { |
michael@0 | 728 | perm = permission.allow; |
michael@0 | 729 | } else { |
michael@0 | 730 | perm = permission.allow ? Ci.nsIPermissionManager.ALLOW_ACTION |
michael@0 | 731 | : Ci.nsIPermissionManager.DENY_ACTION; |
michael@0 | 732 | } |
michael@0 | 733 | |
michael@0 | 734 | if (permission.remove == true) |
michael@0 | 735 | perm = Ci.nsIPermissionManager.UNKNOWN_ACTION; |
michael@0 | 736 | |
michael@0 | 737 | if (originalValue == perm) { |
michael@0 | 738 | continue; |
michael@0 | 739 | } |
michael@0 | 740 | |
michael@0 | 741 | var todo = {'op': 'add', 'type': permission.type, 'permission': perm, 'value': perm, 'url': url, 'appId': appId, 'isInBrowserElement': isInBrowserElement}; |
michael@0 | 742 | if (permission.remove == true) |
michael@0 | 743 | todo.op = 'remove'; |
michael@0 | 744 | |
michael@0 | 745 | pendingPermissions.push(todo); |
michael@0 | 746 | |
michael@0 | 747 | /* Push original permissions value or clear into cleanup array */ |
michael@0 | 748 | var cleanupTodo = {'op': 'add', 'type': permission.type, 'permission': perm, 'value': perm, 'url': url, 'appId': appId, 'isInBrowserElement': isInBrowserElement}; |
michael@0 | 749 | if (originalValue == Ci.nsIPermissionManager.UNKNOWN_ACTION) { |
michael@0 | 750 | cleanupTodo.op = 'remove'; |
michael@0 | 751 | } else { |
michael@0 | 752 | cleanupTodo.value = originalValue; |
michael@0 | 753 | cleanupTodo.permission = originalValue; |
michael@0 | 754 | } |
michael@0 | 755 | cleanupPermissions.push(cleanupTodo); |
michael@0 | 756 | } |
michael@0 | 757 | |
michael@0 | 758 | if (pendingPermissions.length > 0) { |
michael@0 | 759 | // The callback needs to be delayed twice. One delay is because the pref |
michael@0 | 760 | // service doesn't guarantee the order it calls its observers in, so it |
michael@0 | 761 | // may notify the observer holding the callback before the other |
michael@0 | 762 | // observers have been notified and given a chance to make the changes |
michael@0 | 763 | // that the callback checks for. The second delay is because pref |
michael@0 | 764 | // observers often defer making their changes by posting an event to the |
michael@0 | 765 | // event loop. |
michael@0 | 766 | this._permissionsUndoStack.push(cleanupPermissions); |
michael@0 | 767 | this._pendingPermissions.push([pendingPermissions, |
michael@0 | 768 | this._delayCallbackTwice(callback)]); |
michael@0 | 769 | this._applyPermissions(); |
michael@0 | 770 | } else { |
michael@0 | 771 | content.window.setTimeout(callback, 0); |
michael@0 | 772 | } |
michael@0 | 773 | }, |
michael@0 | 774 | |
michael@0 | 775 | popPermissions: function(callback) { |
michael@0 | 776 | if (this._permissionsUndoStack.length > 0) { |
michael@0 | 777 | // See pushPermissions comment regarding delay. |
michael@0 | 778 | let cb = callback ? this._delayCallbackTwice(callback) : null; |
michael@0 | 779 | /* Each pop from the stack will yield an object {op/type/permission/value/url/appid/isInBrowserElement} or null */ |
michael@0 | 780 | this._pendingPermissions.push([this._permissionsUndoStack.pop(), cb]); |
michael@0 | 781 | this._applyPermissions(); |
michael@0 | 782 | } else { |
michael@0 | 783 | content.window.setTimeout(callback, 0); |
michael@0 | 784 | } |
michael@0 | 785 | }, |
michael@0 | 786 | |
michael@0 | 787 | flushPermissions: function(callback) { |
michael@0 | 788 | while (this._permissionsUndoStack.length > 1) |
michael@0 | 789 | this.popPermissions(null); |
michael@0 | 790 | |
michael@0 | 791 | this.popPermissions(callback); |
michael@0 | 792 | }, |
michael@0 | 793 | |
michael@0 | 794 | |
michael@0 | 795 | _permissionObserver: { |
michael@0 | 796 | _lastPermission: {}, |
michael@0 | 797 | _callBack: null, |
michael@0 | 798 | _nextCallback: null, |
michael@0 | 799 | |
michael@0 | 800 | observe: function (aSubject, aTopic, aData) |
michael@0 | 801 | { |
michael@0 | 802 | if (aTopic == "perm-changed") { |
michael@0 | 803 | var permission = aSubject.QueryInterface(Ci.nsIPermission); |
michael@0 | 804 | if (permission.type == this._lastPermission.type) { |
michael@0 | 805 | var os = Components.classes["@mozilla.org/observer-service;1"] |
michael@0 | 806 | .getService(Components.interfaces.nsIObserverService); |
michael@0 | 807 | os.removeObserver(this, "perm-changed"); |
michael@0 | 808 | content.window.setTimeout(this._callback, 0); |
michael@0 | 809 | content.window.setTimeout(this._nextCallback, 0); |
michael@0 | 810 | } |
michael@0 | 811 | } |
michael@0 | 812 | } |
michael@0 | 813 | }, |
michael@0 | 814 | |
michael@0 | 815 | /* |
michael@0 | 816 | Iterate through one atomic set of permissions actions and perform allow/deny as appropriate. |
michael@0 | 817 | All actions performed must modify the relevant permission. |
michael@0 | 818 | */ |
michael@0 | 819 | _applyPermissions: function() { |
michael@0 | 820 | if (this._applyingPermissions || this._pendingPermissions.length <= 0) { |
michael@0 | 821 | return; |
michael@0 | 822 | } |
michael@0 | 823 | |
michael@0 | 824 | /* Set lock and get prefs from the _pendingPrefs queue */ |
michael@0 | 825 | this._applyingPermissions = true; |
michael@0 | 826 | var transaction = this._pendingPermissions.shift(); |
michael@0 | 827 | var pendingActions = transaction[0]; |
michael@0 | 828 | var callback = transaction[1]; |
michael@0 | 829 | var lastPermission = pendingActions[pendingActions.length-1]; |
michael@0 | 830 | |
michael@0 | 831 | var self = this; |
michael@0 | 832 | var os = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService); |
michael@0 | 833 | this._permissionObserver._lastPermission = lastPermission; |
michael@0 | 834 | this._permissionObserver._callback = callback; |
michael@0 | 835 | this._permissionObserver._nextCallback = function () { |
michael@0 | 836 | self._applyingPermissions = false; |
michael@0 | 837 | // Now apply any permissions that may have been queued while we were applying |
michael@0 | 838 | self._applyPermissions(); |
michael@0 | 839 | } |
michael@0 | 840 | |
michael@0 | 841 | os.addObserver(this._permissionObserver, "perm-changed", false); |
michael@0 | 842 | |
michael@0 | 843 | for (var idx in pendingActions) { |
michael@0 | 844 | var perm = pendingActions[idx]; |
michael@0 | 845 | this._sendSyncMessage('SPPermissionManager', perm)[0]; |
michael@0 | 846 | } |
michael@0 | 847 | }, |
michael@0 | 848 | |
michael@0 | 849 | /* |
michael@0 | 850 | * Take in a list of pref changes to make, and invoke |callback| once those |
michael@0 | 851 | * changes have taken effect. When the test finishes, these changes are |
michael@0 | 852 | * reverted. |
michael@0 | 853 | * |
michael@0 | 854 | * |inPrefs| must be an object with up to two properties: "set" and "clear". |
michael@0 | 855 | * pushPrefEnv will set prefs as indicated in |inPrefs.set| and will unset |
michael@0 | 856 | * the prefs indicated in |inPrefs.clear|. |
michael@0 | 857 | * |
michael@0 | 858 | * For example, you might pass |inPrefs| as: |
michael@0 | 859 | * |
michael@0 | 860 | * inPrefs = {'set': [['foo.bar', 2], ['magic.pref', 'baz']], |
michael@0 | 861 | * 'clear': [['clear.this'], ['also.this']] }; |
michael@0 | 862 | * |
michael@0 | 863 | * Notice that |set| and |clear| are both an array of arrays. In |set|, each |
michael@0 | 864 | * of the inner arrays must have the form [pref_name, value] or [pref_name, |
michael@0 | 865 | * value, iid]. (The latter form is used for prefs with "complex" values.) |
michael@0 | 866 | * |
michael@0 | 867 | * In |clear|, each inner array should have the form [pref_name]. |
michael@0 | 868 | * |
michael@0 | 869 | * If you set the same pref more than once (or both set and clear a pref), |
michael@0 | 870 | * the behavior of this method is undefined. |
michael@0 | 871 | * |
michael@0 | 872 | * (Implementation note: _prefEnvUndoStack is a stack of values to revert to, |
michael@0 | 873 | * not values which have been set!) |
michael@0 | 874 | * |
michael@0 | 875 | * TODO: complex values for original cleanup? |
michael@0 | 876 | * |
michael@0 | 877 | */ |
michael@0 | 878 | pushPrefEnv: function(inPrefs, callback) { |
michael@0 | 879 | var prefs = Components.classes["@mozilla.org/preferences-service;1"]. |
michael@0 | 880 | getService(Components.interfaces.nsIPrefBranch); |
michael@0 | 881 | |
michael@0 | 882 | var pref_string = []; |
michael@0 | 883 | pref_string[prefs.PREF_INT] = "INT"; |
michael@0 | 884 | pref_string[prefs.PREF_BOOL] = "BOOL"; |
michael@0 | 885 | pref_string[prefs.PREF_STRING] = "CHAR"; |
michael@0 | 886 | |
michael@0 | 887 | var pendingActions = []; |
michael@0 | 888 | var cleanupActions = []; |
michael@0 | 889 | |
michael@0 | 890 | for (var action in inPrefs) { /* set|clear */ |
michael@0 | 891 | for (var idx in inPrefs[action]) { |
michael@0 | 892 | var aPref = inPrefs[action][idx]; |
michael@0 | 893 | var prefName = aPref[0]; |
michael@0 | 894 | var prefValue = null; |
michael@0 | 895 | var prefIid = null; |
michael@0 | 896 | var prefType = prefs.PREF_INVALID; |
michael@0 | 897 | var originalValue = null; |
michael@0 | 898 | |
michael@0 | 899 | if (aPref.length == 3) { |
michael@0 | 900 | prefValue = aPref[1]; |
michael@0 | 901 | prefIid = aPref[2]; |
michael@0 | 902 | } else if (aPref.length == 2) { |
michael@0 | 903 | prefValue = aPref[1]; |
michael@0 | 904 | } |
michael@0 | 905 | |
michael@0 | 906 | /* If pref is not found or invalid it doesn't exist. */ |
michael@0 | 907 | if (prefs.getPrefType(prefName) != prefs.PREF_INVALID) { |
michael@0 | 908 | prefType = pref_string[prefs.getPrefType(prefName)]; |
michael@0 | 909 | if ((prefs.prefHasUserValue(prefName) && action == 'clear') || |
michael@0 | 910 | (action == 'set')) |
michael@0 | 911 | originalValue = this._getPref(prefName, prefType); |
michael@0 | 912 | } else if (action == 'set') { |
michael@0 | 913 | /* prefName doesn't exist, so 'clear' is pointless */ |
michael@0 | 914 | if (aPref.length == 3) { |
michael@0 | 915 | prefType = "COMPLEX"; |
michael@0 | 916 | } else if (aPref.length == 2) { |
michael@0 | 917 | if (typeof(prefValue) == "boolean") |
michael@0 | 918 | prefType = "BOOL"; |
michael@0 | 919 | else if (typeof(prefValue) == "number") |
michael@0 | 920 | prefType = "INT"; |
michael@0 | 921 | else if (typeof(prefValue) == "string") |
michael@0 | 922 | prefType = "CHAR"; |
michael@0 | 923 | } |
michael@0 | 924 | } |
michael@0 | 925 | |
michael@0 | 926 | /* PREF_INVALID: A non existing pref which we are clearing or invalid values for a set */ |
michael@0 | 927 | if (prefType == prefs.PREF_INVALID) |
michael@0 | 928 | continue; |
michael@0 | 929 | |
michael@0 | 930 | /* We are not going to set a pref if the value is the same */ |
michael@0 | 931 | if (originalValue == prefValue) |
michael@0 | 932 | continue; |
michael@0 | 933 | |
michael@0 | 934 | pendingActions.push({'action': action, 'type': prefType, 'name': prefName, 'value': prefValue, 'Iid': prefIid}); |
michael@0 | 935 | |
michael@0 | 936 | /* Push original preference value or clear into cleanup array */ |
michael@0 | 937 | var cleanupTodo = {'action': action, 'type': prefType, 'name': prefName, 'value': originalValue, 'Iid': prefIid}; |
michael@0 | 938 | if (originalValue == null) { |
michael@0 | 939 | cleanupTodo.action = 'clear'; |
michael@0 | 940 | } else { |
michael@0 | 941 | cleanupTodo.action = 'set'; |
michael@0 | 942 | } |
michael@0 | 943 | cleanupActions.push(cleanupTodo); |
michael@0 | 944 | } |
michael@0 | 945 | } |
michael@0 | 946 | |
michael@0 | 947 | if (pendingActions.length > 0) { |
michael@0 | 948 | // The callback needs to be delayed twice. One delay is because the pref |
michael@0 | 949 | // service doesn't guarantee the order it calls its observers in, so it |
michael@0 | 950 | // may notify the observer holding the callback before the other |
michael@0 | 951 | // observers have been notified and given a chance to make the changes |
michael@0 | 952 | // that the callback checks for. The second delay is because pref |
michael@0 | 953 | // observers often defer making their changes by posting an event to the |
michael@0 | 954 | // event loop. |
michael@0 | 955 | this._prefEnvUndoStack.push(cleanupActions); |
michael@0 | 956 | this._pendingPrefs.push([pendingActions, |
michael@0 | 957 | this._delayCallbackTwice(callback)]); |
michael@0 | 958 | this._applyPrefs(); |
michael@0 | 959 | } else { |
michael@0 | 960 | content.window.setTimeout(callback, 0); |
michael@0 | 961 | } |
michael@0 | 962 | }, |
michael@0 | 963 | |
michael@0 | 964 | popPrefEnv: function(callback) { |
michael@0 | 965 | if (this._prefEnvUndoStack.length > 0) { |
michael@0 | 966 | // See pushPrefEnv comment regarding delay. |
michael@0 | 967 | let cb = callback ? this._delayCallbackTwice(callback) : null; |
michael@0 | 968 | /* Each pop will have a valid block of preferences */ |
michael@0 | 969 | this._pendingPrefs.push([this._prefEnvUndoStack.pop(), cb]); |
michael@0 | 970 | this._applyPrefs(); |
michael@0 | 971 | } else { |
michael@0 | 972 | content.window.setTimeout(callback, 0); |
michael@0 | 973 | } |
michael@0 | 974 | }, |
michael@0 | 975 | |
michael@0 | 976 | flushPrefEnv: function(callback) { |
michael@0 | 977 | while (this._prefEnvUndoStack.length > 1) |
michael@0 | 978 | this.popPrefEnv(null); |
michael@0 | 979 | |
michael@0 | 980 | this.popPrefEnv(callback); |
michael@0 | 981 | }, |
michael@0 | 982 | |
michael@0 | 983 | /* |
michael@0 | 984 | Iterate through one atomic set of pref actions and perform sets/clears as appropriate. |
michael@0 | 985 | All actions performed must modify the relevant pref. |
michael@0 | 986 | */ |
michael@0 | 987 | _applyPrefs: function() { |
michael@0 | 988 | if (this._applyingPrefs || this._pendingPrefs.length <= 0) { |
michael@0 | 989 | return; |
michael@0 | 990 | } |
michael@0 | 991 | |
michael@0 | 992 | /* Set lock and get prefs from the _pendingPrefs queue */ |
michael@0 | 993 | this._applyingPrefs = true; |
michael@0 | 994 | var transaction = this._pendingPrefs.shift(); |
michael@0 | 995 | var pendingActions = transaction[0]; |
michael@0 | 996 | var callback = transaction[1]; |
michael@0 | 997 | |
michael@0 | 998 | var lastPref = pendingActions[pendingActions.length-1]; |
michael@0 | 999 | |
michael@0 | 1000 | var pb = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch); |
michael@0 | 1001 | var self = this; |
michael@0 | 1002 | pb.addObserver(lastPref.name, function prefObs(subject, topic, data) { |
michael@0 | 1003 | pb.removeObserver(lastPref.name, prefObs); |
michael@0 | 1004 | |
michael@0 | 1005 | content.window.setTimeout(callback, 0); |
michael@0 | 1006 | content.window.setTimeout(function () { |
michael@0 | 1007 | self._applyingPrefs = false; |
michael@0 | 1008 | // Now apply any prefs that may have been queued while we were applying |
michael@0 | 1009 | self._applyPrefs(); |
michael@0 | 1010 | }, 0); |
michael@0 | 1011 | }, false); |
michael@0 | 1012 | |
michael@0 | 1013 | for (var idx in pendingActions) { |
michael@0 | 1014 | var pref = pendingActions[idx]; |
michael@0 | 1015 | if (pref.action == 'set') { |
michael@0 | 1016 | this._setPref(pref.name, pref.type, pref.value, pref.Iid); |
michael@0 | 1017 | } else if (pref.action == 'clear') { |
michael@0 | 1018 | this.clearUserPref(pref.name); |
michael@0 | 1019 | } |
michael@0 | 1020 | } |
michael@0 | 1021 | }, |
michael@0 | 1022 | |
michael@0 | 1023 | // Disables the app install prompt for the duration of this test. There is |
michael@0 | 1024 | // no need to re-enable the prompt at the end of the test. |
michael@0 | 1025 | // |
michael@0 | 1026 | // The provided callback is invoked once the prompt is disabled. |
michael@0 | 1027 | autoConfirmAppInstall: function(cb) { |
michael@0 | 1028 | this.pushPrefEnv({set: [['dom.mozApps.auto_confirm_install', true]]}, cb); |
michael@0 | 1029 | }, |
michael@0 | 1030 | |
michael@0 | 1031 | // Allow tests to disable the per platform app validity checks so we can |
michael@0 | 1032 | // test higher level WebApp functionality without full platform support. |
michael@0 | 1033 | setAllAppsLaunchable: function(launchable) { |
michael@0 | 1034 | this._sendSyncMessage("SPWebAppService", { |
michael@0 | 1035 | op: "set-launchable", |
michael@0 | 1036 | launchable: launchable |
michael@0 | 1037 | }); |
michael@0 | 1038 | }, |
michael@0 | 1039 | |
michael@0 | 1040 | // Restore the launchable property to its default value. |
michael@0 | 1041 | flushAllAppsLaunchable: function() { |
michael@0 | 1042 | this._sendSyncMessage("SPWebAppService", { |
michael@0 | 1043 | op: "set-launchable", |
michael@0 | 1044 | launchable: false |
michael@0 | 1045 | }); |
michael@0 | 1046 | }, |
michael@0 | 1047 | |
michael@0 | 1048 | _proxiedObservers: { |
michael@0 | 1049 | "specialpowers-http-notify-request": function(aMessage) { |
michael@0 | 1050 | let uri = aMessage.json.uri; |
michael@0 | 1051 | Services.obs.notifyObservers(null, "specialpowers-http-notify-request", uri); |
michael@0 | 1052 | }, |
michael@0 | 1053 | }, |
michael@0 | 1054 | |
michael@0 | 1055 | _addObserverProxy: function(notification) { |
michael@0 | 1056 | if (notification in this._proxiedObservers) { |
michael@0 | 1057 | this._addMessageListener(notification, this._proxiedObservers[notification]); |
michael@0 | 1058 | } |
michael@0 | 1059 | }, |
michael@0 | 1060 | |
michael@0 | 1061 | _removeObserverProxy: function(notification) { |
michael@0 | 1062 | if (notification in this._proxiedObservers) { |
michael@0 | 1063 | this._removeMessageListener(notification, this._proxiedObservers[notification]); |
michael@0 | 1064 | } |
michael@0 | 1065 | }, |
michael@0 | 1066 | |
michael@0 | 1067 | addObserver: function(obs, notification, weak) { |
michael@0 | 1068 | this._addObserverProxy(notification); |
michael@0 | 1069 | if (typeof obs == 'object' && obs.observe.name != 'SpecialPowersCallbackWrapper') |
michael@0 | 1070 | obs.observe = wrapCallback(obs.observe); |
michael@0 | 1071 | var obsvc = Cc['@mozilla.org/observer-service;1'] |
michael@0 | 1072 | .getService(Ci.nsIObserverService); |
michael@0 | 1073 | obsvc.addObserver(obs, notification, weak); |
michael@0 | 1074 | }, |
michael@0 | 1075 | removeObserver: function(obs, notification) { |
michael@0 | 1076 | this._removeObserverProxy(notification); |
michael@0 | 1077 | var obsvc = Cc['@mozilla.org/observer-service;1'] |
michael@0 | 1078 | .getService(Ci.nsIObserverService); |
michael@0 | 1079 | obsvc.removeObserver(obs, notification); |
michael@0 | 1080 | }, |
michael@0 | 1081 | notifyObservers: function(subject, topic, data) { |
michael@0 | 1082 | var obsvc = Cc['@mozilla.org/observer-service;1'] |
michael@0 | 1083 | .getService(Ci.nsIObserverService); |
michael@0 | 1084 | obsvc.notifyObservers(subject, topic, data); |
michael@0 | 1085 | }, |
michael@0 | 1086 | |
michael@0 | 1087 | can_QI: function(obj) { |
michael@0 | 1088 | return obj.QueryInterface !== undefined; |
michael@0 | 1089 | }, |
michael@0 | 1090 | do_QueryInterface: function(obj, iface) { |
michael@0 | 1091 | return obj.QueryInterface(Ci[iface]); |
michael@0 | 1092 | }, |
michael@0 | 1093 | |
michael@0 | 1094 | call_Instanceof: function (obj1, obj2) { |
michael@0 | 1095 | obj1=unwrapIfWrapped(obj1); |
michael@0 | 1096 | obj2=unwrapIfWrapped(obj2); |
michael@0 | 1097 | return obj1 instanceof obj2; |
michael@0 | 1098 | }, |
michael@0 | 1099 | |
michael@0 | 1100 | // Returns a privileged getter from an object. GetOwnPropertyDescriptor does |
michael@0 | 1101 | // not work here because xray wrappers don't properly implement it. |
michael@0 | 1102 | // |
michael@0 | 1103 | // This terribleness is used by content/base/test/test_object.html because |
michael@0 | 1104 | // <object> and <embed> tags will spawn plugins if their prototype is touched, |
michael@0 | 1105 | // so we need to get and cache the getter of |hasRunningPlugin| if we want to |
michael@0 | 1106 | // call it without paradoxically spawning the plugin. |
michael@0 | 1107 | do_lookupGetter: function(obj, name) { |
michael@0 | 1108 | return Object.prototype.__lookupGetter__.call(obj, name); |
michael@0 | 1109 | }, |
michael@0 | 1110 | |
michael@0 | 1111 | // Mimic the get*Pref API |
michael@0 | 1112 | getBoolPref: function(aPrefName) { |
michael@0 | 1113 | return (this._getPref(aPrefName, 'BOOL')); |
michael@0 | 1114 | }, |
michael@0 | 1115 | getIntPref: function(aPrefName) { |
michael@0 | 1116 | return (this._getPref(aPrefName, 'INT')); |
michael@0 | 1117 | }, |
michael@0 | 1118 | getCharPref: function(aPrefName) { |
michael@0 | 1119 | return (this._getPref(aPrefName, 'CHAR')); |
michael@0 | 1120 | }, |
michael@0 | 1121 | getComplexValue: function(aPrefName, aIid) { |
michael@0 | 1122 | return (this._getPref(aPrefName, 'COMPLEX', aIid)); |
michael@0 | 1123 | }, |
michael@0 | 1124 | |
michael@0 | 1125 | // Mimic the set*Pref API |
michael@0 | 1126 | setBoolPref: function(aPrefName, aValue) { |
michael@0 | 1127 | return (this._setPref(aPrefName, 'BOOL', aValue)); |
michael@0 | 1128 | }, |
michael@0 | 1129 | setIntPref: function(aPrefName, aValue) { |
michael@0 | 1130 | return (this._setPref(aPrefName, 'INT', aValue)); |
michael@0 | 1131 | }, |
michael@0 | 1132 | setCharPref: function(aPrefName, aValue) { |
michael@0 | 1133 | return (this._setPref(aPrefName, 'CHAR', aValue)); |
michael@0 | 1134 | }, |
michael@0 | 1135 | setComplexValue: function(aPrefName, aIid, aValue) { |
michael@0 | 1136 | return (this._setPref(aPrefName, 'COMPLEX', aValue, aIid)); |
michael@0 | 1137 | }, |
michael@0 | 1138 | |
michael@0 | 1139 | // Mimic the clearUserPref API |
michael@0 | 1140 | clearUserPref: function(aPrefName) { |
michael@0 | 1141 | var msg = {'op':'clear', 'prefName': aPrefName, 'prefType': ""}; |
michael@0 | 1142 | this._sendSyncMessage('SPPrefService', msg); |
michael@0 | 1143 | }, |
michael@0 | 1144 | |
michael@0 | 1145 | // Private pref functions to communicate to chrome |
michael@0 | 1146 | _getPref: function(aPrefName, aPrefType, aIid) { |
michael@0 | 1147 | var msg = {}; |
michael@0 | 1148 | if (aIid) { |
michael@0 | 1149 | // Overloading prefValue to handle complex prefs |
michael@0 | 1150 | msg = {'op':'get', 'prefName': aPrefName, 'prefType':aPrefType, 'prefValue':[aIid]}; |
michael@0 | 1151 | } else { |
michael@0 | 1152 | msg = {'op':'get', 'prefName': aPrefName,'prefType': aPrefType}; |
michael@0 | 1153 | } |
michael@0 | 1154 | var val = this._sendSyncMessage('SPPrefService', msg); |
michael@0 | 1155 | |
michael@0 | 1156 | if (val == null || val[0] == null) |
michael@0 | 1157 | throw "Error getting pref"; |
michael@0 | 1158 | return val[0]; |
michael@0 | 1159 | }, |
michael@0 | 1160 | _setPref: function(aPrefName, aPrefType, aValue, aIid) { |
michael@0 | 1161 | var msg = {}; |
michael@0 | 1162 | if (aIid) { |
michael@0 | 1163 | msg = {'op':'set','prefName':aPrefName, 'prefType': aPrefType, 'prefValue': [aIid,aValue]}; |
michael@0 | 1164 | } else { |
michael@0 | 1165 | msg = {'op':'set', 'prefName': aPrefName, 'prefType': aPrefType, 'prefValue': aValue}; |
michael@0 | 1166 | } |
michael@0 | 1167 | return(this._sendSyncMessage('SPPrefService', msg)[0]); |
michael@0 | 1168 | }, |
michael@0 | 1169 | |
michael@0 | 1170 | _getDocShell: function(window) { |
michael@0 | 1171 | return window.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 1172 | .getInterface(Ci.nsIWebNavigation) |
michael@0 | 1173 | .QueryInterface(Ci.nsIDocShell); |
michael@0 | 1174 | }, |
michael@0 | 1175 | _getMUDV: function(window) { |
michael@0 | 1176 | return this._getDocShell(window).contentViewer |
michael@0 | 1177 | .QueryInterface(Ci.nsIMarkupDocumentViewer); |
michael@0 | 1178 | }, |
michael@0 | 1179 | //XXX: these APIs really ought to be removed, they're not e10s-safe. |
michael@0 | 1180 | // (also they're pretty Firefox-specific) |
michael@0 | 1181 | _getTopChromeWindow: function(window) { |
michael@0 | 1182 | return window.QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 1183 | .getInterface(Ci.nsIWebNavigation) |
michael@0 | 1184 | .QueryInterface(Ci.nsIDocShellTreeItem) |
michael@0 | 1185 | .rootTreeItem |
michael@0 | 1186 | .QueryInterface(Ci.nsIInterfaceRequestor) |
michael@0 | 1187 | .getInterface(Ci.nsIDOMWindow) |
michael@0 | 1188 | .QueryInterface(Ci.nsIDOMChromeWindow); |
michael@0 | 1189 | }, |
michael@0 | 1190 | _getAutoCompletePopup: function(window) { |
michael@0 | 1191 | return this._getTopChromeWindow(window).document |
michael@0 | 1192 | .getElementById("PopupAutoComplete"); |
michael@0 | 1193 | }, |
michael@0 | 1194 | addAutoCompletePopupEventListener: function(window, eventname, listener) { |
michael@0 | 1195 | this._getAutoCompletePopup(window).addEventListener(eventname, |
michael@0 | 1196 | listener, |
michael@0 | 1197 | false); |
michael@0 | 1198 | }, |
michael@0 | 1199 | removeAutoCompletePopupEventListener: function(window, eventname, listener) { |
michael@0 | 1200 | this._getAutoCompletePopup(window).removeEventListener(eventname, |
michael@0 | 1201 | listener, |
michael@0 | 1202 | false); |
michael@0 | 1203 | }, |
michael@0 | 1204 | get formHistory() { |
michael@0 | 1205 | let tmp = {}; |
michael@0 | 1206 | Cu.import("resource://gre/modules/FormHistory.jsm", tmp); |
michael@0 | 1207 | return wrapPrivileged(tmp.FormHistory); |
michael@0 | 1208 | }, |
michael@0 | 1209 | getFormFillController: function(window) { |
michael@0 | 1210 | return Components.classes["@mozilla.org/satchel/form-fill-controller;1"] |
michael@0 | 1211 | .getService(Components.interfaces.nsIFormFillController); |
michael@0 | 1212 | }, |
michael@0 | 1213 | attachFormFillControllerTo: function(window) { |
michael@0 | 1214 | this.getFormFillController() |
michael@0 | 1215 | .attachToBrowser(this._getDocShell(window), |
michael@0 | 1216 | this._getAutoCompletePopup(window)); |
michael@0 | 1217 | }, |
michael@0 | 1218 | detachFormFillControllerFrom: function(window) { |
michael@0 | 1219 | this.getFormFillController().detachFromBrowser(this._getDocShell(window)); |
michael@0 | 1220 | }, |
michael@0 | 1221 | isBackButtonEnabled: function(window) { |
michael@0 | 1222 | return !this._getTopChromeWindow(window).document |
michael@0 | 1223 | .getElementById("Browser:Back") |
michael@0 | 1224 | .hasAttribute("disabled"); |
michael@0 | 1225 | }, |
michael@0 | 1226 | //XXX end of problematic APIs |
michael@0 | 1227 | |
michael@0 | 1228 | addChromeEventListener: function(type, listener, capture, allowUntrusted) { |
michael@0 | 1229 | addEventListener(type, listener, capture, allowUntrusted); |
michael@0 | 1230 | }, |
michael@0 | 1231 | removeChromeEventListener: function(type, listener, capture) { |
michael@0 | 1232 | removeEventListener(type, listener, capture); |
michael@0 | 1233 | }, |
michael@0 | 1234 | |
michael@0 | 1235 | // Note: each call to registerConsoleListener MUST be paired with a |
michael@0 | 1236 | // call to postConsoleSentinel; when the callback receives the |
michael@0 | 1237 | // sentinel it will unregister itself (_after_ calling the |
michael@0 | 1238 | // callback). SimpleTest.expectConsoleMessages does this for you. |
michael@0 | 1239 | // If you register more than one console listener, a call to |
michael@0 | 1240 | // postConsoleSentinel will zap all of them. |
michael@0 | 1241 | registerConsoleListener: function(callback) { |
michael@0 | 1242 | let listener = new SPConsoleListener(callback); |
michael@0 | 1243 | Services.console.registerListener(listener); |
michael@0 | 1244 | }, |
michael@0 | 1245 | postConsoleSentinel: function() { |
michael@0 | 1246 | Services.console.logStringMessage("SENTINEL"); |
michael@0 | 1247 | }, |
michael@0 | 1248 | resetConsole: function() { |
michael@0 | 1249 | Services.console.reset(); |
michael@0 | 1250 | }, |
michael@0 | 1251 | |
michael@0 | 1252 | getMaxLineBoxWidth: function(window) { |
michael@0 | 1253 | return this._getMUDV(window).maxLineBoxWidth; |
michael@0 | 1254 | }, |
michael@0 | 1255 | |
michael@0 | 1256 | setMaxLineBoxWidth: function(window, width) { |
michael@0 | 1257 | this._getMUDV(window).changeMaxLineBoxWidth(width); |
michael@0 | 1258 | }, |
michael@0 | 1259 | |
michael@0 | 1260 | getFullZoom: function(window) { |
michael@0 | 1261 | return this._getMUDV(window).fullZoom; |
michael@0 | 1262 | }, |
michael@0 | 1263 | setFullZoom: function(window, zoom) { |
michael@0 | 1264 | this._getMUDV(window).fullZoom = zoom; |
michael@0 | 1265 | }, |
michael@0 | 1266 | getTextZoom: function(window) { |
michael@0 | 1267 | return this._getMUDV(window).textZoom; |
michael@0 | 1268 | }, |
michael@0 | 1269 | setTextZoom: function(window, zoom) { |
michael@0 | 1270 | this._getMUDV(window).textZoom = zoom; |
michael@0 | 1271 | }, |
michael@0 | 1272 | |
michael@0 | 1273 | emulateMedium: function(window, mediaType) { |
michael@0 | 1274 | this._getMUDV(window).emulateMedium(mediaType); |
michael@0 | 1275 | }, |
michael@0 | 1276 | stopEmulatingMedium: function(window) { |
michael@0 | 1277 | this._getMUDV(window).stopEmulatingMedium(); |
michael@0 | 1278 | }, |
michael@0 | 1279 | |
michael@0 | 1280 | snapshotWindowWithOptions: function (win, rect, bgcolor, options) { |
michael@0 | 1281 | var el = this.window.get().document.createElementNS("http://www.w3.org/1999/xhtml", "canvas"); |
michael@0 | 1282 | if (rect === undefined) { |
michael@0 | 1283 | rect = { top: win.scrollY, left: win.scrollX, |
michael@0 | 1284 | width: win.innerWidth, height: win.innerHeight }; |
michael@0 | 1285 | } |
michael@0 | 1286 | if (bgcolor === undefined) { |
michael@0 | 1287 | bgcolor = "rgb(255,255,255)"; |
michael@0 | 1288 | } |
michael@0 | 1289 | if (options === undefined) { |
michael@0 | 1290 | options = { }; |
michael@0 | 1291 | } |
michael@0 | 1292 | |
michael@0 | 1293 | el.width = rect.width; |
michael@0 | 1294 | el.height = rect.height; |
michael@0 | 1295 | var ctx = el.getContext("2d"); |
michael@0 | 1296 | var flags = 0; |
michael@0 | 1297 | |
michael@0 | 1298 | for (var option in options) { |
michael@0 | 1299 | flags |= options[option] && ctx[option]; |
michael@0 | 1300 | } |
michael@0 | 1301 | |
michael@0 | 1302 | ctx.drawWindow(win, |
michael@0 | 1303 | rect.left, rect.top, rect.width, rect.height, |
michael@0 | 1304 | bgcolor, |
michael@0 | 1305 | flags); |
michael@0 | 1306 | return el; |
michael@0 | 1307 | }, |
michael@0 | 1308 | |
michael@0 | 1309 | snapshotWindow: function (win, withCaret, rect, bgcolor) { |
michael@0 | 1310 | return this.snapshotWindowWithOptions(win, rect, bgcolor, |
michael@0 | 1311 | { DRAWWINDOW_DRAW_CARET: withCaret }); |
michael@0 | 1312 | }, |
michael@0 | 1313 | |
michael@0 | 1314 | snapshotRect: function (win, rect, bgcolor) { |
michael@0 | 1315 | return this.snapshotWindowWithOptions(win, rect, bgcolor); |
michael@0 | 1316 | }, |
michael@0 | 1317 | |
michael@0 | 1318 | gc: function() { |
michael@0 | 1319 | this.DOMWindowUtils.garbageCollect(); |
michael@0 | 1320 | }, |
michael@0 | 1321 | |
michael@0 | 1322 | forceGC: function() { |
michael@0 | 1323 | Cu.forceGC(); |
michael@0 | 1324 | }, |
michael@0 | 1325 | |
michael@0 | 1326 | forceCC: function() { |
michael@0 | 1327 | Cu.forceCC(); |
michael@0 | 1328 | }, |
michael@0 | 1329 | |
michael@0 | 1330 | // Due to various dependencies between JS objects and C++ objects, an ordinary |
michael@0 | 1331 | // forceGC doesn't necessarily clear all unused objects, thus the GC and CC |
michael@0 | 1332 | // needs to run several times and when no other JS is running. |
michael@0 | 1333 | // The current number of iterations has been determined according to massive |
michael@0 | 1334 | // cross platform testing. |
michael@0 | 1335 | exactGC: function(win, callback) { |
michael@0 | 1336 | var self = this; |
michael@0 | 1337 | let count = 0; |
michael@0 | 1338 | |
michael@0 | 1339 | function doPreciseGCandCC() { |
michael@0 | 1340 | function scheduledGCCallback() { |
michael@0 | 1341 | self.getDOMWindowUtils(win).cycleCollect(); |
michael@0 | 1342 | |
michael@0 | 1343 | if (++count < 2) { |
michael@0 | 1344 | doPreciseGCandCC(); |
michael@0 | 1345 | } else { |
michael@0 | 1346 | callback(); |
michael@0 | 1347 | } |
michael@0 | 1348 | } |
michael@0 | 1349 | |
michael@0 | 1350 | Cu.schedulePreciseGC(scheduledGCCallback); |
michael@0 | 1351 | } |
michael@0 | 1352 | |
michael@0 | 1353 | doPreciseGCandCC(); |
michael@0 | 1354 | }, |
michael@0 | 1355 | |
michael@0 | 1356 | setGCZeal: function(zeal) { |
michael@0 | 1357 | Cu.setGCZeal(zeal); |
michael@0 | 1358 | }, |
michael@0 | 1359 | |
michael@0 | 1360 | isMainProcess: function() { |
michael@0 | 1361 | try { |
michael@0 | 1362 | return Cc["@mozilla.org/xre/app-info;1"]. |
michael@0 | 1363 | getService(Ci.nsIXULRuntime). |
michael@0 | 1364 | processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; |
michael@0 | 1365 | } catch (e) { } |
michael@0 | 1366 | return true; |
michael@0 | 1367 | }, |
michael@0 | 1368 | |
michael@0 | 1369 | _xpcomabi: null, |
michael@0 | 1370 | |
michael@0 | 1371 | get XPCOMABI() { |
michael@0 | 1372 | if (this._xpcomabi != null) |
michael@0 | 1373 | return this._xpcomabi; |
michael@0 | 1374 | |
michael@0 | 1375 | var xulRuntime = Cc["@mozilla.org/xre/app-info;1"] |
michael@0 | 1376 | .getService(Components.interfaces.nsIXULAppInfo) |
michael@0 | 1377 | .QueryInterface(Components.interfaces.nsIXULRuntime); |
michael@0 | 1378 | |
michael@0 | 1379 | this._xpcomabi = xulRuntime.XPCOMABI; |
michael@0 | 1380 | return this._xpcomabi; |
michael@0 | 1381 | }, |
michael@0 | 1382 | |
michael@0 | 1383 | // The optional aWin parameter allows the caller to specify a given window in |
michael@0 | 1384 | // whose scope the runnable should be dispatched. If aFun throws, the |
michael@0 | 1385 | // exception will be reported to aWin. |
michael@0 | 1386 | executeSoon: function(aFun, aWin) { |
michael@0 | 1387 | // Create the runnable in the scope of aWin to avoid running into COWs. |
michael@0 | 1388 | var runnable = {}; |
michael@0 | 1389 | if (aWin) |
michael@0 | 1390 | runnable = Cu.createObjectIn(aWin); |
michael@0 | 1391 | runnable.run = aFun; |
michael@0 | 1392 | Cu.dispatch(runnable, aWin); |
michael@0 | 1393 | }, |
michael@0 | 1394 | |
michael@0 | 1395 | _os: null, |
michael@0 | 1396 | |
michael@0 | 1397 | get OS() { |
michael@0 | 1398 | if (this._os != null) |
michael@0 | 1399 | return this._os; |
michael@0 | 1400 | |
michael@0 | 1401 | var xulRuntime = Cc["@mozilla.org/xre/app-info;1"] |
michael@0 | 1402 | .getService(Components.interfaces.nsIXULAppInfo) |
michael@0 | 1403 | .QueryInterface(Components.interfaces.nsIXULRuntime); |
michael@0 | 1404 | |
michael@0 | 1405 | this._os = xulRuntime.OS; |
michael@0 | 1406 | return this._os; |
michael@0 | 1407 | }, |
michael@0 | 1408 | |
michael@0 | 1409 | addSystemEventListener: function(target, type, listener, useCapture) { |
michael@0 | 1410 | Cc["@mozilla.org/eventlistenerservice;1"]. |
michael@0 | 1411 | getService(Ci.nsIEventListenerService). |
michael@0 | 1412 | addSystemEventListener(target, type, listener, useCapture); |
michael@0 | 1413 | }, |
michael@0 | 1414 | removeSystemEventListener: function(target, type, listener, useCapture) { |
michael@0 | 1415 | Cc["@mozilla.org/eventlistenerservice;1"]. |
michael@0 | 1416 | getService(Ci.nsIEventListenerService). |
michael@0 | 1417 | removeSystemEventListener(target, type, listener, useCapture); |
michael@0 | 1418 | }, |
michael@0 | 1419 | |
michael@0 | 1420 | getDOMRequestService: function() { |
michael@0 | 1421 | var serv = Cc["@mozilla.org/dom/dom-request-service;1"]. |
michael@0 | 1422 | getService(Ci.nsIDOMRequestService); |
michael@0 | 1423 | var res = { __exposedProps__: {} }; |
michael@0 | 1424 | var props = ["createRequest", "createCursor", "fireError", "fireSuccess", |
michael@0 | 1425 | "fireDone", "fireDetailedError"]; |
michael@0 | 1426 | for (i in props) { |
michael@0 | 1427 | let prop = props[i]; |
michael@0 | 1428 | res[prop] = function() { return serv[prop].apply(serv, arguments) }; |
michael@0 | 1429 | res.__exposedProps__[prop] = "r"; |
michael@0 | 1430 | } |
michael@0 | 1431 | return res; |
michael@0 | 1432 | }, |
michael@0 | 1433 | |
michael@0 | 1434 | setLogFile: function(path) { |
michael@0 | 1435 | this._mfl = new MozillaFileLogger(path); |
michael@0 | 1436 | }, |
michael@0 | 1437 | |
michael@0 | 1438 | log: function(data) { |
michael@0 | 1439 | this._mfl.log(data); |
michael@0 | 1440 | }, |
michael@0 | 1441 | |
michael@0 | 1442 | closeLogFile: function() { |
michael@0 | 1443 | this._mfl.close(); |
michael@0 | 1444 | }, |
michael@0 | 1445 | |
michael@0 | 1446 | addCategoryEntry: function(category, entry, value, persists, replace) { |
michael@0 | 1447 | Components.classes["@mozilla.org/categorymanager;1"]. |
michael@0 | 1448 | getService(Components.interfaces.nsICategoryManager). |
michael@0 | 1449 | addCategoryEntry(category, entry, value, persists, replace); |
michael@0 | 1450 | }, |
michael@0 | 1451 | |
michael@0 | 1452 | deleteCategoryEntry: function(category, entry, persists) { |
michael@0 | 1453 | Components.classes["@mozilla.org/categorymanager;1"]. |
michael@0 | 1454 | getService(Components.interfaces.nsICategoryManager). |
michael@0 | 1455 | deleteCategoryEntry(category, entry, persists); |
michael@0 | 1456 | }, |
michael@0 | 1457 | |
michael@0 | 1458 | copyString: function(str, doc) { |
michael@0 | 1459 | Components.classes["@mozilla.org/widget/clipboardhelper;1"]. |
michael@0 | 1460 | getService(Components.interfaces.nsIClipboardHelper). |
michael@0 | 1461 | copyString(str, doc); |
michael@0 | 1462 | }, |
michael@0 | 1463 | |
michael@0 | 1464 | openDialog: function(win, args) { |
michael@0 | 1465 | return win.openDialog.apply(win, args); |
michael@0 | 1466 | }, |
michael@0 | 1467 | |
michael@0 | 1468 | // :jdm gets credit for this. ex: getPrivilegedProps(window, 'location.href'); |
michael@0 | 1469 | getPrivilegedProps: function(obj, props) { |
michael@0 | 1470 | var parts = props.split('.'); |
michael@0 | 1471 | |
michael@0 | 1472 | for (var i = 0; i < parts.length; i++) { |
michael@0 | 1473 | var p = parts[i]; |
michael@0 | 1474 | if (obj[p]) { |
michael@0 | 1475 | obj = obj[p]; |
michael@0 | 1476 | } else { |
michael@0 | 1477 | return null; |
michael@0 | 1478 | } |
michael@0 | 1479 | } |
michael@0 | 1480 | return obj; |
michael@0 | 1481 | }, |
michael@0 | 1482 | |
michael@0 | 1483 | get focusManager() { |
michael@0 | 1484 | if (this._fm != null) |
michael@0 | 1485 | return this._fm; |
michael@0 | 1486 | |
michael@0 | 1487 | this._fm = Components.classes["@mozilla.org/focus-manager;1"]. |
michael@0 | 1488 | getService(Components.interfaces.nsIFocusManager); |
michael@0 | 1489 | |
michael@0 | 1490 | return this._fm; |
michael@0 | 1491 | }, |
michael@0 | 1492 | |
michael@0 | 1493 | getFocusedElementForWindow: function(targetWindow, aDeep, childTargetWindow) { |
michael@0 | 1494 | return this.focusManager.getFocusedElementForWindow(targetWindow, aDeep, childTargetWindow); |
michael@0 | 1495 | }, |
michael@0 | 1496 | |
michael@0 | 1497 | activeWindow: function() { |
michael@0 | 1498 | return this.focusManager.activeWindow; |
michael@0 | 1499 | }, |
michael@0 | 1500 | |
michael@0 | 1501 | focusedWindow: function() { |
michael@0 | 1502 | return this.focusManager.focusedWindow; |
michael@0 | 1503 | }, |
michael@0 | 1504 | |
michael@0 | 1505 | focus: function(aWindow) { |
michael@0 | 1506 | // This is called inside TestRunner._makeIframe without aWindow, because of assertions in oop mochitests |
michael@0 | 1507 | // With aWindow, it is called in SimpleTest.waitForFocus to allow popup window opener focus switching |
michael@0 | 1508 | if (aWindow) |
michael@0 | 1509 | aWindow.focus(); |
michael@0 | 1510 | sendAsyncMessage("SpecialPowers.Focus", {}); |
michael@0 | 1511 | }, |
michael@0 | 1512 | |
michael@0 | 1513 | getClipboardData: function(flavor, whichClipboard) { |
michael@0 | 1514 | if (this._cb == null) |
michael@0 | 1515 | this._cb = Components.classes["@mozilla.org/widget/clipboard;1"]. |
michael@0 | 1516 | getService(Components.interfaces.nsIClipboard); |
michael@0 | 1517 | if (whichClipboard === undefined) |
michael@0 | 1518 | whichClipboard = this._cb.kGlobalClipboard; |
michael@0 | 1519 | |
michael@0 | 1520 | var xferable = Components.classes["@mozilla.org/widget/transferable;1"]. |
michael@0 | 1521 | createInstance(Components.interfaces.nsITransferable); |
michael@0 | 1522 | xferable.init(this._getDocShell(content.window) |
michael@0 | 1523 | .QueryInterface(Components.interfaces.nsILoadContext)); |
michael@0 | 1524 | xferable.addDataFlavor(flavor); |
michael@0 | 1525 | this._cb.getData(xferable, whichClipboard); |
michael@0 | 1526 | var data = {}; |
michael@0 | 1527 | try { |
michael@0 | 1528 | xferable.getTransferData(flavor, data, {}); |
michael@0 | 1529 | } catch (e) {} |
michael@0 | 1530 | data = data.value || null; |
michael@0 | 1531 | if (data == null) |
michael@0 | 1532 | return ""; |
michael@0 | 1533 | |
michael@0 | 1534 | return data.QueryInterface(Components.interfaces.nsISupportsString).data; |
michael@0 | 1535 | }, |
michael@0 | 1536 | |
michael@0 | 1537 | clipboardCopyString: function(preExpectedVal, doc) { |
michael@0 | 1538 | var cbHelperSvc = Components.classes["@mozilla.org/widget/clipboardhelper;1"]. |
michael@0 | 1539 | getService(Components.interfaces.nsIClipboardHelper); |
michael@0 | 1540 | cbHelperSvc.copyString(preExpectedVal, doc); |
michael@0 | 1541 | }, |
michael@0 | 1542 | |
michael@0 | 1543 | supportsSelectionClipboard: function() { |
michael@0 | 1544 | if (this._cb == null) { |
michael@0 | 1545 | this._cb = Components.classes["@mozilla.org/widget/clipboard;1"]. |
michael@0 | 1546 | getService(Components.interfaces.nsIClipboard); |
michael@0 | 1547 | } |
michael@0 | 1548 | return this._cb.supportsSelectionClipboard(); |
michael@0 | 1549 | }, |
michael@0 | 1550 | |
michael@0 | 1551 | swapFactoryRegistration: function(cid, contractID, newFactory, oldFactory) { |
michael@0 | 1552 | var componentRegistrar = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar); |
michael@0 | 1553 | |
michael@0 | 1554 | var unregisterFactory = newFactory; |
michael@0 | 1555 | var registerFactory = oldFactory; |
michael@0 | 1556 | |
michael@0 | 1557 | if (cid == null) { |
michael@0 | 1558 | if (contractID != null) { |
michael@0 | 1559 | cid = componentRegistrar.contractIDToCID(contractID); |
michael@0 | 1560 | oldFactory = Components.manager.getClassObject(Components.classes[contractID], |
michael@0 | 1561 | Components.interfaces.nsIFactory); |
michael@0 | 1562 | } else { |
michael@0 | 1563 | return {'error': "trying to register a new contract ID: Missing contractID"}; |
michael@0 | 1564 | } |
michael@0 | 1565 | |
michael@0 | 1566 | unregisterFactory = oldFactory; |
michael@0 | 1567 | registerFactory = newFactory; |
michael@0 | 1568 | } |
michael@0 | 1569 | componentRegistrar.unregisterFactory(cid, |
michael@0 | 1570 | unregisterFactory); |
michael@0 | 1571 | |
michael@0 | 1572 | // Restore the original factory. |
michael@0 | 1573 | componentRegistrar.registerFactory(cid, |
michael@0 | 1574 | "", |
michael@0 | 1575 | contractID, |
michael@0 | 1576 | registerFactory); |
michael@0 | 1577 | return {'cid':cid, 'originalFactory':oldFactory}; |
michael@0 | 1578 | }, |
michael@0 | 1579 | |
michael@0 | 1580 | _getElement: function(aWindow, id) { |
michael@0 | 1581 | return ((typeof(id) == "string") ? |
michael@0 | 1582 | aWindow.document.getElementById(id) : id); |
michael@0 | 1583 | }, |
michael@0 | 1584 | |
michael@0 | 1585 | dispatchEvent: function(aWindow, target, event) { |
michael@0 | 1586 | var el = this._getElement(aWindow, target); |
michael@0 | 1587 | return el.dispatchEvent(event); |
michael@0 | 1588 | }, |
michael@0 | 1589 | |
michael@0 | 1590 | get isDebugBuild() { |
michael@0 | 1591 | delete this.isDebugBuild; |
michael@0 | 1592 | var debug = Cc["@mozilla.org/xpcom/debug;1"].getService(Ci.nsIDebug2); |
michael@0 | 1593 | return this.isDebugBuild = debug.isDebugBuild; |
michael@0 | 1594 | }, |
michael@0 | 1595 | assertionCount: function() { |
michael@0 | 1596 | var debugsvc = Cc['@mozilla.org/xpcom/debug;1'].getService(Ci.nsIDebug2); |
michael@0 | 1597 | return debugsvc.assertionCount; |
michael@0 | 1598 | }, |
michael@0 | 1599 | |
michael@0 | 1600 | /** |
michael@0 | 1601 | * Get the message manager associated with an <iframe mozbrowser>. |
michael@0 | 1602 | */ |
michael@0 | 1603 | getBrowserFrameMessageManager: function(aFrameElement) { |
michael@0 | 1604 | return this.wrap(aFrameElement.QueryInterface(Ci.nsIFrameLoaderOwner) |
michael@0 | 1605 | .frameLoader |
michael@0 | 1606 | .messageManager); |
michael@0 | 1607 | }, |
michael@0 | 1608 | |
michael@0 | 1609 | setFullscreenAllowed: function(document) { |
michael@0 | 1610 | var pm = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager); |
michael@0 | 1611 | pm.addFromPrincipal(document.nodePrincipal, "fullscreen", Ci.nsIPermissionManager.ALLOW_ACTION); |
michael@0 | 1612 | var obsvc = Cc['@mozilla.org/observer-service;1'] |
michael@0 | 1613 | .getService(Ci.nsIObserverService); |
michael@0 | 1614 | obsvc.notifyObservers(document, "fullscreen-approved", null); |
michael@0 | 1615 | }, |
michael@0 | 1616 | |
michael@0 | 1617 | removeFullscreenAllowed: function(document) { |
michael@0 | 1618 | var pm = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager); |
michael@0 | 1619 | pm.removeFromPrincipal(document.nodePrincipal, "fullscreen"); |
michael@0 | 1620 | }, |
michael@0 | 1621 | |
michael@0 | 1622 | _getInfoFromPermissionArg: function(arg) { |
michael@0 | 1623 | let url = ""; |
michael@0 | 1624 | let appId = Ci.nsIScriptSecurityManager.NO_APP_ID; |
michael@0 | 1625 | let isInBrowserElement = false; |
michael@0 | 1626 | |
michael@0 | 1627 | if (typeof(arg) == "string") { |
michael@0 | 1628 | // It's an URL. |
michael@0 | 1629 | url = Cc["@mozilla.org/network/io-service;1"] |
michael@0 | 1630 | .getService(Ci.nsIIOService) |
michael@0 | 1631 | .newURI(arg, null, null) |
michael@0 | 1632 | .spec; |
michael@0 | 1633 | } else if (arg.manifestURL) { |
michael@0 | 1634 | // It's a thing representing an app. |
michael@0 | 1635 | let appsSvc = Cc["@mozilla.org/AppsService;1"] |
michael@0 | 1636 | .getService(Ci.nsIAppsService) |
michael@0 | 1637 | let app = appsSvc.getAppByManifestURL(arg.manifestURL); |
michael@0 | 1638 | |
michael@0 | 1639 | if (!app) { |
michael@0 | 1640 | throw "No app for this manifest!"; |
michael@0 | 1641 | } |
michael@0 | 1642 | |
michael@0 | 1643 | appId = appsSvc.getAppLocalIdByManifestURL(arg.manifestURL); |
michael@0 | 1644 | url = app.origin; |
michael@0 | 1645 | isInBrowserElement = arg.isInBrowserElement || false; |
michael@0 | 1646 | } else if (arg.nodePrincipal) { |
michael@0 | 1647 | // It's a document. |
michael@0 | 1648 | url = arg.nodePrincipal.URI.spec; |
michael@0 | 1649 | appId = arg.nodePrincipal.appId; |
michael@0 | 1650 | isInBrowserElement = arg.nodePrincipal.isInBrowserElement; |
michael@0 | 1651 | } else { |
michael@0 | 1652 | url = arg.url; |
michael@0 | 1653 | appId = arg.appId; |
michael@0 | 1654 | isInBrowserElement = arg.isInBrowserElement; |
michael@0 | 1655 | } |
michael@0 | 1656 | |
michael@0 | 1657 | return [ url, appId, isInBrowserElement ]; |
michael@0 | 1658 | }, |
michael@0 | 1659 | |
michael@0 | 1660 | addPermission: function(type, allow, arg) { |
michael@0 | 1661 | let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(arg); |
michael@0 | 1662 | |
michael@0 | 1663 | let permission; |
michael@0 | 1664 | if (typeof allow !== 'boolean') { |
michael@0 | 1665 | permission = allow; |
michael@0 | 1666 | } else { |
michael@0 | 1667 | permission = allow ? Ci.nsIPermissionManager.ALLOW_ACTION |
michael@0 | 1668 | : Ci.nsIPermissionManager.DENY_ACTION; |
michael@0 | 1669 | } |
michael@0 | 1670 | |
michael@0 | 1671 | var msg = { |
michael@0 | 1672 | 'op': 'add', |
michael@0 | 1673 | 'type': type, |
michael@0 | 1674 | 'permission': permission, |
michael@0 | 1675 | 'url': url, |
michael@0 | 1676 | 'appId': appId, |
michael@0 | 1677 | 'isInBrowserElement': isInBrowserElement |
michael@0 | 1678 | }; |
michael@0 | 1679 | |
michael@0 | 1680 | this._sendSyncMessage('SPPermissionManager', msg); |
michael@0 | 1681 | }, |
michael@0 | 1682 | |
michael@0 | 1683 | removePermission: function(type, arg) { |
michael@0 | 1684 | let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(arg); |
michael@0 | 1685 | |
michael@0 | 1686 | var msg = { |
michael@0 | 1687 | 'op': 'remove', |
michael@0 | 1688 | 'type': type, |
michael@0 | 1689 | 'url': url, |
michael@0 | 1690 | 'appId': appId, |
michael@0 | 1691 | 'isInBrowserElement': isInBrowserElement |
michael@0 | 1692 | }; |
michael@0 | 1693 | |
michael@0 | 1694 | this._sendSyncMessage('SPPermissionManager', msg); |
michael@0 | 1695 | }, |
michael@0 | 1696 | |
michael@0 | 1697 | hasPermission: function (type, arg) { |
michael@0 | 1698 | let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(arg); |
michael@0 | 1699 | |
michael@0 | 1700 | var msg = { |
michael@0 | 1701 | 'op': 'has', |
michael@0 | 1702 | 'type': type, |
michael@0 | 1703 | 'url': url, |
michael@0 | 1704 | 'appId': appId, |
michael@0 | 1705 | 'isInBrowserElement': isInBrowserElement |
michael@0 | 1706 | }; |
michael@0 | 1707 | |
michael@0 | 1708 | return this._sendSyncMessage('SPPermissionManager', msg)[0]; |
michael@0 | 1709 | }, |
michael@0 | 1710 | testPermission: function (type, value, arg) { |
michael@0 | 1711 | let [url, appId, isInBrowserElement] = this._getInfoFromPermissionArg(arg); |
michael@0 | 1712 | |
michael@0 | 1713 | var msg = { |
michael@0 | 1714 | 'op': 'test', |
michael@0 | 1715 | 'type': type, |
michael@0 | 1716 | 'value': value, |
michael@0 | 1717 | 'url': url, |
michael@0 | 1718 | 'appId': appId, |
michael@0 | 1719 | 'isInBrowserElement': isInBrowserElement |
michael@0 | 1720 | }; |
michael@0 | 1721 | return this._sendSyncMessage('SPPermissionManager', msg)[0]; |
michael@0 | 1722 | }, |
michael@0 | 1723 | |
michael@0 | 1724 | getMozFullPath: function(file) { |
michael@0 | 1725 | return file.mozFullPath; |
michael@0 | 1726 | }, |
michael@0 | 1727 | |
michael@0 | 1728 | isWindowPrivate: function(win) { |
michael@0 | 1729 | return PrivateBrowsingUtils.isWindowPrivate(win); |
michael@0 | 1730 | }, |
michael@0 | 1731 | |
michael@0 | 1732 | notifyObserversInParentProcess: function(subject, topic, data) { |
michael@0 | 1733 | if (subject) { |
michael@0 | 1734 | throw new Error("Can't send subject to another process!"); |
michael@0 | 1735 | } |
michael@0 | 1736 | if (this.isMainProcess()) { |
michael@0 | 1737 | this.notifyObservers(subject, topic, data); |
michael@0 | 1738 | return; |
michael@0 | 1739 | } |
michael@0 | 1740 | var msg = { |
michael@0 | 1741 | 'op': 'notify', |
michael@0 | 1742 | 'observerTopic': topic, |
michael@0 | 1743 | 'observerData': data |
michael@0 | 1744 | }; |
michael@0 | 1745 | this._sendSyncMessage('SPObserverService', msg); |
michael@0 | 1746 | }, |
michael@0 | 1747 | }; |
michael@0 | 1748 | |
michael@0 | 1749 | this.SpecialPowersAPI = SpecialPowersAPI; |
michael@0 | 1750 | this.bindDOMWindowUtils = bindDOMWindowUtils; |
michael@0 | 1751 | this.getRawComponents = getRawComponents; |