<commit_before>/* 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/. */
// @flow

import type {
  ResponseFromBrowser,
  MessageFromBrowser,
  MessageToBrowser,
} from '../../../app-logic/web-channel';

/**
 * Mock out the WebChannel, a Firefox internal mechanism that allows us to
 * post messages from content pages to privileged contexts.
 */
export function mockWebChannel() {
  const messagesSentToBrowser = [];
  const listeners = [];
  let onMessageToChrome = null;

  jest
    .spyOn(window, 'addEventListener')
    .mockImplementation((name, listener) => {
      if (name === 'WebChannelMessageToContent') {
        listeners.push(listener);
      }
    });

  jest
    .spyOn(window, 'removeEventListener')
    .mockImplementation((name, listener) => {
      if (name === 'WebChannelMessageToContent') {
        const index = listeners.indexOf(listener);
        if (index !== -1) {
          listeners.splice(index, 1);
        }
      }
    });

  jest.spyOn(window, 'dispatchEvent').mockImplementation((event) => {
    if (
      event instanceof CustomEvent &&
      event.type === 'WebChannelMessageToChrome'
    ) {
      messagesSentToBrowser.push(JSON.parse(event.detail));
      if (onMessageToChrome) {
        onMessageToChrome(JSON.parse(event.detail).message);
      }
    }
  });

  function triggerResponse<R: ResponseFromBrowser>(
    message: MessageFromBrowser<R>
  ) {
    for (const listener of listeners.slice()) {
      listener({
        detail: {
          id: 'profiler.firefox.com',
          message,
        },
      });
    }
  }

  function registerMessageToChromeListener(
    listener: (MessageToBrowser) => void
  ): void {
    onMessageToChrome = listener;
  }

  return {
    messagesSentToBrowser,
    listeners,
    triggerResponse,
    registerMessageToChromeListener,
    getLastRequestId: (): number => {
      const message = messagesSentToBrowser[messagesSentToBrowser.length - 1];
      if (!message) {
        throw new Error('No messages were sent to the browser.');
      }
      const { requestId } = message.message;
      if (typeof requestId !== 'number') {
        throw new Error('Could not find the requestId in the message.');
      }
      return requestId;
    },
  };
}

export function simulateOldWebChannelAndFrameScript(
  geckoProfiler: $GeckoProfiler
) {
  const webChannel = mockWebChannel();

  const { registerMessageToChromeListener, triggerResponse } = webChannel;
  // Pretend that this browser does not support obtaining the profile via
  // the WebChannel. This will trigger fallback to the frame script /
  // geckoProfiler API.
  registerMessageToChromeListener((message) => {
    switch (message.type) {
      case 'STATUS_QUERY': {
        triggerResponse(
          ({
            type: 'STATUS_RESPONSE',
            requestId: message.requestId,
            menuButtonIsEnabled: true,
          }: any)
        );
        break;
      }
      default: {
        triggerResponse(
          ({
            error: `Unexpected message ${message.type}`,
          }: any)
        );
        break;
      }
    }
  });

  // Simulate the frame script's geckoProfiler API.
  window.geckoProfilerPromise = Promise.resolve(geckoProfiler);

  return webChannel;
}

export function simulateWebChannel(profileGetter: () => mixed) {
  const webChannel = mockWebChannel();

  const { registerMessageToChromeListener, triggerResponse } = webChannel;
  async function simulateBrowserSide(message) {
    switch (message.type) {
      case 'STATUS_QUERY': {
        triggerResponse({
          type: 'SUCCESS_RESPONSE',
          requestId: message.requestId,
          response: {
            menuButtonIsEnabled: true,
            version: 1,
          },
        });
        break;
      }
      case 'ENABLE_MENU_BUTTON': {
        triggerResponse({
          type: 'ERROR_RESPONSE',
          requestId: message.requestId,
          error:
            'ENABLE_MENU_BUTTON is a valid message but not covered by this test.',
        });
        break;
      }
      case 'GET_PROFILE': {
        const profile: ArrayBuffer | MixedObject = await profileGetter();
        triggerResponse({
          type: 'SUCCESS_RESPONSE',
          requestId: message.requestId,
          response: profile,
        });
        break;
      }
      case 'GET_SYMBOL_TABLE':
      case 'QUERY_SYMBOLICATION_API': {
        triggerResponse({
          type: 'ERROR_RESPONSE',
          requestId: message.requestId,
          error: 'No symbol tables available',
        });
        break;
      }
      default: {
        triggerResponse({
          type: 'ERROR_RESPONSE',
          requestId: message.requestId,
          error: `Unexpected message ${message.type}`,
        });
        break;
      }
    }
  }

  registerMessageToChromeListener((message) => {
    simulateBrowserSide(message);
  });

  return webChannel;
}
<commit_msg>Make mock WebChannel less invasive.<commit_after>/* 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/. */
// @flow

import type {
  ResponseFromBrowser,
  MessageFromBrowser,
  MessageToBrowser,
} from '../../../app-logic/web-channel';

/**
 * Mock out the WebChannel, a Firefox internal mechanism that allows us to
 * post messages from content pages to privileged contexts.
 */
