chromium/chrome/test/data/extensions/api_test/webrequest/test_websocket.js

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

var callbackPass = chrome.test.callbackPass;

chrome.tabs.getCurrent(function(tab) {
  runTestsForTab([
    // Opens a WebSocket connection, writes a message to it, and closes the
    // connection. WebRequest API should observe the entire handshake.
    function handshakeSucceeds() {
      var url = getWSTestURL(testWebSocketPort);
      expect(
        [  //events
          { label: 'onBeforeRequest',
            event: 'onBeforeRequest',
            details: {
              url: url,
              type: 'websocket',
              // TODO(pkalinnikov): Figure out why the frame URL is unknown.
              frameUrl: 'unknown frame URL',
              initiator: getDomain(initiators.WEB_INITIATED),
              documentId: 1
            },
          },
          { label: 'onBeforeSendHeaders',
            event: 'onBeforeSendHeaders',
            details: {
              url: url,
              type: 'websocket',
              initiator: getDomain(initiators.WEB_INITIATED),
              documentId: 1
            },
          },
          { label: 'onSendHeaders',
            event: 'onSendHeaders',
            details: {
              url: url,
              type: 'websocket',
              initiator: getDomain(initiators.WEB_INITIATED),
              documentId: 1
            },
          },
          { label: 'onHeadersReceived',
            event: 'onHeadersReceived',
            details: {
              url: url,
              type: 'websocket',
              statusCode: 101,
              statusLine: 'HTTP/1.1 101 Switching Protocols',
              initiator: getDomain(initiators.WEB_INITIATED),
              documentId: 1
            },
          },
          { label: 'onResponseStarted',
            event: 'onResponseStarted',
            details: {
              url: url,
              type: 'websocket',
              ip: '127.0.0.1',
              fromCache: false,
              statusCode: 101,
              statusLine: 'HTTP/1.1 101 Switching Protocols',
              initiator: getDomain(initiators.WEB_INITIATED),
              documentId: 1
            },
          },
          { label: 'onCompleted',
            event: 'onCompleted',
            details: {
              url: url,
              type: 'websocket',
              ip: '127.0.0.1',
              fromCache: false,
              statusCode: 101,
              statusLine: 'HTTP/1.1 101 Switching Protocols',
              initiator: getDomain(initiators.WEB_INITIATED),
              documentId: 1
            }
          },
        ],
        [  // event order
          ['onBeforeRequest', 'onBeforeSendHeaders', 'onSendHeaders',
          'onHeadersReceived', 'onResponseStarted', 'onCompleted']
        ],
        {urls: ['ws://*/*']},  // filter
        ['blocking']  // extraInfoSpec
      );
      testWebSocketConnection(url, true /* expectedToConnect */);
    },

    // Tries to open a WebSocket connection, with a blocking handler that
    // cancels the request. The connection will not be established.
    function handshakeRequestCancelled() {
      var url = getWSTestURL(testWebSocketPort);
      expect(
        [  // events
          { label: 'onBeforeRequest',
            event: 'onBeforeRequest',
            details: {
              url: url,
              type: 'websocket',
              frameUrl: 'unknown frame URL',
              initiator: getDomain(initiators.WEB_INITIATED),
              documentId: 1
            },
            retval: {cancel: true}
          },
          // Cancelling is considered an error.
          { label: 'onErrorOccurred',
            event: 'onErrorOccurred',
            details: {
              url: url,
              type: 'websocket',
              fromCache: false,
              initiator: getDomain(initiators.WEB_INITIATED),
              error: 'net::ERR_BLOCKED_BY_CLIENT',
              documentId: 1
            }
          },
        ],
        [  // event order
          ['onBeforeRequest', 'onErrorOccurred']
        ],
        {urls: ['ws://*/*']},  // filter
        ['blocking']  // extraInfoSpec
      );
      testWebSocketConnection(url, false /* expectedToConnect */);
    },

    // Opens a WebSocket connection, with a blocking handler that tries to
    // redirect the request. The redirect will be ignored.
    function redirectIsIgnoredAndHandshakeSucceeds() {
      var url = getWSTestURL(testWebSocketPort);
      var redirectedUrl1 = getWSTestURL(testWebSocketPort) + '?redirected1';
      var redirectedUrl2 = getWSTestURL(testWebSocketPort) + '?redirected2';
      expect(
        [  // events
          { label: 'onBeforeRequest',
            event: 'onBeforeRequest',
            details: {
              url: url,
              type: 'websocket',
              frameUrl: 'unknown frame URL',
              initiator: getDomain(initiators.WEB_INITIATED),
              documentId: 1
            },
            retval: {redirectUrl: redirectedUrl1}
          },
          { label: 'onBeforeSendHeaders',
            event: 'onBeforeSendHeaders',
            details: {
              url: url,
              type: 'websocket',
              initiator: getDomain(initiators.WEB_INITIATED),
              documentId: 1
            },
          },
          { label: 'onSendHeaders',
            event: 'onSendHeaders',
            details: {
              url: url,
              type: 'websocket',
              initiator: getDomain(initiators.WEB_INITIATED),
              documentId: 1
            },
          },
          { label: 'onHeadersReceived',
            event: 'onHeadersReceived',
            details: {
              url: url,
              type: 'websocket',
              statusCode: 101,
              statusLine: 'HTTP/1.1 101 Switching Protocols',
              initiator: getDomain(initiators.WEB_INITIATED),
              documentId: 1
            },
            retval: {redirectUrl: redirectedUrl2}
          },
          { label: 'onResponseStarted',
            event: 'onResponseStarted',
            details: {
              url: url,
              type: 'websocket',
              ip: '127.0.0.1',
              fromCache: false,
              initiator: getDomain(initiators.WEB_INITIATED),
              statusCode: 101,
              statusLine: 'HTTP/1.1 101 Switching Protocols',
              documentId: 1
            },
          },
          { label: 'onCompleted',
            event: 'onCompleted',
            details: {
              url: url,
              type: 'websocket',
              ip: '127.0.0.1',
              fromCache: false,
              initiator: getDomain(initiators.WEB_INITIATED),
              statusCode: 101,
              statusLine: 'HTTP/1.1 101 Switching Protocols',
              documentId: 1
            }
          },
        ],
        [  // event order
          ['onBeforeRequest', 'onBeforeSendHeaders', 'onHeadersReceived',
          'onResponseStarted', 'onCompleted']
        ],
        {urls: ['ws://*/*']},  // filter
        ['blocking']  // extraInfoSpec
      );
      testWebSocketConnection(url, true /* expectedToConnect */);
    },

    // Tries to open a WebSocket connection, with a blocking handler that
    // cancels the request. The connection will not be established.
    function handshakeRequestCancelledWithExtraHeaders() {
      var url = getWSTestURL(testWebSocketPort);
      expect(
        [  // events
          { label: 'onBeforeRequest',
            event: 'onBeforeRequest',
            details: {
              url: url,
              type: 'websocket',
              frameUrl: 'unknown frame URL',
              initiator: getDomain(initiators.WEB_INITIATED),
              documentId: 1
            },
            retval: {cancel: true}
          },
          // Cancelling is considered an error.
          { label: 'onErrorOccurred',
            event: 'onErrorOccurred',
            details: {
              url: url,
              type: 'websocket',
              fromCache: false,
              initiator: getDomain(initiators.WEB_INITIATED),
              error: 'net::ERR_BLOCKED_BY_CLIENT',
              documentId: 1
            }
          },
        ],
        [  // event order
          ['onBeforeRequest', 'onErrorOccurred']
        ],
        {urls: ['ws://*/*']},  // filter
        ['blocking', 'extraHeaders']  // extraInfoSpec
      );
      testWebSocketConnection(url, false /* expectedToConnect */);
    },

    // Tests that all the requests headers that are added by net/ are visible
    // if extraHeaders is specified.
    function testExtraRequestHeadersVisible() {
      var url = getWSTestURL(testWebSocketPort);

      var extraHeadersListener = callbackPass(function(details) {
        checkHeaders(details.requestHeaders,
                     ['user-agent', 'accept-language'], []);
        chrome.webRequest.onBeforeSendHeaders.removeListener(
            extraHeadersListener);
      });
      chrome.webRequest.onBeforeSendHeaders.addListener(extraHeadersListener,
          {urls: [url]}, ['requestHeaders', 'extraHeaders']);

      var standardListener = callbackPass(function(details) {
        checkHeaders(details.requestHeaders,
                     ['user-agent'], ['accept-language']);
        chrome.webRequest.onBeforeSendHeaders.removeListener(standardListener);
      });
      chrome.webRequest.onBeforeSendHeaders.addListener(standardListener,
          {urls: [url]}, ['requestHeaders']);

      testWebSocketConnection(url, true /* expectedToConnect */);
    },

    // Ensure that request headers which are added by net/ could be modified if
    // the listener uses extraHeaders.
    function testModifyRequestHeaders() {
      var url = getWSTestURL(testWebSocketPort);

      var beforeSendHeadersListener = callbackPass(function(details) {
        // Test removal.
        removeHeader(details.requestHeaders, 'accept-language');

        // Test modification.
        for (var i = 0; i < details.requestHeaders.length; i++) {
          if (details.requestHeaders[i].name == 'User-Agent')
            details.requestHeaders[i].value = 'Foo';
        }

        // Test addition.
        details.requestHeaders.push({name: 'X-New-Header',
                                     value: 'Bar'});

        return {requestHeaders: details.requestHeaders};
      });
      chrome.webRequest.onBeforeSendHeaders.addListener(
          beforeSendHeadersListener,
          {urls: [url]}, ['requestHeaders', 'blocking', 'extraHeaders']);

      var sendHeadersListener = callbackPass(function(details) {
        checkHeaders(details.requestHeaders, ['x-new-header'],
                     ['accept-language']);

        var seen = false;
        for (var i = 0; i < details.requestHeaders.length; i++) {
          if (details.requestHeaders[i].name == 'User-Agent') {
            chrome.test.assertEq(details.requestHeaders[i].value, 'Foo');
            seen = true;
          }
        }
        chrome.test.assertTrue(seen);

        chrome.webRequest.onBeforeSendHeaders.removeListener(
            beforeSendHeadersListener);
        chrome.webRequest.onSendHeaders.removeListener(sendHeadersListener);
      });
      chrome.webRequest.onSendHeaders.addListener(sendHeadersListener,
          {urls: [url]}, ['requestHeaders', 'extraHeaders']);

      testWebSocketConnection(url, true /* expectedToConnect */);
    },

    // Ensure that response headers can be modified when extraHeaders is used.
    function testModifyResponseHeaders() {
      var url = getWSTestURL(testWebSocketPort);

      var onHeadersReceivedHeadersListener = callbackPass(function(details) {
        // Test addition.
        details.responseHeaders.push({name: 'X-New-Header',
                                      value: 'Bar'});

        return {responseHeaders: details.responseHeaders};
      });
      chrome.webRequest.onHeadersReceived.addListener(
          onHeadersReceivedHeadersListener,
          {urls: [url]}, ['responseHeaders', 'blocking', 'extraHeaders']);

      var onResponseStartedListener = callbackPass(function(details) {
        checkHeaders(details.responseHeaders, ['x-new-header'], []);

        chrome.webRequest.onHeadersReceived.removeListener(
            onHeadersReceivedHeadersListener);
        chrome.webRequest.onResponseStarted.removeListener(
            onResponseStartedListener);
      });
      chrome.webRequest.onResponseStarted.addListener(onResponseStartedListener,
          {urls: [url]}, ['responseHeaders', 'extraHeaders']);

      testWebSocketConnection(url, true /* expectedToConnect */);
    },
  ], tab);
});