1
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3
* The contents of this file are subject to the Mozilla Public
4
* License Version 1.1 (the "License"); you may not use this file
5
* except in compliance with the License. You may obtain a copy of
6
* the License at http://www.mozilla.org/MPL/
8
* Software distributed under the License is distributed on an "AS
9
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
10
* implied. See the License for the specific language governing
11
* rights and limitations under the License.
13
* The Original Code is mozilla.org code.
15
* The Initial Developer of the Original Code is Brian Ryner.
16
* Portions created by Brian Ryner are Copyright (C) 2000 Brian Ryner.
17
* All Rights Reserved.
20
* Scott MacGregor <mscott@netscape.com>
21
* Neil Rashbrook <neil@parkwaycc.co.uk>
26
#include "nsIconChannel.h"
27
#include "nsIIconURI.h"
28
#include "nsIServiceManager.h"
29
#include "nsIInterfaceRequestor.h"
30
#include "nsIInterfaceRequestorUtils.h"
31
#include "nsXPIDLString.h"
32
#include "nsReadableUtils.h"
33
#include "nsMimeTypes.h"
35
#include "nsIStringStream.h"
37
#include "nsNetUtil.h"
39
#include "nsIFileURL.h"
40
#include "nsIMIMEService.h"
41
#include "nsCExternalHandlerService.h"
43
#define INCL_WINWORKPLACE
49
#include <stdlib.h> // for getenv
52
#define SHGFI_ICON 0x0001
53
#define SHGFI_USEFILEATTRIBUTES 0x0002
54
#define SHGFI_LARGEICON 0x0004
55
#define SHGFI_SMALLICON 0x0008
57
// Due to byte swap the second is first of the pair
58
#define FIRSTPEL(x) (0xF & (x >> 4))
59
#define SECONDPEL(x) (0xF & x)
61
// forward declarations of a couple of helper methods.
62
// Takes a bitmap from the windows registry and converts it into 4 byte RGB data.
63
void ConvertColorBitMap(PBYTE aBitmapBuffer, PBITMAPINFO2 pBitMapInfo, nsCString& iconBuffer);
64
void ConvertMaskBitMap(PBYTE aBitMaskBuffer, PBITMAPINFO2 pBitMapInfo, nsCString& iconBuffer);
65
PRUint32 CalcWordAlignedRowSpan(PRUint32 aWidth, PRUint32 aBitCount);
67
// nsIconChannel methods
68
nsIconChannel::nsIconChannel()
72
nsIconChannel::~nsIconChannel()
75
NS_IMPL_THREADSAFE_ISUPPORTS4(nsIconChannel,
81
nsresult nsIconChannel::Init(nsIURI* uri)
83
NS_ASSERTION(uri, "no uri");
87
mPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv);
91
////////////////////////////////////////////////////////////////////////////////
92
// nsIRequest methods:
94
NS_IMETHODIMP nsIconChannel::GetName(nsACString &result)
96
return mUrl->GetSpec(result);
99
NS_IMETHODIMP nsIconChannel::IsPending(PRBool *result)
101
return mPump->IsPending(result);
104
NS_IMETHODIMP nsIconChannel::GetStatus(nsresult *status)
106
return mPump->GetStatus(status);
109
NS_IMETHODIMP nsIconChannel::Cancel(nsresult status)
111
return mPump->Cancel(status);
114
NS_IMETHODIMP nsIconChannel::Suspend(void)
116
return mPump->Suspend();
119
NS_IMETHODIMP nsIconChannel::Resume(void)
121
return mPump->Resume();
124
NS_IMETHODIMP nsIconChannel::GetLoadGroup(nsILoadGroup* *aLoadGroup)
126
*aLoadGroup = mLoadGroup;
127
NS_IF_ADDREF(*aLoadGroup);
131
NS_IMETHODIMP nsIconChannel::SetLoadGroup(nsILoadGroup* aLoadGroup)
133
mLoadGroup = aLoadGroup;
137
NS_IMETHODIMP nsIconChannel::GetLoadFlags(PRUint32 *aLoadAttributes)
139
return mPump->GetLoadFlags(aLoadAttributes);
142
NS_IMETHODIMP nsIconChannel::SetLoadFlags(PRUint32 aLoadAttributes)
144
return mPump->SetLoadFlags(aLoadAttributes);
147
////////////////////////////////////////////////////////////////////////////////
148
// nsIChannel methods:
150
NS_IMETHODIMP nsIconChannel::GetOriginalURI(nsIURI* *aURI)
152
*aURI = mOriginalURI ? mOriginalURI : mUrl;
157
NS_IMETHODIMP nsIconChannel::SetOriginalURI(nsIURI* aURI)
163
NS_IMETHODIMP nsIconChannel::GetURI(nsIURI* *aURI)
171
nsIconChannel::Open(nsIInputStream **_retval)
173
return MakeInputStream(_retval, PR_FALSE);
176
void InvertRows(PBYTE aInitialBuffer, PRUint32 sizeOfBuffer, PRUint32 numBytesPerRow)
178
if (!numBytesPerRow) return;
180
PRUint32 numRows = sizeOfBuffer / numBytesPerRow;
181
void * temporaryRowHolder = (void *) nsMemory::Alloc(numBytesPerRow);
183
PRUint32 currentRow = 0;
184
PRUint32 lastRow = (numRows - 1) * numBytesPerRow;
185
while (currentRow < lastRow)
187
// store the current row into a temporary buffer
188
memcpy(temporaryRowHolder, (void *) &aInitialBuffer[currentRow], numBytesPerRow);
189
memcpy((void *) &aInitialBuffer[currentRow], (void *)&aInitialBuffer[lastRow], numBytesPerRow);
190
memcpy((void *) &aInitialBuffer[lastRow], temporaryRowHolder, numBytesPerRow);
191
lastRow -= numBytesPerRow;
192
currentRow += numBytesPerRow;
195
nsMemory::Free(temporaryRowHolder);
198
nsresult nsIconChannel::ExtractIconInfoFromUrl(nsIFile ** aLocalFile, PRUint32 * aDesiredImageSize, nsACString &aContentType, nsACString &aFileExtension)
201
nsCOMPtr<nsIMozIconURI> iconURI (do_QueryInterface(mUrl, &rv));
202
NS_ENSURE_SUCCESS(rv, rv);
204
iconURI->GetImageSize(aDesiredImageSize);
205
iconURI->GetContentType(aContentType);
206
iconURI->GetFileExtension(aFileExtension);
208
nsCOMPtr<nsIURI> fileURI;
209
rv = iconURI->GetIconFile(getter_AddRefs(fileURI));
210
if (NS_FAILED(rv) || !fileURI) return NS_OK;
212
nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI, &rv);
213
if (NS_FAILED(rv) || !fileURL) return NS_OK;
215
nsCOMPtr<nsIFile> file;
216
rv = fileURL->GetFile(getter_AddRefs(file));
217
if (NS_FAILED(rv) || !file) return NS_OK;
220
NS_IF_ADDREF(*aLocalFile);
224
INT AddBGR(PRGB2 pColorTableEntry, nsCString& iconBuffer)
226
iconBuffer.Append((char) pColorTableEntry->bBlue);
227
iconBuffer.Append((char) pColorTableEntry->bGreen);
228
iconBuffer.Append((char) pColorTableEntry->bRed);
232
void ConvertColorBitMap(PBYTE buffer, PBITMAPINFO2 pBitMapInfo, nsCString& iconBuffer)
234
// windows inverts the row order in their bitmaps. So we need to invert the rows back into a top-down order.
236
PRUint32 bytesPerPixel = pBitMapInfo->cBitCount / 8;
237
PRUint32 unalignedBytesPerRowRGB = pBitMapInfo->cx * 3;
238
PRInt32 iScanLineSize = pBitMapInfo->cSize1; // Stored early
241
InvertRows(buffer, pBitMapInfo->cbImage, iScanLineSize);
243
PRUint32 alignedBytesPerRowRGB = CalcWordAlignedRowSpan(pBitMapInfo->cx, 24);
244
PRUint32 numBytesPaddingPerRowRGB = alignedBytesPerRowRGB - unalignedBytesPerRowRGB;
246
if (numBytesPaddingPerRowRGB < 0) // this should never happen.....
247
numBytesPaddingPerRowRGB = 0;
249
PRGB2 pColorTable = &pBitMapInfo->argbColor[0]; // Note Color tables are only valid for 1, 4, 8 BPP maps
253
// Many OS2 Icons are 16 colors or 4 BPP so we need to map the colors to RGB
254
if (pBitMapInfo->cBitCount == 4 ||
255
pBitMapInfo->cBitCount == 8 )
259
if (pBitMapInfo->cBitCount == 4)
261
iIter = (pBitMapInfo->cx + 1) / 2; // Have 1/2 bytes as pels per row
265
iIter = pBitMapInfo->cx; // Bytes = Pels
267
for (ULONG j = 0; j < pBitMapInfo->cy; j++) //Number of rows
270
pPel = (PBYTE)buffer;
271
for(INT i = 0; i < iIter; i++)
274
if (pBitMapInfo->cBitCount == 4)
276
AddBGR(&pColorTable[FIRSTPEL(*pPelPair)], iconBuffer);
277
AddBGR(&pColorTable[SECONDPEL(*pPelPair)], iconBuffer);
282
AddBGR(&pColorTable[*pPel], iconBuffer);
286
if (numBytesPaddingPerRowRGB)
288
for (PRUint32 k = 0; k < numBytesPaddingPerRowRGB; k++)
290
iconBuffer.Append((char) 0);
293
buffer += iScanLineSize;
297
//// if each pixel uses 16 bits to describe the color then each R, G, and B value uses 5 bites. Use some fancy
298
//// bit operations to blow up the 16 bit case into 1 byte per component color. Actually windows
299
//// is using a 5:6:5 scheme. so the green component gets 6 bits....
300
//if (pBitMapInfo->cBitCount == 16)
302
// PRUint8 redValue, greenValue, blueValue;
303
// while (index < pBitMapInfo->cbImage)
306
// num = (PRUint8) buffer[index+1];
308
// num |= (PRUint8) buffer[index];
309
// // be sure to ignore the top bit
310
// //num &= 0x7FFF; // only want the low 15 bits.....not the 16th...
312
// // use num as an offset into the color table....get the RGBQuad entry and read out
313
// // the RGB values.
314
// //RGBQUAD rgbValues = pBitMapInfo->bmiColors[num];
316
// //redValue = ( (num & 0xf800) >> 11);
317
// //greenValue = ( (num & 0x07E0) >> 5);
318
// //blueValue = ( num & 0x001F);
320
// redValue = ((PRUint32) (((float)(num & 0xf800) / 0xf800) * 0xFF0000) & 0xFF0000)>> 16;
321
// greenValue = ((PRUint32)(((float)(num & 0x07E0) / 0x07E0) * 0x00FF00) & 0x00FF00)>> 8;
322
// blueValue = ((PRUint32)(((float)(num & 0x001F) / 0x001F) * 0x0000FF) & 0x0000FF);
324
// // now we have the right RGB values...
325
// iconBuffer.Append((char) blueValue);
326
// iconBuffer.Append((char) greenValue);
327
// iconBuffer.Append((char) redValue);
329
// if (pos == unalignedBytesPerRow && numBytesPaddingPerRow) // if we have reached the end of a current row, add padding to force dword alignment
332
// for (PRUint32 i = 0; i < numBytesPaddingPerRow; i++)
334
// iconBuffer.Append((char) 0);
337
// index += bytesPerPixel;
340
else // otherwise we must be using 32 bits per pixel so each component value is getting one byte...
342
while (index < pBitMapInfo->cbImage)
344
iconBuffer.Append((char) buffer[index]);
345
iconBuffer.Append((char) buffer[index+1]);
346
iconBuffer.Append((char) buffer[index+2]);
348
if (pos == unalignedBytesPerRowRGB && numBytesPaddingPerRowRGB) // if we have reached the end of a current row, add padding to force dword alignment
351
for (PRUint32 i = 0; i < numBytesPaddingPerRowRGB; i++)
353
iconBuffer.Append((char) 0);
356
index += bytesPerPixel;
361
PRUint32 CalcWordAlignedRowSpan(PRUint32 aWidth, PRUint32 aBitCount)
365
spanBytes = (aWidth * aBitCount) >> 5;
367
if (((PRUint32) aWidth * aBitCount) & 0x1F)
375
void ConvertMaskBitMap(PBYTE aBitMaskBuffer, PBITMAPINFO2 pBitMapInfo, nsCString& iconBuffer)
377
PRInt32 iScanLineSize = pBitMapInfo->cSize1; // Stored early
379
InvertRows(aBitMaskBuffer, pBitMapInfo->cbImage, iScanLineSize);
381
// for some reason the bit mask on windows are flipped from the values we really want for transparency.
382
// So complement each byte in the bit mask.
383
while (index < pBitMapInfo->cbImage)
385
aBitMaskBuffer[index]^=255;
388
iconBuffer.Append((char *) aBitMaskBuffer, pBitMapInfo->cbImage);
391
NS_IMETHODIMP nsIconChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *ctxt)
393
nsCOMPtr<nsIInputStream> inStream;
394
nsresult rv = MakeInputStream(getter_AddRefs(inStream), PR_TRUE);
398
// Init our streampump
399
rv = mPump->Init(inStream, -1, -1, 0, 0, PR_FALSE);
403
rv = mPump->AsyncRead(this, ctxt);
404
if (NS_SUCCEEDED(rv)) {
405
// Store our real listener
406
mListener = aListener;
407
// Add ourself to the load group, if available
409
mLoadGroup->AddRequest(this, nsnull);
414
nsresult nsIconChannel::MakeInputStream(nsIInputStream** _retval, PRBool nonBlocking)
416
nsXPIDLCString contentType;
417
nsCAutoString filePath;
418
nsCOMPtr<nsIFile> localFile; // file we want an icon for
419
PRUint32 desiredImageSize;
420
nsresult rv = ExtractIconInfoFromUrl(getter_AddRefs(localFile), &desiredImageSize, contentType, filePath);
421
NS_ENSURE_SUCCESS(rv, rv);
423
// if the file exists, we are going to use it's real attributes...otherwise we only want to use it for it's extension...
424
UINT infoFlags = SHGFI_ICON;
426
PRBool fileExists = PR_FALSE;
430
localFile->GetNativePath(filePath);
431
localFile->Exists(&fileExists);
435
infoFlags |= SHGFI_USEFILEATTRIBUTES;
437
if (desiredImageSize > 16)
438
infoFlags |= SHGFI_LARGEICON;
440
infoFlags |= SHGFI_SMALLICON;
442
// if we have a content type... then use it! but for existing files, we want
443
// to show their real icon.
444
if (!fileExists && !contentType.IsEmpty())
446
nsCOMPtr<nsIMIMEService> mimeService (do_GetService(NS_MIMESERVICE_CONTRACTID, &rv));
447
NS_ENSURE_SUCCESS(rv, rv);
449
nsXPIDLCString fileExt;
450
mimeService->GetPrimaryExtension(contentType.get(), nsnull, getter_Copies(fileExt));
451
// If the mime service does not know about this mime type, we show
453
// In any case, we need to insert a '.' before the extension.
454
filePath = NS_LITERAL_CSTRING(".") + fileExt;
457
// (1) get an hIcon for the file
458
PSZ pszFileName = (PSZ)filePath.get();
459
HPOINTER hIcon = WinLoadFileIcon(pszFileName, FALSE);
460
if ((hIcon == NULLHANDLE) && (pszFileName[0] == '.')) {
461
/* Just trying to get an icon for an extension */
462
/* Create a temporary file to try to get an icon */
463
char* tmpdir = getenv("TMP");
465
char tmpfile[CCHMAXPATH];
466
strcpy(tmpfile, tmpdir);
467
strcat(tmpfile, pszFileName);
468
FILE* fp = fopen(tmpfile, "wb+");
471
hIcon = WinLoadFileIcon(tmpfile, FALSE);
476
if (hIcon == NULLHANDLE)
477
return NS_ERROR_FAILURE;
478
// we got a handle to an icon. Now we want to get a bitmap for the icon using GetIconInfo....
479
POINTERINFO IconInfo;
480
BOOL fRC = WinQueryPointerInfo(hIcon, &IconInfo);
482
WinFreeFileIcon(hIcon);
483
return NS_ERROR_FAILURE;
485
nsCString iconBuffer;
486
BITMAPINFOHEADER2 BMHeader;
490
// Decide which icon to use
491
if ( infoFlags & SHGFI_LARGEICON )
493
hBitmap = IconInfo.hbmColor;
494
hBitmapMask = IconInfo.hbmPointer;
498
hBitmap = IconInfo.hbmMiniColor;
499
hBitmapMask = IconInfo.hbmMiniPointer;
502
// Get the basic info
503
BMHeader.cbFix = sizeof(BMHeader);
504
fRC = GpiQueryBitmapInfoHeader(hBitmap, &BMHeader);
506
WinFreeFileIcon(hIcon);
507
return NS_ERROR_FAILURE;
510
///// // Calulate size of color table
511
///// LONG cbColorTable;
512
///// if ( BMHeader.cBitCount > 8 )
514
///// cbColorTable = 0;
518
///// cbColorTable = 1 << BMHeader.cBitCount;
520
LONG cbBitMapInfo = sizeof(BITMAPINFO2) + (sizeof(RGB2) * 255); // Max possible
521
LONG iScanLineSize = ((BMHeader.cBitCount * BMHeader.cx + 31) / 32) * 4;
522
LONG cbBuffer = iScanLineSize * BMHeader.cy;
523
// Allocate buffers, fill w/ 0
524
PBITMAPINFO2 pBitMapInfo = (PBITMAPINFO2)nsMemory::Alloc(cbBitMapInfo);
525
memset(pBitMapInfo, 0, cbBitMapInfo);
526
PBYTE buffer = (PBYTE)nsMemory::Alloc(cbBuffer);
527
memset(buffer, 0, cbBuffer);
528
// Copy over the header info
529
*((PBITMAPINFOHEADER2)pBitMapInfo ) = BMHeader;
532
DEVOPENSTRUC dop = {NULL, "DISPLAY", NULL, NULL, NULL, NULL, NULL, NULL, NULL};
533
HDC hdc = DevOpenDC( (HAB)0, OD_MEMORY, "*", 5L, (PDEVOPENDATA)&dop, NULLHANDLE);
535
HPS hps = GpiCreatePS((HAB)0, hdc, &sizel, GPIA_ASSOC | PU_PELS | GPIT_MICRO);
537
// Not sure if you need this but it is good form
538
HBITMAP hOldBM = GpiSetBitmap(hps, hBitmap);
541
LONG lScanLines = GpiQueryBitmapBits(hps, 0L, (LONG)BMHeader.cy, buffer, pBitMapInfo);
544
// Set this since it is used all over
545
pBitMapInfo->cbImage = cbBuffer;
546
pBitMapInfo->cSize1 = iScanLineSize;
548
// temporary hack alert...currently moz-icon urls only support 16, 24 and 32 bit color. we don't support
549
// 8, 4 or 1 bit color yet. So convert OS/2 4 BPP to RGB
551
// The first 2 bytes into our output buffer needs to be the width and the height (in pixels) of the icon
552
// as specified by our data format.
553
iconBuffer.Assign((char) pBitMapInfo->cx);
554
iconBuffer.Append((char) pBitMapInfo->cy);
556
ConvertColorBitMap(buffer, pBitMapInfo, iconBuffer);
558
// now we need to tack on the alpha data...which is hbmMask
560
memset(pBitMapInfo, 0, cbBitMapInfo);
561
BMHeader.cbFix = sizeof(BMHeader);
562
fRC = GpiQueryBitmapInfoHeader(hBitmapMask, &BMHeader);
563
iScanLineSize = ((BMHeader.cBitCount * BMHeader.cx + 31) / 32) * 4;
564
LONG cbBufferMask = iScanLineSize * BMHeader.cy;
565
if (cbBufferMask > cbBuffer) // Need more for mask
567
nsMemory::Free(buffer);
568
buffer = (PBYTE)nsMemory::Alloc(cbBufferMask);
569
memset(buffer, 0, cbBufferMask);
572
*((PBITMAPINFOHEADER2)pBitMapInfo ) = BMHeader;
573
hOldBM = GpiSetBitmap(hps, hBitmapMask);
575
lScanLines = GpiQueryBitmapBits(hps, 0L, (LONG)BMHeader.cy, buffer, pBitMapInfo);
578
pBitMapInfo->cbImage = cbBufferMask;
579
pBitMapInfo->cSize1 = iScanLineSize;
580
ConvertMaskBitMap(buffer, pBitMapInfo, iconBuffer);
582
// Now, create a pipe and stuff our data into it
583
nsCOMPtr<nsIInputStream> inStream;
584
nsCOMPtr<nsIOutputStream> outStream;
585
rv = NS_NewPipe(getter_AddRefs(inStream), getter_AddRefs(outStream),
586
iconBuffer.Length(), iconBuffer.Length(), nonBlocking);
587
if (NS_SUCCEEDED(rv)) {
589
rv = outStream->Write(iconBuffer.get(), iconBuffer.Length(), &written);
590
if (NS_SUCCEEDED(rv)) {
591
NS_ADDREF(*_retval = inStream);
594
} // if we have a mask buffer to apply
596
} // if we got color info
597
nsMemory::Free(buffer);
598
nsMemory::Free(pBitMapInfo);
601
GpiAssociate(hps, NULLHANDLE);
609
WinFreeFileIcon(hIcon);
614
NS_IMETHODIMP nsIconChannel::GetContentType(nsACString &aContentType)
616
aContentType = NS_LITERAL_CSTRING("image/icon");
621
nsIconChannel::SetContentType(const nsACString &aContentType)
623
// It doesn't make sense to set the content-type on this type
625
return NS_ERROR_FAILURE;
628
NS_IMETHODIMP nsIconChannel::GetContentCharset(nsACString &aContentCharset)
630
aContentCharset.Truncate();
635
nsIconChannel::SetContentCharset(const nsACString &aContentCharset)
637
// It doesn't make sense to set the content-charset on this type
639
return NS_ERROR_FAILURE;
642
NS_IMETHODIMP nsIconChannel::GetContentLength(PRInt32 *aContentLength)
644
*aContentLength = mContentLength;
648
NS_IMETHODIMP nsIconChannel::SetContentLength(PRInt32 aContentLength)
650
NS_NOTREACHED("nsIconChannel::SetContentLength");
651
return NS_ERROR_NOT_IMPLEMENTED;
654
NS_IMETHODIMP nsIconChannel::GetOwner(nsISupports* *aOwner)
656
*aOwner = mOwner.get();
657
NS_IF_ADDREF(*aOwner);
661
NS_IMETHODIMP nsIconChannel::SetOwner(nsISupports* aOwner)
667
NS_IMETHODIMP nsIconChannel::GetNotificationCallbacks(nsIInterfaceRequestor* *aNotificationCallbacks)
669
*aNotificationCallbacks = mCallbacks.get();
670
NS_IF_ADDREF(*aNotificationCallbacks);
674
NS_IMETHODIMP nsIconChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks)
676
mCallbacks = aNotificationCallbacks;
680
NS_IMETHODIMP nsIconChannel::GetSecurityInfo(nsISupports * *aSecurityInfo)
682
*aSecurityInfo = nsnull;
686
// nsIRequestObserver methods
687
NS_IMETHODIMP nsIconChannel::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext)
690
return mListener->OnStartRequest(this, aContext);
694
NS_IMETHODIMP nsIconChannel::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatus)
697
mListener->OnStopRequest(this, aContext, aStatus);
701
// Remove from load group
703
mLoadGroup->RemoveRequest(this, nsnull, aStatus);
708
// nsIStreamListener methods
709
NS_IMETHODIMP nsIconChannel::OnDataAvailable(nsIRequest* aRequest,
710
nsISupports* aContext,
711
nsIInputStream* aStream,
716
return mListener->OnDataAvailable(this, aContext, aStream, aOffset, aCount);