chromium/chrome/test/data/extensions/api_test/notifications/api/basic_usage/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.

const notifications = chrome.notifications;

const red_dot = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUA" +
    "AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO" +
    "9TXL0Y4OHwAAAABJRU5ErkJggg==";

// An image URL larger than the max size allowed for a notification image.
// This was generated using the function that follows. Should we increase
// the max size of an image, update the function to increase the width and
// height and run the function again to log the URL.
const bigImageUrl = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA" +
    "AGQAAABkCAYAAABw4pVUAAABZUlEQVR4Xu3TQREAAAiEQK9/aWvsAxMw4O06" +
    "ysAommCuINgTFKQgmAEMp4UUBDOA4bSQgmAGMJwWUhDMAIbTQgqCGcBwWkhB" +
    "MAMYTgspCGYAw2khBcEMYDgtpCCYAQynhRQEM4DhtJCCYAYwnBZSEMwAhtNC" +
    "CoIZwHBaSEEwAxhOCykIZgDDaSEFwQxgOC2kIJgBDKeFFAQzgOG0kIJgBjCc" +
    "FlIQzACG00IKghnAcFpIQTADGE4LKQhmAMNpIQXBDGA4LaQgmAEMp4UUBDOA" +
    "4bSQgmAGMJwWUhDMAIbTQgqCGcBwWkhBMAMYTgspCGYAw2khBcEMYDgtpCCY" +
    "AQynhRQEM4DhtJCCYAYwnBZSEMwAhtNCCoIZwHBaSEEwAxhOCykIZgDDaSEF" +
    "wQxgOC2kIJgBDKeFFAQzgOG0kIJgBjCcFlIQzACG00IKghnAcFpIQTADGE4L" +
    "KQhmAMNpIQXBDGA4LQQL8oTPAGUY76lBAAAAAElFTkSuQmCC";

// Used to generate a data URL with an image that's too large. This
// will not run using a Service Worker-based extension.
function logBigImageUrl() {
  var canvas = document.createElement('canvas');
  // This is just enough to be too large for an icon. See
  // message_center::kNotificationIconSize.
  canvas.width = 100;
  canvas.height = 100;
  var ctx = canvas.getContext('2d');
  ctx.fillStyle = "rgb(200, 0, 0)";
  ctx.fillRect(10, 20, 30, 40);
  console.log(canvas.toDataURL());
}

var basicNotificationOptions = {
  type: "basic",
  title: "Basic title",
  message: "Basic message",
  iconUrl: red_dot
};

function create(id, options) {
  return new Promise(function (resolve, reject) {
    notifications.create(id, options, function (id) {
      if (chrome.runtime.lastError) {
        reject(new Error("Unable to create notification"));
        return;
      }
      resolve(id);
      return;
    });
  });
};

function update(id, options) {
  return new Promise(function (resolve, reject) {
    notifications.update(id, options, function (ok) {
      if (chrome.runtime.lastError || !ok) {
        reject(new Error("Unable to update notification"));
        return;
      }
      resolve(ok);
      return;
    });
  });
}

function clear(id) {
  return new Promise(function (resolve, reject) {
    notifications.clear(id, function (ok) {
      if (chrome.runtime.lastError || !ok) {
        reject(new Error("Unable to clear notification"));
        return;
      }
      resolve(ok);
      return;
    });
  });
}

function getAll() {
  return new Promise(function (resolve, reject) {
    notifications.getAll(function (ids) {
      if (chrome.runtime.lastError) {
        reject(new Error(chrome.runtime.lastError.message));
        return;
      }

      if (ids === undefined) {
        resolve([]);
        return
      }

      var id_list = Object.keys(ids);
      resolve(id_list);
    });
  });
}

function clearAll() {
  return getAll().then(function (ids) {
    var idPromises = ids.map(function (id) { return clear(id); });
    return Promise.all(idPromises);
  });
}

function succeedTest(testName) {
  return function () {
    return clearAll().then(
        function () { chrome.test.succeed(testName); },
        function (error) {
          console.log("Unknown error in clearAll: " +
                      JSON.stringify(arguments));
        });
  };
}

