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

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

var apiFeatures = chrome.test.getApiFeatures();

// Returns a list of all chrome.foo.bar API paths available to an app.
function getApiPaths() {
  var apiPaths = [];
  var apiDefinitions = chrome.test.getApiDefinitions();
  apiDefinitions.forEach(function(module) {
    var namespace = module.namespace;
    var apiFeature = apiFeatures[namespace];
    if (Array.isArray(apiFeature))
      apiFeature = apiFeatures[namespace][0];

    // Skip internal APIs.
    if (apiFeature.internal)
      return;

    // Get the API functions and events.
    [module.functions, module.events].forEach(function(section) {
      if (typeof(section) == "undefined")
        return;
      // Pieces of the module don't inherit from Array/Object.
      Array.prototype.forEach.call(section, function(entry) {
        let fullName = `${namespace}.${entry.name}`;
        // Skip a few functions:
        // - `idle.getAutoLockDelay()` (restricted to certain platforms)
        // - `power.reportActivity()` (restricted to certain platforms)
        // - `runtime.getContexts()` (restricted to MV3)
        // - `runtime.onUserScriptConnect()` (restricted to MV3)
        // - `runtime.onUserScriptMessage()` (restricted to MV3)
        // - `permissions.addSiteAccessRequest()` (restricted to MV3)
        // - `permissions.removeSiteAccessRequest()` (restricted to MV3)
        // TODO(crbug.com/40609281)
        const skipPaths = [
          'idle.getAutoLockDelay', 'power.reportActivity',
          'runtime.getContexts', 'runtime.onUserScriptConnect',
          'runtime.onUserScriptMessage', 'permissions.addSiteAccessRequest',
          'permissions.removeSiteAccessRequest'
        ];
        if (!skipPaths.includes(fullName)) {
          apiPaths.push(fullName);
        }
      });
    });

    // Get the API properties.
    if (module.properties) {
      Object.getOwnPropertyNames(module.properties).forEach(function(propName) {
        const fullPath = namespace + '.' + propName;
        // Skip storage.session, since it's restricted to MV3.
        if (fullPath != 'storage.session') {
          apiPaths.push(fullPath);
        }
      });
    }
  });
  return apiPaths;
}

// Tests whether all parts of an API path can be accessed. The path is a
// namespace or function/property/event etc. within a namespace, and is
// dot-separated.
function testPath(path) {
  var parts = path.split('.');

  var module = chrome;
  for (var i = 0; i < parts.length; i++) {
    // Touch this component of the path. This will die if an API does not have
    // a schema registered.
    module = module[parts[i]];

    // The component should be defined unless it is lastError, which depends on
    // there being an error.
    if (typeof(module) == "undefined" && path != "runtime.lastError")
      return false;
  }
  return true;
}

function doTest() {
  // Run over all API path strings and ensure each path is defined.
  var failures = [];
  getApiPaths().forEach(function(path) {
    if (!testPath(path)) {
      failures.push(path);
    }
  });

  // Lack of failure implies success.
  if (failures.length == 0) {
    chrome.test.notifyPass();
  } else {
    console.log("failures on:\n" + failures.join("\n") +
        "\n\n\n>>> See comment in stubs_apitest.cc for a " +
        "hint about fixing this failure.\n\n");
    chrome.test.notifyFail("failed");
  }
}

chrome.app.runtime.onLaunched.addListener(function() {
  doTest();
});