// 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);