specialpowersAPI.js 70 KB

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