summaryrefslogtreecommitdiffstats
path: root/devtools/server/actors/resources/thread-states.js
blob: 11aef81734a4a0fca0b030e4abe05b47b914fbc6 (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
/* 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 {
  TYPES: { THREAD_STATE },
} = require("resource://devtools/server/actors/resources/index.js");
const Targets = require("resource://devtools/server/actors/targets/index.js");

const {
  PAUSE_REASONS,
  STATES: THREAD_STATES,
} = require("resource://devtools/server/actors/thread.js");

// Possible values of breakpoint's resource's `state` attribute
const STATES = {
  PAUSED: "paused",
  RESUMED: "resumed",
};

/**
 * Emit THREAD_STATE resources, which is emitted each time the target's thread pauses or resumes.
 * So that there is two distinct values for this resource: pauses and resumes.
 * These values are distinguished by `state` attribute which can be either "paused" or "resumed".
 *
 * Resume events, won't expose any other attribute other than `resourceType` and `state`.
 *
 * Pause events will expose the following attributes:
 * - why {Object}: Description of why the thread pauses. See ThreadActor's PAUSE_REASONS definition for more information.
 * - frame {Object}: Description of the frame where we just paused. This is a FrameActor's form.
 */
class BreakpointWatcher {
  constructor() {
    this.onPaused = this.onPaused.bind(this);
    this.onResumed = this.onResumed.bind(this);
  }

  /**
   * Start watching for state changes of the thread actor.
   * This will notify whenever the thread actor pause and resume.
   *
   * @param TargetActor targetActor
   *        The target actor from which we should observe breakpoints
   * @param Object options
   *        Dictionary object with following attributes:
   *        - onAvailable: mandatory function
   *          This will be called for each resource.
   */
  async watch(targetActor, { onAvailable }) {
    // When debugging the whole browser (via the Browser Toolbox), we instantiate both content process and window global (FRAME) targets.
    // But the debugger will only use the content process target's thread actor.
    // Thread actor, Sources and Breakpoints have to be only managed for the content process target,
    // and we should explicitly ignore the window global target.
    if (
      targetActor.sessionContext.type == "all" &&
      targetActor.targetType === Targets.TYPES.FRAME &&
      targetActor.typeName != "parentProcessTarget"
    ) {
      return;
    }

    const { threadActor } = targetActor;
    this.threadActor = threadActor;
    this.onAvailable = onAvailable;

    // If this watcher is created during target creation, attach the thread actor automatically.
    // Otherwise it would not pause on anything (especially debugger statements).
    // However, do not attach the thread actor for Workers. They use a codepath
    // which releases the worker on `attach`. For them, the client will call `attach`. (bug 1691986)
    const isTargetCreation = this.threadActor.state == THREAD_STATES.DETACHED;
    if (isTargetCreation && !targetActor.targetType.endsWith("worker")) {
      await this.threadActor.attach({});
    }

    this.isInterrupted = false;

    threadActor.on("paused", this.onPaused);
    threadActor.on("resumed", this.onResumed);

    // For top-level targets, the thread actor may have been attached by the frontend
    // on toolbox opening, and we start observing for thread state updates much later.
    // In which case, the thread actor may already be paused and we handle this here.
    // It will also occurs for all other targets once bug 1681698 lands,
    // as the thread actor will be initialized before the target starts loading.
    // And it will occur for all targets once bug 1686748 lands.
    //
    // Note that we have to check if we have a "lastPausedPacket",
    // because the thread Actor is immediately set as being paused,
    // but the pause packet is built asynchronously and available slightly later.
    // If the "lastPausedPacket" is null, while the thread actor is paused,
    // it is fine to ignore as the "paused" event will be fire later.
    if (threadActor.isPaused() && threadActor.lastPausedPacket()) {
      this.onPaused(threadActor.lastPausedPacket());
    }
  }

  /**
   * Stop watching for breakpoints
   */
  destroy() {
    if (!this.threadActor) {
      return;
    }
    this.threadActor.off("paused", this.onPaused);
    this.threadActor.off("resumed", this.onResumed);
  }

  onPaused(packet) {
    // If paused by an explicit interrupt, which are generated by the
    // slow script dialog and internal events such as setting
    // breakpoints, ignore the event.
    const { why } = packet;
    if (why.type === PAUSE_REASONS.INTERRUPTED && !why.onNext) {
      this.isInterrupted = true;
      return;
    }

    // Ignore attached events because they are not useful to the user.
    if (why.type == PAUSE_REASONS.ALREADY_PAUSED) {
      return;
    }

    this.onAvailable([
      {
        resourceType: THREAD_STATE,
        state: STATES.PAUSED,
        why,
        frame: packet.frame.form(),
      },
    ]);
  }

  onResumed() {
    // NOTE: resumed events are suppressed while interrupted
    // to prevent unintentional behavior.
    if (this.isInterrupted) {
      this.isInterrupted = false;
      return;
    }

    this.onAvailable([
      {
        resourceType: THREAD_STATE,
        state: STATES.RESUMED,
      },
    ]);
  }
}

module.exports = BreakpointWatcher;