Commit abdbd2a7 authored by David Walsh's avatar David Walsh Committed by Jason Laster
Browse files

Bug 1520957 - [release 119] Implement Event Listeners Panel (#7715). r=dwalsh

parent b3173ca4
Loading
Loading
Loading
Loading
+38 −0
Original line number Diff line number Diff line
@@ -4111,6 +4111,44 @@ html[dir="rtl"] .command-bar {
 * 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/>. */

.event-listeners-pane ul {
	padding: 0;
}

.event-listener-group {
	user-select: none;
}

.event-listener-category {
	font-weight: bold;
}

.event-listeners-pane .arrow {
  margin-inline-start: 4px;
  margin-top: 1px;
}

html[dir="ltr"] .event-listeners-pane .arrow.expanded {
  transform: rotate(0deg);
}

html[dir="rtl"] .event-listeners-pane .arrow.expanded {
  transform: rotate(90deg);
}

.event-listener-event {
	display: flex;
	align-items: center;
}

.event-listener-event input {
	margin-inline-end: 6px;
	margin-inline-start: 40px;
}
/* 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/>. */

.object-node.default-property {
  opacity: 0.6;
}
+19 −155
Original line number Diff line number Diff line
@@ -2,167 +2,31 @@
 * 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/>. */

/* global window gThreadClient setNamedTimeout EVENTS */
/* eslint no-shadow: 0  */
// @flow

/**
 * Redux actions for the event listeners state
 * @module actions/event-listeners
 */
import { asyncStore } from "../utils/prefs";

import { reportException } from "../utils/DevToolsUtils";
import { isPaused, getSourceByURL } from "../selectors";
import { NAME as WAIT_UNTIL } from "./utils/middleware/wait-service";
import type { ThunkArgs } from "./types";
import type { EventListenerBreakpoints } from "../types";

// delay is in ms
const FETCH_EVENT_LISTENERS_DELAY = 200;
let fetchListenersTimerID;

/**
 * @memberof utils/utils
 * @static
 */
async function asPaused(state: any, client: any, func: any) {
  if (!isPaused(state)) {
    await client.interrupt();
    let result;

    try {
      result = await func(client);
    } catch (e) {
      // Try to put the debugger back in a working state by resuming
      // it
      await client.resume();
      throw e;
    }

    await client.resume();
    return result;
  }

  return func(client);
}

/**
 * @memberof actions/event-listeners
 * @static
 */
export function fetchEventListeners() {
  return ({ dispatch, getState, client }) => {
    // Make sure we"re not sending a batch of closely repeated requests.
    // This can easily happen whenever new sources are fetched.
    if (fetchListenersTimerID) {
      clearTimeout(fetchListenersTimerID);
    }

    fetchListenersTimerID = setTimeout(() => {
      // In case there is still a request of listeners going on (it
      // takes several RDP round trips right now), make sure we wait
      // on a currently running request
      if (getState().eventListeners.fetchingListeners) {
        dispatch({
          type: WAIT_UNTIL,
          predicate: action =>
            action.type === "FETCH_EVENT_LISTENERS" && action.status === "done",
          run: dispatch => dispatch(fetchEventListeners())
export function addEventListeners(events: EventListenerBreakpoints) {
  return async ({ dispatch, client }: ThunkArgs) => {
    await dispatch({
      type: "ADD_EVENT_LISTENERS",
      events
    });
        return;
      }

      dispatch({
        type: "FETCH_EVENT_LISTENERS",
        status: "begin"
      });

      asPaused(getState(), client, _getEventListeners).then(listeners => {
        dispatch({
          type: "FETCH_EVENT_LISTENERS",
          status: "done",
          listeners: formatListeners(getState(), listeners)
        });
      });
    }, FETCH_EVENT_LISTENERS_DELAY);
    const newList = await asyncStore.eventListenerBreakpoints;
    client.setEventListenerBreakpoints(newList);
  };
}

function formatListeners(state, listeners) {
  return listeners.map(l => {
    return {
      selector: l.node.selector,
      type: l.type,
      sourceId: getSourceByURL(state, l.function.location.url).id,
      line: l.function.location.line
    };
  });
}

async function _getEventListeners(threadClient) {
  const response = await threadClient.eventListeners();

  // Make sure all the listeners are sorted by the event type, since
  // they"re not guaranteed to be clustered together.
  response.listeners.sort((a, b) => (a.type > b.type ? 1 : -1));

  // Add all the listeners in the debugger view event linsteners container.
  const fetchedDefinitions = new Map();
  const listeners = [];
  for (const listener of response.listeners) {
    let definitionSite;
    if (fetchedDefinitions.has(listener.function.actor)) {
      definitionSite = fetchedDefinitions.get(listener.function.actor);
    } else if (listener.function.class == "Function") {
      definitionSite = await _getDefinitionSite(
        threadClient,
        listener.function
      );
      if (!definitionSite) {
        // We don"t know where this listener comes from so don"t show it in
        // the UI as breaking on it doesn"t work (bug 942899).
        continue;
      }

      fetchedDefinitions.set(listener.function.actor, definitionSite);
    }
    listener.function.url = definitionSite;
    listeners.push(listener);
  }
  fetchedDefinitions.clear();

  return listeners;
}

async function _getDefinitionSite(threadClient, func) {
  const grip = threadClient.pauseGrip(func);
  let response;

  try {
    response = await grip.getDefinitionSite();
  } catch (e) {
    // Don't make this error fatal, it would break the entire events pane.
    reportException("_getDefinitionSite", e);
    return null;
  }

  return response.source.url;
}

/**
 * @memberof actions/event-listeners
 * @static
 * @param {string} eventNames
 */
export function updateEventBreakpoints(eventNames) {
  return dispatch => {
    setNamedTimeout("event-breakpoints-update", 0, () => {
      gThreadClient.pauseOnDOMEvents(eventNames, () => {
        // Notify that event breakpoints were added/removed on the server.
        window.emit(EVENTS.EVENT_BREAKPOINTS_UPDATED);

        dispatch({
          type: "UPDATE_EVENT_BREAKPOINTS",
          eventNames: eventNames
        });
      });
export function removeEventListeners(events: EventListenerBreakpoints) {
  return async ({ dispatch, client }: ThunkArgs) => {
    await dispatch({
      type: "REMOVE_EVENT_LISTENERS",
      events
    });
    const newList = await asyncStore.eventListenerBreakpoints;
    client.setEventListenerBreakpoints(newList);
  };
}
+2 −0
Original line number Diff line number Diff line
@@ -6,6 +6,7 @@

import * as breakpoints from "./breakpoints";
import * as expressions from "./expressions";
import * as eventListeners from "./event-listeners";
import * as pause from "./pause";
import * as navigation from "./navigation";
import * as ui from "./ui";
@@ -24,6 +25,7 @@ export default {
  ...navigation,
  ...breakpoints,
  ...expressions,
  ...eventListeners,
  ...sources,
  ...tabs,
  ...pause,
+1 −0
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@ DIRS += [
DebuggerModules(
    'ast.js',
    'debuggee.js',
    'event-listeners.js',
    'expressions.js',
    'file-search.js',
    'index.js',
+6 −0
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@
import type {
  BreakpointId,
  BreakpointResult,
  EventListenerBreakpoints,
  Frame,
  FrameId,
  SourceLocation,
@@ -386,6 +387,10 @@ function eventListeners(): Promise<*> {
  return threadClient.eventListeners();
}

function setEventListenerBreakpoints(eventTypes: EventListenerBreakpoints) {
  // TODO: Figure out what sendpoint we want to hit
}

function pauseGrip(thread: string, func: Function): ObjectClient {
  return lookupThreadClient(thread).pauseGrip(func);
}
@@ -483,6 +488,7 @@ const clientCommands = {
  sendPacket,
  setPausePoints,
  setSkipPausing,
  setEventListenerBreakpoints,
  registerSource
};

Loading