no bug - Import translations from android-l10n r=release a=l10n CLOSED TREE
[gecko.git] / remote / marionette / packets.sys.mjs
blob5acb455726f5199b7f9f023546a81c64f88c841c
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 const lazy = {};
7 ChromeUtils.defineESModuleGetters(lazy, {
8   StreamUtils: "chrome://remote/content/marionette/stream-utils.sys.mjs",
9 });
11 ChromeUtils.defineLazyGetter(lazy, "unicodeConverter", () => {
12   const unicodeConverter = Cc[
13     "@mozilla.org/intl/scriptableunicodeconverter"
14   ].createInstance(Ci.nsIScriptableUnicodeConverter);
15   unicodeConverter.charset = "UTF-8";
17   return unicodeConverter;
18 });
20 /**
21  * Packets contain read / write functionality for the different packet types
22  * supported by the debugging protocol, so that a transport can focus on
23  * delivery and queue management without worrying too much about the specific
24  * packet types.
25  *
26  * They are intended to be "one use only", so a new packet should be
27  * instantiated for each incoming or outgoing packet.
28  *
29  * A complete Packet type should expose at least the following:
30  *   read(stream, scriptableStream)
31  *     Called when the input stream has data to read
32  *   write(stream)
33  *     Called when the output stream is ready to write
34  *   get done()
35  *     Returns true once the packet is done being read / written
36  *   destroy()
37  *     Called to clean up at the end of use
38  */
40 const defer = function () {
41   let deferred = {
42     promise: new Promise((resolve, reject) => {
43       deferred.resolve = resolve;
44       deferred.reject = reject;
45     }),
46   };
47   return deferred;
50 // The transport's previous check ensured the header length did not
51 // exceed 20 characters.  Here, we opt for the somewhat smaller, but still
52 // large limit of 1 TiB.
53 const PACKET_LENGTH_MAX = Math.pow(2, 40);
55 /**
56  * A generic Packet processing object (extended by two subtypes below).
57  *
58  * @class
59  */
60 export function Packet(transport) {
61   this._transport = transport;
62   this._length = 0;
65 /**
66  * Attempt to initialize a new Packet based on the incoming packet header
67  * we've received so far.  We try each of the types in succession, trying
68  * JSON packets first since they are much more common.
69  *
70  * @param {string} header
71  *     Packet header string to attempt parsing.
72  * @param {DebuggerTransport} transport
73  *     Transport instance that will own the packet.
74  *
75  * @returns {Packet}
76  *     Parsed packet of the matching type, or null if no types matched.
77  */
78 Packet.fromHeader = function (header, transport) {
79   return (
80     JSONPacket.fromHeader(header, transport) ||
81     BulkPacket.fromHeader(header, transport)
82   );
85 Packet.prototype = {
86   get length() {
87     return this._length;
88   },
90   set length(length) {
91     if (length > PACKET_LENGTH_MAX) {
92       throw new Error(
93         "Packet length " +
94           length +
95           " exceeds the max length of " +
96           PACKET_LENGTH_MAX
97       );
98     }
99     this._length = length;
100   },
102   destroy() {
103     this._transport = null;
104   },
108  * With a JSON packet (the typical packet type sent via the transport),
109  * data is transferred as a JSON packet serialized into a string,
110  * with the string length prepended to the packet, followed by a colon
111  * ([length]:[packet]). The contents of the JSON packet are specified in
112  * the Remote Debugging Protocol specification.
114  * @param {DebuggerTransport} transport
115  *     Transport instance that will own the packet.
116  */
117 export function JSONPacket(transport) {
118   Packet.call(this, transport);
119   this._data = "";
120   this._done = false;
124  * Attempt to initialize a new JSONPacket based on the incoming packet
125  * header we've received so far.
127  * @param {string} header
128  *     Packet header string to attempt parsing.
129  * @param {DebuggerTransport} transport
130  *     Transport instance that will own the packet.
132  * @returns {JSONPacket}
133  *     Parsed packet, or null if it's not a match.
134  */
135 JSONPacket.fromHeader = function (header, transport) {
136   let match = this.HEADER_PATTERN.exec(header);
138   if (!match) {
139     return null;
140   }
142   let packet = new JSONPacket(transport);
143   packet.length = +match[1];
144   return packet;
147 JSONPacket.HEADER_PATTERN = /^(\d+):$/;
149 JSONPacket.prototype = Object.create(Packet.prototype);
151 Object.defineProperty(JSONPacket.prototype, "object", {
152   /**
153    * Gets the object (not the serialized string) being read or written.
154    */
155   get() {
156     return this._object;
157   },
159   /**
160    * Sets the object to be sent when write() is called.
161    */
162   set(object) {
163     this._object = object;
164     let data = JSON.stringify(object);
165     this._data = lazy.unicodeConverter.ConvertFromUnicode(data);
166     this.length = this._data.length;
167   },
170 JSONPacket.prototype.read = function (stream, scriptableStream) {
171   // Read in more packet data.
172   this._readData(stream, scriptableStream);
174   if (!this.done) {
175     // Don't have a complete packet yet.
176     return;
177   }
179   let json = this._data;
180   try {
181     json = lazy.unicodeConverter.ConvertToUnicode(json);
182     this._object = JSON.parse(json);
183   } catch (e) {
184     let msg =
185       "Error parsing incoming packet: " +
186       json +
187       " (" +
188       e +
189       " - " +
190       e.stack +
191       ")";
192     console.error(msg);
193     dump(msg + "\n");
194     return;
195   }
197   this._transport._onJSONObjectReady(this._object);
200 JSONPacket.prototype._readData = function (stream, scriptableStream) {
201   let bytesToRead = Math.min(
202     this.length - this._data.length,
203     stream.available()
204   );
205   this._data += scriptableStream.readBytes(bytesToRead);
206   this._done = this._data.length === this.length;
209 JSONPacket.prototype.write = function (stream) {
210   if (this._outgoing === undefined) {
211     // Format the serialized packet to a buffer
212     this._outgoing = this.length + ":" + this._data;
213   }
215   let written = stream.write(this._outgoing, this._outgoing.length);
216   this._outgoing = this._outgoing.slice(written);
217   this._done = !this._outgoing.length;
220 Object.defineProperty(JSONPacket.prototype, "done", {
221   get() {
222     return this._done;
223   },
226 JSONPacket.prototype.toString = function () {
227   return JSON.stringify(this._object, null, 2);
231  * With a bulk packet, data is transferred by temporarily handing over
232  * the transport's input or output stream to the application layer for
233  * writing data directly.  This can be much faster for large data sets,
234  * and avoids various stages of copies and data duplication inherent in
235  * the JSON packet type.  The bulk packet looks like:
237  *     bulk [actor] [type] [length]:[data]
239  * The interpretation of the data portion depends on the kind of actor and
240  * the packet's type.  See the Remote Debugging Protocol Stream Transport
241  * spec for more details.
243  * @param {DebuggerTransport} transport
244  *     Transport instance that will own the packet.
245  */
246 export function BulkPacket(transport) {
247   Packet.call(this, transport);
248   this._done = false;
249   this._readyForWriting = defer();
253  * Attempt to initialize a new BulkPacket based on the incoming packet
254  * header we've received so far.
256  * @param {string} header
257  *     Packet header string to attempt parsing.
258  * @param {DebuggerTransport} transport
259  *     Transport instance that will own the packet.
261  * @returns {BulkPacket}
262  *     Parsed packet, or null if it's not a match.
263  */
264 BulkPacket.fromHeader = function (header, transport) {
265   let match = this.HEADER_PATTERN.exec(header);
267   if (!match) {
268     return null;
269   }
271   let packet = new BulkPacket(transport);
272   packet.header = {
273     actor: match[1],
274     type: match[2],
275     length: +match[3],
276   };
277   return packet;
280 BulkPacket.HEADER_PATTERN = /^bulk ([^: ]+) ([^: ]+) (\d+):$/;
282 BulkPacket.prototype = Object.create(Packet.prototype);
284 BulkPacket.prototype.read = function (stream) {
285   // Temporarily pause monitoring of the input stream
286   this._transport.pauseIncoming();
288   let deferred = defer();
290   this._transport._onBulkReadReady({
291     actor: this.actor,
292     type: this.type,
293     length: this.length,
294     copyTo: output => {
295       let copying = lazy.StreamUtils.copyStream(stream, output, this.length);
296       deferred.resolve(copying);
297       return copying;
298     },
299     stream,
300     done: deferred,
301   });
303   // Await the result of reading from the stream
304   deferred.promise.then(() => {
305     this._done = true;
306     this._transport.resumeIncoming();
307   }, this._transport.close);
309   // Ensure this is only done once
310   this.read = () => {
311     throw new Error("Tried to read() a BulkPacket's stream multiple times.");
312   };
315 BulkPacket.prototype.write = function (stream) {
316   if (this._outgoingHeader === undefined) {
317     // Format the serialized packet header to a buffer
318     this._outgoingHeader =
319       "bulk " + this.actor + " " + this.type + " " + this.length + ":";
320   }
322   // Write the header, or whatever's left of it to write.
323   if (this._outgoingHeader.length) {
324     let written = stream.write(
325       this._outgoingHeader,
326       this._outgoingHeader.length
327     );
328     this._outgoingHeader = this._outgoingHeader.slice(written);
329     return;
330   }
332   // Temporarily pause the monitoring of the output stream
333   this._transport.pauseOutgoing();
335   let deferred = defer();
337   this._readyForWriting.resolve({
338     copyFrom: input => {
339       let copying = lazy.StreamUtils.copyStream(input, stream, this.length);
340       deferred.resolve(copying);
341       return copying;
342     },
343     stream,
344     done: deferred,
345   });
347   // Await the result of writing to the stream
348   deferred.promise.then(() => {
349     this._done = true;
350     this._transport.resumeOutgoing();
351   }, this._transport.close);
353   // Ensure this is only done once
354   this.write = () => {
355     throw new Error("Tried to write() a BulkPacket's stream multiple times.");
356   };
359 Object.defineProperty(BulkPacket.prototype, "streamReadyForWriting", {
360   get() {
361     return this._readyForWriting.promise;
362   },
365 Object.defineProperty(BulkPacket.prototype, "header", {
366   get() {
367     return {
368       actor: this.actor,
369       type: this.type,
370       length: this.length,
371     };
372   },
374   set(header) {
375     this.actor = header.actor;
376     this.type = header.type;
377     this.length = header.length;
378   },
381 Object.defineProperty(BulkPacket.prototype, "done", {
382   get() {
383     return this._done;
384   },
387 BulkPacket.prototype.toString = function () {
388   return "Bulk: " + JSON.stringify(this.header, null, 2);
392  * RawPacket is used to test the transport's error handling of malformed
393  * packets, by writing data directly onto the stream.
395  * @param {DebuggerTransport} transport
396  *     The transport instance that will own the packet.
397  * @param {string} data
398  *     The raw string to send out onto the stream.
399  */
400 export function RawPacket(transport, data) {
401   Packet.call(this, transport);
402   this._data = data;
403   this.length = data.length;
404   this._done = false;
407 RawPacket.prototype = Object.create(Packet.prototype);
409 RawPacket.prototype.read = function () {
410   // this has not yet been needed for testing
411   throw new Error("Not implemented");
414 RawPacket.prototype.write = function (stream) {
415   let written = stream.write(this._data, this._data.length);
416   this._data = this._data.slice(written);
417   this._done = !this._data.length;
420 Object.defineProperty(RawPacket.prototype, "done", {
421   get() {
422     return this._done;
423   },