chromium/chrome/test/data/extensions/api_test/permissions/host_subsets/background.js

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

var ERROR = 'Only permissions specified in the manifest may be requested.';
var test = chrome.test;

// The URL patterns that we've supposedly been granted access to so far. Use
// this to verify queried hosts for each test.
var grantedHosts = [];

// Pushes a value onto an array, asserting that it's unique in the array.
function pushUniqueValue(array, value) {
  test.assertEq(-1, array.indexOf(value));
  array.push(value);
}

// Removes a value from an array, asserting that it's unique in the array.
function removeUniqueValue(array, value) {
  var indexOfValue = array.indexOf(value);
  test.assertTrue(indexOfValue >= 0);
  array.splice(indexOfValue, 1);
  test.assertEq(-1, array.indexOf(value));
}

// Asserts that two arrays are equal when treated as sets.
function assertSetEq(expected, actual) {
  if (!actual)
    actual = [];
  test.assertEq(expected.slice().sort(), actual.slice().sort());
}

// Validates that the origin permissions returned from getAll() and contains()
// match |grantedHosts|.
function checkGrantedHosts(callback) {
  chrome.permissions.getAll(test.callbackPass(function(permissions) {
    assertSetEq(grantedHosts, permissions.origins);
    var countDown = grantedHosts.length;
    if (countDown == 0) {
      callback();
      return;
    }
    grantedHosts.forEach(function(host) {
      chrome.permissions.contains({origins: [host]},
                                  test.callbackPass(function(contains) {
        test.assertTrue(contains);
        if (--countDown == 0)
          callback();
      }));
    });
  }));
}

// Returns a function which requests access to a host, and afterwards checks
// that our expected host permissions agree with Chrome's.
function requestHost(host, expectedGranted, expectedError) {
  return function(callback) {
    chrome.permissions.request({origins: [host]},
                               test.callback(function(granted) {
      if (granted)
        pushUniqueValue(grantedHosts, host);
      if (expectedGranted) {
        test.assertTrue(
            granted,
            "Access to " + host + " was not granted, but should have been");
      } else {
        test.assertFalse(
            !!granted,
            "Access to " + host + " was granted, but should not have been");
      }
      checkGrantedHosts(callback);
    }, expectedError));
  };
}

// Returns a function which removes access to a host, and afterwards checks
// that our expected host permissions agree with Chrome's.
function removeHost(host, expectedRemoved) {
  return function(callback) {
    chrome.permissions.remove({origins: [host]},
                              test.callbackPass(function(removed) {
      if (removed) {
        test.assertTrue(expectedRemoved, "Access to " + host + " removed");
        removeUniqueValue(grantedHosts, host);
      } else {
        test.assertFalse(expectedRemoved, "Access to " + host + " not removed");
      }
      checkGrantedHosts(callback);
    }));
  }
}

// Returns a function which checks that permissions.contains(host) returns
// |expected|.
function contains(host, expected) {
  return function(callback) {
    chrome.permissions.contains({origins: [host]},
                                test.callbackPass(function(result) {
      test.assertEq(expected, result);
      callback();
    }));
  };
}

// Calls every function in |chain| passing each the next function to run as a
// single argument - except for the last function, which is *not* expected to
// have a callback.
//
// The test is kept alive until all callbacks in the chain have been run.
function chain(callbacks) {
  var head = callbacks[0], tail = callbacks.slice(1);
  if (tail.length == 0) {
    head();
    return;
  }
  var callbackCompleted = chrome.test.callbackAdded();
  head(function() {
    try {
      chain(tail);
    } finally {
      callbackCompleted();
    }
  });
}

function main() {
  chain([
                 // Simple request #1.
        contains('http://google.com/*', false),
     requestHost('http://google.com/*', true),
        contains('http://google.com/*', true),
        contains('http://google.com/foo/*', true),
        contains('http://google.com:1234/foo/*', true),
        contains('http://www.google.com/*', false),

                 // Simple request #2.
     requestHost('http://*.yahoo.com/*', true),
        contains('http://yahoo.com/*', true),
        contains('http://yahoo.com/foo/*', true),
        contains('http://www.yahoo.com/*', true),
        contains('http://www.yahoo.com/foo/*', true),
        contains('http://www.yahoo.com:5678/foo/*', true),

                 // Can request access to a subset of a host that's allowed.
     requestHost('http://yahoo.com/*', true),

                 // Can remove it.
      removeHost('http://yahoo.com/*', true),

                 // However, since it's still granted by the *.yahoo.com/*
                 // permission, it's still considered to contain it.
        contains('http://yahoo.com/*', true),

                 // Can remove the whole host, though.
      removeHost('http://*.yahoo.com/*', true),
        contains('http://google.com/*', true),
        contains('http://yahoo.com/*', false),
        contains('http://www.yahoo.com/*', false),

                 // Widening the net (and then deleting it).
     requestHost('http://*.google.com/*', true),
        contains('http://google.com/*', true),
        contains('http://www.google.com/*', true),
      removeHost('http://google.com/*', true),
        contains('http://google.com/*', true),
        contains('http://www.google.com/*', true),
      removeHost('http://*.google.com/*', true),
        contains('http://google.com/*', false),
        contains('http://www.google.com/*', false),

                 // https schemes should fail because they're not covered by
                 // this extension's optional permission pattern.
     requestHost('https://google.com/*', false, ERROR),
     requestHost('https://*.yahoo.com/*', false, ERROR),

                 // There is a sneaky ftp://ftp.example.com/* pattern in the
                 // manifest. Test it.
     requestHost('ftp://example.com/*', false, ERROR),
        contains('ftp://example.com/*', false),
     requestHost('ftp://www.example.com/*', false, ERROR),
        contains('ftp://www.example.com/*', false),
     requestHost('ftp://secret.ftp.example.com/*', false, ERROR),
        contains('ftp://secret.ftp.example.com/*', false),
     requestHost('ftp://ftp.example.com/*', true),
        contains('ftp://ftp.example.com/*', true),
      removeHost('ftp://ftp.example.com/*', true),
        contains('ftp://ftp.example.com/*', false),

                 // And finally...
                 test.succeed]);
}

test.runTests([main]);