Request.js (6243B)
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/. */ 4 5 "use strict"; 6 7 var { 8 findPlaceholders, 9 getPath, 10 } = require("resource://devtools/shared/protocol/utils.js"); 11 var { 12 types, 13 BULK_REQUEST, 14 } = require("resource://devtools/shared/protocol/types.js"); 15 16 /** 17 * Manages a request template. 18 */ 19 class Request { 20 /** 21 * @param {string} type 22 * The type defined in the specification for this request. 23 * For methods, it will be the attribute name in "methods" dictionary. 24 * For events, it will be the attribute name in "events" dictionary. 25 * @param {object} template 26 * The request template. 27 */ 28 constructor(type, template = {}) { 29 // The EventEmitter event name (this.type, attribute name in the event specification file) emitted on the Actor/Front, 30 // may be different from the RDP JSON packet event name (ret[type], type attribute value in the event specification file) 31 // In the specification: 32 // "my-event": { // <= EventEmitter name 33 // type: "myEvent", // <= RDP packet type attribute 34 // ... 35 // } 36 this.type = template.type || type; 37 38 this.template = template; 39 this.args = findPlaceholders(template, Arg); 40 } 41 42 /** 43 * Write a request. 44 * 45 * @param {Array} fnArgs 46 * The function arguments to place in the request. 47 * @param {object} ctx 48 * The object making the request. 49 * @returns a request packet. 50 */ 51 write(fnArgs, ctx) { 52 // Bulk request can't send custom attributes/custom JSON packet. 53 // Only communicate "type" and "length" attributes to the transport layer, 54 // which will emit a JSON RDP packet with an additional "actor attribute. 55 if (this.template === BULK_REQUEST) { 56 // The Front's method is expected to be called with a unique object argument 57 // with a "length" attribute, which refers to the total size of bytes to be 58 // sent via a the bulk StreamCopier. 59 if (typeof fnArgs[0].length != "number") { 60 throw new Error( 61 "This front's method is expected to send a bulk request and should be called with an object argument with a length attribute." 62 ); 63 } 64 return { type: this.type, length: fnArgs[0].length }; 65 } 66 67 const ret = { 68 type: this.type, 69 }; 70 for (const key in this.template) { 71 const value = this.template[key]; 72 if (value instanceof Arg || value instanceof Option) { 73 ret[key] = value.write( 74 value.index in fnArgs ? fnArgs[value.index] : undefined, 75 ctx, 76 key 77 ); 78 } else if (key == "type") { 79 // Ignore the type attribute which have already been considered in the constructor. 80 continue; 81 } else { 82 throw new Error( 83 "Request can only an object with `Arg` or `Option` properties" 84 ); 85 } 86 } 87 return ret; 88 } 89 90 /** 91 * Read a request. 92 * 93 * @param {object} packet 94 * The request packet. 95 * @param {object} ctx 96 * The object making the request. 97 * @returns an arguments array 98 */ 99 read(packet, ctx) { 100 if (this.template === BULK_REQUEST) { 101 // The transport layer will convey a custom packet object with length, copyTo and copyToBuffer, 102 // which we transfer to the Actor's method via a unique object argument. 103 // This help know about the incoming data size and read the binary buffer via `copyTo` 104 // or `copyToBuffer`. 105 return [ 106 { 107 length: packet.length, 108 copyTo: packet.copyTo, 109 copyToBuffer: packet.copyToBuffer, 110 }, 111 ]; 112 } 113 114 const fnArgs = []; 115 for (const templateArg of this.args) { 116 const arg = templateArg.placeholder; 117 const path = templateArg.path; 118 const name = path[path.length - 1]; 119 arg.read(getPath(packet, path), ctx, fnArgs, name); 120 } 121 return fnArgs; 122 } 123 } 124 125 exports.Request = Request; 126 127 /** 128 * Request/Response templates and generation 129 * 130 * Request packets are specified as json templates with 131 * Arg and Option placeholders where arguments should be 132 * placed. 133 * 134 * Reponse packets are also specified as json templates, 135 * with a RetVal placeholder where the return value should be 136 * placed. 137 */ 138 139 /** 140 * Placeholder for simple arguments. 141 */ 142 class Arg { 143 /** 144 * @param {number} index 145 * The argument index to place at this position. 146 * @param type type 147 * The argument should be marshalled as this type. 148 */ 149 constructor(index, type) { 150 this.index = index; 151 // Prevent force loading all Arg types by accessing it only when needed 152 loader.lazyGetter(this, "type", function () { 153 return types.getType(type); 154 }); 155 } 156 157 write(arg, ctx) { 158 return this.type.write(arg, ctx); 159 } 160 161 read(v, ctx, outArgs) { 162 outArgs[this.index] = this.type.read(v, ctx); 163 } 164 } 165 166 // Outside of protocol.js, Arg is called as factory method, without the new keyword. 167 exports.Arg = function (index, type) { 168 return new Arg(index, type); 169 }; 170 171 /** 172 * Placeholder for an options argument value that should be hoisted 173 * into the packet. 174 * 175 * If provided in a method specification: 176 * 177 * { optionArg: Option(1)} 178 * 179 * Then arguments[1].optionArg will be placed in the packet in this 180 * value's place. 181 */ 182 class Option extends Arg { 183 /** 184 * @param {number} index 185 * The argument index of the options value. 186 * @param type type 187 * The argument should be marshalled as this type. 188 */ 189 constructor(index, type) { 190 super(index, type); 191 } 192 193 write(arg, ctx, name) { 194 // Ignore if arg is undefined or null; allow other falsy values 195 if (arg == undefined || arg[name] == undefined) { 196 return undefined; 197 } 198 const v = arg[name]; 199 return this.type.write(v, ctx); 200 } 201 202 read(v, ctx, outArgs, name) { 203 if (outArgs[this.index] === undefined) { 204 outArgs[this.index] = {}; 205 } 206 if (v === undefined) { 207 return; 208 } 209 outArgs[this.index][name] = this.type.read(v, ctx); 210 } 211 } 212 213 // Outside of protocol.js, Option is called as factory method, without the new keyword. 214 exports.Option = function (index, type) { 215 return new Option(index, type); 216 };