driver.js 90 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908
  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 file,
  3. * You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. var {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
  6. var loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
  7. .getService(Ci.mozIJSSubScriptLoader);
  8. Cu.import("resource://gre/modules/Log.jsm");
  9. Cu.import("resource://gre/modules/Preferences.jsm");
  10. Cu.import("resource://gre/modules/Services.jsm");
  11. Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  12. XPCOMUtils.defineLazyServiceGetter(
  13. this, "cookieManager", "@mozilla.org/cookiemanager;1", "nsICookieManager2");
  14. Cu.import("chrome://marionette/content/accessibility.js");
  15. Cu.import("chrome://marionette/content/addon.js");
  16. Cu.import("chrome://marionette/content/assert.js");
  17. Cu.import("chrome://marionette/content/atom.js");
  18. Cu.import("chrome://marionette/content/browser.js");
  19. Cu.import("chrome://marionette/content/capture.js");
  20. Cu.import("chrome://marionette/content/cert.js");
  21. Cu.import("chrome://marionette/content/element.js");
  22. Cu.import("chrome://marionette/content/error.js");
  23. Cu.import("chrome://marionette/content/evaluate.js");
  24. Cu.import("chrome://marionette/content/event.js");
  25. Cu.import("chrome://marionette/content/interaction.js");
  26. Cu.import("chrome://marionette/content/l10n.js");
  27. Cu.import("chrome://marionette/content/legacyaction.js");
  28. Cu.import("chrome://marionette/content/logging.js");
  29. Cu.import("chrome://marionette/content/modal.js");
  30. Cu.import("chrome://marionette/content/proxy.js");
  31. Cu.import("chrome://marionette/content/session.js");
  32. Cu.import("chrome://marionette/content/simpletest.js");
  33. this.EXPORTED_SYMBOLS = ["GeckoDriver", "Context"];
  34. var FRAME_SCRIPT = "chrome://marionette/content/listener.js";
  35. const BROWSER_STARTUP_FINISHED = "browser-delayed-startup-finished";
  36. const CLICK_TO_START_PREF = "marionette.debugging.clicktostart";
  37. const CONTENT_LISTENER_PREF = "marionette.contentListener";
  38. const SUPPORTED_STRATEGIES = new Set([
  39. element.Strategy.ClassName,
  40. element.Strategy.Selector,
  41. element.Strategy.ID,
  42. element.Strategy.TagName,
  43. element.Strategy.XPath,
  44. element.Strategy.Anon,
  45. element.Strategy.AnonAttribute,
  46. ]);
  47. const logger = Log.repository.getLogger("Marionette");
  48. const globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"]
  49. .getService(Ci.nsIMessageBroadcaster);
  50. // This is used to prevent newSession from returning before the telephony
  51. // API's are ready; see bug 792647. This assumes that marionette-server.js
  52. // will be loaded before the 'system-message-listener-ready' message
  53. // is fired. If this stops being true, this approach will have to change.
  54. var systemMessageListenerReady = false;
  55. Services.obs.addObserver(function() {
  56. systemMessageListenerReady = true;
  57. }, "system-message-listener-ready", false);
  58. // This is used on desktop to prevent newSession from returning before a page
  59. // load initiated by the Firefox command line has completed.
  60. var delayedBrowserStarted = false;
  61. Services.obs.addObserver(function () {
  62. delayedBrowserStarted = true;
  63. }, BROWSER_STARTUP_FINISHED, false);
  64. this.Context = {
  65. CHROME: "chrome",
  66. CONTENT: "content",
  67. };
  68. this.Context.fromString = function (s) {
  69. s = s.toUpperCase();
  70. if (s in this) {
  71. return this[s];
  72. }
  73. return null;
  74. };
  75. /**
  76. * Implements (parts of) the W3C WebDriver protocol. GeckoDriver lives
  77. * in chrome space and mediates calls to the message listener of the current
  78. * browsing context's content frame message listener via ListenerProxy.
  79. *
  80. * Throughout this prototype, functions with the argument {@code cmd}'s
  81. * documentation refers to the contents of the {@code cmd.parameters}
  82. * object.
  83. *
  84. * @param {string} appName
  85. * Description of the product, for example "B2G" or "Firefox".
  86. * @param {MarionetteServer} server
  87. * The instance of Marionette server.
  88. */
  89. this.GeckoDriver = function (appName, server) {
  90. this.appName = appName;
  91. this._server = server;
  92. this.sessionId = null;
  93. this.wins = new browser.Windows();
  94. this.browsers = {};
  95. // points to current browser
  96. this.curBrowser = null;
  97. // topmost chrome frame
  98. this.mainFrame = null;
  99. // chrome iframe that currently has focus
  100. this.curFrame = null;
  101. this.mainContentFrameId = null;
  102. this.mozBrowserClose = null;
  103. this.currentFrameElement = null;
  104. // frame ID of the current remote frame, used for mozbrowserclose events
  105. this.oopFrameId = null;
  106. this.observing = null;
  107. this._browserIds = new WeakMap();
  108. // The curent context decides if commands should affect chrome- or
  109. // content space.
  110. this.context = Context.CONTENT;
  111. this.importedScripts = new evaluate.ScriptStorageService(
  112. [Context.CHROME, Context.CONTENT]);
  113. this.sandboxes = new Sandboxes(() => this.getCurrentWindow());
  114. this.legacyactions = new legacyaction.Chain();
  115. this.timer = null;
  116. this.inactivityTimer = null;
  117. this.marionetteLog = new logging.ContentLogger();
  118. this.testName = null;
  119. this.capabilities = new session.Capabilities();
  120. this.mm = globalMessageManager;
  121. this.listener = proxy.toListener(() => this.mm, this.sendAsync.bind(this));
  122. // always keep weak reference to current dialogue
  123. this.dialog = null;
  124. let handleDialog = (subject, topic) => {
  125. let winr;
  126. if (topic == modal.COMMON_DIALOG_LOADED) {
  127. winr = Cu.getWeakReference(subject);
  128. }
  129. this.dialog = new modal.Dialog(() => this.curBrowser, winr);
  130. };
  131. modal.addHandler(handleDialog);
  132. };
  133. Object.defineProperty(GeckoDriver.prototype, "a11yChecks", {
  134. get: function () {
  135. return this.capabilities.get("moz:accessibilityChecks");
  136. }
  137. });
  138. Object.defineProperty(GeckoDriver.prototype, "proxy", {
  139. get: function () {
  140. return this.capabilities.get("proxy");
  141. }
  142. });
  143. Object.defineProperty(GeckoDriver.prototype, "secureTLS", {
  144. get: function () {
  145. return !this.capabilities.get("acceptInsecureCerts");
  146. }
  147. });
  148. Object.defineProperty(GeckoDriver.prototype, "timeouts", {
  149. get: function () {
  150. return this.capabilities.get("timeouts");
  151. },
  152. set: function (newTimeouts) {
  153. this.capabilities.set("timeouts", newTimeouts);
  154. },
  155. });
  156. Object.defineProperty(GeckoDriver.prototype, "windowHandles", {
  157. get: function () {
  158. let hs = [];
  159. let winEn = Services.wm.getEnumerator(null);
  160. while (winEn.hasMoreElements()) {
  161. let win = winEn.getNext();
  162. let tabBrowser = browser.getTabBrowser(win);
  163. if (tabBrowser) {
  164. tabBrowser.tabs.forEach(tab => {
  165. let winId = this.getIdForBrowser(browser.getBrowserForTab(tab));
  166. if (winId !== null) {
  167. hs.push(winId);
  168. }
  169. });
  170. } else {
  171. // For other chrome windows beside the browser window, only add the window itself.
  172. hs.push(getOuterWindowId(win));
  173. }
  174. }
  175. return hs;
  176. },
  177. });
  178. Object.defineProperty(GeckoDriver.prototype, "chromeWindowHandles", {
  179. get : function () {
  180. let hs = [];
  181. let winEn = Services.wm.getEnumerator(null);
  182. while (winEn.hasMoreElements()) {
  183. hs.push(getOuterWindowId(winEn.getNext()));
  184. }
  185. return hs;
  186. },
  187. });
  188. GeckoDriver.prototype.QueryInterface = XPCOMUtils.generateQI([
  189. Ci.nsIMessageListener,
  190. Ci.nsIObserver,
  191. Ci.nsISupportsWeakReference,
  192. ]);
  193. /**
  194. * Switches to the global ChromeMessageBroadcaster, potentially replacing
  195. * a frame-specific ChromeMessageSender. Has no effect if the global
  196. * ChromeMessageBroadcaster is already in use. If this replaces a
  197. * frame-specific ChromeMessageSender, it removes the message listeners
  198. * from that sender, and then puts the corresponding frame script "to
  199. * sleep", which removes most of the message listeners from it as well.
  200. */
  201. GeckoDriver.prototype.switchToGlobalMessageManager = function() {
  202. if (this.curBrowser && this.curBrowser.frameManager.currentRemoteFrame !== null) {
  203. this.curBrowser.frameManager.removeMessageManagerListeners(this.mm);
  204. this.sendAsync("sleepSession");
  205. this.curBrowser.frameManager.currentRemoteFrame = null;
  206. }
  207. this.mm = globalMessageManager;
  208. };
  209. /**
  210. * Helper method to send async messages to the content listener.
  211. * Correct usage is to pass in the name of a function in listener.js,
  212. * a serialisable object, and optionally the current command's ID
  213. * when not using the modern dispatching technique.
  214. *
  215. * @param {string} name
  216. * Suffix of the targetted message listener
  217. * ({@code Marionette:<suffix>}).
  218. * @param {Object=} msg
  219. * Optional JSON serialisable object to send to the listener.
  220. * @param {number=} commandID
  221. * Optional command ID to ensure synchronisity.
  222. */
  223. GeckoDriver.prototype.sendAsync = function (name, data, commandID) {
  224. name = "Marionette:" + name;
  225. let payload = copy(data);
  226. // TODO(ato): When proxy.AsyncMessageChannel
  227. // is used for all chrome <-> content communication
  228. // this can be removed.
  229. if (commandID) {
  230. payload.command_id = commandID;
  231. }
  232. if (!this.curBrowser.frameManager.currentRemoteFrame) {
  233. this.broadcastDelayedAsyncMessage_(name, payload);
  234. } else {
  235. this.sendTargettedAsyncMessage_(name, payload);
  236. }
  237. };
  238. GeckoDriver.prototype.broadcastDelayedAsyncMessage_ = function (name, payload) {
  239. this.curBrowser.executeWhenReady(() => {
  240. if (this.curBrowser.curFrameId) {
  241. const target = name + this.curBrowser.curFrameId;
  242. this.mm.broadcastAsyncMessage(target, payload);
  243. } else {
  244. throw new NoSuchWindowError(
  245. "No such content frame; perhaps the listener was not registered?");
  246. }
  247. });
  248. };
  249. GeckoDriver.prototype.sendTargettedAsyncMessage_ = function (name, payload) {
  250. const curRemoteFrame = this.curBrowser.frameManager.currentRemoteFrame;
  251. const target = name + curRemoteFrame.targetFrameId;
  252. try {
  253. this.mm.sendAsyncMessage(target, payload);
  254. } catch (e) {
  255. switch (e.result) {
  256. case Cr.NS_ERROR_FAILURE:
  257. case Cr.NS_ERROR_NOT_INITIALIZED:
  258. throw new NoSuchWindowError();
  259. default:
  260. throw new WebDriverError(e);
  261. }
  262. }
  263. };
  264. /**
  265. * Gets the current active window.
  266. *
  267. * @return {nsIDOMWindow}
  268. */
  269. GeckoDriver.prototype.getCurrentWindow = function() {
  270. let typ = null;
  271. if (this.curFrame === null) {
  272. if (this.curBrowser === null) {
  273. if (this.context == Context.CONTENT) {
  274. typ = "navigator:browser";
  275. }
  276. return Services.wm.getMostRecentWindow(typ);
  277. } else {
  278. return this.curBrowser.window;
  279. }
  280. } else {
  281. return this.curFrame;
  282. }
  283. };
  284. GeckoDriver.prototype.addFrameCloseListener = function (action) {
  285. let win = this.getCurrentWindow();
  286. this.mozBrowserClose = e => {
  287. if (e.target.id == this.oopFrameId) {
  288. win.removeEventListener("mozbrowserclose", this.mozBrowserClose, true);
  289. this.switchToGlobalMessageManager();
  290. throw new NoSuchWindowError("The window closed during action: " + action);
  291. }
  292. };
  293. win.addEventListener("mozbrowserclose", this.mozBrowserClose, true);
  294. };
  295. /**
  296. * Create a new browsing context for window and add to known browsers.
  297. *
  298. * @param {nsIDOMWindow} win
  299. * Window for which we will create a browsing context.
  300. *
  301. * @return {string}
  302. * Returns the unique server-assigned ID of the window.
  303. */
  304. GeckoDriver.prototype.addBrowser = function (win) {
  305. let bc = new browser.Context(win, this);
  306. let winId = getOuterWindowId(win);
  307. this.browsers[winId] = bc;
  308. this.curBrowser = this.browsers[winId];
  309. if (!this.wins.has(winId)) {
  310. // add this to seenItems so we can guarantee
  311. // the user will get winId as this window's id
  312. this.wins.set(winId, win);
  313. }
  314. };
  315. /**
  316. * Registers a new browser, win, with Marionette.
  317. *
  318. * If we have not seen the browser content window before, the listener
  319. * frame script will be loaded into it. If isNewSession is true, we will
  320. * switch focus to the start frame when it registers.
  321. *
  322. * @param {nsIDOMWindow} win
  323. * Window whose browser we need to access.
  324. * @param {boolean=false} isNewSession
  325. * True if this is the first time we're talking to this browser.
  326. */
  327. GeckoDriver.prototype.startBrowser = function (win, isNewSession = false) {
  328. this.mainFrame = win;
  329. this.curFrame = null;
  330. this.addBrowser(win);
  331. this.curBrowser.isNewSession = isNewSession;
  332. this.curBrowser.startSession(isNewSession, win, this.whenBrowserStarted.bind(this));
  333. };
  334. /**
  335. * Callback invoked after a new session has been started in a browser.
  336. * Loads the Marionette frame script into the browser if needed.
  337. *
  338. * @param {nsIDOMWindow} win
  339. * Window whose browser we need to access.
  340. * @param {boolean} isNewSession
  341. * True if this is the first time we're talking to this browser.
  342. */
  343. GeckoDriver.prototype.whenBrowserStarted = function (win, isNewSession) {
  344. let mm = win.window.messageManager;
  345. if (mm) {
  346. if (!isNewSession) {
  347. // Loading the frame script corresponds to a situation we need to
  348. // return to the server. If the messageManager is a message broadcaster
  349. // with no children, we don't have a hope of coming back from this call,
  350. // so send the ack here. Otherwise, make a note of how many child scripts
  351. // will be loaded so we known when it's safe to return.
  352. // Child managers may not have child scripts yet (e.g. socialapi), only
  353. // count child managers that have children, but only count the top level
  354. // children as they are the ones that we expect a response from.
  355. if (mm.childCount !== 0) {
  356. this.curBrowser.frameRegsPending = 0;
  357. for (let i = 0; i < mm.childCount; i++) {
  358. if (mm.getChildAt(i).childCount !== 0) {
  359. this.curBrowser.frameRegsPending += 1;
  360. }
  361. }
  362. }
  363. }
  364. if (!Preferences.get(CONTENT_LISTENER_PREF) || !isNewSession) {
  365. // load listener into the remote frame
  366. // and any applicable new frames
  367. // opened after this call
  368. mm.loadFrameScript(FRAME_SCRIPT, true);
  369. Preferences.set(CONTENT_LISTENER_PREF, true);
  370. }
  371. } else {
  372. logger.error(
  373. `Could not load listener into content for page ${win.location.href}`);
  374. }
  375. };
  376. /**
  377. * Recursively get all labeled text.
  378. *
  379. * @param {nsIDOMElement} el
  380. * The parent element.
  381. * @param {Array.<string>} lines
  382. * Array that holds the text lines.
  383. */
  384. GeckoDriver.prototype.getVisibleText = function (el, lines) {
  385. try {
  386. if (atom.isElementDisplayed(el, this.getCurrentWindow())) {
  387. if (el.value) {
  388. lines.push(el.value);
  389. }
  390. for (let child in el.childNodes) {
  391. this.getVisibleText(el.childNodes[child], lines);
  392. }
  393. }
  394. } catch (e) {
  395. if (el.nodeName == "#text") {
  396. lines.push(el.textContent);
  397. }
  398. }
  399. };
  400. /**
  401. * Handles registration of new content listener browsers. Depending on
  402. * their type they are either accepted or ignored.
  403. */
  404. GeckoDriver.prototype.registerBrowser = function (id, be) {
  405. let nullPrevious = this.curBrowser.curFrameId === null;
  406. let listenerWindow = Services.wm.getOuterWindowWithId(id);
  407. // go in here if we're already in a remote frame
  408. if (this.curBrowser.frameManager.currentRemoteFrame !== null &&
  409. (!listenerWindow || this.mm == this.curBrowser.frameManager
  410. .currentRemoteFrame.messageManager.get())) {
  411. // The outerWindowID from an OOP frame will not be meaningful to
  412. // the parent process here, since each process maintains its own
  413. // independent window list. So, it will either be null (!listenerWindow)
  414. // if we're already in a remote frame, or it will point to some
  415. // random window, which will hopefully cause an href mismatch.
  416. // Currently this only happens in B2G for OOP frames registered in
  417. // Marionette:switchToFrame, so we'll acknowledge the switchToFrame
  418. // message here.
  419. //
  420. // TODO: Should have a better way of determining that this message
  421. // is from a remote frame.
  422. this.curBrowser.frameManager.currentRemoteFrame.targetFrameId =
  423. this.generateFrameId(id);
  424. }
  425. let reg = {};
  426. // this will be sent to tell the content process if it is the main content
  427. let mainContent = this.curBrowser.mainContentId === null;
  428. if (be.getAttribute("type") != "content") {
  429. // curBrowser holds all the registered frames in knownFrames
  430. let uid = this.generateFrameId(id);
  431. reg.id = uid;
  432. reg.remotenessChange = this.curBrowser.register(uid, be);
  433. }
  434. // set to true if we updated mainContentId
  435. mainContent = mainContent && this.curBrowser.mainContentId !== null;
  436. if (mainContent) {
  437. this.mainContentFrameId = this.curBrowser.curFrameId;
  438. }
  439. this.wins.set(reg.id, listenerWindow);
  440. if (nullPrevious && (this.curBrowser.curFrameId !== null)) {
  441. this.sendAsync(
  442. "newSession",
  443. this.capabilities.toJSON(),
  444. this.newSessionCommandId);
  445. if (this.curBrowser.isNewSession) {
  446. this.newSessionCommandId = null;
  447. }
  448. }
  449. return [reg, mainContent, this.capabilities.toJSON()];
  450. };
  451. GeckoDriver.prototype.registerPromise = function () {
  452. const li = "Marionette:register";
  453. return new Promise(resolve => {
  454. let cb = msg => {
  455. let wid = msg.json.value;
  456. let be = msg.target;
  457. let rv = this.registerBrowser(wid, be);
  458. if (this.curBrowser.frameRegsPending > 0) {
  459. this.curBrowser.frameRegsPending--;
  460. }
  461. if (this.curBrowser.frameRegsPending === 0) {
  462. this.mm.removeMessageListener(li, cb);
  463. resolve();
  464. }
  465. // this is a sync message and listeners expect the ID back
  466. return rv;
  467. };
  468. this.mm.addMessageListener(li, cb);
  469. });
  470. };
  471. GeckoDriver.prototype.listeningPromise = function () {
  472. const li = "Marionette:listenersAttached";
  473. return new Promise(resolve => {
  474. let cb = () => {
  475. this.mm.removeMessageListener(li, cb);
  476. resolve();
  477. };
  478. this.mm.addMessageListener(li, cb);
  479. });
  480. };
  481. /** Create a new session. */
  482. GeckoDriver.prototype.newSession = function* (cmd, resp) {
  483. if (this.sessionId) {
  484. throw new SessionNotCreatedError("Maximum number of active sessions");
  485. }
  486. this.sessionId = cmd.parameters.sessionId ||
  487. cmd.parameters.session_id ||
  488. element.generateUUID();
  489. this.newSessionCommandId = cmd.id;
  490. try {
  491. this.capabilities = session.Capabilities.fromJSON(
  492. cmd.parameters.capabilities, {merge: true});
  493. logger.config("Matched capabilities: " +
  494. JSON.stringify(this.capabilities));
  495. } catch (e) {
  496. throw new SessionNotCreatedError(e);
  497. }
  498. if (!this.secureTLS) {
  499. logger.warn("TLS certificate errors will be ignored for this session");
  500. let acceptAllCerts = new cert.InsecureSweepingOverride();
  501. cert.installOverride(acceptAllCerts);
  502. }
  503. if (this.proxy.init()) {
  504. logger.info("Proxy settings initialised: " + JSON.stringify(this.proxy));
  505. }
  506. // If we are testing accessibility with marionette, start a11y service in
  507. // chrome first. This will ensure that we do not have any content-only
  508. // services hanging around.
  509. if (this.a11yChecks && accessibility.service) {
  510. logger.info("Preemptively starting accessibility service in Chrome");
  511. }
  512. let registerBrowsers = this.registerPromise();
  513. let browserListening = this.listeningPromise();
  514. let waitForWindow = function() {
  515. let win = this.getCurrentWindow();
  516. if (!win) {
  517. // if the window isn't even created, just poll wait for it
  518. let checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  519. checkTimer.initWithCallback(waitForWindow.bind(this), 100,
  520. Ci.nsITimer.TYPE_ONE_SHOT);
  521. } else if (win.document.readyState != "complete") {
  522. // otherwise, wait for it to be fully loaded before proceeding
  523. let listener = ev => {
  524. // ensure that we proceed, on the top level document load event
  525. // (not an iframe one...)
  526. if (ev.target != win.document) {
  527. return;
  528. }
  529. win.removeEventListener("load", listener);
  530. waitForWindow.call(this);
  531. };
  532. win.addEventListener("load", listener, true);
  533. } else {
  534. let clickToStart = Preferences.get(CLICK_TO_START_PREF);
  535. if (clickToStart && (this.appName != "B2G")) {
  536. let pService = Cc["@mozilla.org/embedcomp/prompt-service;1"]
  537. .getService(Ci.nsIPromptService);
  538. pService.alert(win, "", "Click to start execution of marionette tests");
  539. }
  540. this.startBrowser(win, true);
  541. }
  542. };
  543. let runSessionStart = function() {
  544. if (!Preferences.get(CONTENT_LISTENER_PREF)) {
  545. waitForWindow.call(this);
  546. } else if (this.appName != "Firefox" && this.curBrowser === null) {
  547. // if there is a content listener, then we just wake it up
  548. this.addBrowser(this.getCurrentWindow());
  549. this.curBrowser.startSession(this.whenBrowserStarted.bind(this));
  550. this.mm.broadcastAsyncMessage("Marionette:restart", {});
  551. } else {
  552. throw new WebDriverError("Session already running");
  553. }
  554. this.switchToGlobalMessageManager();
  555. };
  556. if (!delayedBrowserStarted && this.appName != "B2G") {
  557. let self = this;
  558. Services.obs.addObserver(function onStart() {
  559. Services.obs.removeObserver(onStart, BROWSER_STARTUP_FINISHED);
  560. runSessionStart.call(self);
  561. }, BROWSER_STARTUP_FINISHED, false);
  562. } else {
  563. runSessionStart.call(this);
  564. }
  565. yield registerBrowsers;
  566. yield browserListening;
  567. if (this.curBrowser.tab) {
  568. browser.getBrowserForTab(this.curBrowser.tab).focus();
  569. }
  570. return {
  571. sessionId: this.sessionId,
  572. capabilities: this.capabilities,
  573. };
  574. };
  575. /**
  576. * Send the current session's capabilities to the client.
  577. *
  578. * Capabilities informs the client of which WebDriver features are
  579. * supported by Firefox and Marionette. They are immutable for the
  580. * length of the session.
  581. *
  582. * The return value is an immutable map of string keys
  583. * ("capabilities") to values, which may be of types boolean,
  584. * numerical or string.
  585. */
  586. GeckoDriver.prototype.getSessionCapabilities = function (cmd, resp) {
  587. resp.body.capabilities = this.capabilities;
  588. };
  589. /**
  590. * Log message. Accepts user defined log-level.
  591. *
  592. * @param {string} value
  593. * Log message.
  594. * @param {string} level
  595. * Arbitrary log level.
  596. */
  597. GeckoDriver.prototype.log = function (cmd, resp) {
  598. // if level is null, we want to use ContentLogger#send's default
  599. this.marionetteLog.log(
  600. cmd.parameters.value,
  601. cmd.parameters.level || undefined);
  602. };
  603. /** Return all logged messages. */
  604. GeckoDriver.prototype.getLogs = function (cmd, resp) {
  605. resp.body = this.marionetteLog.get();
  606. };
  607. /**
  608. * Sets the context of the subsequent commands to be either "chrome" or
  609. * "content".
  610. *
  611. * @param {string} value
  612. * Name of the context to be switched to. Must be one of "chrome" or
  613. * "content".
  614. */
  615. GeckoDriver.prototype.setContext = function (cmd, resp) {
  616. let val = cmd.parameters.value;
  617. let ctx = Context.fromString(val);
  618. if (ctx === null) {
  619. throw new WebDriverError(`Invalid context: ${val}`);
  620. }
  621. this.context = ctx;
  622. };
  623. /** Gets the context of the server, either "chrome" or "content". */
  624. GeckoDriver.prototype.getContext = function (cmd, resp) {
  625. resp.body.value = this.context.toString();
  626. };
  627. /**
  628. * Executes a JavaScript function in the context of the current browsing
  629. * context, if in content space, or in chrome space otherwise, and returns
  630. * the return value of the function.
  631. *
  632. * It is important to note that if the {@code sandboxName} parameter
  633. * is left undefined, the script will be evaluated in a mutable sandbox,
  634. * causing any change it makes on the global state of the document to have
  635. * lasting side-effects.
  636. *
  637. * @param {string} script
  638. * Script to evaluate as a function body.
  639. * @param {Array.<(string|boolean|number|object|WebElement)>} args
  640. * Arguments exposed to the script in {@code arguments}. The array
  641. * items must be serialisable to the WebDriver protocol.
  642. * @param {number} scriptTimeout
  643. * Duration in milliseconds of when to interrupt and abort the
  644. * script evaluation.
  645. * @param {string=} sandbox
  646. * Name of the sandbox to evaluate the script in. The sandbox is
  647. * cached for later re-use on the same Window object if
  648. * {@code newSandbox} is false. If he parameter is undefined,
  649. * the script is evaluated in a mutable sandbox. If the parameter
  650. * is "system", it will be evaluted in a sandbox with elevated system
  651. * privileges, equivalent to chrome space.
  652. * @param {boolean=} newSandbox
  653. * Forces the script to be evaluated in a fresh sandbox. Note that if
  654. * it is undefined, the script will normally be evaluted in a fresh
  655. * sandbox.
  656. * @param {string=} filename
  657. * Filename of the client's program where this script is evaluated.
  658. * @param {number=} line
  659. * Line in the client's program where this script is evaluated.
  660. * @param {boolean=} debug_script
  661. * Attach an {@code onerror} event handler on the Window object.
  662. * It does not differentiate content errors from chrome errors.
  663. * @param {boolean=} directInject
  664. * Evaluate the script without wrapping it in a function.
  665. *
  666. * @return {(string|boolean|number|object|WebElement)}
  667. * Return value from the script, or null which signifies either the
  668. * JavaScript notion of null or undefined.
  669. *
  670. * @throws ScriptTimeoutError
  671. * If the script was interrupted due to reaching the {@code
  672. * scriptTimeout} or default timeout.
  673. * @throws JavaScriptError
  674. * If an Error was thrown whilst evaluating the script.
  675. */
  676. GeckoDriver.prototype.executeScript = function*(cmd, resp) {
  677. let {script, args, scriptTimeout} = cmd.parameters;
  678. scriptTimeout = scriptTimeout || this.timeouts.script;
  679. let opts = {
  680. sandboxName: cmd.parameters.sandbox,
  681. newSandbox: !!(typeof cmd.parameters.newSandbox == "undefined") ||
  682. cmd.parameters.newSandbox,
  683. filename: cmd.parameters.filename,
  684. line: cmd.parameters.line,
  685. debug: cmd.parameters.debug_script,
  686. };
  687. resp.body.value = yield this.execute_(script, args, scriptTimeout, opts);
  688. };
  689. /**
  690. * Executes a JavaScript function in the context of the current browsing
  691. * context, if in content space, or in chrome space otherwise, and returns
  692. * the object passed to the callback.
  693. *
  694. * The callback is always the last argument to the {@code arguments}
  695. * list passed to the function scope of the script. It can be retrieved
  696. * as such:
  697. *
  698. * let callback = arguments[arguments.length - 1];
  699. * callback("foo");
  700. * // "foo" is returned
  701. *
  702. * It is important to note that if the {@code sandboxName} parameter
  703. * is left undefined, the script will be evaluated in a mutable sandbox,
  704. * causing any change it makes on the global state of the document to have
  705. * lasting side-effects.
  706. *
  707. * @param {string} script
  708. * Script to evaluate as a function body.
  709. * @param {Array.<(string|boolean|number|object|WebElement)>} args
  710. * Arguments exposed to the script in {@code arguments}. The array
  711. * items must be serialisable to the WebDriver protocol.
  712. * @param {number} scriptTimeout
  713. * Duration in milliseconds of when to interrupt and abort the
  714. * script evaluation.
  715. * @param {string=} sandbox
  716. * Name of the sandbox to evaluate the script in. The sandbox is
  717. * cached for later re-use on the same Window object if
  718. * {@code newSandbox} is false. If the parameter is undefined,
  719. * the script is evaluated in a mutable sandbox. If the parameter
  720. * is "system", it will be evaluted in a sandbox with elevated system
  721. * privileges, equivalent to chrome space.
  722. * @param {boolean=} newSandbox
  723. * Forces the script to be evaluated in a fresh sandbox. Note that if
  724. * it is undefined, the script will normally be evaluted in a fresh
  725. * sandbox.
  726. * @param {string=} filename
  727. * Filename of the client's program where this script is evaluated.
  728. * @param {number=} line
  729. * Line in the client's program where this script is evaluated.
  730. * @param {boolean=} debug_script
  731. * Attach an {@code onerror} event handler on the Window object.
  732. * It does not differentiate content errors from chrome errors.
  733. * @param {boolean=} directInject
  734. * Evaluate the script without wrapping it in a function.
  735. *
  736. * @return {(string|boolean|number|object|WebElement)}
  737. * Return value from the script, or null which signifies either the
  738. * JavaScript notion of null or undefined.
  739. *
  740. * @throws ScriptTimeoutError
  741. * If the script was interrupted due to reaching the {@code
  742. * scriptTimeout} or default timeout.
  743. * @throws JavaScriptError
  744. * If an Error was thrown whilst evaluating the script.
  745. */
  746. GeckoDriver.prototype.executeAsyncScript = function* (cmd, resp) {
  747. let {script, args, scriptTimeout} = cmd.parameters;
  748. scriptTimeout = scriptTimeout || this.timeouts.script;
  749. let opts = {
  750. sandboxName: cmd.parameters.sandbox,
  751. newSandbox: !!(typeof cmd.parameters.newSandbox == "undefined") ||
  752. cmd.parameters.newSandbox,
  753. filename: cmd.parameters.filename,
  754. line: cmd.parameters.line,
  755. debug: cmd.parameters.debug_script,
  756. async: true,
  757. };
  758. resp.body.value = yield this.execute_(script, args, scriptTimeout, opts);
  759. };
  760. GeckoDriver.prototype.execute_ = function (script, args, timeout, opts = {}) {
  761. switch (this.context) {
  762. case Context.CONTENT:
  763. // evaluate in content with lasting side-effects
  764. if (!opts.sandboxName) {
  765. return this.listener.execute(script, args, timeout, opts);
  766. // evaluate in content with sandbox
  767. } else {
  768. return this.listener.executeInSandbox(script, args, timeout, opts);
  769. }
  770. case Context.CHROME:
  771. let sb = this.sandboxes.get(opts.sandboxName, opts.newSandbox);
  772. if (opts.sandboxName) {
  773. sb = sandbox.augment(sb, new logging.Adapter(this.marionetteLog));
  774. sb = sandbox.augment(sb, {global: sb});
  775. }
  776. opts.timeout = timeout;
  777. script = this.importedScripts.for(Context.CHROME).concat(script);
  778. let wargs = element.fromJson(args, this.curBrowser.seenEls, sb.window);
  779. let evaluatePromise = evaluate.sandbox(sb, script, wargs, opts);
  780. return evaluatePromise.then(res => element.toJson(res, this.curBrowser.seenEls));
  781. }
  782. };
  783. /**
  784. * Execute pure JavaScript. Used to execute simpletest harness tests,
  785. * which are like mochitests only injected using Marionette.
  786. *
  787. * Scripts are expected to call the {@code finish} global when done.
  788. */
  789. GeckoDriver.prototype.executeJSScript = function* (cmd, resp) {
  790. let {script, args, scriptTimeout} = cmd.parameters;
  791. scriptTimeout = scriptTimeout || this.timeouts.script;
  792. let opts = {
  793. filename: cmd.parameters.filename,
  794. line: cmd.parameters.line,
  795. async: cmd.parameters.async,
  796. };
  797. switch (this.context) {
  798. case Context.CHROME:
  799. let win = this.getCurrentWindow();
  800. let wargs = element.fromJson(args, this.curBrowser.seenEls, win);
  801. let harness = new simpletest.Harness(
  802. win,
  803. Context.CHROME,
  804. this.marionetteLog,
  805. scriptTimeout,
  806. function() {},
  807. this.testName);
  808. let sb = sandbox.createSimpleTest(win, harness);
  809. // TODO(ato): Not sure this is needed:
  810. sb = sandbox.augment(sb, new logging.Adapter(this.marionetteLog));
  811. let res = yield evaluate.sandbox(sb, script, wargs, opts);
  812. resp.body.value = element.toJson(res, this.curBrowser.seenEls);
  813. break;
  814. case Context.CONTENT:
  815. resp.body.value = yield this.listener.executeSimpleTest(script, args, scriptTimeout, opts);
  816. break;
  817. }
  818. };
  819. /**
  820. * Navigate to given URL.
  821. *
  822. * Navigates the current browsing context to the given URL and waits for
  823. * the document to load or the session's page timeout duration to elapse
  824. * before returning.
  825. *
  826. * The command will return with a failure if there is an error loading
  827. * the document or the URL is blocked. This can occur if it fails to
  828. * reach host, the URL is malformed, or if there is a certificate issue
  829. * to name some examples.
  830. *
  831. * The document is considered successfully loaded when the
  832. * DOMContentLoaded event on the frame element associated with the
  833. * current window triggers and document.readyState is "complete".
  834. *
  835. * In chrome context it will change the current window's location to
  836. * the supplied URL and wait until document.readyState equals "complete"
  837. * or the page timeout duration has elapsed.
  838. *
  839. * @param {string} url
  840. * URL to navigate to.
  841. */
  842. GeckoDriver.prototype.get = function*(cmd, resp) {
  843. assert.content(this.context);
  844. let url = cmd.parameters.url;
  845. let get = this.listener.get({url: url, pageTimeout: this.timeouts.pageLoad});
  846. // If a remoteness update interrupts our page load, this will never return
  847. // We need to re-issue this request to correctly poll for readyState and
  848. // send errors.
  849. this.curBrowser.pendingCommands.push(() => {
  850. let parameters = {
  851. // TODO(ato): Bug 1242595
  852. command_id: this.listener.activeMessageId,
  853. pageTimeout: this.timeouts.pageLoad,
  854. startTime: new Date().getTime(),
  855. };
  856. this.mm.broadcastAsyncMessage(
  857. "Marionette:pollForReadyState" + this.curBrowser.curFrameId,
  858. parameters);
  859. });
  860. yield get;
  861. browser.getBrowserForTab(this.curBrowser.tab).focus();
  862. };
  863. /**
  864. * Get a string representing the current URL.
  865. *
  866. * On Desktop this returns a string representation of the URL of the
  867. * current top level browsing context. This is equivalent to
  868. * document.location.href.
  869. *
  870. * When in the context of the chrome, this returns the canonical URL
  871. * of the current resource.
  872. */
  873. GeckoDriver.prototype.getCurrentUrl = function (cmd) {
  874. switch (this.context) {
  875. case Context.CHROME:
  876. return this.getCurrentWindow().location.href;
  877. case Context.CONTENT:
  878. let isB2G = this.appName == "B2G";
  879. return this.listener.getCurrentUrl(isB2G);
  880. }
  881. };
  882. /** Gets the current title of the window. */
  883. GeckoDriver.prototype.getTitle = function* (cmd, resp) {
  884. switch (this.context) {
  885. case Context.CHROME:
  886. let win = this.getCurrentWindow();
  887. resp.body.value = win.document.documentElement.getAttribute("title");
  888. break;
  889. case Context.CONTENT:
  890. resp.body.value = yield this.listener.getTitle();
  891. break;
  892. }
  893. };
  894. /** Gets the current type of the window. */
  895. GeckoDriver.prototype.getWindowType = function (cmd, resp) {
  896. let win = this.getCurrentWindow();
  897. resp.body.value = win.document.documentElement.getAttribute("windowtype");
  898. };
  899. /** Gets the page source of the content document. */
  900. GeckoDriver.prototype.getPageSource = function* (cmd, resp) {
  901. switch (this.context) {
  902. case Context.CHROME:
  903. let win = this.getCurrentWindow();
  904. let s = new win.XMLSerializer();
  905. resp.body.value = s.serializeToString(win.document);
  906. break;
  907. case Context.CONTENT:
  908. resp.body.value = yield this.listener.getPageSource();
  909. break;
  910. }
  911. };
  912. /**
  913. * Cause the browser to traverse one step backward in the joint history
  914. * of the current browsing context.
  915. */
  916. GeckoDriver.prototype.goBack = function* (cmd, resp) {
  917. assert.content(this.context);
  918. if (!this.curBrowser.tab) {
  919. // Navigation does not work for non-browser windows
  920. return;
  921. }
  922. let contentBrowser = browser.getBrowserForTab(this.curBrowser.tab)
  923. if (!contentBrowser.webNavigation.canGoBack) {
  924. return;
  925. }
  926. let currentURL = yield this.listener.getCurrentUrl();
  927. let goBack = this.listener.goBack({pageTimeout: this.timeouts.pageLoad});
  928. // If a remoteness update interrupts our page load, this will never return
  929. // We need to re-issue this request to correctly poll for readyState and
  930. // send errors.
  931. this.curBrowser.pendingCommands.push(() => {
  932. let parameters = {
  933. // TODO(ato): Bug 1242595
  934. command_id: this.listener.activeMessageId,
  935. lastSeenURL: currentURL,
  936. pageTimeout: this.timeouts.pageLoad,
  937. startTime: new Date().getTime(),
  938. };
  939. this.mm.broadcastAsyncMessage(
  940. // TODO: combine with
  941. // "Marionette:pollForReadyState" + this.curBrowser.curFrameId,
  942. "Marionette:pollForReadyState" + this.curBrowser.curFrameId,
  943. parameters);
  944. });
  945. yield goBack;
  946. };
  947. /**
  948. * Cause the browser to traverse one step forward in the joint history
  949. * of the current browsing context.
  950. */
  951. GeckoDriver.prototype.goForward = function* (cmd, resp) {
  952. assert.content(this.context);
  953. if (!this.curBrowser.tab) {
  954. // Navigation does not work for non-browser windows
  955. return;
  956. }
  957. let contentBrowser = browser.getBrowserForTab(this.curBrowser.tab)
  958. if (!contentBrowser.webNavigation.canGoForward) {
  959. return;
  960. }
  961. let currentURL = yield this.listener.getCurrentUrl();
  962. let goForward = this.listener.goForward({pageTimeout: this.timeouts.pageLoad});
  963. // If a remoteness update interrupts our page load, this will never return
  964. // We need to re-issue this request to correctly poll for readyState and
  965. // send errors.
  966. this.curBrowser.pendingCommands.push(() => {
  967. let parameters = {
  968. // TODO(ato): Bug 1242595
  969. command_id: this.listener.activeMessageId,
  970. lastSeenURL: currentURL,
  971. pageTimeout: this.timeouts.pageLoad,
  972. startTime: new Date().getTime(),
  973. };
  974. this.mm.broadcastAsyncMessage(
  975. // TODO: combine with
  976. // "Marionette:pollForReadyState" + this.curBrowser.curFrameId,
  977. "Marionette:pollForReadyState" + this.curBrowser.curFrameId,
  978. parameters);
  979. });
  980. yield goForward;
  981. };
  982. /** Refresh the page. */
  983. GeckoDriver.prototype.refresh = function*(cmd, resp) {
  984. assert.content(this.context);
  985. yield this.listener.refresh();
  986. };
  987. /**
  988. * Forces an update for the given browser's id.
  989. */
  990. GeckoDriver.prototype.updateIdForBrowser = function (browser, newId) {
  991. this._browserIds.set(browser.permanentKey, newId);
  992. };
  993. /**
  994. * Retrieves a listener id for the given xul browser element. In case
  995. * the browser is not known, an attempt is made to retrieve the id from
  996. * a CPOW, and null is returned if this fails.
  997. */
  998. GeckoDriver.prototype.getIdForBrowser = function (browser) {
  999. if (browser === null) {
  1000. return null;
  1001. }
  1002. let permKey = browser.permanentKey;
  1003. if (this._browserIds.has(permKey)) {
  1004. return this._browserIds.get(permKey);
  1005. }
  1006. let winId = browser.outerWindowID;
  1007. if (winId) {
  1008. winId = winId.toString();
  1009. this._browserIds.set(permKey, winId);
  1010. return winId;
  1011. }
  1012. return null;
  1013. },
  1014. /**
  1015. * Get the current window's handle. On desktop this typically corresponds
  1016. * to the currently selected tab.
  1017. *
  1018. * Return an opaque server-assigned identifier to this window that
  1019. * uniquely identifies it within this Marionette instance. This can
  1020. * be used to switch to this window at a later point.
  1021. *
  1022. * @return {string}
  1023. * Unique window handle.
  1024. */
  1025. GeckoDriver.prototype.getWindowHandle = function (cmd, resp) {
  1026. // curFrameId always holds the current tab.
  1027. if (this.curBrowser.curFrameId) {
  1028. resp.body.value = this.curBrowser.curFrameId;
  1029. return;
  1030. }
  1031. for (let i in this.browsers) {
  1032. if (this.curBrowser == this.browsers[i]) {
  1033. resp.body.value = i;
  1034. return;
  1035. }
  1036. }
  1037. };
  1038. /**
  1039. * Get a list of top-level browsing contexts. On desktop this typically
  1040. * corresponds to the set of open tabs for browser windows, or the window itself
  1041. * for non-browser chrome windows.
  1042. *
  1043. * Each window handle is assigned by the server and is guaranteed unique,
  1044. * however the return array does not have a specified ordering.
  1045. *
  1046. * @return {Array.<string>}
  1047. * Unique window handles.
  1048. */
  1049. GeckoDriver.prototype.getWindowHandles = function (cmd, resp) {
  1050. return this.windowHandles;
  1051. }
  1052. /**
  1053. * Get the current window's handle. This corresponds to a window that
  1054. * may itself contain tabs.
  1055. *
  1056. * Return an opaque server-assigned identifier to this window that
  1057. * uniquely identifies it within this Marionette instance. This can
  1058. * be used to switch to this window at a later point.
  1059. *
  1060. * @return {string}
  1061. * Unique window handle.
  1062. */
  1063. GeckoDriver.prototype.getChromeWindowHandle = function (cmd, resp) {
  1064. for (let i in this.browsers) {
  1065. if (this.curBrowser == this.browsers[i]) {
  1066. resp.body.value = i;
  1067. return;
  1068. }
  1069. }
  1070. };
  1071. /**
  1072. * Returns identifiers for each open chrome window for tests interested in
  1073. * managing a set of chrome windows and tabs separately.
  1074. *
  1075. * @return {Array.<string>}
  1076. * Unique window handles.
  1077. */
  1078. GeckoDriver.prototype.getChromeWindowHandles = function (cmd, resp) {
  1079. return this.chromeWindowHandles;
  1080. }
  1081. /**
  1082. * Get the current window position.
  1083. *
  1084. * @return {Object.<string, number>}
  1085. * Object with |x| and |y| coordinates.
  1086. */
  1087. GeckoDriver.prototype.getWindowPosition = function (cmd, resp) {
  1088. return this.curBrowser.position;
  1089. };
  1090. /**
  1091. * Set the window position of the browser on the OS Window Manager
  1092. *
  1093. * @param {number} x
  1094. * X coordinate of the top/left of the window that it will be
  1095. * moved to.
  1096. * @param {number} y
  1097. * Y coordinate of the top/left of the window that it will be
  1098. * moved to.
  1099. *
  1100. * @return {Object.<string, number>}
  1101. * Object with |x| and |y| coordinates.
  1102. */
  1103. GeckoDriver.prototype.setWindowPosition = function (cmd, resp) {
  1104. assert.firefox()
  1105. let {x, y} = cmd.parameters;
  1106. assert.positiveInteger(x);
  1107. assert.positiveInteger(y);
  1108. let win = this.getCurrentWindow();
  1109. win.moveTo(x, y);
  1110. return this.curBrowser.position;
  1111. };
  1112. /**
  1113. * Switch current top-level browsing context by name or server-assigned ID.
  1114. * Searches for windows by name, then ID. Content windows take precedence.
  1115. *
  1116. * @param {string} name
  1117. * Target name or ID of the window to switch to.
  1118. * @param {boolean=} focus
  1119. * A boolean value which determines whether to focus
  1120. * the window. Defaults to true.
  1121. */
  1122. GeckoDriver.prototype.switchToWindow = function* (cmd, resp) {
  1123. let switchTo = cmd.parameters.name;
  1124. let focus = (cmd.parameters.focus !== undefined) ? cmd.parameters.focus : true;
  1125. let found;
  1126. let byNameOrId = function (name, windowId) {
  1127. return switchTo === name || switchTo === windowId;
  1128. };
  1129. let winEn = Services.wm.getEnumerator(null);
  1130. while (winEn.hasMoreElements()) {
  1131. let win = winEn.getNext();
  1132. let outerId = getOuterWindowId(win);
  1133. let tabBrowser = browser.getTabBrowser(win);
  1134. if (byNameOrId(win.name, outerId)) {
  1135. // In case the wanted window is a chrome window, we are done.
  1136. found = {win: win, outerId: outerId, hasTabBrowser: !!tabBrowser};
  1137. break;
  1138. } else if (tabBrowser) {
  1139. // Otherwise check if the chrome window has a tab browser, and that it
  1140. // contains a tab with the wanted window handle.
  1141. for (let i = 0; i < tabBrowser.tabs.length; ++i) {
  1142. let contentBrowser = browser.getBrowserForTab(tabBrowser.tabs[i]);
  1143. let contentWindowId = this.getIdForBrowser(contentBrowser);
  1144. if (byNameOrId(win.name, contentWindowId)) {
  1145. found = {
  1146. win: win,
  1147. outerId: outerId,
  1148. hasTabBrowser: true,
  1149. tabIndex: i,
  1150. };
  1151. break;
  1152. }
  1153. }
  1154. }
  1155. }
  1156. if (found) {
  1157. if (!(found.outerId in this.browsers)) {
  1158. // Initialise Marionette if the current chrome window has not been seen
  1159. // before. Also register the initial tab, if one exists.
  1160. let registerBrowsers, browserListening;
  1161. if (found.hasTabBrowser) {
  1162. registerBrowsers = this.registerPromise();
  1163. browserListening = this.listeningPromise();
  1164. }
  1165. this.startBrowser(found.win, false /* isNewSession */);
  1166. if (registerBrowsers && browserListening) {
  1167. yield registerBrowsers;
  1168. yield browserListening;
  1169. }
  1170. } else {
  1171. // Otherwise switch to the known chrome window, and activate the tab
  1172. // if it's a content browser.
  1173. this.curBrowser = this.browsers[found.outerId];
  1174. if ("tabIndex" in found) {
  1175. this.curBrowser.switchToTab(found.tabIndex, found.win, focus);
  1176. }
  1177. }
  1178. } else {
  1179. throw new NoSuchWindowError(`Unable to locate window: ${switchTo}`);
  1180. }
  1181. };
  1182. GeckoDriver.prototype.getActiveFrame = function (cmd, resp) {
  1183. switch (this.context) {
  1184. case Context.CHROME:
  1185. // no frame means top-level
  1186. resp.body.value = null;
  1187. if (this.curFrame) {
  1188. let elRef = this.curBrowser.seenEls
  1189. .add(this.curFrame.frameElement);
  1190. let el = element.makeWebElement(elRef);
  1191. resp.body.value = el;
  1192. }
  1193. break;
  1194. case Context.CONTENT:
  1195. resp.body.value = null;
  1196. if (this.currentFrameElement !== null) {
  1197. let el = element.makeWebElement(this.currentFrameElement);
  1198. resp.body.value = el;
  1199. }
  1200. break;
  1201. }
  1202. };
  1203. GeckoDriver.prototype.switchToParentFrame = function*(cmd, resp) {
  1204. let res = yield this.listener.switchToParentFrame();
  1205. };
  1206. /**
  1207. * Switch to a given frame within the current window.
  1208. *
  1209. * @param {Object} element
  1210. * A web element reference to the element to switch to.
  1211. * @param {(string|number)} id
  1212. * If element is not defined, then this holds either the id, name,
  1213. * or index of the frame to switch to.
  1214. */
  1215. GeckoDriver.prototype.switchToFrame = function* (cmd, resp) {
  1216. let {id, element, focus} = cmd.parameters;
  1217. const otherErrorsExpr = /about:.+(error)|(blocked)\?/;
  1218. const checkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  1219. let curWindow = this.getCurrentWindow();
  1220. let checkLoad = function() {
  1221. let win = this.getCurrentWindow();
  1222. if (win.document.readyState == "complete") {
  1223. return;
  1224. } else if (win.document.readyState == "interactive") {
  1225. let baseURI = win.document.baseURI;
  1226. if (baseURI.startsWith("about:certerror")) {
  1227. throw new InsecureCertificateError();
  1228. } else if (otherErrorsExpr.exec(win.document.baseURI)) {
  1229. throw new UnknownError("Error loading page");
  1230. }
  1231. }
  1232. checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
  1233. };
  1234. if (this.context == Context.CHROME) {
  1235. let foundFrame = null;
  1236. // just focus
  1237. if (typeof id == "undefined" && typeof element == "undefined") {
  1238. this.curFrame = null;
  1239. if (focus) {
  1240. this.mainFrame.focus();
  1241. }
  1242. checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
  1243. return;
  1244. }
  1245. // by element
  1246. if (this.curBrowser.seenEls.has(element)) {
  1247. // HTMLIFrameElement
  1248. let wantedFrame = this.curBrowser.seenEls.get(element, {frame: curWindow});
  1249. // Deal with an embedded xul:browser case
  1250. if (wantedFrame.tagName == "xul:browser" || wantedFrame.tagName == "browser") {
  1251. curWindow = wantedFrame.contentWindow;
  1252. this.curFrame = curWindow;
  1253. if (focus) {
  1254. this.curFrame.focus();
  1255. }
  1256. checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
  1257. return;
  1258. }
  1259. // Check if the frame is XBL anonymous
  1260. let parent = curWindow.document.getBindingParent(wantedFrame);
  1261. // Shadow nodes also show up in getAnonymousNodes, we should ignore them.
  1262. if (parent && !(parent.shadowRoot && parent.shadowRoot.contains(wantedFrame))) {
  1263. let anonNodes = [...curWindow.document.getAnonymousNodes(parent) || []];
  1264. if (anonNodes.length > 0) {
  1265. let el = wantedFrame;
  1266. while (el) {
  1267. if (anonNodes.indexOf(el) > -1) {
  1268. curWindow = wantedFrame.contentWindow;
  1269. this.curFrame = curWindow;
  1270. if (focus) {
  1271. this.curFrame.focus();
  1272. }
  1273. checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
  1274. return;
  1275. }
  1276. el = el.parentNode;
  1277. }
  1278. }
  1279. }
  1280. // else, assume iframe
  1281. let frames = curWindow.document.getElementsByTagName("iframe");
  1282. let numFrames = frames.length;
  1283. for (let i = 0; i < numFrames; i++) {
  1284. if (new XPCNativeWrapper(frames[i]) == new XPCNativeWrapper(wantedFrame)) {
  1285. curWindow = frames[i].contentWindow;
  1286. this.curFrame = curWindow;
  1287. if (focus) {
  1288. this.curFrame.focus();
  1289. }
  1290. checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
  1291. return;
  1292. }
  1293. }
  1294. }
  1295. switch (typeof id) {
  1296. case "string" :
  1297. let foundById = null;
  1298. let frames = curWindow.document.getElementsByTagName("iframe");
  1299. let numFrames = frames.length;
  1300. for (let i = 0; i < numFrames; i++) {
  1301. //give precedence to name
  1302. let frame = frames[i];
  1303. if (frame.getAttribute("name") == id) {
  1304. foundFrame = i;
  1305. curWindow = frame.contentWindow;
  1306. break;
  1307. } else if (foundById === null && frame.id == id) {
  1308. foundById = i;
  1309. }
  1310. }
  1311. if (foundFrame === null && foundById !== null) {
  1312. foundFrame = foundById;
  1313. curWindow = frames[foundById].contentWindow;
  1314. }
  1315. break;
  1316. case "number":
  1317. if (typeof curWindow.frames[id] != "undefined") {
  1318. foundFrame = id;
  1319. curWindow = curWindow.frames[foundFrame].frameElement.contentWindow;
  1320. }
  1321. break;
  1322. }
  1323. if (foundFrame !== null) {
  1324. this.curFrame = curWindow;
  1325. if (focus) {
  1326. this.curFrame.focus();
  1327. }
  1328. checkTimer.initWithCallback(checkLoad.bind(this), 100, Ci.nsITimer.TYPE_ONE_SHOT);
  1329. } else {
  1330. throw new NoSuchFrameError(`Unable to locate frame: ${id}`);
  1331. }
  1332. } else if (this.context == Context.CONTENT) {
  1333. if (!id && !element &&
  1334. this.curBrowser.frameManager.currentRemoteFrame !== null) {
  1335. // We're currently using a ChromeMessageSender for a remote frame, so this
  1336. // request indicates we need to switch back to the top-level (parent) frame.
  1337. // We'll first switch to the parent's (global) ChromeMessageBroadcaster, so
  1338. // we send the message to the right listener.
  1339. this.switchToGlobalMessageManager();
  1340. }
  1341. cmd.command_id = cmd.id;
  1342. let res = yield this.listener.switchToFrame(cmd.parameters);
  1343. if (res) {
  1344. let {win: winId, frame: frameId} = res;
  1345. this.mm = this.curBrowser.frameManager.getFrameMM(winId, frameId);
  1346. let registerBrowsers = this.registerPromise();
  1347. let browserListening = this.listeningPromise();
  1348. this.oopFrameId =
  1349. this.curBrowser.frameManager.switchToFrame(winId, frameId);
  1350. yield registerBrowsers;
  1351. yield browserListening;
  1352. }
  1353. }
  1354. };
  1355. GeckoDriver.prototype.getTimeouts = function (cmd, resp) {
  1356. return this.timeouts;
  1357. };
  1358. /**
  1359. * Set timeout for page loading, searching, and scripts.
  1360. *
  1361. * @param {Object.<string, number>}
  1362. * Dictionary of timeout types and their new value, where all timeout
  1363. * types are optional.
  1364. *
  1365. * @throws {InvalidArgumentError}
  1366. * If timeout type key is unknown, or the value provided with it is
  1367. * not an integer.
  1368. */
  1369. GeckoDriver.prototype.setTimeouts = function (cmd, resp) {
  1370. // backwards compatibility with old API
  1371. // that accepted a dictionary {type: <string>, ms: <number>}
  1372. let json = {};
  1373. if (typeof cmd.parameters == "object" &&
  1374. "type" in cmd.parameters &&
  1375. "ms" in cmd.parameters) {
  1376. logger.warn("Using deprecated data structure for setting timeouts");
  1377. json = {[cmd.parameters.type]: parseInt(cmd.parameters.ms)};
  1378. } else {
  1379. json = cmd.parameters;
  1380. }
  1381. // merge with existing timeouts
  1382. let merged = Object.assign(this.timeouts.toJSON(), json);
  1383. this.timeouts = session.Timeouts.fromJSON(merged);
  1384. };
  1385. /** Single tap. */
  1386. GeckoDriver.prototype.singleTap = function*(cmd, resp) {
  1387. let {id, x, y} = cmd.parameters;
  1388. switch (this.context) {
  1389. case Context.CHROME:
  1390. throw new UnsupportedOperationError(
  1391. "Command 'singleTap' is not yet available in chrome context");
  1392. case Context.CONTENT:
  1393. this.addFrameCloseListener("tap");
  1394. yield this.listener.singleTap(id, x, y);
  1395. break;
  1396. }
  1397. };
  1398. /**
  1399. * Perform a series of grouped actions at the specified points in time.
  1400. *
  1401. * @param {Array.<?>} actions
  1402. * Array of objects that each represent an action sequence.
  1403. *
  1404. * @throws {UnsupportedOperationError}
  1405. * If the command is made in chrome context.
  1406. */
  1407. GeckoDriver.prototype.performActions = function(cmd, resp) {
  1408. switch (this.context) {
  1409. case Context.CHROME:
  1410. throw new UnsupportedOperationError(
  1411. "Command 'performActions' is not yet available in chrome context");
  1412. case Context.CONTENT:
  1413. return this.listener.performActions({"actions": cmd.parameters.actions});
  1414. }
  1415. };
  1416. /**
  1417. * Release all the keys and pointer buttons that are currently depressed.
  1418. */
  1419. GeckoDriver.prototype.releaseActions = function(cmd, resp) {
  1420. switch (this.context) {
  1421. case Context.CHROME:
  1422. throw new UnsupportedOperationError(
  1423. "Command 'releaseActions' is not yet available in chrome context");
  1424. case Context.CONTENT:
  1425. return this.listener.releaseActions();
  1426. }
  1427. };
  1428. /**
  1429. * An action chain.
  1430. *
  1431. * @param {Object} value
  1432. * A nested array where the inner array represents each event,
  1433. * and the outer array represents a collection of events.
  1434. *
  1435. * @return {number}
  1436. * Last touch ID.
  1437. */
  1438. GeckoDriver.prototype.actionChain = function*(cmd, resp) {
  1439. let {chain, nextId} = cmd.parameters;
  1440. switch (this.context) {
  1441. case Context.CHROME:
  1442. // be conservative until this has a use case and is established
  1443. // to work as expected in Fennec
  1444. assert.firefox()
  1445. let win = this.getCurrentWindow();
  1446. resp.body.value = yield this.legacyactions.dispatchActions(
  1447. chain, nextId, {frame: win}, this.curBrowser.seenEls);
  1448. break;
  1449. case Context.CONTENT:
  1450. this.addFrameCloseListener("action chain");
  1451. resp.body.value = yield this.listener.actionChain(chain, nextId);
  1452. break;
  1453. }
  1454. };
  1455. /**
  1456. * A multi-action chain.
  1457. *
  1458. * @param {Object} value
  1459. * A nested array where the inner array represents eache vent,
  1460. * the middle array represents a collection of events for each
  1461. * finger, and the outer array represents all fingers.
  1462. */
  1463. GeckoDriver.prototype.multiAction = function*(cmd, resp) {
  1464. switch (this.context) {
  1465. case Context.CHROME:
  1466. throw new UnsupportedOperationError(
  1467. "Command 'multiAction' is not yet available in chrome context");
  1468. case Context.CONTENT:
  1469. this.addFrameCloseListener("multi action chain");
  1470. yield this.listener.multiAction(cmd.parameters.value, cmd.parameters.max_length);
  1471. break;
  1472. }
  1473. };
  1474. /**
  1475. * Find an element using the indicated search strategy.
  1476. *
  1477. * @param {string} using
  1478. * Indicates which search method to use.
  1479. * @param {string} value
  1480. * Value the client is looking for.
  1481. */
  1482. GeckoDriver.prototype.findElement = function*(cmd, resp) {
  1483. let strategy = cmd.parameters.using;
  1484. let expr = cmd.parameters.value;
  1485. let opts = {
  1486. startNode: cmd.parameters.element,
  1487. timeout: this.timeouts.implicit,
  1488. all: false,
  1489. };
  1490. switch (this.context) {
  1491. case Context.CHROME:
  1492. if (!SUPPORTED_STRATEGIES.has(strategy)) {
  1493. throw new InvalidSelectorError(`Strategy not supported: ${strategy}`);
  1494. }
  1495. let container = {frame: this.getCurrentWindow()};
  1496. if (opts.startNode) {
  1497. opts.startNode = this.curBrowser.seenEls.get(opts.startNode, container);
  1498. }
  1499. let el = yield element.find(container, strategy, expr, opts);
  1500. let elRef = this.curBrowser.seenEls.add(el);
  1501. let webEl = element.makeWebElement(elRef);
  1502. resp.body.value = webEl;
  1503. break;
  1504. case Context.CONTENT:
  1505. resp.body.value = yield this.listener.findElementContent(
  1506. strategy,
  1507. expr,
  1508. opts);
  1509. break;
  1510. }
  1511. };
  1512. /**
  1513. * Find elements using the indicated search strategy.
  1514. *
  1515. * @param {string} using
  1516. * Indicates which search method to use.
  1517. * @param {string} value
  1518. * Value the client is looking for.
  1519. */
  1520. GeckoDriver.prototype.findElements = function*(cmd, resp) {
  1521. let strategy = cmd.parameters.using;
  1522. let expr = cmd.parameters.value;
  1523. let opts = {
  1524. startNode: cmd.parameters.element,
  1525. timeout: this.timeouts.implicit,
  1526. all: true,
  1527. };
  1528. switch (this.context) {
  1529. case Context.CHROME:
  1530. if (!SUPPORTED_STRATEGIES.has(strategy)) {
  1531. throw new InvalidSelectorError(`Strategy not supported: ${strategy}`);
  1532. }
  1533. let container = {frame: this.getCurrentWindow()};
  1534. if (opts.startNode) {
  1535. opts.startNode = this.curBrowser.seenEls.get(opts.startNode, container);
  1536. }
  1537. let els = yield element.find(container, strategy, expr, opts);
  1538. let elRefs = this.curBrowser.seenEls.addAll(els);
  1539. let webEls = elRefs.map(element.makeWebElement);
  1540. resp.body = webEls;
  1541. break;
  1542. case Context.CONTENT:
  1543. resp.body = yield this.listener.findElementsContent(
  1544. cmd.parameters.using,
  1545. cmd.parameters.value,
  1546. opts);
  1547. break;
  1548. }
  1549. };
  1550. /** Return the active element on the page. */
  1551. GeckoDriver.prototype.getActiveElement = function*(cmd, resp) {
  1552. switch (this.context) {
  1553. case Context.CHROME:
  1554. throw new UnsupportedOperationError(
  1555. "Command 'getActiveElement' is not yet available in chrome context");
  1556. case Context.CONTENT:
  1557. resp.body.value = yield this.listener.getActiveElement();
  1558. break;
  1559. }
  1560. };
  1561. /**
  1562. * Send click event to element.
  1563. *
  1564. * @param {string} id
  1565. * Reference ID to the element that will be clicked.
  1566. */
  1567. GeckoDriver.prototype.clickElement = function*(cmd, resp) {
  1568. let id = cmd.parameters.id;
  1569. switch (this.context) {
  1570. case Context.CHROME:
  1571. let win = this.getCurrentWindow();
  1572. let el = this.curBrowser.seenEls.get(id, {frame: win});
  1573. yield interaction.clickElement(el, this.a11yChecks);
  1574. break;
  1575. case Context.CONTENT:
  1576. // We need to protect against the click causing an OOP frame to close.
  1577. // This fires the mozbrowserclose event when it closes so we need to
  1578. // listen for it and then just send an error back. The person making the
  1579. // call should be aware something isnt right and handle accordingly
  1580. this.addFrameCloseListener("click");
  1581. yield this.listener.clickElement(id);
  1582. break;
  1583. }
  1584. };
  1585. /**
  1586. * Get a given attribute of an element.
  1587. *
  1588. * @param {string} id
  1589. * Web element reference ID to the element that will be inspected.
  1590. * @param {string} name
  1591. * Name of the attribute which value to retrieve.
  1592. *
  1593. * @return {string}
  1594. * Value of the attribute.
  1595. */
  1596. GeckoDriver.prototype.getElementAttribute = function*(cmd, resp) {
  1597. let {id, name} = cmd.parameters;
  1598. switch (this.context) {
  1599. case Context.CHROME:
  1600. let win = this.getCurrentWindow();
  1601. let el = this.curBrowser.seenEls.get(id, {frame: win});
  1602. resp.body.value = el.getAttribute(name);
  1603. break;
  1604. case Context.CONTENT:
  1605. resp.body.value = yield this.listener.getElementAttribute(id, name);
  1606. break;
  1607. }
  1608. };
  1609. /**
  1610. * Returns the value of a property associated with given element.
  1611. *
  1612. * @param {string} id
  1613. * Web element reference ID to the element that will be inspected.
  1614. * @param {string} name
  1615. * Name of the property which value to retrieve.
  1616. *
  1617. * @return {string}
  1618. * Value of the property.
  1619. */
  1620. GeckoDriver.prototype.getElementProperty = function*(cmd, resp) {
  1621. let {id, name} = cmd.parameters;
  1622. switch (this.context) {
  1623. case Context.CHROME:
  1624. let win = this.getCurrentWindow();
  1625. let el = this.curBrowser.seenEls.get(id, {frame: win});
  1626. resp.body.value = el[name];
  1627. break;
  1628. case Context.CONTENT:
  1629. resp.body.value = yield this.listener.getElementProperty(id, name);
  1630. break;
  1631. }
  1632. };
  1633. /**
  1634. * Get the text of an element, if any. Includes the text of all child
  1635. * elements.
  1636. *
  1637. * @param {string} id
  1638. * Reference ID to the element that will be inspected.
  1639. */
  1640. GeckoDriver.prototype.getElementText = function*(cmd, resp) {
  1641. let id = cmd.parameters.id;
  1642. switch (this.context) {
  1643. case Context.CHROME:
  1644. // for chrome, we look at text nodes, and any node with a "label" field
  1645. let win = this.getCurrentWindow();
  1646. let el = this.curBrowser.seenEls.get(id, {frame: win});
  1647. let lines = [];
  1648. this.getVisibleText(el, lines);
  1649. resp.body.value = lines.join("\n");
  1650. break;
  1651. case Context.CONTENT:
  1652. resp.body.value = yield this.listener.getElementText(id);
  1653. break;
  1654. }
  1655. };
  1656. /**
  1657. * Get the tag name of the element.
  1658. *
  1659. * @param {string} id
  1660. * Reference ID to the element that will be inspected.
  1661. */
  1662. GeckoDriver.prototype.getElementTagName = function*(cmd, resp) {
  1663. let id = cmd.parameters.id;
  1664. switch (this.context) {
  1665. case Context.CHROME:
  1666. let win = this.getCurrentWindow();
  1667. let el = this.curBrowser.seenEls.get(id, {frame: win});
  1668. resp.body.value = el.tagName.toLowerCase();
  1669. break;
  1670. case Context.CONTENT:
  1671. resp.body.value = yield this.listener.getElementTagName(id);
  1672. break;
  1673. }
  1674. };
  1675. /**
  1676. * Check if element is displayed.
  1677. *
  1678. * @param {string} id
  1679. * Reference ID to the element that will be inspected.
  1680. */
  1681. GeckoDriver.prototype.isElementDisplayed = function*(cmd, resp) {
  1682. let id = cmd.parameters.id;
  1683. switch (this.context) {
  1684. case Context.CHROME:
  1685. let win = this.getCurrentWindow();
  1686. let el = this.curBrowser.seenEls.get(id, {frame: win});
  1687. resp.body.value = yield interaction.isElementDisplayed(
  1688. el, this.a11yChecks);
  1689. break;
  1690. case Context.CONTENT:
  1691. resp.body.value = yield this.listener.isElementDisplayed(id);
  1692. break;
  1693. }
  1694. };
  1695. /**
  1696. * Return the property of the computed style of an element.
  1697. *
  1698. * @param {string} id
  1699. * Reference ID to the element that will be checked.
  1700. * @param {string} propertyName
  1701. * CSS rule that is being requested.
  1702. */
  1703. GeckoDriver.prototype.getElementValueOfCssProperty = function*(cmd, resp) {
  1704. let {id, propertyName: prop} = cmd.parameters;
  1705. switch (this.context) {
  1706. case Context.CHROME:
  1707. let win = this.getCurrentWindow();
  1708. let el = this.curBrowser.seenEls.get(id, {frame: win});
  1709. let sty = win.document.defaultView.getComputedStyle(el, null);
  1710. resp.body.value = sty.getPropertyValue(prop);
  1711. break;
  1712. case Context.CONTENT:
  1713. resp.body.value = yield this.listener.getElementValueOfCssProperty(id, prop);
  1714. break;
  1715. }
  1716. };
  1717. /**
  1718. * Check if element is enabled.
  1719. *
  1720. * @param {string} id
  1721. * Reference ID to the element that will be checked.
  1722. */
  1723. GeckoDriver.prototype.isElementEnabled = function*(cmd, resp) {
  1724. let id = cmd.parameters.id;
  1725. switch (this.context) {
  1726. case Context.CHROME:
  1727. // Selenium atom doesn't quite work here
  1728. let win = this.getCurrentWindow();
  1729. let el = this.curBrowser.seenEls.get(id, {frame: win});
  1730. resp.body.value = yield interaction.isElementEnabled(
  1731. el, this.a11yChecks);
  1732. break;
  1733. case Context.CONTENT:
  1734. resp.body.value = yield this.listener.isElementEnabled(id);
  1735. break;
  1736. }
  1737. },
  1738. /**
  1739. * Check if element is selected.
  1740. *
  1741. * @param {string} id
  1742. * Reference ID to the element that will be checked.
  1743. */
  1744. GeckoDriver.prototype.isElementSelected = function*(cmd, resp) {
  1745. let id = cmd.parameters.id;
  1746. switch (this.context) {
  1747. case Context.CHROME:
  1748. // Selenium atom doesn't quite work here
  1749. let win = this.getCurrentWindow();
  1750. let el = this.curBrowser.seenEls.get(id, {frame: win});
  1751. resp.body.value = yield interaction.isElementSelected(
  1752. el, this.a11yChecks);
  1753. break;
  1754. case Context.CONTENT:
  1755. resp.body.value = yield this.listener.isElementSelected(id);
  1756. break;
  1757. }
  1758. };
  1759. GeckoDriver.prototype.getElementRect = function*(cmd, resp) {
  1760. let id = cmd.parameters.id;
  1761. switch (this.context) {
  1762. case Context.CHROME:
  1763. let win = this.getCurrentWindow();
  1764. let el = this.curBrowser.seenEls.get(id, {frame: win});
  1765. let rect = el.getBoundingClientRect();
  1766. resp.body = {
  1767. x: rect.x + win.pageXOffset,
  1768. y: rect.y + win.pageYOffset,
  1769. width: rect.width,
  1770. height: rect.height
  1771. };
  1772. break;
  1773. case Context.CONTENT:
  1774. resp.body = yield this.listener.getElementRect(id);
  1775. break;
  1776. }
  1777. };
  1778. /**
  1779. * Send key presses to element after focusing on it.
  1780. *
  1781. * @param {string} id
  1782. * Reference ID to the element that will be checked.
  1783. * @param {string} value
  1784. * Value to send to the element.
  1785. */
  1786. GeckoDriver.prototype.sendKeysToElement = function*(cmd, resp) {
  1787. let {id, value} = cmd.parameters;
  1788. assert.defined(value, `Expected character sequence: ${value}`);
  1789. switch (this.context) {
  1790. case Context.CHROME:
  1791. let win = this.getCurrentWindow();
  1792. let el = this.curBrowser.seenEls.get(id, {frame: win});
  1793. yield interaction.sendKeysToElement(
  1794. el, value, true, this.a11yChecks);
  1795. break;
  1796. case Context.CONTENT:
  1797. yield this.listener.sendKeysToElement(id, value);
  1798. break;
  1799. }
  1800. };
  1801. /** Sets the test name. The test name is used for logging purposes. */
  1802. GeckoDriver.prototype.setTestName = function*(cmd, resp) {
  1803. let val = cmd.parameters.value;
  1804. this.testName = val;
  1805. yield this.listener.setTestName({value: val});
  1806. };
  1807. /**
  1808. * Clear the text of an element.
  1809. *
  1810. * @param {string} id
  1811. * Reference ID to the element that will be cleared.
  1812. */
  1813. GeckoDriver.prototype.clearElement = function*(cmd, resp) {
  1814. let id = cmd.parameters.id;
  1815. switch (this.context) {
  1816. case Context.CHROME:
  1817. // the selenium atom doesn't work here
  1818. let win = this.getCurrentWindow();
  1819. let el = this.curBrowser.seenEls.get(id, {frame: win});
  1820. if (el.nodeName == "textbox") {
  1821. el.value = "";
  1822. } else if (el.nodeName == "checkbox") {
  1823. el.checked = false;
  1824. }
  1825. break;
  1826. case Context.CONTENT:
  1827. yield this.listener.clearElement(id);
  1828. break;
  1829. }
  1830. };
  1831. /**
  1832. * Switch to shadow root of the given host element.
  1833. *
  1834. * @param {string} id element id.
  1835. */
  1836. GeckoDriver.prototype.switchToShadowRoot = function*(cmd, resp) {
  1837. assert.content(this.context)
  1838. let id;
  1839. if (cmd.parameters) { id = cmd.parameters.id; }
  1840. yield this.listener.switchToShadowRoot(id);
  1841. };
  1842. /** Add a cookie to the document. */
  1843. GeckoDriver.prototype.addCookie = function*(cmd, resp) {
  1844. assert.content(this.context)
  1845. let cb = msg => {
  1846. this.mm.removeMessageListener("Marionette:addCookie", cb);
  1847. let cookie = msg.json;
  1848. Services.cookies.add(
  1849. cookie.domain,
  1850. cookie.path,
  1851. cookie.name,
  1852. cookie.value,
  1853. cookie.secure,
  1854. cookie.httpOnly,
  1855. cookie.session,
  1856. cookie.expiry,
  1857. {}); // originAttributes
  1858. return true;
  1859. };
  1860. this.mm.addMessageListener("Marionette:addCookie", cb);
  1861. yield this.listener.addCookie(cmd.parameters.cookie);
  1862. };
  1863. /**
  1864. * Get all the cookies for the current domain.
  1865. *
  1866. * This is the equivalent of calling {@code document.cookie} and parsing
  1867. * the result.
  1868. */
  1869. GeckoDriver.prototype.getCookies = function*(cmd, resp) {
  1870. assert.content(this.context)
  1871. resp.body = yield this.listener.getCookies();
  1872. };
  1873. /** Delete all cookies that are visible to a document. */
  1874. GeckoDriver.prototype.deleteAllCookies = function*(cmd, resp) {
  1875. assert.content(this.context)
  1876. let cb = msg => {
  1877. let cookie = msg.json;
  1878. cookieManager.remove(
  1879. cookie.host,
  1880. cookie.name,
  1881. cookie.path,
  1882. false,
  1883. cookie.originAttributes);
  1884. return true;
  1885. };
  1886. this.mm.addMessageListener("Marionette:deleteCookie", cb);
  1887. yield this.listener.deleteAllCookies();
  1888. this.mm.removeMessageListener("Marionette:deleteCookie", cb);
  1889. };
  1890. /** Delete a cookie by name. */
  1891. GeckoDriver.prototype.deleteCookie = function*(cmd, resp) {
  1892. assert.content(this.context)
  1893. let cb = msg => {
  1894. this.mm.removeMessageListener("Marionette:deleteCookie", cb);
  1895. let cookie = msg.json;
  1896. cookieManager.remove(
  1897. cookie.host,
  1898. cookie.name,
  1899. cookie.path,
  1900. false,
  1901. cookie.originAttributes);
  1902. return true;
  1903. };
  1904. this.mm.addMessageListener("Marionette:deleteCookie", cb);
  1905. yield this.listener.deleteCookie(cmd.parameters.name);
  1906. };
  1907. /**
  1908. * Close the currently selected tab/window.
  1909. *
  1910. * With multiple open tabs present the currently selected tab will be closed.
  1911. * Otherwise the window itself will be closed. If it is the last window
  1912. * currently open, the window will not be closed to prevent a shutdown of the
  1913. * application. Instead the returned list of window handles is empty.
  1914. *
  1915. * @return {Array.<string>}
  1916. * Unique window handles of remaining windows.
  1917. */
  1918. GeckoDriver.prototype.close = function (cmd, resp) {
  1919. let nwins = 0;
  1920. let winEn = Services.wm.getEnumerator(null);
  1921. while (winEn.hasMoreElements()) {
  1922. let win = winEn.getNext();
  1923. // For browser windows count the tabs. Otherwise take the window itself.
  1924. let tabbrowser = browser.getTabBrowser(win);
  1925. if (tabbrowser) {
  1926. nwins += tabbrowser.tabs.length;
  1927. } else {
  1928. nwins++;
  1929. }
  1930. }
  1931. // If there is only 1 window left, do not close it. Instead return a faked
  1932. // empty array of window handles. This will instruct geckodriver to terminate
  1933. // the application.
  1934. if (nwins == 1) {
  1935. return [];
  1936. }
  1937. if (this.mm != globalMessageManager) {
  1938. this.mm.removeDelayedFrameScript(FRAME_SCRIPT);
  1939. }
  1940. return this.curBrowser.closeTab().then(() => this.windowHandles);
  1941. };
  1942. /**
  1943. * Close the currently selected chrome window.
  1944. *
  1945. * If it is the last window currently open, the chrome window will not be
  1946. * closed to prevent a shutdown of the application. Instead the returned
  1947. * list of chrome window handles is empty.
  1948. *
  1949. * @return {Array.<string>}
  1950. * Unique chrome window handles of remaining chrome windows.
  1951. */
  1952. GeckoDriver.prototype.closeChromeWindow = function (cmd, resp) {
  1953. assert.firefox();
  1954. // Get the total number of windows
  1955. let nwins = 0;
  1956. let winEn = Services.wm.getEnumerator(null);
  1957. while (winEn.hasMoreElements()) {
  1958. nwins++;
  1959. winEn.getNext();
  1960. }
  1961. // If there is only 1 window left, do not close it. Instead return a faked
  1962. // empty array of window handles. This will instruct geckodriver to terminate
  1963. // the application.
  1964. if (nwins == 1) {
  1965. return [];
  1966. }
  1967. // reset frame to the top-most frame
  1968. this.curFrame = null;
  1969. if (this.mm != globalMessageManager) {
  1970. this.mm.removeDelayedFrameScript(FRAME_SCRIPT);
  1971. }
  1972. return this.curBrowser.closeWindow().then(() => this.chromeWindowHandles);
  1973. };
  1974. /** Delete Marionette session. */
  1975. GeckoDriver.prototype.deleteSession = function (cmd, resp) {
  1976. if (this.curBrowser !== null) {
  1977. // frame scripts can be safely reused
  1978. Preferences.set(CONTENT_LISTENER_PREF, false);
  1979. // delete session in each frame in each browser
  1980. for (let win in this.browsers) {
  1981. let browser = this.browsers[win];
  1982. for (let i in browser.knownFrames) {
  1983. globalMessageManager.broadcastAsyncMessage(
  1984. "Marionette:deleteSession" + browser.knownFrames[i], {});
  1985. }
  1986. }
  1987. let winEn = Services.wm.getEnumerator(null);
  1988. while (winEn.hasMoreElements()) {
  1989. let win = winEn.getNext();
  1990. if (win.messageManager) {
  1991. win.messageManager.removeDelayedFrameScript(FRAME_SCRIPT);
  1992. } else {
  1993. logger.error(
  1994. `Could not remove listener from page ${win.location.href}`);
  1995. }
  1996. }
  1997. this.curBrowser.frameManager.removeMessageManagerListeners(
  1998. globalMessageManager);
  1999. }
  2000. this.switchToGlobalMessageManager();
  2001. // reset frame to the top-most frame
  2002. this.curFrame = null;
  2003. if (this.mainFrame) {
  2004. try {
  2005. this.mainFrame.focus();
  2006. } catch (e) {
  2007. this.mainFrame = null;
  2008. }
  2009. }
  2010. if (this.observing !== null) {
  2011. for (let topic in this.observing) {
  2012. Services.obs.removeObserver(this.observing[topic], topic);
  2013. }
  2014. this.observing = null;
  2015. }
  2016. this.sandboxes.clear();
  2017. cert.uninstallOverride();
  2018. this.sessionId = null;
  2019. this.capabilities = new session.Capabilities();
  2020. };
  2021. /** Returns the current status of the Application Cache. */
  2022. GeckoDriver.prototype.getAppCacheStatus = function* (cmd, resp) {
  2023. switch (this.context) {
  2024. case Context.CHROME:
  2025. throw new UnsupportedOperationError(
  2026. "Command 'getAppCacheStatus' is not yet available in chrome context");
  2027. case Context.CONTENT:
  2028. resp.body.value = yield this.listener.getAppCacheStatus();
  2029. break;
  2030. }
  2031. };
  2032. /**
  2033. * Import script to the JS evaluation runtime.
  2034. *
  2035. * Imported scripts are exposed in the contexts of all subsequent
  2036. * calls to {@code executeScript}, {@code executeAsyncScript}, and
  2037. * {@code executeJSScript} by prepending them to the evaluated script.
  2038. *
  2039. * Scripts can be cleared with the {@code clearImportedScripts} command.
  2040. *
  2041. * @param {string} script
  2042. * Script to include. If the script is byte-by-byte equal to an
  2043. * existing imported script, it is not imported.
  2044. */
  2045. GeckoDriver.prototype.importScript = function*(cmd, resp) {
  2046. let script = cmd.parameters.script;
  2047. this.importedScripts.for(this.context).add(script);
  2048. };
  2049. /**
  2050. * Clear all scripts that are imported into the JS evaluation runtime.
  2051. *
  2052. * Scripts can be imported using the {@code importScript} command.
  2053. */
  2054. GeckoDriver.prototype.clearImportedScripts = function*(cmd, resp) {
  2055. this.importedScripts.for(this.context).clear();
  2056. };
  2057. /**
  2058. * Takes a screenshot of a web element, current frame, or viewport.
  2059. *
  2060. * The screen capture is returned as a lossless PNG image encoded as
  2061. * a base 64 string.
  2062. *
  2063. * If called in the content context, the <code>id</code> argument is not null
  2064. * and refers to a present and visible web element's ID, the capture area
  2065. * will be limited to the bounding box of that element. Otherwise, the
  2066. * capture area will be the bounding box of the current frame.
  2067. *
  2068. * If called in the chrome context, the screenshot will always represent the
  2069. * entire viewport.
  2070. *
  2071. * @param {string=} id
  2072. * Optional web element reference to take a screenshot of.
  2073. * If undefined, a screenshot will be taken of the document element.
  2074. * @param {Array.<string>=} highlights
  2075. * List of web elements to highlight.
  2076. * @param {boolean} full
  2077. * True to take a screenshot of the entire document element. Is not
  2078. * considered if {@code id} is not defined. Defaults to true.
  2079. * @param {boolean=} hash
  2080. * True if the user requests a hash of the image data.
  2081. * @param {boolean=} scroll
  2082. * Scroll to element if |id| is provided. If undefined, it will
  2083. * scroll to the element.
  2084. *
  2085. * @return {string}
  2086. * If {@code hash} is false, PNG image encoded as base64 encoded string. If
  2087. * 'hash' is True, hex digest of the SHA-256 hash of the base64 encoded
  2088. * string.
  2089. */
  2090. GeckoDriver.prototype.takeScreenshot = function (cmd, resp) {
  2091. let {id, highlights, full, hash, scroll} = cmd.parameters;
  2092. highlights = highlights || [];
  2093. let format = hash ? capture.Format.Hash : capture.Format.Base64;
  2094. switch (this.context) {
  2095. case Context.CHROME:
  2096. let container = {frame: this.getCurrentWindow().document.defaultView};
  2097. if (!container.frame) {
  2098. throw new NoSuchWindowError("Unable to locate window");
  2099. }
  2100. let highlightEls = highlights.map(
  2101. ref => this.curBrowser.seenEls.get(ref, container));
  2102. // viewport
  2103. let canvas;
  2104. if (!id && !full) {
  2105. canvas = capture.viewport(container.frame, highlightEls);
  2106. // element or full document element
  2107. } else {
  2108. let node;
  2109. if (id) {
  2110. node = this.curBrowser.seenEls.get(id, container);
  2111. } else {
  2112. node = container.frame.document.documentElement;
  2113. }
  2114. canvas = capture.element(node, highlightEls);
  2115. }
  2116. switch (format) {
  2117. case capture.Format.Hash:
  2118. return capture.toHash(canvas);
  2119. case capture.Format.Base64:
  2120. return capture.toBase64(canvas);
  2121. }
  2122. break;
  2123. case Context.CONTENT:
  2124. return this.listener.takeScreenshot(format, cmd.parameters);
  2125. }
  2126. };
  2127. /**
  2128. * Get the current browser orientation.
  2129. *
  2130. * Will return one of the valid primary orientation values
  2131. * portrait-primary, landscape-primary, portrait-secondary, or
  2132. * landscape-secondary.
  2133. */
  2134. GeckoDriver.prototype.getScreenOrientation = function (cmd, resp) {
  2135. assert.fennec();
  2136. resp.body.value = this.getCurrentWindow().screen.mozOrientation;
  2137. };
  2138. /**
  2139. * Set the current browser orientation.
  2140. *
  2141. * The supplied orientation should be given as one of the valid
  2142. * orientation values. If the orientation is unknown, an error will
  2143. * be raised.
  2144. *
  2145. * Valid orientations are "portrait" and "landscape", which fall
  2146. * back to "portrait-primary" and "landscape-primary" respectively,
  2147. * and "portrait-secondary" as well as "landscape-secondary".
  2148. */
  2149. GeckoDriver.prototype.setScreenOrientation = function (cmd, resp) {
  2150. assert.fennec();
  2151. const ors = [
  2152. "portrait", "landscape",
  2153. "portrait-primary", "landscape-primary",
  2154. "portrait-secondary", "landscape-secondary",
  2155. ];
  2156. let or = String(cmd.parameters.orientation);
  2157. assert.string(or);
  2158. let mozOr = or.toLowerCase();
  2159. if (!ors.includes(mozOr)) {
  2160. throw new InvalidArgumentError(`Unknown screen orientation: ${or}`);
  2161. }
  2162. let win = this.getCurrentWindow();
  2163. if (!win.screen.mozLockOrientation(mozOr)) {
  2164. throw new WebDriverError(`Unable to set screen orientation: ${or}`);
  2165. }
  2166. };
  2167. /**
  2168. * Get the size of the browser window currently in focus.
  2169. *
  2170. * Will return the current browser window size in pixels. Refers to
  2171. * window outerWidth and outerHeight values, which include scroll bars,
  2172. * title bars, etc.
  2173. */
  2174. GeckoDriver.prototype.getWindowSize = function (cmd, resp) {
  2175. let win = this.getCurrentWindow();
  2176. resp.body.width = win.outerWidth;
  2177. resp.body.height = win.outerHeight;
  2178. };
  2179. /**
  2180. * Set the size of the browser window currently in focus.
  2181. *
  2182. * Not supported on B2G. The supplied width and height values refer to
  2183. * the window outerWidth and outerHeight values, which include scroll
  2184. * bars, title bars, etc.
  2185. */
  2186. GeckoDriver.prototype.setWindowSize = function (cmd, resp) {
  2187. assert.firefox()
  2188. let {width, height} = cmd.parameters;
  2189. let win = this.getCurrentWindow();
  2190. win.resizeTo(width, height);
  2191. this.getWindowSize(cmd, resp);
  2192. };
  2193. /**
  2194. * Maximizes the user agent window as if the user pressed the maximise
  2195. * button.
  2196. *
  2197. * Not Supported on B2G or Fennec.
  2198. */
  2199. GeckoDriver.prototype.maximizeWindow = function (cmd, resp) {
  2200. assert.firefox()
  2201. let win = this.getCurrentWindow();
  2202. win.maximize()
  2203. };
  2204. /**
  2205. * Dismisses a currently displayed tab modal, or returns no such alert if
  2206. * no modal is displayed.
  2207. */
  2208. GeckoDriver.prototype.dismissDialog = function (cmd, resp) {
  2209. this._checkIfAlertIsPresent();
  2210. let {button0, button1} = this.dialog.ui;
  2211. (button1 ? button1 : button0).click();
  2212. this.dialog = null;
  2213. };
  2214. /**
  2215. * Accepts a currently displayed tab modal, or returns no such alert if
  2216. * no modal is displayed.
  2217. */
  2218. GeckoDriver.prototype.acceptDialog = function (cmd, resp) {
  2219. this._checkIfAlertIsPresent();
  2220. let {button0} = this.dialog.ui;
  2221. button0.click();
  2222. this.dialog = null;
  2223. };
  2224. /**
  2225. * Returns the message shown in a currently displayed modal, or returns a no such
  2226. * alert error if no modal is currently displayed.
  2227. */
  2228. GeckoDriver.prototype.getTextFromDialog = function (cmd, resp) {
  2229. this._checkIfAlertIsPresent();
  2230. let {infoBody} = this.dialog.ui;
  2231. resp.body.value = infoBody.textContent;
  2232. };
  2233. /**
  2234. * Sends keys to the input field of a currently displayed modal, or
  2235. * returns a no such alert error if no modal is currently displayed. If
  2236. * a tab modal is currently displayed but has no means for text input,
  2237. * an element not visible error is returned.
  2238. */
  2239. GeckoDriver.prototype.sendKeysToDialog = function (cmd, resp) {
  2240. this._checkIfAlertIsPresent();
  2241. // see toolkit/components/prompts/content/commonDialog.js
  2242. let {loginContainer, loginTextbox} = this.dialog.ui;
  2243. if (loginContainer.hidden) {
  2244. throw new ElementNotInteractableError(
  2245. "This prompt does not accept text input");
  2246. }
  2247. let win = this.dialog.window ? this.dialog.window : this.getCurrentWindow();
  2248. event.sendKeysToElement(
  2249. cmd.parameters.value,
  2250. loginTextbox,
  2251. {ignoreVisibility: true},
  2252. win);
  2253. };
  2254. GeckoDriver.prototype._checkIfAlertIsPresent = function() {
  2255. if (!this.dialog || !this.dialog.ui) {
  2256. throw new NoAlertOpenError(
  2257. "No tab modal was open when attempting to get the dialog text");
  2258. }
  2259. };
  2260. /**
  2261. * Enables or disables accepting new socket connections.
  2262. *
  2263. * By calling this method with `false` the server will not accept any further
  2264. * connections, but existing connections will not be forcible closed. Use `true`
  2265. * to re-enable accepting connections.
  2266. *
  2267. * Please note that when closing the connection via the client you can end-up in
  2268. * a non-recoverable state if it hasn't been enabled before.
  2269. *
  2270. * This method is used for custom in application shutdowns via marionette.quit()
  2271. * or marionette.restart(), like File -> Quit.
  2272. *
  2273. * @param {boolean} state
  2274. * True if the server should accept new socket connections.
  2275. */
  2276. GeckoDriver.prototype.acceptConnections = function (cmd, resp) {
  2277. assert.boolean(cmd.parameters.value);
  2278. this._server.acceptConnections = cmd.parameters.value;
  2279. }
  2280. /**
  2281. * Quits Firefox with the provided flags and tears down the current
  2282. * session.
  2283. */
  2284. GeckoDriver.prototype.quitApplication = function (cmd, resp) {
  2285. assert.firefox("Bug 1298921 - In app initiated quit not yet available beside Firefox")
  2286. let flags = Ci.nsIAppStartup.eAttemptQuit;
  2287. for (let k of cmd.parameters.flags || []) {
  2288. flags |= Ci.nsIAppStartup[k];
  2289. }
  2290. this._server.acceptConnections = false;
  2291. resp.send();
  2292. this.deleteSession();
  2293. Services.startup.quit(flags);
  2294. };
  2295. GeckoDriver.prototype.installAddon = function (cmd, resp) {
  2296. assert.firefox()
  2297. let path = cmd.parameters.path;
  2298. let temp = cmd.parameters.temporary || false;
  2299. if (typeof path == "undefined" || typeof path != "string" ||
  2300. typeof temp != "boolean") {
  2301. throw InvalidArgumentError();
  2302. }
  2303. return addon.install(path, temp);
  2304. };
  2305. GeckoDriver.prototype.uninstallAddon = function (cmd, resp) {
  2306. assert.firefox()
  2307. let id = cmd.parameters.id;
  2308. if (typeof id == "undefined" || typeof id != "string") {
  2309. throw new InvalidArgumentError();
  2310. }
  2311. return addon.uninstall(id);
  2312. };
  2313. /**
  2314. * Helper function to convert an outerWindowID into a UID that Marionette
  2315. * tracks.
  2316. */
  2317. GeckoDriver.prototype.generateFrameId = function (id) {
  2318. let uid = id + (this.appName == "B2G" ? "-b2g" : "");
  2319. return uid;
  2320. };
  2321. /** Receives all messages from content messageManager. */
  2322. GeckoDriver.prototype.receiveMessage = function (message) {
  2323. switch (message.name) {
  2324. case "Marionette:ok":
  2325. case "Marionette:done":
  2326. case "Marionette:error":
  2327. // check if we need to remove the mozbrowserclose listener
  2328. if (this.mozBrowserClose !== null) {
  2329. let win = this.getCurrentWindow();
  2330. win.removeEventListener("mozbrowserclose", this.mozBrowserClose, true);
  2331. this.mozBrowserClose = null;
  2332. }
  2333. break;
  2334. case "Marionette:log":
  2335. // log server-side messages
  2336. logger.info(message.json.message);
  2337. break;
  2338. case "Marionette:shareData":
  2339. // log messages from tests
  2340. if (message.json.log) {
  2341. this.marionetteLog.addAll(message.json.log);
  2342. }
  2343. break;
  2344. case "Marionette:switchToModalOrigin":
  2345. this.curBrowser.frameManager.switchToModalOrigin(message);
  2346. this.mm = this.curBrowser.frameManager
  2347. .currentRemoteFrame.messageManager.get();
  2348. break;
  2349. case "Marionette:switchedToFrame":
  2350. if (message.json.restorePrevious) {
  2351. this.currentFrameElement = this.previousFrameElement;
  2352. } else {
  2353. // we don't arbitrarily save previousFrameElement, since
  2354. // we allow frame switching after modals appear, which would
  2355. // override this value and we'd lose our reference
  2356. if (message.json.storePrevious) {
  2357. this.previousFrameElement = this.currentFrameElement;
  2358. }
  2359. this.currentFrameElement = message.json.frameValue;
  2360. }
  2361. break;
  2362. case "Marionette:getVisibleCookies":
  2363. let [currentPath, host] = message.json;
  2364. let isForCurrentPath = path => currentPath.indexOf(path) != -1;
  2365. let results = [];
  2366. let en = cookieManager.getCookiesFromHost(host, {});
  2367. while (en.hasMoreElements()) {
  2368. let cookie = en.getNext().QueryInterface(Ci.nsICookie2);
  2369. // take the hostname and progressively shorten
  2370. let hostname = host;
  2371. do {
  2372. if ((cookie.host == "." + hostname || cookie.host == hostname) &&
  2373. isForCurrentPath(cookie.path)) {
  2374. results.push({
  2375. "name": cookie.name,
  2376. "value": cookie.value,
  2377. "path": cookie.path,
  2378. "host": cookie.host,
  2379. "secure": cookie.isSecure,
  2380. "expiry": cookie.expires,
  2381. "httpOnly": cookie.isHttpOnly,
  2382. "originAttributes": cookie.originAttributes
  2383. });
  2384. break;
  2385. }
  2386. hostname = hostname.replace(/^.*?\./, "");
  2387. } while (hostname.indexOf(".") != -1);
  2388. }
  2389. return results;
  2390. case "Marionette:emitTouchEvent":
  2391. globalMessageManager.broadcastAsyncMessage(
  2392. "MarionetteMainListener:emitTouchEvent", message.json);
  2393. break;
  2394. case "Marionette:register":
  2395. let wid = message.json.value;
  2396. let be = message.target;
  2397. let rv = this.registerBrowser(wid, be);
  2398. return rv;
  2399. case "Marionette:listenersAttached":
  2400. if (message.json.listenerId === this.curBrowser.curFrameId) {
  2401. // If remoteness gets updated we need to call newSession. In the case
  2402. // of desktop this just sets up a small amount of state that doesn't
  2403. // change over the course of a session.
  2404. this.sendAsync("newSession", this.capabilities);
  2405. this.curBrowser.flushPendingCommands();
  2406. }
  2407. break;
  2408. }
  2409. };
  2410. GeckoDriver.prototype.responseCompleted = function () {
  2411. if (this.curBrowser !== null) {
  2412. this.curBrowser.pendingCommands = [];
  2413. }
  2414. };
  2415. /**
  2416. * Retrieve the localized string for the specified entity id.
  2417. *
  2418. * Example:
  2419. * localizeEntity(["chrome://global/locale/about.dtd"], "about.version")
  2420. *
  2421. * @param {Array.<string>} urls
  2422. * Array of .dtd URLs.
  2423. * @param {string} id
  2424. * The ID of the entity to retrieve the localized string for.
  2425. *
  2426. * @return {string}
  2427. * The localized string for the requested entity.
  2428. */
  2429. GeckoDriver.prototype.localizeEntity = function (cmd, resp) {
  2430. let {urls, id} = cmd.parameters;
  2431. if (!Array.isArray(urls)) {
  2432. throw new InvalidArgumentError("Value of `urls` should be of type 'Array'");
  2433. }
  2434. if (typeof id != "string") {
  2435. throw new InvalidArgumentError("Value of `id` should be of type 'string'");
  2436. }
  2437. resp.body.value = l10n.localizeEntity(urls, id);
  2438. }
  2439. /**
  2440. * Retrieve the localized string for the specified property id.
  2441. *
  2442. * Example:
  2443. * localizeProperty(["chrome://global/locale/findbar.properties"], "FastFind")
  2444. *
  2445. * @param {Array.<string>} urls
  2446. * Array of .properties URLs.
  2447. * @param {string} id
  2448. * The ID of the property to retrieve the localized string for.
  2449. *
  2450. * @return {string}
  2451. * The localized string for the requested property.
  2452. */
  2453. GeckoDriver.prototype.localizeProperty = function (cmd, resp) {
  2454. let {urls, id} = cmd.parameters;
  2455. if (!Array.isArray(urls)) {
  2456. throw new InvalidArgumentError("Value of `urls` should be of type 'Array'");
  2457. }
  2458. if (typeof id != "string") {
  2459. throw new InvalidArgumentError("Value of `id` should be of type 'string'");
  2460. }
  2461. resp.body.value = l10n.localizeProperty(urls, id);
  2462. }
  2463. GeckoDriver.prototype.commands = {
  2464. "getMarionetteID": GeckoDriver.prototype.getMarionetteID,
  2465. "sayHello": GeckoDriver.prototype.sayHello,
  2466. "newSession": GeckoDriver.prototype.newSession,
  2467. "getSessionCapabilities": GeckoDriver.prototype.getSessionCapabilities,
  2468. "log": GeckoDriver.prototype.log,
  2469. "getLogs": GeckoDriver.prototype.getLogs,
  2470. "setContext": GeckoDriver.prototype.setContext,
  2471. "getContext": GeckoDriver.prototype.getContext,
  2472. "executeScript": GeckoDriver.prototype.executeScript,
  2473. "getTimeouts": GeckoDriver.prototype.getTimeouts,
  2474. "timeouts": GeckoDriver.prototype.setTimeouts, // deprecated until Firefox 55
  2475. "setTimeouts": GeckoDriver.prototype.setTimeouts,
  2476. "singleTap": GeckoDriver.prototype.singleTap,
  2477. "performActions": GeckoDriver.prototype.performActions,
  2478. "releaseActions": GeckoDriver.prototype.releaseActions,
  2479. "actionChain": GeckoDriver.prototype.actionChain, // deprecated
  2480. "multiAction": GeckoDriver.prototype.multiAction, // deprecated
  2481. "executeAsyncScript": GeckoDriver.prototype.executeAsyncScript,
  2482. "executeJSScript": GeckoDriver.prototype.executeJSScript,
  2483. "findElement": GeckoDriver.prototype.findElement,
  2484. "findElements": GeckoDriver.prototype.findElements,
  2485. "clickElement": GeckoDriver.prototype.clickElement,
  2486. "getElementAttribute": GeckoDriver.prototype.getElementAttribute,
  2487. "getElementProperty": GeckoDriver.prototype.getElementProperty,
  2488. "getElementText": GeckoDriver.prototype.getElementText,
  2489. "getElementTagName": GeckoDriver.prototype.getElementTagName,
  2490. "isElementDisplayed": GeckoDriver.prototype.isElementDisplayed,
  2491. "getElementValueOfCssProperty": GeckoDriver.prototype.getElementValueOfCssProperty,
  2492. "getElementRect": GeckoDriver.prototype.getElementRect,
  2493. "isElementEnabled": GeckoDriver.prototype.isElementEnabled,
  2494. "isElementSelected": GeckoDriver.prototype.isElementSelected,
  2495. "sendKeysToElement": GeckoDriver.prototype.sendKeysToElement,
  2496. "clearElement": GeckoDriver.prototype.clearElement,
  2497. "getTitle": GeckoDriver.prototype.getTitle,
  2498. "getWindowType": GeckoDriver.prototype.getWindowType,
  2499. "getPageSource": GeckoDriver.prototype.getPageSource,
  2500. "get": GeckoDriver.prototype.get,
  2501. "getCurrentUrl": GeckoDriver.prototype.getCurrentUrl,
  2502. "goBack": GeckoDriver.prototype.goBack,
  2503. "goForward": GeckoDriver.prototype.goForward,
  2504. "refresh": GeckoDriver.prototype.refresh,
  2505. "getWindowHandle": GeckoDriver.prototype.getWindowHandle,
  2506. "getChromeWindowHandle": GeckoDriver.prototype.getChromeWindowHandle,
  2507. "getCurrentChromeWindowHandle": GeckoDriver.prototype.getChromeWindowHandle,
  2508. "getWindowHandles": GeckoDriver.prototype.getWindowHandles,
  2509. "getChromeWindowHandles": GeckoDriver.prototype.getChromeWindowHandles,
  2510. "getWindowPosition": GeckoDriver.prototype.getWindowPosition,
  2511. "setWindowPosition": GeckoDriver.prototype.setWindowPosition,
  2512. "getActiveFrame": GeckoDriver.prototype.getActiveFrame,
  2513. "switchToFrame": GeckoDriver.prototype.switchToFrame,
  2514. "switchToParentFrame": GeckoDriver.prototype.switchToParentFrame,
  2515. "switchToWindow": GeckoDriver.prototype.switchToWindow,
  2516. "switchToShadowRoot": GeckoDriver.prototype.switchToShadowRoot,
  2517. "deleteSession": GeckoDriver.prototype.deleteSession,
  2518. "importScript": GeckoDriver.prototype.importScript,
  2519. "clearImportedScripts": GeckoDriver.prototype.clearImportedScripts,
  2520. "getAppCacheStatus": GeckoDriver.prototype.getAppCacheStatus,
  2521. "close": GeckoDriver.prototype.close,
  2522. "closeChromeWindow": GeckoDriver.prototype.closeChromeWindow,
  2523. "setTestName": GeckoDriver.prototype.setTestName,
  2524. "takeScreenshot": GeckoDriver.prototype.takeScreenshot,
  2525. "addCookie": GeckoDriver.prototype.addCookie,
  2526. "getCookies": GeckoDriver.prototype.getCookies,
  2527. "deleteAllCookies": GeckoDriver.prototype.deleteAllCookies,
  2528. "deleteCookie": GeckoDriver.prototype.deleteCookie,
  2529. "getActiveElement": GeckoDriver.prototype.getActiveElement,
  2530. "getScreenOrientation": GeckoDriver.prototype.getScreenOrientation,
  2531. "setScreenOrientation": GeckoDriver.prototype.setScreenOrientation,
  2532. "getWindowSize": GeckoDriver.prototype.getWindowSize,
  2533. "setWindowSize": GeckoDriver.prototype.setWindowSize,
  2534. "maximizeWindow": GeckoDriver.prototype.maximizeWindow,
  2535. "dismissDialog": GeckoDriver.prototype.dismissDialog,
  2536. "acceptDialog": GeckoDriver.prototype.acceptDialog,
  2537. "getTextFromDialog": GeckoDriver.prototype.getTextFromDialog,
  2538. "sendKeysToDialog": GeckoDriver.prototype.sendKeysToDialog,
  2539. "acceptConnections": GeckoDriver.prototype.acceptConnections,
  2540. "quitApplication": GeckoDriver.prototype.quitApplication,
  2541. "localization:l10n:localizeEntity": GeckoDriver.prototype.localizeEntity,
  2542. "localization:l10n:localizeProperty": GeckoDriver.prototype.localizeProperty,
  2543. "addon:install": GeckoDriver.prototype.installAddon,
  2544. "addon:uninstall": GeckoDriver.prototype.uninstallAddon,
  2545. };
  2546. function copy (obj) {
  2547. if (Array.isArray(obj)) {
  2548. return obj.slice();
  2549. } else if (typeof obj == "object") {
  2550. return Object.assign({}, obj);
  2551. }
  2552. return obj;
  2553. }
  2554. /**
  2555. * Get the outer window ID for the specified window.
  2556. *
  2557. * @param {nsIDOMWindow} win
  2558. * Window whose browser we need to access.
  2559. *
  2560. * @return {string}
  2561. * Returns the unique window ID.
  2562. */
  2563. function getOuterWindowId(win) {
  2564. let id = win.QueryInterface(Ci.nsIInterfaceRequestor)
  2565. .getInterface(Ci.nsIDOMWindowUtils)
  2566. .outerWindowID;
  2567. return id.toString();
  2568. }