no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / remote / marionette / message.sys.mjs
blobd8b5dd60f9e8a0af966204264fe9ce1d22ebbe32
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/. */
5 const lazy = {};
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 });
13 /** Representation of the packets transproted 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   }
23   toString() {
24     function replacer(key, value) {
25       if (typeof value === "string") {
26         return lazy.truncate`${value}`;
27       }
28       return value;
29     }
31     return JSON.stringify(this.toPacket(), replacer);
32   }
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;
52     switch (type) {
53       case Command.Type:
54         return Command.fromPacket(data);
56       case Response.Type:
57         return Response.fromPacket(data);
59       default:
60         throw new TypeError(
61           "Unrecognised message type in packet: " + JSON.stringify(data)
62         );
63     }
64   }
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,
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.
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.
107  *   <dt><var>name</var> (string)
108  *   <dd>
109  *     String representing the command name with an associated set
110  *     of remote end steps.
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>
118  * A command has an associated message <var>id</var> that prevents
119  * the dispatcher from sending responses in the wrong order.
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>:
127  * @param {number} messageID
128  *     Message ID unique identifying this message.
129  * @param {string} name
130  *     Command name.
131  * @param {Object<string, ?>} params
132  *     Command parameters.
133  */
134 export class Command extends Message {
135   constructor(messageID, name, params = {}) {
136     super(messageID);
138     this.name = lazy.assert.string(name);
139     this.parameters = lazy.assert.object(params);
141     this.onerror = null;
142     this.onresult = null;
144     this.origin = Message.Origin.Client;
145     this.sent = false;
146   }
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   }
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   }
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);
191     // if parameters are given but null, treat them as undefined
192     if (params === null) {
193       params = undefined;
194     }
196     return new Command(msgID, name, params);
197   }
200 Command.Type = 0;
203  * @callback ResponseCallback
205  * @param {Response} resp
206  *     Response to handle.
207  */
210  * Represents the response returned from the remote end after execution
211  * of its corresponding command.
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.
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.
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);
233     this.respHandler_ = lazy.assert.callable(respHandler);
235     this.error = null;
236     this.body = { value: null };
238     this.origin = Message.Origin.Server;
239     this.sent = false;
240   }
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   }
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   }
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();
288     // propagate errors which are implementation problems
289     if (!lazy.error.isWebDriverError(err)) {
290       throw err;
291     }
292   }
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   }
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);
321     let resp = new Response(msgID);
322     resp.error = lazy.assert.string(err);
324     resp.body = body;
325     return resp;
326   }
329 Response.Type = 1;