1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
|
// Copyright 2014 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.
var exceptionHandler = require('uncaught_exception_handler');
var natives = requireNative('setIcon');
var SetIconCommon = natives.SetIconCommon;
var inServiceWorker = natives.IsInServiceWorker();
function loadImagePathForServiceWorker(path, callback, failureCallback) {
let fetchPromise = fetch(path);
let blobPromise = $Promise.then(fetchPromise, (response) => {
if (!response.ok) {
throw $Error.self('Could not fetch action icon \'' + path + '\'.');
}
return response.blob();
});
let imagePromise = $Promise.then(blobPromise, (blob) => {
return createImageBitmap(blob);
});
let imageDataPromise = $Promise.then(imagePromise, (image) => {
var canvas = new OffscreenCanvas(image.width, image.height);
var canvasContext = canvas.getContext('2d');
canvasContext.clearRect(0, 0, canvas.width, canvas.height);
canvasContext.drawImage(image, 0, 0, canvas.width, canvas.height);
var imageData = canvasContext.getImageData(0, 0, canvas.width,
canvas.height);
callback(imageData);
});
$Promise.catch(imageDataPromise, function(error) {
failureCallback(exceptionHandler.safeErrorToString(error, true));
});
}
function loadImagePathForNonServiceWorker(path, callback, failureCallback) {
var img = new Image();
img.onerror = function() {
var message = 'Could not load action icon \'' + path + '\'.';
console.error(message);
};
img.onload = function() {
var canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height
var canvasContext = canvas.getContext('2d');
canvasContext.clearRect(0, 0, canvas.width, canvas.height);
canvasContext.drawImage(img, 0, 0, canvas.width, canvas.height);
var imageData = canvasContext.getImageData(0, 0, canvas.width,
canvas.height);
callback(imageData);
};
img.src = path;
}
function loadImagePath(path, callback, failureCallback) {
if (inServiceWorker) {
loadImagePathForServiceWorker(path, callback, failureCallback);
} else {
loadImagePathForNonServiceWorker(path, callback, failureCallback);
}
}
function smellsLikeImageData(imageData) {
// See if this object at least looks like an ImageData element.
// Unfortunately, we cannot use instanceof because the ImageData
// constructor is not public.
//
// We do this manually instead of using JSONSchema to avoid having these
// properties show up in the doc.
return (typeof imageData == 'object') && ('width' in imageData) &&
('height' in imageData) && ('data' in imageData);
}
function verifyImageData(imageData) {
if (!smellsLikeImageData(imageData)) {
throw new Error(
'The imageData property must contain an ImageData object or' +
' dictionary of ImageData objects.');
}
}
/**
* Normalizes |details| to a format suitable for sending to the browser,
* for example converting ImageData to a binary representation.
*
* @param {ImageDetails} details
* The ImageDetails passed into an extension action-style API.
* @param {Function} callback
* The callback function to pass processed imageData back to. Note that this
* callback may be called reentrantly.
* @param {Function} failureCallback
* The callback function to be called in case of an error.
*/
function setIcon(details, callback, failureCallback) {
// NOTE: |details| should already have gone through API argument validation,
// and, as part of that, will be a null-proto'd object. As such, it's safer
// to directly access and manipulate fields.
// Note that iconIndex is actually deprecated, and only available to the
// pageAction API.
// TODO(kalman): Investigate whether this is for the pageActions API, and if
// so, delete it.
if ('iconIndex' in details) {
callback(details);
return;
}
if ('imageData' in details) {
if (smellsLikeImageData(details.imageData)) {
var imageData = details.imageData;
details.imageData = {__proto__: null};
details.imageData[imageData.width.toString()] = imageData;
} else if (typeof details.imageData == 'object' &&
Object.getOwnPropertyNames(details.imageData).length !== 0) {
for (var sizeKey in details.imageData) {
verifyImageData(details.imageData[sizeKey]);
}
} else {
verifyImageData(false);
}
callback(SetIconCommon(details));
return;
}
if ('path' in details) {
if (typeof details.path == 'object') {
details.imageData = {__proto__: null};
var detailKeyCount = 0;
for (var iconSize in details.path) {
++detailKeyCount;
loadImagePath(details.path[iconSize], function(size, imageData) {
details.imageData[size] = imageData;
if (--detailKeyCount == 0)
callback(SetIconCommon(details));
}.bind(null, iconSize), failureCallback);
}
if (detailKeyCount == 0)
throw new Error('The path property must not be empty.');
} else if (typeof details.path == 'string') {
details.imageData = {__proto__: null};
loadImagePath(details.path, function(imageData) {
details.imageData[imageData.width.toString()] = imageData;
delete details.path;
callback(SetIconCommon(details));
}, failureCallback);
}
return;
}
throw new Error('Either the path or imageData property must be specified.');
}
exports.$set('setIcon', setIcon);
|