summaryrefslogtreecommitdiffstats
path: root/devtools/shared/protocol/Request.js
blob: 20befd938ac2f05eb792e4c9a3dd57d0a6ec8df5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

const { extend } = require("resource://devtools/shared/extend.js");
var {
  findPlaceholders,
  getPath,
} = require("resource://devtools/shared/protocol/utils.js");
var { types } = require("resource://devtools/shared/protocol/types.js");

/**
 * Manages a request template.
 *
 * @param object template
 *    The request template.
 * @construcor
 */
var Request = function (template = {}) {
  this.type = template.type;
  this.template = template;
  this.args = findPlaceholders(template, Arg);
};

Request.prototype = {
  /**
   * Write a request.
   *
   * @param array fnArgs
   *    The function arguments to place in the request.
   * @param object ctx
   *    The object making the request.
   * @returns a request packet.
   */
  write(fnArgs, ctx) {
    const ret = {};
    for (const key in this.template) {
      const value = this.template[key];
      if (value instanceof Arg) {
        ret[key] = value.write(
          value.index in fnArgs ? fnArgs[value.index] : undefined,
          ctx,
          key
        );
      } else if (key == "type") {
        ret[key] = value;
      } else {
        throw new Error(
          "Request can only an object with `Arg` or `Option` properties"
        );
      }
    }
    return ret;
  },

  /**
   * Read a request.
   *
   * @param object packet
   *    The request packet.
   * @param object ctx
   *    The object making the request.
   * @returns an arguments array
   */
  read(packet, ctx) {
    const fnArgs = [];
    for (const templateArg of this.args) {
      const arg = templateArg.placeholder;
      const path = templateArg.path;
      const name = path[path.length - 1];
      arg.read(getPath(packet, path), ctx, fnArgs, name);
    }
    return fnArgs;
  },
};

exports.Request = Request;

/**
 * Request/Response templates and generation
 *
 * Request packets are specified as json templates with
 * Arg and Option placeholders where arguments should be
 * placed.
 *
 * Reponse packets are also specified as json templates,
 * with a RetVal placeholder where the return value should be
 * placed.
 */

/**
 * Placeholder for simple arguments.
 *
 * @param number index
 *    The argument index to place at this position.
 * @param type type
 *    The argument should be marshalled as this type.
 * @constructor
 */
var Arg = function (index, type) {
  this.index = index;
  // Prevent force loading all Arg types by accessing it only when needed
  loader.lazyGetter(this, "type", function () {
    return types.getType(type);
  });
};

Arg.prototype = {
  write(arg, ctx) {
    return this.type.write(arg, ctx);
  },

  read(v, ctx, outArgs) {
    outArgs[this.index] = this.type.read(v, ctx);
  },
};

// Outside of protocol.js, Arg is called as factory method, without the new keyword.
exports.Arg = function (index, type) {
  return new Arg(index, type);
};

/**
 * Placeholder for an options argument value that should be hoisted
 * into the packet.
 *
 * If provided in a method specification:
 *
 *   { optionArg: Option(1)}
 *
 * Then arguments[1].optionArg will be placed in the packet in this
 * value's place.
 *
 * @param number index
 *    The argument index of the options value.
 * @param type type
 *    The argument should be marshalled as this type.
 * @constructor
 */
var Option = function (index, type) {
  Arg.call(this, index, type);
};

Option.prototype = extend(Arg.prototype, {
  write(arg, ctx, name) {
    // Ignore if arg is undefined or null; allow other falsy values
    if (arg == undefined || arg[name] == undefined) {
      return undefined;
    }
    const v = arg[name];
    return this.type.write(v, ctx);
  },
  read(v, ctx, outArgs, name) {
    if (outArgs[this.index] === undefined) {
      outArgs[this.index] = {};
    }
    if (v === undefined) {
      return;
    }
    outArgs[this.index][name] = this.type.read(v, ctx);
  },
});

// Outside of protocol.js, Option is called as factory method, without the new keyword.
exports.Option = function (index, type) {
  return new Option(index, type);
};