summaryrefslogtreecommitdiffstats
path: root/devtools/shared/worker/helper.js
blob: 20ccd3de5e2e7a3e00477186c6a9b549678afd99 (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
/* 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/. */

/* eslint-env amd */

"use strict";

/* global workerHelper */
/* exported workerHelper */
(function (root, factory) {
  if (typeof define === "function" && define.amd) {
    define(factory);
  } else if (typeof exports === "object") {
    module.exports = factory();
  } else {
    root.workerHelper = factory();
  }
})(this, function () {
  /**
   * 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) {
      const { id, task, data } = e.data;
      const taskFn = self._tasks[task];

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

      try {
        handleResponse(taskFn(data));
      } catch (ex) {
        handleError(ex);
      }

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

      function handleError(error = "Error") {
        try {
          // First, try and structured clone the error across directly.
          self.postMessage({ id, error });
        } catch (x) {
          // 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 (ex) {
            errorString += "<could not stringify error>";
          }

          if ("stack" in error) {
            try {
              errorString += "\n" + error.stack;
            } catch (err) {
              // Do nothing
            }
          }

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

  return { createTask };
});