This is the code I am using. (code is at way bottom of this post but here is link to GitHubGist :: Noitidart / _ff-addon-snippet-browseForBadgeThenCreateSaveAnApply.js) It is c
Alright. This is actually quite reproducible, but only when using BMP
icons, but not PNG
icons.
Seems the icon encoder that Firefox ships is pretty bad/buggy indeed (for RGBA stuff). Well, actually it is the BMP encoder that the ICO encoder uses...
So since Belgium/Algeria (the game, football, not American) was mostly boring just now, I wrote my own icon encoder, which isn't too hard actually.
So here is my complete example code incl. icon encoder (just setting the 32x32 icon), but which lacks deposing of icons. But as a bonus, it shows how to set the icon via the WNDCLASS.
Cu.import('resource://gre/modules/ctypes.jsm');
Cu.import('resource://gre/modules/osfile.jsm');
let IMAGE_BITMAP = 0;
let IMAGE_ICON = 1;
let WM_SETICON = 128;
let GCLP_HICON = -14;
let user32 = ctypes.open('user32.dll');
let SendMessage = user32.declare(
'SendMessageW',
ctypes.winapi_abi,
ctypes.intptr_t,
ctypes.voidptr_t, // HWND
ctypes.uint32_t, // MSG
ctypes.uintptr_t, // WPARAM
ctypes.intptr_t // LPARAM
);
let CreateIconFromResourceEx = user32.declare(
'CreateIconFromResourceEx',
ctypes.winapi_abi,
ctypes.voidptr_t,
ctypes.uint8_t.ptr, // icon
ctypes.uint32_t, // size
ctypes.int32_t, // icon
ctypes.uint32_t, // dwVersion
ctypes.int, // dx
ctypes.int, // dy
ctypes.uint32_t // flags
);
let SetClassLongPtr = user32.declare(
ctypes.intptr_t.size == 8 ? 'SetClassLongPtrW' : 'SetClassLongW',
ctypes.winapi_abi,
ctypes.uintptr_t,
ctypes.voidptr_t, // HWND
ctypes.int, // index
ctypes.uintptr_t // value
);
let gdi32 = ctypes.open('gdi32.dll');
let DeleteObject = gdi32.declare(
'DeleteObject',
ctypes.winapi_abi,
ctypes.int,
ctypes.voidptr_t // Object
);
let setPerWindow = false;
let badges = [
'chrome://browser/skin/places/starred48.png',
'chrome://browser/skin/places/downloads.png',
'chrome://browser/skin/places/tag.png',
'chrome://browser/skin/places/livemark-item.png',
'chrome://browser/skin/places/query.png',
'chrome://browser/skin/pluginInstall-64.png',
'chrome://browser/skin/pluginInstall-16.png',
];
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
Task.spawn(function* setIcon() {
"use strict";
try {
let p = Promise.defer();
let img = new Image();
img.onload = () => p.resolve();
img.src = 'chrome://branding/content/icon32.png';
yield p.promise;
p = Promise.defer();
let badge = new Image();
badge.onload = () => p.resolve();
badge.src = badges[getRandomInt(0, badges.length - 1)];
console.log(badge.src);
yield p.promise;
let canvas = document.createElementNS(
'http://www.w3.org/1999/xhtml',
'canvas');
canvas.width = img.naturalWidth;
canvas.height = img.naturalHeight;
let ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
let onethird = canvas.width / 3;
ctx.drawImage(
badge,
onethird,
onethird,
canvas.width - onethird,
canvas.height - onethird);
// Our own little ico encoder
// http://msdn.microsoft.com/en-us/library/ms997538.aspx
// Note: We would have been able to skip ICONDIR/ICONDIRENTRY,
// if we were to use CreateIconFromResourceEx only instead of also
// writing the icon to a file.
let data = ctx.getImageData(0, 0, canvas.width, canvas.height).data;
let XOR = data.length;
let AND = canvas.width * canvas.height / 8;
let size = 22 /* ICONDIR + ICONDIRENTRY */ + 40 /* BITMAPHEADER */ + XOR + AND;
let buffer = new ArrayBuffer(size);
// ICONDIR
let view = new DataView(buffer);
view.setUint16(2, 1, true); // type 1
view.setUint16(4, 1, true); // count;
// ICONDIRENTRY
view = new DataView(buffer, 6);
view.setUint8(0, canvas.width % 256);
view.setUint8(1, canvas.height % 256);
view.setUint16(4, 1, true); // Planes
view.setUint16(6, 32, true); // BPP
view.setUint32(8, 40 + XOR + AND, true); // data size
view.setUint32(12, 22, true); // data start
// BITMAPHEADER
view = new DataView(buffer, 22);
view.setUint32(0, 40, true); // BITMAPHEADER size
view.setInt32(4, canvas.width, true);
view.setInt32(8, canvas.height * 2, true);
view.setUint16(12, 1, true); // Planes
view.setUint16(14, 32, true); // BPP
view.setUint32(20, XOR + AND, true); // size of data
// Reorder RGBA -> BGRA
for (let i = 0; i < XOR; i += 4) {
let temp = data[i];
data[i] = data[i + 2];
data[i + 2] = temp;
}
let ico = new Uint8Array(buffer, 22 + 40);
let stride = canvas.width * 4;
// Write bottom to top
for (let i = 0; i < canvas.height; ++i) {
let su = data.subarray(XOR - i * stride, XOR - i * stride + stride);
ico.set(su, i * stride);
}
// Write the icon to inspect later. (We don't really need to write it at all)
let writePath = OS.Path.join(OS.Constants.Path.desktopDir, 'icon32.ico');
yield OS.File.writeAtomic(writePath, new Uint8Array(buffer), {
tmpPath: writePath + '.tmp'
});
// Cut off ICONDIR/ICONDIRENTRY for CreateIconFromResourceEx
buffer = buffer.slice(22);
let hicon = CreateIconFromResourceEx(
ctypes.uint8_t.ptr(buffer),
buffer.byteLength,
IMAGE_ICON,
0x30000,
0,
0,
0);
if (hicon.isNull()) {
throw new Error("Failed to load icon");
}
if (setPerWindow) {
let DOMWindows = Services.wm.getEnumerator(null);
while (DOMWindows.hasMoreElements()) {
let win = DOMWindows.getNext().QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIWebNavigation).
QueryInterface(Ci.nsIDocShellTreeItem).
treeOwner.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIBaseWindow);
let handle = ctypes.voidptr_t(ctypes.UInt64(win.nativeHandle));
if (handle.isNull()) {
console.error("Failed to get window handle");
continue;
}
var lparam = ctypes.cast(hicon, ctypes.intptr_t);
var oldIcon = SendMessage(handle, WM_SETICON, 1, lparam);
if (ctypes.voidptr_t(oldIcon).isNull()) {
console.log("There was no old icon", oldIcon.toString());
}
else {
console.log("There was an old icon already", oldIcon.toString());
// In a perfect world, we should actually kill our old icons
// using DeleteObject...
}
}
}
else {
let win = Services.wm.getMostRecentWindow(null).
QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIWebNavigation).
QueryInterface(Ci.nsIDocShellTreeItem).
treeOwner.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIBaseWindow);
let handle = ctypes.voidptr_t(ctypes.UInt64(win.nativeHandle));
if (handle.isNull()) {
throw new Error("Failed to get window handle");
}
let oldIcon = SetClassLongPtr(handle, GCLP_HICON, ctypes.cast(hicon, ctypes.uintptr_t));
if (ctypes.voidptr_t(oldIcon).isNull()) {
console.log("There was no old icon", oldIcon.toString());
}
else {
console.log("There was an old icon already", oldIcon.toString());
// In a perfect world, we should actually kill our old icons
// using DeleteObject...
}
}
console.log("done", badge.src);
}
catch (ex) {
console.error(ex);
}
});
PS: Here is a screenshot from the Task Switcher on XP: