testing/specialpowers/content/specialpowersAPI.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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;

mercurial