|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
|
2 * |
|
3 * This Source Code Form is subject to the terms of the Mozilla Public |
|
4 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
6 |
|
7 #include "nsIconChannel.h" |
|
8 #include "mozilla/Endian.h" |
|
9 #include "nsIIconURI.h" |
|
10 #include "nsIServiceManager.h" |
|
11 #include "nsIInterfaceRequestor.h" |
|
12 #include "nsIInterfaceRequestorUtils.h" |
|
13 #include "nsXPIDLString.h" |
|
14 #include "nsMimeTypes.h" |
|
15 #include "nsMemory.h" |
|
16 #include "nsIStringStream.h" |
|
17 #include "nsIURL.h" |
|
18 #include "nsNetUtil.h" |
|
19 #include "nsIMIMEService.h" |
|
20 #include "nsCExternalHandlerService.h" |
|
21 #include "nsILocalFileMac.h" |
|
22 #include "nsIFileURL.h" |
|
23 #include "nsTArray.h" |
|
24 #include "nsObjCExceptions.h" |
|
25 |
|
26 #include <Cocoa/Cocoa.h> |
|
27 |
|
28 // nsIconChannel methods |
|
29 nsIconChannel::nsIconChannel() |
|
30 { |
|
31 } |
|
32 |
|
33 nsIconChannel::~nsIconChannel() |
|
34 {} |
|
35 |
|
36 NS_IMPL_ISUPPORTS(nsIconChannel, |
|
37 nsIChannel, |
|
38 nsIRequest, |
|
39 nsIRequestObserver, |
|
40 nsIStreamListener) |
|
41 |
|
42 nsresult nsIconChannel::Init(nsIURI* uri) |
|
43 { |
|
44 NS_ASSERTION(uri, "no uri"); |
|
45 mUrl = uri; |
|
46 mOriginalURI = uri; |
|
47 nsresult rv; |
|
48 mPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv); |
|
49 return rv; |
|
50 } |
|
51 |
|
52 //////////////////////////////////////////////////////////////////////////////// |
|
53 // nsIRequest methods: |
|
54 |
|
55 NS_IMETHODIMP nsIconChannel::GetName(nsACString &result) |
|
56 { |
|
57 return mUrl->GetSpec(result); |
|
58 } |
|
59 |
|
60 NS_IMETHODIMP nsIconChannel::IsPending(bool *result) |
|
61 { |
|
62 return mPump->IsPending(result); |
|
63 } |
|
64 |
|
65 NS_IMETHODIMP nsIconChannel::GetStatus(nsresult *status) |
|
66 { |
|
67 return mPump->GetStatus(status); |
|
68 } |
|
69 |
|
70 NS_IMETHODIMP nsIconChannel::Cancel(nsresult status) |
|
71 { |
|
72 return mPump->Cancel(status); |
|
73 } |
|
74 |
|
75 NS_IMETHODIMP nsIconChannel::Suspend(void) |
|
76 { |
|
77 return mPump->Suspend(); |
|
78 } |
|
79 |
|
80 NS_IMETHODIMP nsIconChannel::Resume(void) |
|
81 { |
|
82 return mPump->Resume(); |
|
83 } |
|
84 |
|
85 // nsIRequestObserver methods |
|
86 NS_IMETHODIMP nsIconChannel::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) |
|
87 { |
|
88 if (mListener) |
|
89 return mListener->OnStartRequest(this, aContext); |
|
90 return NS_OK; |
|
91 } |
|
92 |
|
93 NS_IMETHODIMP nsIconChannel::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatus) |
|
94 { |
|
95 if (mListener) { |
|
96 mListener->OnStopRequest(this, aContext, aStatus); |
|
97 mListener = nullptr; |
|
98 } |
|
99 |
|
100 // Remove from load group |
|
101 if (mLoadGroup) |
|
102 mLoadGroup->RemoveRequest(this, nullptr, aStatus); |
|
103 |
|
104 return NS_OK; |
|
105 } |
|
106 |
|
107 // nsIStreamListener methods |
|
108 NS_IMETHODIMP nsIconChannel::OnDataAvailable(nsIRequest* aRequest, |
|
109 nsISupports* aContext, |
|
110 nsIInputStream* aStream, |
|
111 uint64_t aOffset, |
|
112 uint32_t aCount) |
|
113 { |
|
114 if (mListener) |
|
115 return mListener->OnDataAvailable(this, aContext, aStream, aOffset, aCount); |
|
116 return NS_OK; |
|
117 } |
|
118 |
|
119 //////////////////////////////////////////////////////////////////////////////// |
|
120 // nsIChannel methods: |
|
121 |
|
122 NS_IMETHODIMP nsIconChannel::GetOriginalURI(nsIURI* *aURI) |
|
123 { |
|
124 *aURI = mOriginalURI; |
|
125 NS_ADDREF(*aURI); |
|
126 return NS_OK; |
|
127 } |
|
128 |
|
129 NS_IMETHODIMP nsIconChannel::SetOriginalURI(nsIURI* aURI) |
|
130 { |
|
131 NS_ENSURE_ARG_POINTER(aURI); |
|
132 mOriginalURI = aURI; |
|
133 return NS_OK; |
|
134 } |
|
135 |
|
136 NS_IMETHODIMP nsIconChannel::GetURI(nsIURI* *aURI) |
|
137 { |
|
138 *aURI = mUrl; |
|
139 NS_IF_ADDREF(*aURI); |
|
140 return NS_OK; |
|
141 } |
|
142 |
|
143 NS_IMETHODIMP |
|
144 nsIconChannel::Open(nsIInputStream **_retval) |
|
145 { |
|
146 return MakeInputStream(_retval, false); |
|
147 } |
|
148 |
|
149 nsresult nsIconChannel::ExtractIconInfoFromUrl(nsIFile ** aLocalFile, uint32_t * aDesiredImageSize, nsACString &aContentType, nsACString &aFileExtension) |
|
150 { |
|
151 nsresult rv = NS_OK; |
|
152 nsCOMPtr<nsIMozIconURI> iconURI (do_QueryInterface(mUrl, &rv)); |
|
153 NS_ENSURE_SUCCESS(rv, rv); |
|
154 |
|
155 iconURI->GetImageSize(aDesiredImageSize); |
|
156 iconURI->GetContentType(aContentType); |
|
157 iconURI->GetFileExtension(aFileExtension); |
|
158 |
|
159 nsCOMPtr<nsIURL> url; |
|
160 rv = iconURI->GetIconURL(getter_AddRefs(url)); |
|
161 if (NS_FAILED(rv) || !url) return NS_OK; |
|
162 |
|
163 nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(url, &rv); |
|
164 if (NS_FAILED(rv) || !fileURL) return NS_OK; |
|
165 |
|
166 nsCOMPtr<nsIFile> file; |
|
167 rv = fileURL->GetFile(getter_AddRefs(file)); |
|
168 if (NS_FAILED(rv) || !file) return NS_OK; |
|
169 |
|
170 nsCOMPtr<nsILocalFileMac> localFileMac (do_QueryInterface(file, &rv)); |
|
171 if (NS_FAILED(rv) || !localFileMac) return NS_OK; |
|
172 |
|
173 *aLocalFile = file; |
|
174 NS_IF_ADDREF(*aLocalFile); |
|
175 |
|
176 return NS_OK; |
|
177 } |
|
178 |
|
179 NS_IMETHODIMP nsIconChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *ctxt) |
|
180 { |
|
181 nsCOMPtr<nsIInputStream> inStream; |
|
182 nsresult rv = MakeInputStream(getter_AddRefs(inStream), true); |
|
183 NS_ENSURE_SUCCESS(rv, rv); |
|
184 |
|
185 // Init our stream pump |
|
186 rv = mPump->Init(inStream, int64_t(-1), int64_t(-1), 0, 0, false); |
|
187 NS_ENSURE_SUCCESS(rv, rv); |
|
188 |
|
189 rv = mPump->AsyncRead(this, ctxt); |
|
190 if (NS_SUCCEEDED(rv)) { |
|
191 // Store our real listener |
|
192 mListener = aListener; |
|
193 // Add ourself to the load group, if available |
|
194 if (mLoadGroup) |
|
195 mLoadGroup->AddRequest(this, nullptr); |
|
196 } |
|
197 |
|
198 return rv; |
|
199 } |
|
200 |
|
201 nsresult nsIconChannel::MakeInputStream(nsIInputStream** _retval, bool nonBlocking) |
|
202 { |
|
203 NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; |
|
204 |
|
205 nsXPIDLCString contentType; |
|
206 nsAutoCString fileExt; |
|
207 nsCOMPtr<nsIFile> fileloc; // file we want an icon for |
|
208 uint32_t desiredImageSize; |
|
209 nsresult rv = ExtractIconInfoFromUrl(getter_AddRefs(fileloc), &desiredImageSize, contentType, fileExt); |
|
210 NS_ENSURE_SUCCESS(rv, rv); |
|
211 |
|
212 bool fileExists = false; |
|
213 if (fileloc) { |
|
214 // ensure that we DO NOT resolve aliases, very important for file views |
|
215 fileloc->SetFollowLinks(false); |
|
216 fileloc->Exists(&fileExists); |
|
217 } |
|
218 |
|
219 NSImage* iconImage = nil; |
|
220 |
|
221 // first try to get the icon from the file if it exists |
|
222 if (fileExists) { |
|
223 nsCOMPtr<nsILocalFileMac> localFileMac(do_QueryInterface(fileloc, &rv)); |
|
224 NS_ENSURE_SUCCESS(rv, rv); |
|
225 |
|
226 CFURLRef macURL; |
|
227 if (NS_SUCCEEDED(localFileMac->GetCFURL(&macURL))) { |
|
228 iconImage = [[NSWorkspace sharedWorkspace] iconForFile:[(NSURL*)macURL path]]; |
|
229 ::CFRelease(macURL); |
|
230 } |
|
231 } |
|
232 |
|
233 // if we don't have an icon yet try to get one by extension |
|
234 if (!iconImage && !fileExt.IsEmpty()) { |
|
235 NSString* fileExtension = [NSString stringWithUTF8String:fileExt.get()]; |
|
236 iconImage = [[NSWorkspace sharedWorkspace] iconForFileType:fileExtension]; |
|
237 } |
|
238 |
|
239 // If we still don't have an icon, get the generic document icon. |
|
240 if (!iconImage) |
|
241 iconImage = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeUnknown]; |
|
242 |
|
243 if (!iconImage) |
|
244 return NS_ERROR_FAILURE; |
|
245 |
|
246 // we have an icon now, size it |
|
247 NSRect desiredSizeRect = NSMakeRect(0, 0, desiredImageSize, desiredImageSize); |
|
248 [iconImage setSize:desiredSizeRect.size]; |
|
249 |
|
250 [iconImage lockFocus]; |
|
251 NSBitmapImageRep* bitmapRep = [[[NSBitmapImageRep alloc] initWithFocusedViewRect:desiredSizeRect] autorelease]; |
|
252 [iconImage unlockFocus]; |
|
253 |
|
254 // we expect the following things to be true about our bitmapRep |
|
255 NS_ENSURE_TRUE(![bitmapRep isPlanar] && |
|
256 // Not necessarily: on a HiDPI-capable system, we'll get a 2x bitmap |
|
257 // (unsigned int)[bitmapRep bytesPerPlane] == desiredImageSize * desiredImageSize * 4 && |
|
258 [bitmapRep bitsPerPixel] == 32 && |
|
259 [bitmapRep samplesPerPixel] == 4 && |
|
260 [bitmapRep hasAlpha] == YES, |
|
261 NS_ERROR_UNEXPECTED); |
|
262 |
|
263 // check what size we actually got, and ensure it isn't too big to return |
|
264 uint32_t actualImageSize = [bitmapRep bytesPerRow] / 4; |
|
265 NS_ENSURE_TRUE(actualImageSize < 256, NS_ERROR_UNEXPECTED); |
|
266 |
|
267 // now we can validate the amount of data |
|
268 NS_ENSURE_TRUE((unsigned int)[bitmapRep bytesPerPlane] == actualImageSize * actualImageSize * 4, |
|
269 NS_ERROR_UNEXPECTED); |
|
270 |
|
271 // rgba, pre-multiplied data |
|
272 uint8_t* bitmapRepData = (uint8_t*)[bitmapRep bitmapData]; |
|
273 |
|
274 // create our buffer |
|
275 int32_t bufferCapacity = 2 + [bitmapRep bytesPerPlane]; |
|
276 nsAutoTArray<uint8_t, 3 + 16 * 16 * 5> iconBuffer; // initial size is for 16x16 |
|
277 iconBuffer.SetLength(bufferCapacity); |
|
278 |
|
279 uint8_t* iconBufferPtr = iconBuffer.Elements(); |
|
280 |
|
281 // write header data into buffer |
|
282 *iconBufferPtr++ = actualImageSize; |
|
283 *iconBufferPtr++ = actualImageSize; |
|
284 |
|
285 uint32_t dataCount = [bitmapRep bytesPerPlane]; |
|
286 uint32_t index = 0; |
|
287 while (index < dataCount) { |
|
288 // get data from the bitmap |
|
289 uint8_t r = bitmapRepData[index++]; |
|
290 uint8_t g = bitmapRepData[index++]; |
|
291 uint8_t b = bitmapRepData[index++]; |
|
292 uint8_t a = bitmapRepData[index++]; |
|
293 |
|
294 // write data out to our buffer |
|
295 // non-cairo uses native image format, but the A channel is ignored. |
|
296 // cairo uses ARGB (highest to lowest bits) |
|
297 #if MOZ_LITTLE_ENDIAN |
|
298 *iconBufferPtr++ = b; |
|
299 *iconBufferPtr++ = g; |
|
300 *iconBufferPtr++ = r; |
|
301 *iconBufferPtr++ = a; |
|
302 #else |
|
303 *iconBufferPtr++ = a; |
|
304 *iconBufferPtr++ = r; |
|
305 *iconBufferPtr++ = g; |
|
306 *iconBufferPtr++ = b; |
|
307 #endif |
|
308 } |
|
309 |
|
310 NS_ASSERTION(iconBufferPtr == iconBuffer.Elements() + bufferCapacity, |
|
311 "buffer size miscalculation"); |
|
312 |
|
313 // Now, create a pipe and stuff our data into it |
|
314 nsCOMPtr<nsIInputStream> inStream; |
|
315 nsCOMPtr<nsIOutputStream> outStream; |
|
316 rv = NS_NewPipe(getter_AddRefs(inStream), getter_AddRefs(outStream), |
|
317 bufferCapacity, bufferCapacity, nonBlocking); |
|
318 |
|
319 if (NS_SUCCEEDED(rv)) { |
|
320 uint32_t written; |
|
321 rv = outStream->Write((char*)iconBuffer.Elements(), bufferCapacity, &written); |
|
322 if (NS_SUCCEEDED(rv)) |
|
323 NS_IF_ADDREF(*_retval = inStream); |
|
324 } |
|
325 |
|
326 // Drop notification callbacks to prevent cycles. |
|
327 mCallbacks = nullptr; |
|
328 |
|
329 return NS_OK; |
|
330 |
|
331 NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; |
|
332 } |
|
333 |
|
334 NS_IMETHODIMP nsIconChannel::GetLoadFlags(uint32_t *aLoadAttributes) |
|
335 { |
|
336 return mPump->GetLoadFlags(aLoadAttributes); |
|
337 } |
|
338 |
|
339 NS_IMETHODIMP nsIconChannel::SetLoadFlags(uint32_t aLoadAttributes) |
|
340 { |
|
341 return mPump->SetLoadFlags(aLoadAttributes); |
|
342 } |
|
343 |
|
344 NS_IMETHODIMP nsIconChannel::GetContentType(nsACString &aContentType) |
|
345 { |
|
346 aContentType.AssignLiteral(IMAGE_ICON_MS); |
|
347 return NS_OK; |
|
348 } |
|
349 |
|
350 NS_IMETHODIMP |
|
351 nsIconChannel::SetContentType(const nsACString &aContentType) |
|
352 { |
|
353 //It doesn't make sense to set the content-type on this type |
|
354 // of channel... |
|
355 return NS_ERROR_FAILURE; |
|
356 } |
|
357 |
|
358 NS_IMETHODIMP nsIconChannel::GetContentCharset(nsACString &aContentCharset) |
|
359 { |
|
360 aContentCharset.AssignLiteral(IMAGE_ICON_MS); |
|
361 return NS_OK; |
|
362 } |
|
363 |
|
364 NS_IMETHODIMP |
|
365 nsIconChannel::SetContentCharset(const nsACString &aContentCharset) |
|
366 { |
|
367 //It doesn't make sense to set the content-type on this type |
|
368 // of channel... |
|
369 return NS_ERROR_FAILURE; |
|
370 } |
|
371 |
|
372 NS_IMETHODIMP |
|
373 nsIconChannel::GetContentDisposition(uint32_t *aContentDisposition) |
|
374 { |
|
375 return NS_ERROR_NOT_AVAILABLE; |
|
376 } |
|
377 |
|
378 NS_IMETHODIMP |
|
379 nsIconChannel::SetContentDisposition(uint32_t aContentDisposition) |
|
380 { |
|
381 return NS_ERROR_NOT_AVAILABLE; |
|
382 } |
|
383 |
|
384 NS_IMETHODIMP |
|
385 nsIconChannel::GetContentDispositionFilename(nsAString &aContentDispositionFilename) |
|
386 { |
|
387 return NS_ERROR_NOT_AVAILABLE; |
|
388 } |
|
389 |
|
390 NS_IMETHODIMP |
|
391 nsIconChannel::SetContentDispositionFilename(const nsAString &aContentDispositionFilename) |
|
392 { |
|
393 return NS_ERROR_NOT_AVAILABLE; |
|
394 } |
|
395 |
|
396 NS_IMETHODIMP |
|
397 nsIconChannel::GetContentDispositionHeader(nsACString &aContentDispositionHeader) |
|
398 { |
|
399 return NS_ERROR_NOT_AVAILABLE; |
|
400 } |
|
401 |
|
402 NS_IMETHODIMP nsIconChannel::GetContentLength(int64_t *aContentLength) |
|
403 { |
|
404 *aContentLength = mContentLength; |
|
405 return NS_OK; |
|
406 } |
|
407 |
|
408 NS_IMETHODIMP nsIconChannel::SetContentLength(int64_t aContentLength) |
|
409 { |
|
410 NS_NOTREACHED("nsIconChannel::SetContentLength"); |
|
411 return NS_ERROR_NOT_IMPLEMENTED; |
|
412 } |
|
413 |
|
414 NS_IMETHODIMP nsIconChannel::GetLoadGroup(nsILoadGroup* *aLoadGroup) |
|
415 { |
|
416 *aLoadGroup = mLoadGroup; |
|
417 NS_IF_ADDREF(*aLoadGroup); |
|
418 return NS_OK; |
|
419 } |
|
420 |
|
421 NS_IMETHODIMP nsIconChannel::SetLoadGroup(nsILoadGroup* aLoadGroup) |
|
422 { |
|
423 mLoadGroup = aLoadGroup; |
|
424 return NS_OK; |
|
425 } |
|
426 |
|
427 NS_IMETHODIMP nsIconChannel::GetOwner(nsISupports* *aOwner) |
|
428 { |
|
429 *aOwner = mOwner.get(); |
|
430 NS_IF_ADDREF(*aOwner); |
|
431 return NS_OK; |
|
432 } |
|
433 |
|
434 NS_IMETHODIMP nsIconChannel::SetOwner(nsISupports* aOwner) |
|
435 { |
|
436 mOwner = aOwner; |
|
437 return NS_OK; |
|
438 } |
|
439 |
|
440 NS_IMETHODIMP nsIconChannel::GetNotificationCallbacks(nsIInterfaceRequestor* *aNotificationCallbacks) |
|
441 { |
|
442 *aNotificationCallbacks = mCallbacks.get(); |
|
443 NS_IF_ADDREF(*aNotificationCallbacks); |
|
444 return NS_OK; |
|
445 } |
|
446 |
|
447 NS_IMETHODIMP nsIconChannel::SetNotificationCallbacks(nsIInterfaceRequestor* aNotificationCallbacks) |
|
448 { |
|
449 mCallbacks = aNotificationCallbacks; |
|
450 return NS_OK; |
|
451 } |
|
452 |
|
453 NS_IMETHODIMP nsIconChannel::GetSecurityInfo(nsISupports * *aSecurityInfo) |
|
454 { |
|
455 *aSecurityInfo = nullptr; |
|
456 return NS_OK; |
|
457 } |
|
458 |