message.sys.mjs (8820B)
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 5 const lazy = {}; 6 7 ChromeUtils.defineESModuleGetters(lazy, { 8 assert: "chrome://remote/content/shared/webdriver/Assert.sys.mjs", 9 error: "chrome://remote/content/shared/webdriver/Errors.sys.mjs", 10 truncate: "chrome://remote/content/shared/Format.sys.mjs", 11 }); 12 13 /** Representation of the packets transported over the wire. */ 14 export class Message { 15 /** 16 * @param {number} messageID 17 * Message ID unique identifying this message. 18 */ 19 constructor(messageID) { 20 this.id = lazy.assert.integer(messageID); 21 } 22 23 toString() { 24 function replacer(key, value) { 25 if (typeof value === "string") { 26 return lazy.truncate`${value}`; 27 } 28 return value; 29 } 30 31 return JSON.stringify(this.toPacket(), replacer); 32 } 33 34 /** 35 * Converts a data packet into a {@link Command} or {@link Response}. 36 * 37 * @param {Array.<number, number, ?, ?>} data 38 * A four element array where the elements, in sequence, signifies 39 * message type, message ID, method name or error, and parameters 40 * or result. 41 * 42 * @returns {Message} 43 * Based on the message type, a {@link Command} or {@link Response} 44 * instance. 45 * 46 * @throws {TypeError} 47 * If the message type is not recognised. 48 */ 49 static fromPacket(data) { 50 const [type] = data; 51 52 switch (type) { 53 case Command.Type: 54 return Command.fromPacket(data); 55 56 case Response.Type: 57 return Response.fromPacket(data); 58 59 default: 60 throw new TypeError( 61 "Unrecognised message type in packet: " + JSON.stringify(data) 62 ); 63 } 64 } 65 } 66 67 /** 68 * Messages may originate from either the server or the client. 69 * Because the remote protocol is full duplex, both endpoints may be 70 * the origin of both commands and responses. 71 * 72 * @enum 73 * @see {@link Message} 74 */ 75 Message.Origin = { 76 /** Indicates that the message originates from the client. */ 77 Client: 0, 78 /** Indicates that the message originates from the server. */ 79 Server: 1, 80 }; 81 82 /** 83 * A command is a request from the client to run a series of remote end 84 * steps and return a fitting response. 85 * 86 * The command can be synthesised from the message passed over the 87 * Marionette socket using the {@link fromPacket} function. The format of 88 * a message is: 89 * 90 * <pre> 91 * [<var>type</var>, <var>id</var>, <var>name</var>, <var>params</var>] 92 * </pre> 93 * 94 * where 95 * 96 * <dl> 97 * <dt><var>type</var> (integer) 98 * <dd> 99 * Must be zero (integer). Zero means that this message is 100 * a command. 101 * 102 * <dt><var>id</var> (integer) 103 * <dd> 104 * Integer used as a sequence number. The server replies with 105 * the same ID for the response. 106 * 107 * <dt><var>name</var> (string) 108 * <dd> 109 * String representing the command name with an associated set 110 * of remote end steps. 111 * 112 * <dt><var>params</var> (JSON Object or null) 113 * <dd> 114 * Object of command function arguments. The keys of this object 115 * must be strings, but the values can be arbitrary values. 116 * </dl> 117 * 118 * A command has an associated message <var>id</var> that prevents 119 * the dispatcher from sending responses in the wrong order. 120 * 121 * The command may also have optional error- and result handlers that 122 * are called when the client returns with a response. These are 123 * <code>function onerror({Object})</code>, 124 * <code>function onresult({Object})</code>, and 125 * <code>function onresult({Response})</code>: 126 * 127 * @param {number} messageID 128 * Message ID unique identifying this message. 129 * @param {string} name 130 * Command name. 131 * @param {Record<string, ?>} params 132 * Command parameters. 133 */ 134 export class Command extends Message { 135 constructor(messageID, name, params = {}) { 136 super(messageID); 137 138 this.name = lazy.assert.string(name); 139 this.parameters = lazy.assert.object(params); 140 141 this.onerror = null; 142 this.onresult = null; 143 144 this.origin = Message.Origin.Client; 145 this.sent = false; 146 } 147 148 /** 149 * Calls the error- or result handler associated with this command. 150 * This function can be replaced with a custom response handler. 151 * 152 * @param {Response} resp 153 * The response to pass on to the result or error to the 154 * <code>onerror</code> or <code>onresult</code> handlers to. 155 */ 156 onresponse(resp) { 157 if (this.onerror && resp.error) { 158 this.onerror(resp.error); 159 } else if (this.onresult && resp.body) { 160 this.onresult(resp.body); 161 } 162 } 163 164 /** 165 * Encodes the command to a packet. 166 * 167 * @returns {Array} 168 * Packet. 169 */ 170 toPacket() { 171 return [Command.Type, this.id, this.name, this.parameters]; 172 } 173 174 /** 175 * Converts a data packet into {@link Command}. 176 * 177 * @param {Array.<number, number, *, *>} payload 178 * A four element array where the elements, in sequence, signifies 179 * message type, message ID, command name, and parameters. 180 * 181 * @returns {Command} 182 * Representation of packet. 183 * 184 * @throws {TypeError} 185 * If the message type is not recognised. 186 */ 187 static fromPacket(payload) { 188 let [type, msgID, name, params] = payload; 189 lazy.assert.that(n => n === Command.Type)(type); 190 191 // if parameters are given but null, treat them as undefined 192 if (params === null) { 193 params = undefined; 194 } 195 196 return new Command(msgID, name, params); 197 } 198 } 199 200 Command.Type = 0; 201 202 /** 203 * @callback ResponseCallback 204 * 205 * @param {Response} resp 206 * Response to handle. 207 */ 208 209 /** 210 * Represents the response returned from the remote end after execution 211 * of its corresponding command. 212 * 213 * The response is a mutable object passed to each command for 214 * modification through the available setters. To send data in a response, 215 * you modify the body property on the response. The body property can 216 * also be replaced completely. 217 * 218 * The response is sent implicitly by 219 * {@link server.TCPConnection#execute when a command has finished 220 * executing, and any modifications made subsequent to that will have 221 * no effect. 222 * 223 * @param {number} messageID 224 * Message ID tied to the corresponding command request this is 225 * a response for. 226 * @param {ResponseHandler} respHandler 227 * Function callback called on sending the response. 228 */ 229 export class Response extends Message { 230 constructor(messageID, respHandler = () => {}) { 231 super(messageID); 232 233 this.respHandler_ = lazy.assert.callable(respHandler); 234 235 this.error = null; 236 this.body = { value: null }; 237 238 this.origin = Message.Origin.Server; 239 this.sent = false; 240 } 241 242 /** 243 * Sends response conditionally, given a predicate. 244 * 245 * @param {function(Response): boolean} predicate 246 * A predicate taking a Response object and returning a boolean. 247 */ 248 sendConditionally(predicate) { 249 if (predicate(this)) { 250 this.send(); 251 } 252 } 253 254 /** 255 * Sends response using the response handler provided on 256 * construction. 257 * 258 * @throws {RangeError} 259 * If the response has already been sent. 260 */ 261 send() { 262 if (this.sent) { 263 throw new RangeError("Response has already been sent: " + this); 264 } 265 this.respHandler_(this); 266 this.sent = true; 267 } 268 269 /** 270 * Send error to client. 271 * 272 * Turns the response into an error response, clears any previously 273 * set body data, and sends it using the response handler provided 274 * on construction. 275 * 276 * @param {Error} err 277 * The Error instance to send. 278 * 279 * @throws {Error} 280 * If <var>err</var> is not a {@link WebDriverError}, the error 281 * is propagated, i.e. rethrown. 282 */ 283 sendError(err) { 284 this.error = lazy.error.wrap(err).toJSON(); 285 this.body = null; 286 this.send(); 287 288 // propagate errors which are implementation problems 289 if (!lazy.error.isWebDriverError(err)) { 290 throw err; 291 } 292 } 293 294 /** 295 * Encodes the response to a packet. 296 * 297 * @returns {Array} 298 * Packet. 299 */ 300 toPacket() { 301 return [Response.Type, this.id, this.error, this.body]; 302 } 303 304 /** 305 * Converts a data packet into {@link Response}. 306 * 307 * @param {Array.<number, number, ?, ?>} payload 308 * A four element array where the elements, in sequence, signifies 309 * message type, message ID, error, and result. 310 * 311 * @returns {Response} 312 * Representation of packet. 313 * 314 * @throws {TypeError} 315 * If the message type is not recognised. 316 */ 317 static fromPacket(payload) { 318 let [type, msgID, err, body] = payload; 319 lazy.assert.that(n => n === Response.Type)(type); 320 321 let resp = new Response(msgID); 322 resp.error = lazy.assert.string(err); 323 324 resp.body = body; 325 return resp; 326 } 327 } 328 329 Response.Type = 1;