function failTest(testName) {
  return function () {
    return clearAll().then(
        function () { chrome.test.fail(testName); },
        function (error) {
          console.log("Unknown error in clearAll: " +
                      JSON.stringify(error.message));
        });
  };
}

function testIdUsage() {
  var testName = "testIdUsage";
  console.log("Starting testIdUsage.");
  var succeed = succeedTest(testName);
  var fail = failTest(testName);

  var createNotification = function (idString) {
    var options = {
      type: "basic",
      iconUrl: red_dot,
      title: "Attention!",
      message: "Check out Cirque du Soleil"
    };

    return create(idString, options);
  };

  var updateNotification = function (idString) {
    var options = { title: "!", message: "!" };
    return update(idString, options);
  };

  // Should successfully create the notification
  createNotification("foo")
    // And update it.
    .then(updateNotification)
    .catch(fail)
    // Next try to update a non-existent notification.
    .then(function () { return updateNotification("foo2"); })
    // And fail if it returns true.
    .then(fail)
    // Next try to clear a non-existent notification.
    .catch(function () { return clear("foo2"); })
    .then(fail)
    // And finally clear the original notification.
    .catch(function () { return clear("foo"); })
    .catch(fail)
    .then(succeed);
};

function testIdLimit() {
  var testName = "testIdLimit";
  console.log("Starting " + testName);
  var succeed = succeedTest(testName);
  var fail = failTest(testName);

  // Notification Ids are limited to 500 characters in length.
  var id = 'a'.repeat(501);

  create(id, {
    type: 'basic',
    iconUrl: red_dot,
    title: 'My title',
    message: 'My message'
  }).then(fail, succeed);
}

function testBaseFormat() {
  var testName = "testBaseFormat";
  console.log("Starting " + testName);
  var succeed = succeedTest(testName);
  var fail = failTest(testName);

  var createNotificationWithout = function(toDelete) {
    var options = {
      type: "basic",
      iconUrl: red_dot,
      title: "Attention!",
      message: "Check out Cirque du Soleil",
      contextMessage: "Foobar.",
      priority: 1,
      eventTime: 123457896.12389,
      expandedMessage: "This is a longer expanded message.",
    };

    for (var i = 0; i < toDelete.length; i++) {
      delete options[toDelete[i]];
    }

    return create("", options);
  };

  // Construct some exclusion lists.  The |createNotificationWithout| function
  // starts with a complex notification and then deletes items in this list.
  var basicNotification= [
    "buttons",
    "items",
    "progress",
    "imageUrl"
  ];
  var bareNotification = basicNotification.concat([
    "priority",
    "eventTime",
    "expandedMessage",
  ]);
  var basicNoType = basicNotification.concat(["type"]);
  var basicNoIcon = basicNotification.concat(["iconUrl"]);
  var basicNoTitle = basicNotification.concat(["title"]);
  var basicNoMessage = basicNotification.concat(["message"]);

  // Try creating a basic notification with just some of the fields.
  createNotificationWithout(basicNotification)
    // Try creating a basic notification with all possible fields.
    .then(function () { return createNotificationWithout([]); })
    // Try creating a basic notification with the minimum in fields.
    .then(function () { return createNotificationWithout(bareNotification); })
    // After this line we are checking to make sure that there is an error
    // when notifications are created without the proper fields.
    .catch(fail)
    // Error if no type.
    .then(function () { return createNotificationWithout(basicNoType) })
    // Error if no icon.
    .catch(function () { return createNotificationWithout(basicNoIcon) })
    // Error if no title.
    .catch(function () { return createNotificationWithout(basicNoTitle) })
    // Error if no message.
    .catch(function () { return createNotificationWithout(basicNoMessage) })
    .then(fail, succeed);
};

