|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 * http://creativecommons.org/publicdomain/zero/1.0/ */ |
|
3 |
|
4 /* |
|
5 * Tests for mozIAsyncFavicons::replaceFaviconData() |
|
6 */ |
|
7 |
|
8 let iconsvc = PlacesUtils.favicons; |
|
9 let histsvc = PlacesUtils.history; |
|
10 |
|
11 let originalFavicon = { |
|
12 file: do_get_file("favicon-normal16.png"), |
|
13 uri: uri(do_get_file("favicon-normal16.png")), |
|
14 data: readFileData(do_get_file("favicon-normal16.png")), |
|
15 mimetype: "image/png" |
|
16 }; |
|
17 |
|
18 let uniqueFaviconId = 0; |
|
19 function createFavicon(fileName) { |
|
20 let tempdir = Services.dirsvc.get("TmpD", Ci.nsILocalFile); |
|
21 |
|
22 // remove any existing file at the path we're about to copy to |
|
23 let outfile = tempdir.clone(); |
|
24 outfile.append(fileName); |
|
25 try { outfile.remove(false); } catch (e) {} |
|
26 |
|
27 originalFavicon.file.copyToFollowingLinks(tempdir, fileName); |
|
28 |
|
29 let stream = Cc["@mozilla.org/network/file-output-stream;1"] |
|
30 .createInstance(Ci.nsIFileOutputStream); |
|
31 stream.init(outfile, 0x02 | 0x08 | 0x10, 0600, 0); |
|
32 |
|
33 // append some data that sniffers/encoders will ignore that will distinguish |
|
34 // the different favicons we'll create |
|
35 uniqueFaviconId++; |
|
36 let uniqueStr = "uid:" + uniqueFaviconId; |
|
37 stream.write(uniqueStr, uniqueStr.length); |
|
38 stream.close(); |
|
39 |
|
40 do_check_eq(outfile.leafName.substr(0, fileName.length), fileName); |
|
41 |
|
42 return { |
|
43 file: outfile, |
|
44 uri: uri(outfile), |
|
45 data: readFileData(outfile), |
|
46 mimetype: "image/png" |
|
47 }; |
|
48 } |
|
49 |
|
50 function createDataURLForFavicon(favicon) { |
|
51 return "data:" + favicon.mimetype + ";base64," + toBase64(favicon.data); |
|
52 } |
|
53 |
|
54 function checkCallbackSucceeded(callbackMimetype, callbackData, sourceMimetype, sourceData) { |
|
55 do_check_eq(callbackMimetype, sourceMimetype); |
|
56 do_check_true(compareArrays(callbackData, sourceData)); |
|
57 } |
|
58 |
|
59 function run_test() { |
|
60 // check that the favicon loaded correctly |
|
61 do_check_eq(originalFavicon.data.length, 286); |
|
62 run_next_test(); |
|
63 }; |
|
64 |
|
65 add_task(function test_replaceFaviconDataFromDataURL_validHistoryURI() { |
|
66 do_log_info("test replaceFaviconDataFromDataURL for valid history uri"); |
|
67 |
|
68 let pageURI = uri("http://test1.bar/"); |
|
69 yield promiseAddVisits(pageURI); |
|
70 |
|
71 let favicon = createFavicon("favicon1.png"); |
|
72 iconsvc.replaceFaviconDataFromDataURL(favicon.uri, createDataURLForFavicon(favicon)); |
|
73 |
|
74 let deferSetAndFetchFavicon = Promise.defer(); |
|
75 iconsvc.setAndFetchFaviconForPage(pageURI, favicon.uri, true, |
|
76 PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, |
|
77 function test_replaceFaviconDataFromDataURL_validHistoryURI_check(aURI, aDataLen, aData, aMimeType) { |
|
78 checkCallbackSucceeded(aMimeType, aData, favicon.mimetype, favicon.data); |
|
79 checkFaviconDataForPage( |
|
80 pageURI, favicon.mimetype, favicon.data, |
|
81 function test_replaceFaviconDataFromDataURL_validHistoryURI_callback() { |
|
82 favicon.file.remove(false); |
|
83 deferSetAndFetchFavicon.resolve(); |
|
84 }); |
|
85 }); |
|
86 yield deferSetAndFetchFavicon.promise; |
|
87 |
|
88 yield promiseClearHistory(); |
|
89 }); |
|
90 |
|
91 add_task(function test_replaceFaviconDataFromDataURL_overrideDefaultFavicon() { |
|
92 do_log_info("test replaceFaviconDataFromDataURL to override a later setAndFetchFaviconForPage"); |
|
93 |
|
94 let pageURI = uri("http://test2.bar/"); |
|
95 yield promiseAddVisits(pageURI); |
|
96 |
|
97 let firstFavicon = createFavicon("favicon2.png"); |
|
98 let secondFavicon = createFavicon("favicon3.png"); |
|
99 |
|
100 iconsvc.replaceFaviconDataFromDataURL(firstFavicon.uri, createDataURLForFavicon(secondFavicon)); |
|
101 |
|
102 let deferSetAndFetchFavicon = Promise.defer(); |
|
103 iconsvc.setAndFetchFaviconForPage( |
|
104 pageURI, firstFavicon.uri, true, |
|
105 PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, |
|
106 function test_replaceFaviconDataFromDataURL_overrideDefaultFavicon_check(aURI, aDataLen, aData, aMimeType) { |
|
107 checkCallbackSucceeded(aMimeType, aData, secondFavicon.mimetype, secondFavicon.data); |
|
108 checkFaviconDataForPage( |
|
109 pageURI, secondFavicon.mimetype, secondFavicon.data, |
|
110 function test_replaceFaviconDataFromDataURL_overrideDefaultFavicon_callback() { |
|
111 firstFavicon.file.remove(false); |
|
112 secondFavicon.file.remove(false); |
|
113 deferSetAndFetchFavicon.resolve(); |
|
114 }); |
|
115 }); |
|
116 yield deferSetAndFetchFavicon.promise; |
|
117 |
|
118 yield promiseClearHistory(); |
|
119 }); |
|
120 |
|
121 add_task(function test_replaceFaviconDataFromDataURL_replaceExisting() { |
|
122 do_log_info("test replaceFaviconDataFromDataURL to override a previous setAndFetchFaviconForPage"); |
|
123 |
|
124 let pageURI = uri("http://test3.bar"); |
|
125 yield promiseAddVisits(pageURI); |
|
126 |
|
127 let firstFavicon = createFavicon("favicon4.png"); |
|
128 let secondFavicon = createFavicon("favicon5.png"); |
|
129 |
|
130 let deferSetAndFetchFavicon = Promise.defer(); |
|
131 iconsvc.setAndFetchFaviconForPage( |
|
132 pageURI, firstFavicon.uri, true, |
|
133 PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, |
|
134 function test_replaceFaviconDataFromDataURL_replaceExisting_firstSet_check(aURI, aDataLen, aData, aMimeType) { |
|
135 checkCallbackSucceeded(aMimeType, aData, firstFavicon.mimetype, firstFavicon.data); |
|
136 checkFaviconDataForPage( |
|
137 pageURI, firstFavicon.mimetype, firstFavicon.data, |
|
138 function test_replaceFaviconDataFromDataURL_replaceExisting_firstCallback() { |
|
139 iconsvc.replaceFaviconDataFromDataURL(firstFavicon.uri, createDataURLForFavicon(secondFavicon)); |
|
140 checkFaviconDataForPage( |
|
141 pageURI, secondFavicon.mimetype, secondFavicon.data, |
|
142 function test_replaceFaviconDataFromDataURL_replaceExisting_secondCallback() { |
|
143 firstFavicon.file.remove(false); |
|
144 secondFavicon.file.remove(false); |
|
145 deferSetAndFetchFavicon.resolve(); |
|
146 }); |
|
147 }); |
|
148 }); |
|
149 yield deferSetAndFetchFavicon.promise; |
|
150 |
|
151 yield promiseClearHistory(); |
|
152 }); |
|
153 |
|
154 add_task(function test_replaceFaviconDataFromDataURL_unrelatedReplace() { |
|
155 do_log_info("test replaceFaviconDataFromDataURL to not make unrelated changes"); |
|
156 |
|
157 let pageURI = uri("http://test4.bar/"); |
|
158 yield promiseAddVisits(pageURI); |
|
159 |
|
160 let favicon = createFavicon("favicon6.png"); |
|
161 let unrelatedFavicon = createFavicon("favicon7.png"); |
|
162 |
|
163 iconsvc.replaceFaviconDataFromDataURL(unrelatedFavicon.uri, createDataURLForFavicon(unrelatedFavicon)); |
|
164 |
|
165 let deferSetAndFetchFavicon = Promise.defer(); |
|
166 iconsvc.setAndFetchFaviconForPage( |
|
167 pageURI, favicon.uri, true, |
|
168 PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, |
|
169 function test_replaceFaviconDataFromDataURL_unrelatedReplace_check(aURI, aDataLen, aData, aMimeType) { |
|
170 checkCallbackSucceeded(aMimeType, aData, favicon.mimetype, favicon.data); |
|
171 checkFaviconDataForPage( |
|
172 pageURI, favicon.mimetype, favicon.data, |
|
173 function test_replaceFaviconDataFromDataURL_unrelatedReplace_callback() { |
|
174 favicon.file.remove(false); |
|
175 unrelatedFavicon.file.remove(false); |
|
176 deferSetAndFetchFavicon.resolve(); |
|
177 }); |
|
178 }); |
|
179 yield deferSetAndFetchFavicon.promise; |
|
180 |
|
181 yield promiseClearHistory(); |
|
182 }); |
|
183 |
|
184 add_task(function test_replaceFaviconDataFromDataURL_badInputs() { |
|
185 do_log_info("test replaceFaviconDataFromDataURL to throw on bad inputs"); |
|
186 |
|
187 let favicon = createFavicon("favicon8.png"); |
|
188 |
|
189 let ex = null; |
|
190 try { |
|
191 iconsvc.replaceFaviconDataFromDataURL(favicon.uri, ""); |
|
192 } catch (e) { |
|
193 ex = e; |
|
194 } finally { |
|
195 do_check_true(!!ex); |
|
196 } |
|
197 |
|
198 ex = null; |
|
199 try { |
|
200 iconsvc.replaceFaviconDataFromDataURL(null, createDataURLForFavicon(favicon)); |
|
201 } catch (e) { |
|
202 ex = e; |
|
203 } finally { |
|
204 do_check_true(!!ex); |
|
205 } |
|
206 |
|
207 favicon.file.remove(false); |
|
208 |
|
209 yield promiseClearHistory(); |
|
210 }); |
|
211 |
|
212 add_task(function test_replaceFaviconDataFromDataURL_twiceReplace() { |
|
213 do_log_info("test replaceFaviconDataFromDataURL on multiple replacements"); |
|
214 |
|
215 let pageURI = uri("http://test5.bar/"); |
|
216 yield promiseAddVisits(pageURI); |
|
217 |
|
218 let firstFavicon = createFavicon("favicon9.png"); |
|
219 let secondFavicon = createFavicon("favicon10.png"); |
|
220 |
|
221 iconsvc.replaceFaviconDataFromDataURL(firstFavicon.uri, createDataURLForFavicon(firstFavicon)); |
|
222 iconsvc.replaceFaviconDataFromDataURL(firstFavicon.uri, createDataURLForFavicon(secondFavicon)); |
|
223 |
|
224 let deferSetAndFetchFavicon = Promise.defer(); |
|
225 iconsvc.setAndFetchFaviconForPage( |
|
226 pageURI, firstFavicon.uri, true, |
|
227 PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, |
|
228 function test_replaceFaviconDataFromDataURL_twiceReplace_check(aURI, aDataLen, aData, aMimeType) { |
|
229 checkCallbackSucceeded(aMimeType, aData, secondFavicon.mimetype, secondFavicon.data); |
|
230 checkFaviconDataForPage( |
|
231 pageURI, secondFavicon.mimetype, secondFavicon.data, |
|
232 function test_replaceFaviconDataFromDataURL_twiceReplace_callback() { |
|
233 firstFavicon.file.remove(false); |
|
234 secondFavicon.file.remove(false); |
|
235 deferSetAndFetchFavicon.resolve(); |
|
236 }); |
|
237 }); |
|
238 yield deferSetAndFetchFavicon.promise; |
|
239 |
|
240 yield promiseClearHistory(); |
|
241 }); |
|
242 |
|
243 add_task(function test_replaceFaviconDataFromDataURL_afterRegularAssign() { |
|
244 do_log_info("test replaceFaviconDataFromDataURL after replaceFaviconData"); |
|
245 |
|
246 let pageURI = uri("http://test6.bar/"); |
|
247 yield promiseAddVisits(pageURI); |
|
248 |
|
249 let firstFavicon = createFavicon("favicon11.png"); |
|
250 let secondFavicon = createFavicon("favicon12.png"); |
|
251 |
|
252 iconsvc.replaceFaviconData( |
|
253 firstFavicon.uri, firstFavicon.data, firstFavicon.data.length, |
|
254 firstFavicon.mimetype); |
|
255 iconsvc.replaceFaviconDataFromDataURL(firstFavicon.uri, createDataURLForFavicon(secondFavicon)); |
|
256 |
|
257 let deferSetAndFetchFavicon = Promise.defer(); |
|
258 iconsvc.setAndFetchFaviconForPage( |
|
259 pageURI, firstFavicon.uri, true, |
|
260 PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, |
|
261 function test_replaceFaviconDataFromDataURL_afterRegularAssign_check(aURI, aDataLen, aData, aMimeType) { |
|
262 checkCallbackSucceeded(aMimeType, aData, secondFavicon.mimetype, secondFavicon.data); |
|
263 checkFaviconDataForPage( |
|
264 pageURI, secondFavicon.mimetype, secondFavicon.data, |
|
265 function test_replaceFaviconDataFromDataURL_afterRegularAssign_callback() { |
|
266 firstFavicon.file.remove(false); |
|
267 secondFavicon.file.remove(false); |
|
268 deferSetAndFetchFavicon.resolve(); |
|
269 }); |
|
270 }); |
|
271 yield deferSetAndFetchFavicon.promise; |
|
272 |
|
273 yield promiseClearHistory(); |
|
274 }); |
|
275 |
|
276 add_task(function test_replaceFaviconDataFromDataURL_beforeRegularAssign() { |
|
277 do_log_info("test replaceFaviconDataFromDataURL before replaceFaviconData"); |
|
278 |
|
279 let pageURI = uri("http://test7.bar/"); |
|
280 yield promiseAddVisits(pageURI); |
|
281 |
|
282 let firstFavicon = createFavicon("favicon13.png"); |
|
283 let secondFavicon = createFavicon("favicon14.png"); |
|
284 |
|
285 iconsvc.replaceFaviconDataFromDataURL(firstFavicon.uri, createDataURLForFavicon(firstFavicon)); |
|
286 iconsvc.replaceFaviconData( |
|
287 firstFavicon.uri, secondFavicon.data, secondFavicon.data.length, |
|
288 secondFavicon.mimetype); |
|
289 |
|
290 let deferSetAndFetchFavicon = Promise.defer(); |
|
291 iconsvc.setAndFetchFaviconForPage( |
|
292 pageURI, firstFavicon.uri, true, |
|
293 PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, |
|
294 function test_replaceFaviconDataFromDataURL_beforeRegularAssign_check(aURI, aDataLen, aData, aMimeType) { |
|
295 checkCallbackSucceeded(aMimeType, aData, secondFavicon.mimetype, secondFavicon.data); |
|
296 checkFaviconDataForPage( |
|
297 pageURI, secondFavicon.mimetype, secondFavicon.data, |
|
298 function test_replaceFaviconDataFromDataURL_beforeRegularAssign_callback() { |
|
299 firstFavicon.file.remove(false); |
|
300 secondFavicon.file.remove(false); |
|
301 deferSetAndFetchFavicon.resolve(); |
|
302 }); |
|
303 }); |
|
304 yield deferSetAndFetchFavicon.promise; |
|
305 |
|
306 yield promiseClearHistory(); |
|
307 }); |
|
308 |
|
309 /* toBase64 copied from image/test/unit/test_encoder_png.js */ |
|
310 |
|
311 /* Convert data (an array of integers) to a Base64 string. */ |
|
312 const toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + |
|
313 '0123456789+/'; |
|
314 const base64Pad = '='; |
|
315 function toBase64(data) { |
|
316 let result = ''; |
|
317 let length = data.length; |
|
318 let i; |
|
319 // Convert every three bytes to 4 ascii characters. |
|
320 for (i = 0; i < (length - 2); i += 3) { |
|
321 result += toBase64Table[data[i] >> 2]; |
|
322 result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; |
|
323 result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)]; |
|
324 result += toBase64Table[data[i+2] & 0x3f]; |
|
325 } |
|
326 |
|
327 // Convert the remaining 1 or 2 bytes, pad out to 4 characters. |
|
328 if (length%3) { |
|
329 i = length - (length%3); |
|
330 result += toBase64Table[data[i] >> 2]; |
|
331 if ((length%3) == 2) { |
|
332 result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; |
|
333 result += toBase64Table[(data[i+1] & 0x0f) << 2]; |
|
334 result += base64Pad; |
|
335 } else { |
|
336 result += toBase64Table[(data[i] & 0x03) << 4]; |
|
337 result += base64Pad + base64Pad; |
|
338 } |
|
339 } |
|
340 |
|
341 return result; |
|
342 } |