message.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  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 {utils: Cu} = Components;
  6. Cu.import("resource://gre/modules/Log.jsm");
  7. Cu.import("resource://gre/modules/Task.jsm");
  8. Cu.import("chrome://marionette/content/error.js");
  9. this.EXPORTED_SYMBOLS = [
  10. "Command",
  11. "Message",
  12. "MessageOrigin",
  13. "Response",
  14. ];
  15. const logger = Log.repository.getLogger("Marionette");
  16. this.MessageOrigin = {
  17. Client: 0,
  18. Server: 1,
  19. };
  20. this.Message = {};
  21. /**
  22. * Converts a data packet into a Command or Response type.
  23. *
  24. * @param {Array.<number, number, ?, ?>} data
  25. * A four element array where the elements, in sequence, signifies
  26. * message type, message ID, method name or error, and parameters
  27. * or result.
  28. *
  29. * @return {(Command,Response)}
  30. * Based on the message type, a Command or Response instance.
  31. *
  32. * @throws {TypeError}
  33. * If the message type is not recognised.
  34. */
  35. Message.fromMsg = function (data) {
  36. switch (data[0]) {
  37. case Command.TYPE:
  38. return Command.fromMsg(data);
  39. case Response.TYPE:
  40. return Response.fromMsg(data);
  41. default:
  42. throw new TypeError(
  43. "Unrecognised message type in packet: " + JSON.stringify(data));
  44. }
  45. };
  46. /**
  47. * A command is a request from the client to run a series of remote end
  48. * steps and return a fitting response.
  49. *
  50. * The command can be synthesised from the message passed over the
  51. * Marionette socket using the {@code fromMsg} function. The format of
  52. * a message is:
  53. *
  54. * [type, id, name, params]
  55. *
  56. * where
  57. *
  58. * type:
  59. * Must be zero (integer). Zero means that this message is a command.
  60. *
  61. * id:
  62. * Number used as a sequence number. The server replies with a
  63. * requested id.
  64. *
  65. * name:
  66. * String representing the command name with an associated set of
  67. * remote end steps.
  68. *
  69. * params:
  70. * Object of command function arguments. The keys of this object
  71. * must be strings, but the values can be arbitrary values.
  72. *
  73. * A command has an associated message {@code id} that prevents the
  74. * dispatcher from sending responses in the wrong order.
  75. *
  76. * The command may also have optional error- and result handlers that
  77. * are called when the client returns with a response. These are
  78. * {@code function onerror({Object})}, {@code function onresult({Object})},
  79. * and {@code function onresult({Response})}.
  80. *
  81. * @param {number} msgId
  82. * Message ID unique identifying this message.
  83. * @param {string} name
  84. * Command name.
  85. * @param {Object<string, ?>} params
  86. * Command parameters.
  87. */
  88. this.Command = class {
  89. constructor(msgId, name, params={}) {
  90. this.id = msgId;
  91. this.name = name;
  92. this.parameters = params;
  93. this.onerror = null;
  94. this.onresult = null;
  95. this.origin = MessageOrigin.Client;
  96. this.sent = false;
  97. }
  98. /**
  99. * Calls the error- or result handler associated with this command.
  100. * This function can be replaced with a custom response handler.
  101. *
  102. * @param {Response} resp
  103. * The response to pass on to the result or error to the
  104. * {@code onerror} or {@code onresult} handlers to.
  105. */
  106. onresponse(resp) {
  107. if (resp.error && this.onerror) {
  108. this.onerror(resp.error);
  109. } else if (resp.body && this.onresult) {
  110. this.onresult(resp.body);
  111. }
  112. }
  113. toMsg() {
  114. return [Command.TYPE, this.id, this.name, this.parameters];
  115. }
  116. toString() {
  117. return "Command {id: " + this.id + ", " +
  118. "name: " + JSON.stringify(this.name) + ", " +
  119. "parameters: " + JSON.stringify(this.parameters) + "}"
  120. }
  121. static fromMsg(msg) {
  122. return new Command(msg[1], msg[2], msg[3]);
  123. }
  124. };
  125. Command.TYPE = 0;
  126. const validator = {
  127. exclusionary: {
  128. "capabilities": ["error", "value"],
  129. "error": ["value", "sessionId", "capabilities"],
  130. "sessionId": ["error", "value"],
  131. "value": ["error", "sessionId", "capabilities"],
  132. },
  133. set: function (obj, prop, val) {
  134. let tests = this.exclusionary[prop];
  135. if (tests) {
  136. for (let t of tests) {
  137. if (obj.hasOwnProperty(t)) {
  138. throw new TypeError(`${t} set, cannot set ${prop}`);
  139. }
  140. }
  141. }
  142. obj[prop] = val;
  143. return true;
  144. },
  145. };
  146. /**
  147. * The response body is exposed as an argument to commands.
  148. * Commands can set fields on the body through defining properties.
  149. *
  150. * Setting properties invokes a validator that performs tests for
  151. * mutually exclusionary fields on the input against the existing data
  152. * in the body.
  153. *
  154. * For example setting the {@code error} property on the body when
  155. * {@code value}, {@code sessionId}, or {@code capabilities} have been
  156. * set previously will cause an error.
  157. */
  158. this.ResponseBody = () => new Proxy({}, validator);
  159. /**
  160. * Represents the response returned from the remote end after execution
  161. * of its corresponding command.
  162. *
  163. * The response is a mutable object passed to each command for
  164. * modification through the available setters. To send data in a response,
  165. * you modify the body property on the response. The body property can
  166. * also be replaced completely.
  167. *
  168. * The response is sent implicitly by CommandProcessor when a command
  169. * has finished executing, and any modifications made subsequent to that
  170. * will have no effect.
  171. *
  172. * @param {number} msgId
  173. * Message ID tied to the corresponding command request this is a
  174. * response for.
  175. * @param {function(Response|Message)} respHandler
  176. * Function callback called on sending the response.
  177. */
  178. this.Response = class {
  179. constructor(msgId, respHandler) {
  180. this.id = msgId;
  181. this.error = null;
  182. this.body = ResponseBody();
  183. this.origin = MessageOrigin.Server;
  184. this.sent = false;
  185. this.respHandler_ = respHandler;
  186. }
  187. /**
  188. * Sends response conditionally, given a predicate.
  189. *
  190. * @param {function(Response): boolean} predicate
  191. * A predicate taking a Response object and returning a boolean.
  192. */
  193. sendConditionally(predicate) {
  194. if (predicate(this)) {
  195. this.send();
  196. }
  197. }
  198. /**
  199. * Sends response using the response handler provided on construction.
  200. *
  201. * @throws {RangeError}
  202. * If the response has already been sent.
  203. */
  204. send() {
  205. if (this.sent) {
  206. throw new RangeError("Response has already been sent: " + this);
  207. }
  208. this.respHandler_(this);
  209. this.sent = true;
  210. }
  211. /**
  212. * Send given Error to client.
  213. *
  214. * Turns the response into an error response, clears any previously
  215. * set body data, and sends it using the response handler provided
  216. * on construction.
  217. *
  218. * @param {Error} err
  219. * The Error instance to send.
  220. *
  221. * @throws {Error}
  222. * If the {@code error} is not a WebDriverError, the error is
  223. * propagated.
  224. */
  225. sendError(err) {
  226. this.error = error.wrap(err).toJSON();
  227. this.body = null;
  228. this.send();
  229. // propagate errors which are implementation problems
  230. if (!error.isWebDriverError(err)) {
  231. throw err;
  232. }
  233. }
  234. toMsg() {
  235. return [Response.TYPE, this.id, this.error, this.body];
  236. }
  237. toString() {
  238. return "Response {id: " + this.id + ", " +
  239. "error: " + JSON.stringify(this.error) + ", " +
  240. "body: " + JSON.stringify(this.body) + "}";
  241. }
  242. static fromMsg(msg) {
  243. let resp = new Response(msg[1], null);
  244. resp.error = msg[2];
  245. resp.body = msg[3];
  246. return resp;
  247. }
  248. };
  249. Response.TYPE = 1;