Польза
Хардкор диагностики

Хардкор диагностики. Ориентирование на местности. Часть 2

19 января 2020 года
Битрикс, диагностика, BX, события, javascript, devtools.

Продолжаем накапливать инструменты, которые могут облегчить жизнь хардкорщикам. Следующая часть серии статей посвящена перехвату подписывания и инициализации событий через библиотеку BX.

BX.addCustomEvent

По аналогии с тем, как устроена работа с событиями в jQuery, методы BX-библиотеки являются функциями-аккумуляторами. Подписывание на событие фактически явлется регистрацией символьного кода и колбэк-функции в глобальном объекте, а выброс события — выполнение колбэк-функции.

Регистрация события производится через функцию BX.addCustomEvent.

Как правило регистрацию события сопровождают:

  • событие в виде произвольного символьного кода («click», «load», «on-location-search-focus», ...);
  • колбэк-функция, которая должна быть вызвана при наступлении события.

Как и в случае с jQuery, переопределяем исходный метод и переопределяем его своим.

Переопределение метода

Предлагается сделать следующее:

  • ставим в девтулзах глобальный брекпоинт на событие document.onDOMContentLoaded, перезагружаем страницу;
  • на первом же срабатывании брекпоинта убеждаемся, что объект BX уже доступен и переопределяем ему метод addCustomEvent;
  • снимаем брекпоинт и отпускаем выполнение скрипта.

Код для выполнения в консоли:

let originalBxAddCustomEvent = BX.addCustomEvent;

// Глобальный объект,
// в который будет собираться статистика
hardcoreBXFrontLog = {
  events: {},
};

/**
 * eventOrObj - example: BX.addCustomEvent(opener, 'onOpenerMenuClose', ...)
 */
BX.addCustomEvent = function (eventOrObj, handlerOrEvent, arParams, handlerContextObject) {
  let info = {};
  let realEvent, realCallback, someObj;

  if (typeof eventOrObj === 'string') {
    realEvent = eventOrObj;
  } else if (typeof handlerOrEvent === 'string') {
    realEvent = handlerOrEvent;
  }

  if (eventOrObj instanceof Function) {
    realCallback = eventOrObj;
  } else if (handlerOrEvent instanceof Function) {
    realCallback = handlerOrEvent;
  } else if (arParams instanceof Function) {
    realCallback = arParams;
  }

  if (typeof eventOrObj === 'object') {
    someObj = eventOrObj;
  } else if (typeof handlerOrEvent === 'object') {
    someObj = handlerOrEvent;
  } else if (typeof arParams === 'object') {
    someObj = arParams;
  }

  let err = new Error();
  info.trace = err.stack;
  info.event = realEvent;
  info.callback = realCallback;
  info.obj = someObj;
  info.arguments = arguments;

  // live! =)
  hardcoreBXFrontPrintResult(info, true);

  // Регистрируем в глобальном объекте
  let eventNameList = realEvent.split(' ');
  eventNameList.forEach(function (evt) {
    if (!hardcoreBXFrontLog.events[evt]) {
      hardcoreBXFrontLog.events[evt] = [];
    }
    hardcoreBXFrontLog.events[evt].push(info);
  });

  // Пинаем оригинальный метод
  if (!arParams) {
    arParams = [];
  }

  if (!handlerContextObject) {
    handlerContextObject = false;
  }

  return originalBxAddCustomEvent.call(this, eventOrObj, handlerOrEvent, arParams, handlerContextObject);
};

// Метод для поиска среди собранной статистики
// информации по названию события
hardcoreBXAddEventLookingByEvent = function (event) {
  for (let e in hardcoreBXFrontLog.events) {
    if (
      !hardcoreBXFrontLog.events.hasOwnProperty(e)
      || e !== event
    ) {
      continue;
    }

    for (let ins = 0; ins < hardcoreBXFrontLog.events[e].length; ins++) {
      hardcoreBXFrontPrintResult(hardcoreBXFrontLog.events[e][ins]);
    }
  }
};

// Вывод в консоль
hardcoreBXFrontPrintResult = function (info, live) {

  let localInfo = Object.assign({}, info);

  console.log(
    'BX.add%c%s',
    'background: #1d1b57; color: #fff; ' +
    'font-weight: bold; padding: 3px 9px;' +
    'border-radius: 0 30px 30px 0;' +
    'border-right: 7px solid #fa8544',
    info.event
  );

  if (localInfo.obj) {
    console.log(localInfo.obj);
  }

  console.groupCollapsed('trace');
  if (live) {
    console.trace();
    delete (localInfo.trace);
  } else {
    console.log(localInfo.trace);
  }
  console.groupEnd();

  console.groupCollapsed('info');
  for (let i in localInfo) {
    if (localInfo.hasOwnProperty(i)) {
      console.log(i + ':%o', localInfo[i]);
    }
  }

  console.groupEnd();
};

Статистика

