summaryrefslogtreecommitdiffstats
path: root/devtools/shared/worker/helper.js
blob: 69512550bb31700aba5aa20a0a4c1855b8e83fa0 (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
/* 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/. */
(function (root, factory) {
  "use strict";

  if (typeof define === "function" && define.amd) {
    define(factory);
  } else if (typeof exports === "object") {
    module.exports = factory();
  } else {
    root.workerHelper = factory();
  }
}(this, function () {
  "use strict";

  /**
   * This file is to only be included by ChromeWorkers. This exposes
   * a `createTask` function to workers to register tasks for communication
   * back to `devtools/shared/worker`.
   *
   * Tasks can be send their responses via a return value, either a primitive
   * or a promise.
   *
   * createTask(self, "average", function (data) {
   *   return data.reduce((sum, val) => sum + val, 0) / data.length;
   * });
   *
   * createTask(self, "average", function (data) {
   *   return new Promise((resolve, reject) => {
   *     resolve(data.reduce((sum, val) => sum + val, 0) / data.length);
   *   });
   * });
   *
   *
   * Errors:
   *
   * Returning an Error value, or if the returned promise is rejected, this
   * propagates to the DevToolsWorker as a rejected promise. If an error is
   * thrown in a synchronous function, that error is also propagated.
   */

  /**
   * Takes a worker's `self` object, a task name, and a function to
   * be called when that task is called. The task is called with the
   * passed in data as the first argument
   *
   * @param {object} self
   * @param {string} name
   * @param {function} fn
   */
  function createTask(self, name, fn) {
    // Store a hash of task name to function on the Worker
    if (!self._tasks) {
      self._tasks = {};
    }

    // Create the onmessage handler if not yet created.
    if (!self.onmessage) {
      self.onmessage = createHandler(self);
    }

    // Store the task on the worker.
    self._tasks[name] = fn;
  }

  /**
   * Creates the `self.onmessage` handler for a Worker.
   *
   * @param {object} self
   * @return {function}
   */
  function createHandler(self) {
    return function (e) {
      let { id, task, data } = e.data;
      let taskFn = self._tasks[task];

      if (!taskFn) {
        self.postMessage({ id, error: `Task "${task}" not found in worker.` });
        return;
      }

      try {
        let results;
        handleResponse(taskFn(data));
      } catch (e) {
        handleError(e);
      }

      function handleResponse(response) {
        // If a promise
        if (response && typeof response.then === "function") {
          response.then(val => self.postMessage({ id, response: val }), handleError);
        }
        // If an error object
        else if (response instanceof Error) {
          handleError(response);
        }
        // If anything else
        else {
          self.postMessage({ id, response });
        }
      }

      function handleError(error = "Error") {
        try {
          // First, try and structured clone the error across directly.
          self.postMessage({ id, error });
        } catch (_) {
          // We could not clone whatever error value was given. Do our best to
          // stringify it.
          let errorString = `Error while performing task "${task}": `;

          try {
            errorString += error.toString();
          } catch (_) {
            errorString += "<could not stringify error>";
          }

          if ("stack" in error) {
            try {
              errorString += "\n" + error.stack;
            } catch (_) { }
          }

          self.postMessage({ id, error: errorString });
        }
      }
    };
  }

  return { createTask: createTask };
}.bind(this)));