summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/panel.js
blob: 352d7b284657aa8c574db5b8f0cb9e5feb480022 (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
170
171
172
173
174
175
176
177
178
179
180
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
/* 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 { Cc, Ci, Cu, Cr } = require("chrome");
const promise = require("promise");
const EventEmitter = require("devtools/shared/event-emitter");
const DevToolsUtils = require("devtools/shared/DevToolsUtils");

function DebuggerPanel(iframeWindow, toolbox) {
  this.panelWin = iframeWindow;
  this._toolbox = toolbox;
  this._destroyer = null;

  this._view = this.panelWin.DebuggerView;
  this._controller = this.panelWin.DebuggerController;
  this._view._hostType = this._toolbox.hostType;
  this._controller._target = this.target;
  this._controller._toolbox = this._toolbox;

  this.handleHostChanged = this.handleHostChanged.bind(this);
  EventEmitter.decorate(this);
}

exports.DebuggerPanel = DebuggerPanel;

DebuggerPanel.prototype = {
  /**
   * Open is effectively an asynchronous constructor.
   *
   * @return object
   *         A promise that is resolved when the Debugger completes opening.
   */
  open: function () {
    let targetPromise;

    // Local debugging needs to make the target remote.
    if (!this.target.isRemote) {
      targetPromise = this.target.makeRemote();
      // Listen for tab switching events to manage focus when the content window
      // is paused and events suppressed.
      this.target.tab.addEventListener("TabSelect", this);
    } else {
      targetPromise = promise.resolve(this.target);
    }

    return targetPromise
      .then(() => this._controller.startupDebugger())
      .then(() => this._controller.connect())
      .then(() => {
        this._toolbox.on("host-changed", this.handleHostChanged);
        // Add keys from this document's keyset to the toolbox, so they
        // can work when the split console is focused.
        let keysToClone = ["resumeKey", "stepOverKey", "stepInKey", "stepOutKey"];
        for (let key of keysToClone) {
          let elm = this.panelWin.document.getElementById(key);
          let keycode = elm.getAttribute("keycode");
          let modifiers = elm.getAttribute("modifiers");
          let command = elm.getAttribute("command");
          let handler = this._view.Toolbar.getCommandHandler(command);

          let keyShortcut = this.translateToKeyShortcut(keycode, modifiers);
          this._toolbox.useKeyWithSplitConsole(keyShortcut, handler, "jsdebugger");
        }
        this.isReady = true;
        this.emit("ready");
        return this;
      })
      .then(null, function onError(aReason) {
        DevToolsUtils.reportException("DebuggerPanel.prototype.open", aReason);
      });
  },

  /**
   * Translate a VK_ keycode, with modifiers, to a key shortcut that can be used with
   * shared/key-shortcut.
   *
   * @param {String} keycode
   *        The VK_* keycode to translate
   * @param {String} modifiers
   *        The list (blank-space separated) of modifiers applying to this keycode.
   * @return {String} a key shortcut ready to be used with shared/key-shortcut.js
   */
  translateToKeyShortcut: function (keycode, modifiers) {
    // Remove the VK_ prefix.
    keycode = keycode.replace("VK_", "");

    // Translate modifiers
    if (modifiers.includes("shift")) {
      keycode = "Shift+" + keycode;
    }
    if (modifiers.includes("alt")) {
      keycode = "Alt+" + keycode;
    }
    if (modifiers.includes("control")) {
      keycode = "Ctrl+" + keycode;
    }
    if (modifiers.includes("meta")) {
      keycode = "Cmd+" + keycode;
    }
    if (modifiers.includes("accel")) {
      keycode = "CmdOrCtrl+" + keycode;
    }

    return keycode;
  },

  // DevToolPanel API

  get target() {
    return this._toolbox.target;
  },

  destroy: function () {
    // Make sure this panel is not already destroyed.
    if (this._destroyer) {
      return this._destroyer;
    }

    if (!this.target.isRemote) {
      this.target.tab.removeEventListener("TabSelect", this);
    }

    return this._destroyer = this._controller.shutdownDebugger().then(() => {
      this.emit("destroyed");
    });
  },

  // DebuggerPanel API

  getFrames() {
    let framesController = this.panelWin.DebuggerController.StackFrames;
    let thread = framesController.activeThread;
    if (thread && thread.paused) {
      return {
        frames: thread.cachedFrames,
        selected: framesController.currentFrameDepth,
      };
    }

    return null;
  },

  addBreakpoint: function (location) {
    const { actions } = this.panelWin;
    const { dispatch } = this._controller;

    return dispatch(actions.addBreakpoint(location));
  },

  removeBreakpoint: function (location) {
    const { actions } = this.panelWin;
    const { dispatch } = this._controller;

    return dispatch(actions.removeBreakpoint(location));
  },

  blackbox: function (source, flag) {
    const { actions } = this.panelWin;
    const { dispatch } = this._controller;
    return dispatch(actions.blackbox(source, flag));
  },

  handleHostChanged: function () {
    this._view.handleHostChanged(this._toolbox.hostType);
  },

  // nsIDOMEventListener API

  handleEvent: function (aEvent) {
    if (aEvent.target == this.target.tab &&
        this._controller.activeThread.state == "paused") {
      // Wait a tick for the content focus event to be delivered.
      DevToolsUtils.executeSoon(() => this._toolbox.focusTool("jsdebugger"));
    }
  }
};