В консоли будет накапливаться информация о каждом событии, которое было проинициализировано через BX.addCustomEvent, с трейсингом вызова, что позволит нам понять, откуда именно это было запущено.

Битрикс. Статистика по каждому навешанному событию

Также вся информация будет накапливаться в глобальный объект hardcoreBXFrontLog, отсортированная по событиям. К сожалению, полноценный трейсинг в переменную не запомнить, но часть цепочки сохраняется, через объект Error. Это хотя бы частично позволяет сориентироваться в том, откуда пришёл сигнал.

Собранную на текущий момент статистику по событиям получаем через функцию
hardcoreBXAddEventLookingByEvent("название_события").

BX.onCustomEvent

Выброс события производится через BX.onCustomEvent и происходит в любом случае, вне зависимости от того, есть ли слушатели события или нет. Для нас — разработчиков — это является информацией о том, в каком месте существуют точки входа, на которые при необходимости можно было бы подписаться и реагировать.

Код для выполнения в консоли:

let originalBxOnCustomEvent = BX.onCustomEvent;

// Глобальный объект,
// в который будет собираться статистика
hardcoreBXOnEventFrontLog = {
  events: {},
};

BX.onCustomEvent = function (objOrEvent, eventIHope, eventParams, secureParams) {

  if (!objOrEvent) {
    objOrEvent = null;
  }

  if (!eventIHope) {
    eventIHope = null;
  }

  if (!eventParams) {
    eventParams = null;
  }

  if (!secureParams) {
    secureParams = null;
  }

  let info = {};
  let realEvent, realObj;

  if (typeof objOrEvent === 'string') {
    realEvent = objOrEvent;
  } else if (typeof eventIHope === 'string') {
    realEvent = eventIHope;
  }

  if (typeof objOrEvent === 'object') {
    realObj = objOrEvent;
  } else if (typeof eventIHope === 'object') {
    realObj = eventIHope;
  } else if (typeof eventParams === 'object') {
    realObj = eventParams;
  }

  let err = new Error();
  info.trace = err.stack;
  info.event = realEvent;
  info.obj = realObj;
  info.params = eventParams;
  info.arguments = arguments;

  // live! =)
  hardcoreBXFrontPrintOnEvent(info, true);

  // Регистрируем в глобальном объекте
  let eventNameList = realEvent.split(' ');
  eventNameList.forEach(function (evt) {
    if (!hardcoreBXOnEventFrontLog.events[evt]) {
      hardcoreBXOnEventFrontLog.events[evt] = [];
    }
    hardcoreBXOnEventFrontLog.events[evt].push(info);
  });

  // Пинаем оригинальный метод
  return originalBxOnCustomEvent.call(this, objOrEvent, eventIHope, eventParams, secureParams);
};

// Метод для поиска среди собранной статистики
// информации по названию события
hardcoreBXOnEventLookingByEvent = function (event) {
  for (let e in hardcoreBXOnEventFrontLog.events) {
    if (
      !hardcoreBXOnEventFrontLog.events.hasOwnProperty(e)
      || e !== event
    ) {
      continue;
    }

    for (let ins = 0; ins < hardcoreBXOnEventFrontLog.events[e].length; ins++) {
      hardcoreBXFrontPrintOnEvent(hardcoreBXOnEventFrontLog.events[e][ins]);
    }
  }
};

// Вывод в консоль
hardcoreBXFrontPrintOnEvent = function (info, live) {

  let localInfo = Object.assign({}, info);

  console.log(
    'BX.on%c%s',
    'background: #fa8544; color: #fff; ' +
    'font-weight: bold; padding: 3px 9px;' +
    'border-radius: 30px 0 0 30px;' +
    'border-left: 7px solid #1d1b57',
    localInfo.event
  );

  if (localInfo.obj) {
    console.log(localInfo.obj);
  }

  console.groupCollapsed('trace');
  if (live) {
    console.trace();
    delete (localInfo.trace);
  } else {
    console.log(localInfo.trace);
  }
  console.groupEnd();

  console.groupCollapsed('info');
  for (let i in localInfo) {
    if (localInfo.hasOwnProperty(i)) {
      console.log(i + ':%o', localInfo[i]);
    }
  }

  console.groupEnd();
};

Статистика

В консоли будет накапливаться информация о каждом событии, которое было проинициализировано через BX.onCustomEvent, с трейсингом вызова, что позволит нам понять, откуда именно это было запущено.

Битрикс. Статистика по каждому навешанному событию

Также вся информация будет накапливаться в глобальный объект hardcoreBXOnEventFrontLog, отсортированная по событиям. К сожалению, полноценный трейсинг в переменную не запомнить, но часть цепочки сохраняется, через объект Error. Это хотя бы частично позволяет сориентироваться в том, откуда пришёл сигнал.

Собранную на текущий момент статистику по событиям получаем через функцию
hardcoreBXOnEventLookingByEvent("название_события").

Обсуждение статьи

Написать