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

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

// To debug failing tests, we need to see all observed requests in order to
// determine whether an unexpected request was received (which suggests that the
// implementation is broken) or whether an expected event was observed too early
// (which can be an indication of unreasonable test expectations
// suggests that some previous events were hidden, indicating a broken test).
// To do so, we override logAllRequests=false from framework.js.
logAllRequests = true;

// The URL that fakedevtools.html requests upon completion.
function getCompletionURL() {
  return getURL('does_not_exist.html');
}

function expectNormalTabNavigationEvents(url) {
  var scriptUrl = new URL(url);
  var frontendHost = scriptUrl.hostname;
  scriptUrl.search = scriptUrl.hash = '';
  scriptUrl.pathname = scriptUrl.pathname.replace(/\.html$/, '.js');
  scriptUrl = scriptUrl.href;

  expect(
      [
        {
          label: 'onBeforeRequest-1',
          event: 'onBeforeRequest',
          details: {
            type: 'main_frame',
            url,
            frameUrl: url,
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        {
          label: 'onBeforeSendHeaders-1',
          event: 'onBeforeSendHeaders',
          details: {
            type: 'main_frame',
            url,
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        {
          label: 'onSendHeaders-1',
          event: 'onSendHeaders',
          details: {
            type: 'main_frame',
            url,
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        {
          label: 'onHeadersReceived-1',
          event: 'onHeadersReceived',
          details: {
            type: 'main_frame',
            url,
            statusLine: 'HTTP/1.1 200 OK',
            statusCode: 200,
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        {
          label: 'onResponseStarted-1',
          event: 'onResponseStarted',
          details: {
            type: 'main_frame',
            url,
            statusCode: 200,
            ip: '127.0.0.1',
            fromCache: false,
            statusLine: 'HTTP/1.1 200 OK',
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        {
          label: 'onCompleted-1',
          event: 'onCompleted',
          details: {
            type: 'main_frame',
            url,
            statusCode: 200,
            ip: '127.0.0.1',
            fromCache: false,
            statusLine: 'HTTP/1.1 200 OK',
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        {
          label: 'onBeforeRequest-2',
          event: 'onBeforeRequest',
          details: {
            type: 'script',
            url: scriptUrl,
            frameUrl: url,
            initiator: getServerDomain(initiators.WEB_INITIATED, frontendHost),
            documentId: 1
          }
        },
        {
          label: 'onBeforeSendHeaders-2',
          event: 'onBeforeSendHeaders',
          details: {
            type: 'script',
            url: scriptUrl,
            initiator: getServerDomain(initiators.WEB_INITIATED, frontendHost),
            documentId: 1
          }
        },
        {
          label: 'onSendHeaders-2',
          event: 'onSendHeaders',
          details: {
            type: 'script',
            url: scriptUrl,
            initiator: getServerDomain(initiators.WEB_INITIATED, frontendHost),
            documentId: 1
          }
        },
        {
          label: 'onHeadersReceived-2',
          event: 'onHeadersReceived',
          details: {
            type: 'script',
            url: scriptUrl,
            statusLine: 'HTTP/1.1 200 OK',
            statusCode: 200,
            initiator: getServerDomain(initiators.WEB_INITIATED, frontendHost),
            documentId: 1
          }
        },
        {
          label: 'onResponseStarted-2',
          event: 'onResponseStarted',
          details: {
            type: 'script',
            url: scriptUrl,
            statusCode: 200,
            ip: '127.0.0.1',
            fromCache: false,
            statusLine: 'HTTP/1.1 200 OK',
            initiator: getServerDomain(initiators.WEB_INITIATED, frontendHost),
            documentId: 1
          }
        },
        {
          label: 'onCompleted-2',
          event: 'onCompleted',
          details: {
            type: 'script',
            url: scriptUrl,
            statusCode: 200,
            ip: '127.0.0.1',
            fromCache: false,
            statusLine: 'HTTP/1.1 200 OK',
            initiator: getServerDomain(initiators.WEB_INITIATED, frontendHost),
            documentId: 1
          }
        },
      ],
      [[
        'onBeforeRequest-1', 'onBeforeSendHeaders-1', 'onSendHeaders-1',
        'onHeadersReceived-1', 'onResponseStarted-1', 'onCompleted-1',
        'onBeforeRequest-2', 'onBeforeSendHeaders-2', 'onSendHeaders-2',
        'onHeadersReceived-2', 'onResponseStarted-2', 'onCompleted-2'
      ]]);
}

// When the response is mocked via URLRequestMockHTTPJob, then the response
// differs from reality. Notably all header-related events are missing, the
// HTTP status line is different and the IP is missing.
// This difference is not a problem, since we are primarily interested in
// determining whether a request was observed or not.
function expectMockedTabNavigationEvents(url) {
  var scriptUrl = new URL(url);
  var frontendOrigin = scriptUrl.origin;
  scriptUrl.search = scriptUrl.hash = '';
  scriptUrl.pathname = scriptUrl.pathname.replace(/\.html$/, '.js');
  scriptUrl = scriptUrl.href;

  expect(
      [
        {
          label: 'onBeforeRequest-1',
          event: 'onBeforeRequest',
          details: {
            type: 'main_frame',
            url,
            frameUrl: url,
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        {
          label: 'onBeforeSendHeaders-1',
          event: 'onBeforeSendHeaders',
          details: {
            type: 'main_frame',
            url,
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        {
          label: 'onSendHeaders-1',
          event: 'onSendHeaders',
          details: {
            type: 'main_frame',
            url,
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        {
          label: 'onHeadersReceived-1',
          event: 'onHeadersReceived',
          details: {
            type: 'main_frame',
            url,
            statusCode: 200,
            statusLine: 'HTTP/1.0 200 OK',
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        {
          label: 'onResponseStarted-1',
          event: 'onResponseStarted',
          details: {
            type: 'main_frame',
            url,
            statusCode: 200,
            fromCache: false,
            statusLine: 'HTTP/1.0 200 OK',
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        {
          label: 'onCompleted-1',
          event: 'onCompleted',
          details: {
            type: 'main_frame',
            url,
            statusCode: 200,
            fromCache: false,
            statusLine: 'HTTP/1.0 200 OK',
            initiator: getServerDomain(initiators.BROWSER_INITIATED)
          }
        },
        {
          label: 'onBeforeRequest-2',
          event: 'onBeforeRequest',
          details: {
            type: 'script',
            url: scriptUrl,
            frameUrl: url,
            // Cannot use getServerDomain(initiators.WEB_INITIATED) because it
            // always adds a port, while this request does not have any ports.
            initiator: frontendOrigin,
            documentId: 1
          }
        },
        {
          label: 'onBeforeSendHeaders-2',
          event: 'onBeforeSendHeaders',
          details: {
            type: 'script',
            url: scriptUrl,
            initiator: frontendOrigin,
            documentId: 1
          }
        },
        {
          label: 'onSendHeaders-2',
          event: 'onSendHeaders',
          details: {
            type: 'script',
            url: scriptUrl,
            initiator: frontendOrigin,
            documentId: 1
          }
        },
        {
          label: 'onHeadersReceived-2',
          event: 'onHeadersReceived',
          details: {
            type: 'script',
            url: scriptUrl,
            statusCode: 200,
            statusLine: 'HTTP/1.0 200 OK',
            initiator: frontendOrigin,
            documentId: 1
          }
        },
        {
          label: 'onResponseStarted-2',
          event: 'onResponseStarted',
          details: {
            type: 'script',
            url: scriptUrl,
            statusCode: 200,
            fromCache: false,
            statusLine: 'HTTP/1.0 200 OK',
            initiator: frontendOrigin,
            documentId: 1
          }
        },
        {
          label: 'onCompleted-2',
          event: 'onCompleted',
          details: {
            type: 'script',
            url: scriptUrl,
            statusCode: 200,
            fromCache: false,
            statusLine: 'HTTP/1.0 200 OK',
            initiator: frontendOrigin,
            documentId: 1
          }
        },
      ],
      [[
        'onBeforeRequest-1', 'onResponseStarted-1', 'onCompleted-1',
        'onBeforeRequest-2', 'onResponseStarted-2', 'onCompleted-2'
      ]]);
}

var requestsIntercepted = [];
var onBeforeRequest = function(details) {
  // Ignore favicon requests.
  if (details.url.match(/\/favicon.ico$/))
    return;

  requestsIntercepted.push(details.url);
};

function addRequestListener() {
  chrome.webRequest.onBeforeRequest.addListener(
      onBeforeRequest, {urls: ['*://*/*']}, []);
};

function removeRequestListener() {
  chrome.webRequest.onBeforeRequest.removeListener(onBeforeRequest);
};

function verifyInterceptedRequests(expectedRequests) {
  chrome.test.assertEq(
      expectedRequests, requestsIntercepted,
      'Expected: ' + JSON.stringify(expectedRequests) +
          ' Actual: ' + JSON.stringify(requestsIntercepted));
  requestsIntercepted = [];
};

runTests([
  // Tests that devtools://devtools/custom/ is hidden from webRequest.
  function testDevToolsCustomFrontendRequest() {
    // The extension shouldn't be able to observe the requests for the devtools
    // resources. It should also not be able to intercept the request to the
    // completion url, since it doesn't have access to the initiator
    // devtools://devtools/.
    var expectedRequests = [];

    addRequestListener();

    // DevToolsFrontendInWebRequestApuTest has set the kCustomDevtoolsFrontend
    // switch to the customfrontend/ subdirectory, so we do not include the path
    // name in the URL again.
    navigateAndWait(
        'devtools://devtools/custom/fakedevtools.html#' +
            getCompletionURL(),
        chrome.test.callbackPass(() => {
          verifyInterceptedRequests(expectedRequests);
          removeRequestListener();
        }));
  },

  // Tests that the custom front-end URL is visible in non-DevTools requests.
  function testNonDevToolsCustomFrontendRequest() {
    // The URL that would be loaded by devtools://devtools/custom/...
    var customFrontendUrl = getServerURL(
        'devtoolsfrontend/fakedevtools.html', 'customfrontend.example.com');
    expectNormalTabNavigationEvents(customFrontendUrl);
    navigateAndWait(customFrontendUrl);
  },

  // Tests that devtools://devtools/remote/ is hidden from webRequest.
  function testDevToolsRemoteFrontendRequest() {
    // The extension shouldn't be able to observe the requests for the devtools
    // resources. It should also not be able to intercept the request to the
    // completion url, since it doesn't have access to the initiator
    // devtools://devtools/.
    var expectedRequests = [];
    addRequestListener();
    navigateAndWait(
        'devtools://devtools/remote/devtoolsfrontend/fakedevtools.html' +
            '#' + getCompletionURL(),
        chrome.test.callbackPass(() => {
          verifyInterceptedRequests(expectedRequests);
          removeRequestListener();
        }));
  },

  // Tests that the custom front-end URL is visible in non-DevTools requests.
  function testNonDevToolsRemoteFrontendRequest() {
    // The URL that would be loaded by devtools://devtools/remote/...
    var remoteFrontendUrl = 'https://chrome-devtools-frontend.appspot.com/' +
        'devtoolsfrontend/fakedevtools.html';
    expectMockedTabNavigationEvents(remoteFrontendUrl);
    navigateAndWait(remoteFrontendUrl);
  },
]);