chromium/chrome/test/data/extensions/api_test/debugger/background.js

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

var pass = chrome.test.callbackPass;
var fail = chrome.test.callbackFail;

var tabId;
var debuggee;
var protocolVersion = "1.3";
var protocolPreviousVersion = "1.2";
var unsupportedMinorProtocolVersion = "1.5";
var unsupportedMajorProtocolVersion = "100.0";

var DETACHED_WHILE_HANDLING = "Detached while handling command.";

let openTab;

chrome.test.getConfig(config => chrome.test.runTests([

  function attachMalformedVersion() {
    chrome.tabs.query({active: true}, function(tabs) {
      chrome.debugger.attach({tabId: tabs[0].id}, "malformed-version", fail(
          "Requested protocol version is not supported: malformed-version."));
    });
  },

  function attachUnsupportedMinorVersion() {
    chrome.tabs.query({active: true}, function(tabs) {
      chrome.debugger.attach({tabId: tabs[0].id},
                             unsupportedMinorProtocolVersion,
          fail("Requested protocol version is not supported: " +
              unsupportedMinorProtocolVersion + "."));
    });
  },

  function attachUnsupportedVersion() {
    chrome.tabs.query({active: true}, function(tabs) {
      chrome.debugger.attach({tabId: tabs[0].id},
                             unsupportedMajorProtocolVersion,
          fail("Requested protocol version is not supported: " +
              unsupportedMajorProtocolVersion + "."));
    });
  },

  function attachPreviousVersion() {
    chrome.tabs.query({active: true}, function(tabs) {
      debuggee = {tabId: tabs[0].id};
      chrome.debugger.attach(debuggee, protocolPreviousVersion, function() {
        chrome.debugger.detach(debuggee, pass());
      });
    });
  },

  function attachLatestVersion() {
    chrome.tabs.query({active: true}, function(tabs) {
      tabId = tabs[0].id;
      debuggee = {tabId: tabId};
      chrome.debugger.attach(debuggee, protocolVersion, pass());
    });
  },

  function attachAgain() {
    chrome.debugger.attach(debuggee, protocolVersion,
        fail("Another debugger is already attached to the tab with id: " +
            tabId + "."));
  },

  function sendCommand() {
    function onResponse() {
      if (chrome.runtime.lastError &&
          chrome.runtime.lastError.message.indexOf("invalidMethod") != -1)
        chrome.test.succeed();
      else
        chrome.test.fail();
    }
    chrome.debugger.sendCommand(debuggee,
                               "DOM.invalidMethod",
                               null,
                               onResponse);
  },

  function detach() {
    chrome.debugger.detach(debuggee, pass());
  },

  function sendCommandAfterDetach() {
    chrome.debugger.sendCommand(debuggee, "Foo", null,
        fail("Debugger is not attached to the tab with id: " + tabId + "."));
  },

  function detachAgain() {
    chrome.debugger.detach(debuggee,
        fail("Debugger is not attached to the tab with id: " + tabId + "."));
  },

  async function closeTab() {
    ({openTab} = await import('/_test_resources/test_util/tabs_util.js'));
    const tab = await openTab(chrome.runtime.getURL('inspected.html'));
    function onDetach(debuggee, reason) {
      chrome.test.assertEq(tab.id, debuggee.tabId);
      chrome.test.assertEq("target_closed", reason);
      chrome.debugger.onDetach.removeListener(onDetach);
      chrome.test.succeed();
    }

    const debuggee2 = {tabId: tab.id};
    chrome.debugger.attach(debuggee2, protocolVersion, function() {
      chrome.debugger.onDetach.addListener(onDetach);
      chrome.tabs.remove(tab.id);
    });
  },

  async function attachToWebUI() {
    const tab = await openTab('chrome://version');
    const debuggee = {tabId: tab.id};
    chrome.debugger.attach(debuggee, protocolVersion,
        fail("Cannot access a chrome:// URL"));
    chrome.tabs.remove(tab.id);
  },

  async function navigateToWebUI() {
    const tab = await openTab(chrome.runtime.getURL('inspected.html'));
    const debuggee = {tabId: tab.id};
    chrome.debugger.attach(debuggee, protocolVersion, function() {
      var responded = false;

      function onResponse() {
        chrome.test.assertLastError(DETACHED_WHILE_HANDLING);
        responded = true;
      }

      function onDetach(from, reason) {
        chrome.debugger.onDetach.removeListener(onDetach);
        chrome.debugger.attach(
            debuggee, protocolVersion, fail('Cannot access a chrome:// URL'));
        chrome.test.assertTrue(responded);
        chrome.test.assertEq(debuggee.tabId, from.tabId);
        chrome.test.assertEq("target_closed", reason);
        chrome.tabs.remove(tab.id, pass())
      }

      chrome.test.assertNoLastError();
      chrome.debugger.onDetach.addListener(onDetach);
      chrome.debugger.sendCommand(
        debuggee, "Page.navigate", {url: "chrome://version"}, onResponse);
    });
  },

  async function detachDuringCommand() {
    const tab = await openTab(chrome.runtime.getURL('inspected.html'));
    const debuggee = {tabId: tab.id};
    chrome.debugger.attach(debuggee, protocolVersion, function() {
      var responded = false;

      function onResponse() {
        chrome.test.assertLastError(DETACHED_WHILE_HANDLING);
        responded = true;
      }

      function onDetach() {
        chrome.debugger.onDetach.removeListener(onDetach);
        chrome.test.assertTrue(responded);
        chrome.tabs.remove(tab.id, function() {
          chrome.test.assertNoLastError();
          chrome.test.succeed();
        });
      }

      chrome.test.assertNoLastError();
      chrome.debugger.sendCommand(debuggee, "command", null, onResponse);
      chrome.debugger.detach(debuggee, onDetach);
    });
  },

  function attachToMissing() {
    var missingDebuggee = {tabId: -1};
    chrome.debugger.attach(missingDebuggee, protocolVersion,
        fail("No tab with given id " + missingDebuggee.tabId + "."));
  },

  function attachToOwnBackgroundPageWithNoSilentFlag() {
    var ownExtensionId = chrome.runtime.getURL('').split('/')[2];
    debuggee = {extensionId: ownExtensionId};
    chrome.debugger.attach(debuggee, protocolVersion, pass());
  },

  function discoverOwnBackgroundPageWithNoSilentFlag() {
    chrome.debugger.getTargets(function(targets) {
      var target = targets.filter(
          function(target) { return target.type == 'background_page'})[0];
      if (target) {
        chrome.debugger.attach({targetId: target.id}, protocolVersion, fail(
            "Another debugger is already attached to the target with id: " +
            target.id + "."));
      } else {
        chrome.test.succeed();
      }
    });
  },

  function detachFromOwnBackgroundPage() {
    chrome.debugger.detach(debuggee, pass());
  },

  async function createAndDiscoverTab() {
    const tab = await openTab(chrome.runtime.getURL('inspected.html'));
    chrome.debugger.getTargets(function(targets) {
      var page = targets.filter(
          function(t) {
            return t.type == 'page' &&
                   t.tabId == tab.id &&
                   t.title == 'Test page';
          })[0];
      if (page) {
        chrome.debugger.attach(
            {targetId: page.id}, protocolVersion, pass());
      } else {
        chrome.test.fail("Cannot discover a newly created tab");
      }
    });
  },

  function discoverWorker() {
    var workerPort = new SharedWorker("worker.js").port;
    workerPort.onmessage = function() {
      chrome.debugger.getTargets(function(targets) {
        var page = targets.filter(
            function(t) { return t.type == 'worker' })[0];
        if (page) {
          debuggee = {targetId: page.id};
          chrome.debugger.attach(debuggee, protocolVersion, pass());
        } else {
          chrome.test.fail("Cannot discover a newly created worker");
        }
      });
    };
    workerPort.start();
  },

  function detachFromWorker() {
    chrome.debugger.detach(debuggee, pass());
  },

  async function sendCommandDuringNavigation() {
    const tab = await openTab(chrome.runtime.getURL('inspected.html'));
    const debuggee = {tabId: tab.id};

    function checkError() {
      if (chrome.runtime.lastError) {
        chrome.test.fail(chrome.runtime.lastError.message);
      } else {
        chrome.tabs.remove(tab.id);
        chrome.test.succeed();
      }
    }

    function onNavigateDone() {
      chrome.debugger.sendCommand(debuggee, "Page.disable", null, checkError);
    }

    function onAttach() {
      chrome.debugger.sendCommand(debuggee, "Page.enable");
      chrome.debugger.sendCommand(
          debuggee, "Page.navigate", {url:"about:blank"}, onNavigateDone);
    }

    chrome.debugger.attach(debuggee, protocolVersion, onAttach);
  },

  async function sendCommandToDataUri() {
    const tab = await openTab('data:text/html,<h1>hi</h1>');
    const debuggee = {tabId: tab.id};

    function checkError() {
      if (chrome.runtime.lastError) {
        chrome.test.fail(chrome.runtime.lastError.message);
      } else {
        chrome.tabs.remove(tab.id);
        chrome.test.succeed();
      }
    }

    function onAttach() {
      chrome.debugger.sendCommand(debuggee, "Page.enable", null, checkError);
    }

    chrome.debugger.attach(debuggee, protocolVersion, onAttach);
  },

  // http://crbug.com/824174
  async function getResponseBodyInvalidChar() {
    let requestId;

    function onEvent(debuggeeId, message, params) {
      if (message === 'Network.responseReceived' &&
          params.response.url.endsWith('invalid_char.html')) {
        requestId = params.requestId;
      } else if (message === 'Network.loadingFinished' &&
                 params.requestId === requestId) {
        chrome.debugger.sendCommand(
            debuggeeId, 'Network.getResponseBody',
            {requestId: params.requestId}, function(responseBody) {
              chrome.debugger.onEvent.removeListener(onEvent);
              chrome.debugger.detach(debuggeeId);
              chrome.test.succeed();
            });
      }
    }

    chrome.debugger.onEvent.addListener(onEvent);
    const tab = await openTab(chrome.runtime.getURL('inspected.html'));
    const debuggee = {tabId: tab.id};
    chrome.debugger.attach(debuggee, protocolVersion, function() {
      chrome.debugger.sendCommand(
          debuggee, 'Network.enable', null, function() {
            chrome.debugger.sendCommand(
                debuggee, 'Page.enable', null, function() {
                  // Navigate to a new page after attaching so we don't miss
                  // any protocol events that we might have missed while
                  // attaching to the first page.
                  chrome.debugger.sendCommand(
                      debuggee, 'Page.navigate',
                      {url: window.location.origin + '/fetch.html'});
                });
          });
    });
  },

  /* TODO(crbug.com/40904113): This test is flaky.
  async function offlineErrorPage() {
    const url = 'http://127.0.0.1//extensions/api_test/debugger/inspected.html';
    const tab = await openTab(url);
    const debuggee = {tabId: tab.id};
    var finished = false;
    var failure = '';
    var expectingFrameNavigated = false;

    function finishIfError() {
      if (chrome.runtime.lastError) {
        failure = chrome.runtime.lastError.message;
        finish(true);
        return true;
      }
      return false;
    }

    function onAttach() {
      chrome.debugger.sendCommand(debuggee, 'Network.enable', null,
          finishIfError);
      chrome.debugger.sendCommand(debuggee, 'Page.enable', null,
          finishIfError);
      var offlineParams = { offline: true, latency: 0,
          downloadThroughput: 0, uploadThroughput: 0 };
      chrome.debugger.sendCommand(debuggee,
          'Network.emulateNetworkConditions',
          offlineParams, onOffline);
    }

    function onOffline() {
      if (finishIfError())
        return;
      expectingFrameNavigated = true;
      chrome.debugger.sendCommand(debuggee, 'Page.reload', null,
          finishIfError);
    }

    function finish(detach) {
      if (finished)
        return;
      finished = true;
      chrome.debugger.onDetach.removeListener(onDetach);
      chrome.debugger.onEvent.removeListener(onEvent);
      if (detach)
        chrome.debugger.detach(debuggee);
      chrome.tabs.remove(tab.id, () => {
        if (failure)
          chrome.test.fail(failure);
        else
          chrome.test.succeed();
      });
    }

    function onDetach() {
      failure = 'Detached before navigated to error page';
      finish(false);
    }

    function onEvent(_, method, params) {
      if (!expectingFrameNavigated || method !== 'Page.frameNavigated')
        return;

      if (finishIfError())
        return;

      expectingFrameNavigated = false;
      chrome.debugger.sendCommand(
          debuggee, 'Page.navigate', {url: 'about:blank'}, onNavigateDone);
    }

    function onNavigateDone() {
      if (finishIfError())
        return;
      finish(true);
    }

    chrome.debugger.onDetach.addListener(onDetach);
    chrome.debugger.onEvent.addListener(onEvent);
    chrome.debugger.attach(debuggee, protocolVersion, onAttach);
  },
  */

  function autoAttachToOOPIF() {
    if (!config.customArg) {
      chrome.test.succeed();
      return;
    }

    var urls = config.customArg.split(";");
    var mainFrameUrl = urls[0];
    var oopFrameUrl = urls[1];

    chrome.tabs.query({url: "http://*/*" + mainFrameUrl}, function(tabs) {
      chrome.test.assertNoLastError();
      var debuggee = {tabId: tabs[0].id};
      var gotTarget = false;

      function onEvent(_, method, params) {
        if (method === "Target.attachedToTarget") {
          chrome.test.assertTrue(
              params.targetInfo.url.indexOf(oopFrameUrl) !== -1);
          gotTarget = true;
        }
      }

      function finish() {
        chrome.test.assertNoLastError();
        chrome.test.assertTrue(gotTarget);
        chrome.debugger.onEvent.removeListener(onEvent);
        chrome.debugger.detach(debuggee, pass());
      }

      chrome.debugger.attach(debuggee, protocolVersion, () => {
        chrome.test.assertNoLastError();
        chrome.debugger.onEvent.addListener(onEvent);
        chrome.debugger.sendCommand(debuggee, "Target.setAutoAttach",
            {autoAttach: true, waitForDebuggerOnStart: false}, finish);
      });
    });
  }
]));