function testListItem() {
  var testName = "testListItem";
  console.log("Starting " + testName);
  var succeed = succeedTest(testName);
  var fail = failTest(testName);

  var item = { title: "Item title.", message: "Item message." };
  var options = {
    type: "list",
    iconUrl: red_dot,
    title: "Attention!",
    message: "Check out Cirque du Soleil",
    contextMessage: "Foobar.",
    priority: 1,
    eventTime: 123457896.12389,
    items: [item, item, item, item, item],
  };
  create("id", options).then(succeed, fail);
};

function arrayEquals(a, b) {
  if (a === b) return true;
  if (a == null || b == null) return false;
  if (a.length !== b.length) return false;

  for (var i = 0; i < a.length; i++) {
    if (a[i] !== b[i]) return false;
  }
  return true;
};

function testGetAll() {
  var testName = "testGetAll";
  console.log("Starting " + testName);
  var succeed = succeedTest(testName);
  var fail = failTest(testName);
  var in_ids = ["a", "b", "c", "d"];

  // First do a get all, make sure the list is empty.
  getAll()
    .then(function (ids) {
        chrome.test.assertEq(0, ids.length);
    })
    // Then create a bunch of notifications.
    .then(function () {
      var newNotifications = in_ids.map(function (id) {
        return create(id, basicNotificationOptions);
      });
      return Promise.all(newNotifications);
    })
    // Try getAll again.
    .then(function () { return getAll(); })
    // Check that the right set of notifications is in the center.
    .then(function (ids) {
      chrome.test.assertEq(4, ids.length);
      chrome.test.assertTrue(arrayEquals(ids, in_ids));
      succeed();
    }, fail);
}

function testProgress() {
  var testName = "testProgress";
  console.log("Starting " + testName);
  var succeed = succeedTest(testName);
  var fail = failTest(testName);
  var progressOptions = {
    type: "progress",
    title: "Basic title",
    message: "Basic message",
    iconUrl: red_dot,
    progress: 30
  };

  // First, create a basic progress notification.
  create("progress", progressOptions)
    // and update it to have a different progress level.
    .then(function () { return update("progress", { progress: 60 }); })
    // If either of the above failed, the test fails.
    .catch(fail)
    // Now the following parts should all cause an error:
    // First update the progress to a low value, out-of-range
    .then(function () { return update("progress", { progress: -10 }); })
    // First update the progress to a high value, out-of-range
    .then(fail, function () { return update("progress", { progress: 101 }); })
    .then(function () { return clear("progress"); })
    // Finally try to create a notification that has a progress value but not
    // progress type.
    .then(fail, function () {
      progressOptions.type = "basic";
      return create("progress", progressOptions);
    }).then(fail, succeed);
}

function testLargeImage() {
  var testName = "testLargeImage";
  console.log("Starting " + testName);
  var succeed = succeedTest(testName);
  var fail = failTest(testName);
  var options = {
    type: "basic",
    title: "Basic title",
    message: "Basic message",
    iconUrl: bigImageUrl,
  };
  create("largeImage", options).then(succeed, fail);
}

function testOptionalParameters() {
  var testName = "testOptionalParameters";
  var succeed = succeedTest(testName);
  var fail = failTest(testName);
  function createCallback(notificationId) {
    new Promise(function() {
      chrome.test.assertNoLastError();
      chrome.test.assertEq("string", typeof notificationId);
      // Optional callback - should run without problems
      chrome.notifications.clear(notificationId);
      // Note: The point of the previous line is to show that a callback can be
      // optional. Because .clear is asynchronous, we have to be careful with
      // calling .clear again. Since .clear is processed in order, calling
      // clear() synchronously is okay.
      // If this assumption does not hold and leaks to flaky tests, file a bug
      // report and/or put the following call in a setTimeout call.

      // The notification should not exist any more, so clear() should fail.
      clear(notificationId).then(fail, succeed);
    }).then(null, fail);
  }
  // .create should succeed even when notificationId is omitted.
  chrome.notifications.create(basicNotificationOptions, createCallback);
}

chrome.test.runTests([
    testIdUsage, testIdLimit, testBaseFormat, testListItem, testGetAll,
    testProgress, testLargeImage, testOptionalParameters
]);