summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/extensions/api/notification/notification_apitest.cc18
-rw-r--r--chrome/chrome_renderer.gypi1
-rw-r--r--chrome/common/extensions/api/experimental_notification.idl1
-rw-r--r--chrome/renderer/extensions/dispatcher.cc3
-rw-r--r--chrome/renderer/resources/extensions/image_util.js69
-rw-r--r--chrome/renderer/resources/extensions/notification_custom_bindings.js128
-rw-r--r--chrome/renderer/resources/renderer_resources.grd2
-rw-r--r--chrome/test/data/extensions/api_test/notification/api/csp/background.js165
-rw-r--r--chrome/test/data/extensions/api_test/notification/api/csp/manifest.json13
-rw-r--r--chrome/test/data/extensions/api_test/notification/api/events/background.js3
10 files changed, 391 insertions, 12 deletions
diff --git a/chrome/browser/extensions/api/notification/notification_apitest.cc b/chrome/browser/extensions/api/notification/notification_apitest.cc
index 7c9970f..eead88f 100644
--- a/chrome/browser/extensions/api/notification/notification_apitest.cc
+++ b/chrome/browser/extensions/api/notification/notification_apitest.cc
@@ -41,8 +41,7 @@ IN_PROC_BROWSER_TEST_F(NotificationApiTest, TestIdUsage) {
"[\"\", " // Empty string: ask API to generate ID
"{"
"\"templateType\": \"simple\","
- "\"iconUrl\": \"http://www.google.com/intl/en/chrome/assets/"
- "common/images/chrome_logo_2x.png\","
+ "\"iconUrl\": \"an/image/that/does/not/exist.png\","
"\"title\": \"Attention!\","
"\"message\": \"Check out Cirque du Soleil\""
"}]",
@@ -67,8 +66,7 @@ IN_PROC_BROWSER_TEST_F(NotificationApiTest, TestIdUsage) {
"[\"" + notification_id + "\", "
"{"
"\"templateType\": \"simple\","
- "\"iconUrl\": \"http://www.google.com/intl/en/chrome/assets/"
- "common/images/chrome_logo_2x.png\","
+ "\"iconUrl\": \"an/image/that/does/not/exist.png\","
"\"title\": \"Attention!\","
"\"message\": \"Too late! The show ended yesterday\""
"}]",
@@ -100,8 +98,7 @@ IN_PROC_BROWSER_TEST_F(NotificationApiTest, TestIdUsage) {
"[\"xxxxxxxxxxxx\", "
"{"
"\"templateType\": \"simple\","
- "\"iconUrl\": \"http://www.google.com/intl/en/chrome/assets/"
- "common/images/chrome_logo_2x.png\","
+ "\"iconUrl\": \"an/image/that/does/not/exist.png\","
"\"title\": \"!\","
"\"message\": \"!\""
"}]",
@@ -166,8 +163,7 @@ IN_PROC_BROWSER_TEST_F(NotificationApiTest, TestBaseFormatNotification) {
"[\"\", "
"{"
"\"templateType\": \"basic\","
- "\"iconUrl\": \"http://www.google.com/intl/en/chrome/assets/"
- "common/images/chrome_logo_2x.png\","
+ "\"iconUrl\": \"an/image/that/does/not/exist.png\","
"\"title\": \"Attention!\","
"\"message\": \"Check out Cirque du Soleil\","
"\"priority\": 1,"
@@ -206,7 +202,7 @@ IN_PROC_BROWSER_TEST_F(NotificationApiTest, TestMultipleItemNotification) {
"[\"\", "
"{"
"\"templateType\": \"list\","
- "\"iconUrl\": \"https://code.google.com/p/chromium/logo\","
+ "\"iconUrl\": \"an/image/that/does/not/exist.png\","
"\"title\": \"Multiple Item Notification Title\","
"\"message\": \"Multiple item notification message.\","
"\"items\": ["
@@ -236,3 +232,7 @@ IN_PROC_BROWSER_TEST_F(NotificationApiTest, TestMultipleItemNotification) {
IN_PROC_BROWSER_TEST_F(NotificationApiTest, TestEvents) {
ASSERT_TRUE(RunExtensionTest("notification/api/events")) << message_;
}
+
+IN_PROC_BROWSER_TEST_F(NotificationApiTest, TestCSP) {
+ ASSERT_TRUE(RunExtensionTest("notification/api/csp")) << message_;
+}
diff --git a/chrome/chrome_renderer.gypi b/chrome/chrome_renderer.gypi
index b240bce..aa48477 100644
--- a/chrome/chrome_renderer.gypi
+++ b/chrome/chrome_renderer.gypi
@@ -168,6 +168,7 @@
'renderer/resources/extensions/json_schema.js',
'renderer/resources/extensions/last_error.js',
'renderer/resources/extensions/miscellaneous_bindings.js',
+ 'renderer/resources/extensions/notification_custom_bindings.js',
'renderer/resources/extensions/omnibox_custom_bindings.js',
'renderer/resources/extensions/page_action_custom_bindings.js',
'renderer/resources/extensions/page_actions_custom_bindings.js',
diff --git a/chrome/common/extensions/api/experimental_notification.idl b/chrome/common/extensions/api/experimental_notification.idl
index a59c630..2c43dc0 100644
--- a/chrome/common/extensions/api/experimental_notification.idl
+++ b/chrome/common/extensions/api/experimental_notification.idl
@@ -35,7 +35,6 @@
TemplateType templateType;
// Sender's avatar, app icon, or a thumbnail for image notifications.
- // TODO(miket): see browserAction.setIcon() for a better approach.
DOMString iconUrl;
// Title of the notification (e.g. sender name for email).
diff --git a/chrome/renderer/extensions/dispatcher.cc b/chrome/renderer/extensions/dispatcher.cc
index dcd161f..a76441f 100644
--- a/chrome/renderer/extensions/dispatcher.cc
+++ b/chrome/renderer/extensions/dispatcher.cc
@@ -632,6 +632,7 @@ void Dispatcher::PopulateSourceMap() {
// Libraries.
source_map_.RegisterSource("contentWatcher", IDR_CONTENT_WATCHER_JS);
+ source_map_.RegisterSource("imageUtil", IDR_IMAGE_UTIL_JS);
source_map_.RegisterSource("lastError", IDR_LAST_ERROR_JS);
source_map_.RegisterSource("schemaUtils", IDR_SCHEMA_UTILS_JS);
source_map_.RegisterSource("sendRequest", IDR_SEND_REQUEST_JS);
@@ -659,6 +660,8 @@ void Dispatcher::PopulateSourceMap() {
IDR_EXPERIMENTAL_MEDIA_GALLERIES_CUSTOM_BINDINGS_JS);
source_map_.RegisterSource("experimental.offscreen",
IDR_EXPERIMENTAL_OFFSCREENTABS_CUSTOM_BINDINGS_JS);
+ source_map_.RegisterSource("experimental.notification",
+ IDR_NOTIFICATION_CUSTOM_BINDINGS_JS);
source_map_.RegisterSource("extension", IDR_EXTENSION_CUSTOM_BINDINGS_JS);
source_map_.RegisterSource("fileBrowserHandler",
IDR_FILE_BROWSER_HANDLER_CUSTOM_BINDINGS_JS);
diff --git a/chrome/renderer/resources/extensions/image_util.js b/chrome/renderer/resources/extensions/image_util.js
new file mode 100644
index 0000000..4f418e2
--- /dev/null
+++ b/chrome/renderer/resources/extensions/image_util.js
@@ -0,0 +1,69 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// This function takes an object |imageSpec| with the key |path| -
+// corresponding to the internet URL to be translated - and optionally
+// |width| and |height| which are the maximum dimensions to be used when
+// converting the image.
+function loadDataUrl(imageSpec, callbacks) {
+ var path = imageSpec.path;
+ var img = new Image();
+ if (typeof callbacks.onerror === 'function') {
+ img.onerror = function() {
+ callbacks.onerror({ problem: 'could_not_load', path: path });
+ };
+ }
+ img.onload = function() {
+ var canvas = document.createElement('canvas');
+
+ canvas.width = img.width;
+ if (imageSpec.width && imageSpec.width < img.width)
+ canvas.width = imageSpec.width;
+ canvas.height = img.height;
+ if (imageSpec.height && imageSpec.height < img.height)
+ canvas.height = imageSpec.height;
+
+ var canvas_context = canvas.getContext('2d');
+ canvas_context.clearRect(0, 0, canvas.width, canvas.height);
+ canvas_context.drawImage(img, 0, 0, canvas.width, canvas.height);
+ try {
+ var dataUrl = canvas.toDataURL();
+ if (typeof callbacks.oncomplete === 'function') {
+ callbacks.oncomplete(dataUrl);
+ }
+ } catch (e) {
+ if (typeof callbacks.onerror === 'function') {
+ callbacks.onerror({ problem: 'data_url_unavailable', path: path });
+ }
+ }
+ }
+ img.src = path;
+}
+
+function on_complete_index(index, err, loading, finished, callbacks) {
+ return function(imageData) {
+ delete loading[index];
+ finished[index] = imageData;
+ if (err)
+ callbacks.onerror(index);
+ if (Object.keys(loading).length == 0)
+ callbacks.oncomplete(finished);
+ }
+}
+
+function loadAllImages(imageSpecs, callbacks) {
+ var loading = {}, finished = [],
+ index, pathname;
+
+ for (var index = 0; index < imageSpecs.length; index++) {
+ loading[index] = imageSpecs[index];
+ loadDataUrl(imageSpecs[index], {
+ oncomplete: on_complete_index(index, false, loading, finished, callbacks),
+ onerror: on_complete_index(index, true, loading, finished, callbacks)
+ });
+ }
+}
+
+exports.loadDataUrl = loadDataUrl;
+exports.loadAllImages = loadAllImages;
diff --git a/chrome/renderer/resources/extensions/notification_custom_bindings.js b/chrome/renderer/resources/extensions/notification_custom_bindings.js
new file mode 100644
index 0000000..b88be09
--- /dev/null
+++ b/chrome/renderer/resources/extensions/notification_custom_bindings.js
@@ -0,0 +1,128 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// Custom bindings for the notification API.
+
+var chromeHidden = requireNative('chrome_hidden').GetChromeHidden();
+var sendRequest = require('sendRequest').sendRequest;
+var imageUtil = require('imageUtil');
+var lastError = require('lastError');
+
+function url_getter(context, key) {
+ var f = function() {
+ return this[key];
+ };
+ return f.bind(context);
+}
+
+function url_setter(context, key) {
+ var f = function(val) {
+ this[key] = val;
+ };
+ return f.bind(context);
+}
+
+function replaceNotificationOptionURLs(notification_details, callback) {
+ // A URL Spec is an object with the following keys:
+ // path: The resource to be downloaded.
+ // callback: A function to be called when the URL is complete. It
+ // should accept an ImageData object and set the appropriate
+ // field in the output of create.
+
+ // TODO(dewittj): Try to remove hard-coding.
+ // |iconUrl| is required.
+ var url_specs = [{
+ path: notification_details.iconUrl,
+ width: 80,
+ height: 80,
+ callback: url_setter(notification_details, 'iconUrl')
+ }];
+
+ // |secondIconUrl| is optional.
+ if (notification_details.secondIconUrl) {
+ url_specs.push({
+ path: notification_details.secondIconUrl,
+ width: 80,
+ height: 80,
+ callback: url_setter(notification_details, 'secondIconUrl')
+ });
+ }
+
+ // |imageUrl| is optional.
+ if (notification_details.imageUrl) {
+ url_specs.push({
+ path: notification_details.imageUrl,
+ width: 300,
+ height: 300,
+ callback: url_setter(notification_details, 'imageUrl')
+ });
+ }
+
+ // Each button has an optional icon.
+ var button_list = notification_details.buttons;
+ if (button_list && typeof button_list.length === 'number') {
+ var num_buttons = button_list.length;
+ for (var i = 0; i < num_buttons; i++) {
+ if (button_list[i].iconUrl) {
+ url_specs.push({
+ path: button_list[i].iconUrl,
+ width: 16,
+ height: 16,
+ callback: url_setter(button_list[i], 'iconUrl')
+ });
+ }
+ }
+ }
+
+ var errors = 0;
+
+ imageUtil.loadAllImages(url_specs, {
+ onerror: function(index) {
+ errors++;
+ },
+ oncomplete: function(imageData) {
+ if (errors > 0) {
+ callback(false);
+ return;
+ }
+ for (var index = 0; index < url_specs.length; index++) {
+ var url_spec = url_specs[index];
+ url_spec.callback(imageData[index]);
+ }
+ callback(true);
+ }
+ });
+}
+
+function genHandle(failure_function) {
+ return function(id, input_notification_details, callback) {
+ // TODO(dewittj): Remove this hack. This is used as a way to deep
+ // copy a complex JSON object.
+ var notification_details = JSON.parse(
+ JSON.stringify(input_notification_details));
+ var that = this;
+ replaceNotificationOptionURLs(notification_details, function(success) {
+ if (success) {
+ sendRequest(that.name,
+ [id, notification_details, callback],
+ that.definition.parameters);
+ return;
+ }
+ lastError.set('Unable to download all specified images.');
+ failure_function(callback, id);
+ });
+ };
+}
+
+var handleCreate = genHandle(function(callback, id) { callback(id); });
+var handleUpdate = genHandle(function(callback, id) { callback(false); });
+
+var experimentalNotificationCustomHook = function(bindingsAPI, extensionId) {
+ var apiFunctions = bindingsAPI.apiFunctions;
+ apiFunctions.setHandleRequest('create', handleCreate);
+ apiFunctions.setHandleRequest('update', handleCreate);
+};
+
+chromeHidden.registerCustomHook('experimental.notification',
+ experimentalNotificationCustomHook);
diff --git a/chrome/renderer/resources/renderer_resources.grd b/chrome/renderer/resources/renderer_resources.grd
index 1643ff4..225a75d 100644
--- a/chrome/renderer/resources/renderer_resources.grd
+++ b/chrome/renderer/resources/renderer_resources.grd
@@ -37,6 +37,7 @@ without changes to the corresponding grd file. fb9 -->
<!-- Libraries. -->
<include name="IDR_TAG_WATCHER_JS" file="extensions\tag_watcher.js" type="BINDATA" />
+ <include name="IDR_IMAGE_UTIL_JS" file="extensions\image_util.js" type="BINDATA" />
<include name="IDR_LAST_ERROR_JS" file="extensions\last_error.js" type="BINDATA" />
<include name="IDR_SCHEMA_UTILS_JS" file="extensions\schema_utils.js" type="BINDATA" />
<include name="IDR_SEND_REQUEST_JS" file="extensions\send_request.js" type="BINDATA" />
@@ -65,6 +66,7 @@ without changes to the corresponding grd file. fb9 -->
<include name="IDR_I18N_CUSTOM_BINDINGS_JS" file="extensions\i18n_custom_bindings.js" type="BINDATA" />
<include name="IDR_INPUT_IME_CUSTOM_BINDINGS_JS" file="extensions\input.ime_custom_bindings.js" type="BINDATA" />
<include name="IDR_MEDIA_GALLERIES_CUSTOM_BINDINGS_JS" file="extensions\media_galleries_custom_bindings.js" type="BINDATA" />
+ <include name="IDR_NOTIFICATION_CUSTOM_BINDINGS_JS" file="extensions\notification_custom_bindings.js" type="BINDATA" />
<include name="IDR_OMNIBOX_CUSTOM_BINDINGS_JS" file="extensions\omnibox_custom_bindings.js" type="BINDATA" />
<include name="IDR_PAGE_ACTIONS_CUSTOM_BINDINGS_JS" file="extensions\page_actions_custom_bindings.js" type="BINDATA" />
<include name="IDR_PAGE_ACTION_CUSTOM_BINDINGS_JS" file="extensions\page_action_custom_bindings.js" type="BINDATA" />
diff --git a/chrome/test/data/extensions/api_test/notification/api/csp/background.js b/chrome/test/data/extensions/api_test/notification/api/csp/background.js
new file mode 100644
index 0000000..266de2d
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/notification/api/csp/background.js
@@ -0,0 +1,165 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+const notification = chrome.experimental.notification;
+
+var idString = "foo";
+
+var testCSP = function() {
+ var onCreateCallback = function(id) {
+ if (chrome.runtime.lastError) {
+ chrome.test.succeed();
+ return;
+ }
+ chrome.test.fail();
+ }
+
+ var options = {
+ templateType: "basic",
+ iconUrl: "http://google.com/clearly-a-security-problem.png",
+ title: "Attention!",
+ message: "Check out Cirque du Soleil"
+ };
+ notification.create(idString, options, onCreateCallback);
+};
+
+function testDataURL() {
+ chrome.runtime.lastError = undefined;
+ var onCreateCallback = function(id) {
+ if (chrome.runtime.lastError) {
+ chrome.test.fail();
+ return;
+ }
+ chrome.test.succeed();
+ }
+ var options = {
+ templateType: "basic",
+ iconUrl: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAA" +
+ "CNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHw" +
+ "AAAABJRU5ErkJggg==",
+ title: "Attention!",
+ message: "Check out Cirque du Soleil"
+ };
+ notification.create(idString, options, onCreateCallback);
+}
+
+function testCSPUpdateIconURL() {
+ var onUpdateCallback = function(id) {
+ if (chrome.runtime.lastError) {
+ chrome.test.succeed();
+ return;
+ }
+ chrome.test.fail();
+ };
+ var onCreateCallback = function(id) {
+ if (chrome.runtime.lastError) {
+ chrome.test.fail();
+ return;
+ }
+ var options2 = {
+ templateType: "basic",
+ iconUrl: "http://www.google.com/favicon.ico",
+ title: "Attention!",
+ message: "Check out Cirque du Soleil"
+ };
+ notification.update(idString, options2, onUpdateCallback);
+ }
+ var options = {
+ templateType: "basic",
+ iconUrl: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAA" +
+ "CNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHw" +
+ "AAAABJRU5ErkJggg==",
+ title: "Attention!",
+ message: "Check out Cirque du Soleil"
+ };
+ notification.create(idString, options, onCreateCallback);
+}
+
+function testCSPUpdateImageURL() {
+ var onUpdateCallback = function(id) {
+ if (chrome.runtime.lastError) {
+ chrome.test.succeed();
+ return;
+ }
+ chrome.test.fail();
+ };
+ var onCreateCallback = function(id) {
+ if (chrome.runtime.lastError) {
+ chrome.test.fail();
+ return;
+ }
+ var options2 = {
+ templateType: "basic",
+ iconUrl: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAA" +
+ "CNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHw" +
+ "AAAABJRU5ErkJggg==",
+ imageUrl: "http://www.google.com/favicon.ico",
+ title: "Attention!",
+ message: "Check out Cirque du Soleil"
+ };
+ notification.update(idString, options2, onUpdateCallback);
+ }
+ var options = {
+ templateType: "image",
+ iconUrl: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAA" +
+ "CNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHw" +
+ "AAAABJRU5ErkJggg==",
+ imageUrl: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAA" +
+ "CNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHw" +
+ "AAAABJRU5ErkJggg==",
+ title: "Attention!",
+ message: "Check out Cirque du Soleil"
+ };
+ notification.create(idString, options, onCreateCallback);
+}
+
+function testCSPUpdateButtonIconURL() {
+ var onUpdateCallback = function(id) {
+ if (chrome.runtime.lastError) {
+ chrome.test.succeed();
+ return;
+ }
+ chrome.test.fail();
+ };
+ var onCreateCallback = function(id) {
+ if (chrome.runtime.lastError) {
+ chrome.test.fail();
+ return;
+ }
+ var options2 = {
+ templateType: "basic",
+ iconUrl: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAA" +
+ "CNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHw" +
+ "AAAABJRU5ErkJggg==",
+ title: "Attention!",
+ buttons: [ {
+ title: "Foo",
+ iconUrl: "http://www.google.com/favicon.ico"
+ } ],
+ message: "Check out Cirque du Soleil"
+ };
+ notification.update(idString, options2, onUpdateCallback);
+ }
+ var options = {
+ templateType: "basic",
+ iconUrl: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAA" +
+ "CNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHw" +
+ "AAAABJRU5ErkJggg==",
+ title: "Attention!",
+ buttons: [ {
+ title: "Foo",
+ iconUrl: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAA" +
+ "CNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHw" +
+ "AAAABJRU5ErkJggg=="
+ } ],
+ message: "Check out Cirque du Soleil"
+ };
+ notification.create(idString, options, onCreateCallback);
+}
+chrome.test.runTests([
+ testCSP,
+ testDataURL,
+ testCSPUpdateIconURL,
+ testCSPUpdateImageURL,
+ testCSPUpdateButtonIconURL ]);
diff --git a/chrome/test/data/extensions/api_test/notification/api/csp/manifest.json b/chrome/test/data/extensions/api_test/notification/api/csp/manifest.json
new file mode 100644
index 0000000..de79f22
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/notification/api/csp/manifest.json
@@ -0,0 +1,13 @@
+{
+ "name": "chrome.notification",
+ "version": "0.1",
+ "description": "chrome.notification API events",
+ "app": {
+ "background": {
+ "scripts": ["background.js"]
+ }
+ },
+ "permissions": [
+ "experimental"
+ ]
+}
diff --git a/chrome/test/data/extensions/api_test/notification/api/events/background.js b/chrome/test/data/extensions/api_test/notification/api/events/background.js
index f041b53..7aa21ec 100644
--- a/chrome/test/data/extensions/api_test/notification/api/events/background.js
+++ b/chrome/test/data/extensions/api_test/notification/api/events/background.js
@@ -26,8 +26,7 @@ var testBasicEvents = function() {
var options = {
templateType: "basic",
- iconUrl: "http://www.google.com/intl/en/chrome/assets/" +
- "common/images/chrome_logo_2x.png",
+ iconUrl: "/icon.png",
title: "Attention!",
message: "Check out Cirque du Soleil"
};