export function mockWebChannel() {
  const messagesSentToBrowser = [];
  const listeners = [];
  const originalAddEventListener = window.addEventListener;
  const originalRemoveEventListener = window.removeEventListener;
  const originalDispatchEvent = window.dispatchEvent;
  let onMessageToChrome = null;

  jest
    .spyOn(window, 'addEventListener')
    .mockImplementation((name, listener, options) => {
      if (name === 'WebChannelMessageToContent') {
        listeners.push(listener);
      }
      originalAddEventListener.call(window, name, listener, options);
    });

  jest
    .spyOn(window, 'removeEventListener')
    .mockImplementation((name, listener, options) => {
      if (name === 'WebChannelMessageToContent') {
        const index = listeners.indexOf(listener);
        if (index !== -1) {
          listeners.splice(index, 1);
        }
      }
      originalRemoveEventListener.call(window, name, listener, options);
    });

  jest.spyOn(window, 'dispatchEvent').mockImplementation((event) => {
    if (
      event instanceof CustomEvent &&
      event.type === 'WebChannelMessageToChrome'
    ) {
      messagesSentToBrowser.push(JSON.parse(event.detail));
      if (onMessageToChrome) {
        onMessageToChrome(JSON.parse(event.detail).message);
      }
    } else {
      originalDispatchEvent.call(window, event);
    }
  });

  function triggerResponse<R: ResponseFromBrowser>(
    message: MessageFromBrowser<R>
  ) {
    for (const listener of listeners.slice()) {
      listener({
        detail: {
          id: 'profiler.firefox.com',
          message,
        },
      });
    }
  }

  function registerMessageToChromeListener(
    listener: (MessageToBrowser) => void
  ): void {
    onMessageToChrome = listener;
  }

  return {
    messagesSentToBrowser,
    listeners,
    triggerResponse,
    registerMessageToChromeListener,
    getLastRequestId: (): number => {
      const message = messagesSentToBrowser[messagesSentToBrowser.length - 1];
      if (!message) {
        throw new Error('No messages were sent to the browser.');
      }
      const { requestId } = message.message;
      if (typeof requestId !== 'number') {
        throw new Error('Could not find the requestId in the message.');
      }
      return requestId;
    },
  };
}

export function simulateOldWebChannelAndFrameScript(
  geckoProfiler: $GeckoProfiler
) {
  const webChannel = mockWebChannel();

  const { registerMessageToChromeListener, triggerResponse } = webChannel;
  // Pretend that this browser does not support obtaining the profile via
  // the WebChannel. This will trigger fallback to the frame script /
  // geckoProfiler API.
  registerMessageToChromeListener((message) => {
    switch (message.type) {
      case 'STATUS_QUERY': {
        triggerResponse(
          ({
            type: 'STATUS_RESPONSE',
            requestId: message.requestId,
            menuButtonIsEnabled: true,
          }: any)
        );
        break;
      }
      default: {
        triggerResponse(
          ({
            error: `Unexpected message ${message.type}`,
          }: any)
        );
        break;
      }
    }
  });

  // Simulate the frame script's geckoProfiler API.
  window.geckoProfilerPromise = Promise.resolve(geckoProfiler);

  return webChannel;
}

export function simulateWebChannel(profileGetter: () => mixed) {
  const webChannel = mockWebChannel();

  const { registerMessageToChromeListener, triggerResponse } = webChannel;
  async function simulateBrowserSide(message) {
    switch (message.type) {
      case 'STATUS_QUERY': {
        triggerResponse({
          type: 'SUCCESS_RESPONSE',
          requestId: message.requestId,
          response: {
            menuButtonIsEnabled: true,
            version: 1,
          },
        });
        break;
      }
      case 'ENABLE_MENU_BUTTON': {
        triggerResponse({
          type: 'ERROR_RESPONSE',
          requestId: message.requestId,
          error:
            'ENABLE_MENU_BUTTON is a valid message but not covered by this test.',
        });
        break;
      }
      case 'GET_PROFILE': {
        const profile: ArrayBuffer | MixedObject = await profileGetter();
        triggerResponse({
          type: 'SUCCESS_RESPONSE',
          requestId: message.requestId,
          response: profile,
        });
        break;
      }
      case 'GET_SYMBOL_TABLE':
      case 'QUERY_SYMBOLICATION_API': {
        triggerResponse({
          type: 'ERROR_RESPONSE',
          requestId: message.requestId,
          error: 'No symbol tables available',
        });
        break;
      }
      default: {
        triggerResponse({
          type: 'ERROR_RESPONSE',
          requestId: message.requestId,
          error: `Unexpected message ${message.type}`,
        });
        break;
      }
    }
  }

  registerMessageToChromeListener((message) => {
    simulateBrowserSide(message);
  });

  return webChannel;
}

{'fraction_non_alphanumeric': 0.10701036811566632, 'fraction_numerical': 0.0015219252354228099, 'mean_word_length': 2.4191869918699185, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6945555', 'n_tokens_mistral': 2943, 'n_tokens_neox': 2897, 'n_words': 698}