michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: * http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: /* michael@0: * Tests for mozIAsyncFavicons::replaceFaviconData() michael@0: */ michael@0: michael@0: let iconsvc = PlacesUtils.favicons; michael@0: let histsvc = PlacesUtils.history; michael@0: michael@0: let originalFavicon = { michael@0: file: do_get_file("favicon-normal16.png"), michael@0: uri: uri(do_get_file("favicon-normal16.png")), michael@0: data: readFileData(do_get_file("favicon-normal16.png")), michael@0: mimetype: "image/png" michael@0: }; michael@0: michael@0: let uniqueFaviconId = 0; michael@0: function createFavicon(fileName) { michael@0: let tempdir = Services.dirsvc.get("TmpD", Ci.nsILocalFile); michael@0: michael@0: // remove any existing file at the path we're about to copy to michael@0: let outfile = tempdir.clone(); michael@0: outfile.append(fileName); michael@0: try { outfile.remove(false); } catch (e) {} michael@0: michael@0: originalFavicon.file.copyToFollowingLinks(tempdir, fileName); michael@0: michael@0: let stream = Cc["@mozilla.org/network/file-output-stream;1"] michael@0: .createInstance(Ci.nsIFileOutputStream); michael@0: stream.init(outfile, 0x02 | 0x08 | 0x10, 0600, 0); michael@0: michael@0: // append some data that sniffers/encoders will ignore that will distinguish michael@0: // the different favicons we'll create michael@0: uniqueFaviconId++; michael@0: let uniqueStr = "uid:" + uniqueFaviconId; michael@0: stream.write(uniqueStr, uniqueStr.length); michael@0: stream.close(); michael@0: michael@0: do_check_eq(outfile.leafName.substr(0, fileName.length), fileName); michael@0: michael@0: return { michael@0: file: outfile, michael@0: uri: uri(outfile), michael@0: data: readFileData(outfile), michael@0: mimetype: "image/png" michael@0: }; michael@0: } michael@0: michael@0: function createDataURLForFavicon(favicon) { michael@0: return "data:" + favicon.mimetype + ";base64," + toBase64(favicon.data); michael@0: } michael@0: michael@0: function checkCallbackSucceeded(callbackMimetype, callbackData, sourceMimetype, sourceData) { michael@0: do_check_eq(callbackMimetype, sourceMimetype); michael@0: do_check_true(compareArrays(callbackData, sourceData)); michael@0: } michael@0: michael@0: function run_test() { michael@0: // check that the favicon loaded correctly michael@0: do_check_eq(originalFavicon.data.length, 286); michael@0: run_next_test(); michael@0: }; michael@0: michael@0: add_task(function test_replaceFaviconDataFromDataURL_validHistoryURI() { michael@0: do_log_info("test replaceFaviconDataFromDataURL for valid history uri"); michael@0: michael@0: let pageURI = uri("http://test1.bar/"); michael@0: yield promiseAddVisits(pageURI); michael@0: michael@0: let favicon = createFavicon("favicon1.png"); michael@0: iconsvc.replaceFaviconDataFromDataURL(favicon.uri, createDataURLForFavicon(favicon)); michael@0: michael@0: let deferSetAndFetchFavicon = Promise.defer(); michael@0: iconsvc.setAndFetchFaviconForPage(pageURI, favicon.uri, true, michael@0: PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, michael@0: function test_replaceFaviconDataFromDataURL_validHistoryURI_check(aURI, aDataLen, aData, aMimeType) { michael@0: checkCallbackSucceeded(aMimeType, aData, favicon.mimetype, favicon.data); michael@0: checkFaviconDataForPage( michael@0: pageURI, favicon.mimetype, favicon.data, michael@0: function test_replaceFaviconDataFromDataURL_validHistoryURI_callback() { michael@0: favicon.file.remove(false); michael@0: deferSetAndFetchFavicon.resolve(); michael@0: }); michael@0: }); michael@0: yield deferSetAndFetchFavicon.promise; michael@0: michael@0: yield promiseClearHistory(); michael@0: }); michael@0: michael@0: add_task(function test_replaceFaviconDataFromDataURL_overrideDefaultFavicon() { michael@0: do_log_info("test replaceFaviconDataFromDataURL to override a later setAndFetchFaviconForPage"); michael@0: michael@0: let pageURI = uri("http://test2.bar/"); michael@0: yield promiseAddVisits(pageURI); michael@0: michael@0: let firstFavicon = createFavicon("favicon2.png"); michael@0: let secondFavicon = createFavicon("favicon3.png"); michael@0: michael@0: iconsvc.replaceFaviconDataFromDataURL(firstFavicon.uri, createDataURLForFavicon(secondFavicon)); michael@0: michael@0: let deferSetAndFetchFavicon = Promise.defer(); michael@0: iconsvc.setAndFetchFaviconForPage( michael@0: pageURI, firstFavicon.uri, true, michael@0: PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, michael@0: function test_replaceFaviconDataFromDataURL_overrideDefaultFavicon_check(aURI, aDataLen, aData, aMimeType) { michael@0: checkCallbackSucceeded(aMimeType, aData, secondFavicon.mimetype, secondFavicon.data); michael@0: checkFaviconDataForPage( michael@0: pageURI, secondFavicon.mimetype, secondFavicon.data, michael@0: function test_replaceFaviconDataFromDataURL_overrideDefaultFavicon_callback() { michael@0: firstFavicon.file.remove(false); michael@0: secondFavicon.file.remove(false); michael@0: deferSetAndFetchFavicon.resolve(); michael@0: }); michael@0: }); michael@0: yield deferSetAndFetchFavicon.promise; michael@0: michael@0: yield promiseClearHistory(); michael@0: }); michael@0: michael@0: add_task(function test_replaceFaviconDataFromDataURL_replaceExisting() { michael@0: do_log_info("test replaceFaviconDataFromDataURL to override a previous setAndFetchFaviconForPage"); michael@0: michael@0: let pageURI = uri("http://test3.bar"); michael@0: yield promiseAddVisits(pageURI); michael@0: michael@0: let firstFavicon = createFavicon("favicon4.png"); michael@0: let secondFavicon = createFavicon("favicon5.png"); michael@0: michael@0: let deferSetAndFetchFavicon = Promise.defer(); michael@0: iconsvc.setAndFetchFaviconForPage( michael@0: pageURI, firstFavicon.uri, true, michael@0: PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, michael@0: function test_replaceFaviconDataFromDataURL_replaceExisting_firstSet_check(aURI, aDataLen, aData, aMimeType) { michael@0: checkCallbackSucceeded(aMimeType, aData, firstFavicon.mimetype, firstFavicon.data); michael@0: checkFaviconDataForPage( michael@0: pageURI, firstFavicon.mimetype, firstFavicon.data, michael@0: function test_replaceFaviconDataFromDataURL_replaceExisting_firstCallback() { michael@0: iconsvc.replaceFaviconDataFromDataURL(firstFavicon.uri, createDataURLForFavicon(secondFavicon)); michael@0: checkFaviconDataForPage( michael@0: pageURI, secondFavicon.mimetype, secondFavicon.data, michael@0: function test_replaceFaviconDataFromDataURL_replaceExisting_secondCallback() { michael@0: firstFavicon.file.remove(false); michael@0: secondFavicon.file.remove(false); michael@0: deferSetAndFetchFavicon.resolve(); michael@0: }); michael@0: }); michael@0: }); michael@0: yield deferSetAndFetchFavicon.promise; michael@0: michael@0: yield promiseClearHistory(); michael@0: }); michael@0: michael@0: add_task(function test_replaceFaviconDataFromDataURL_unrelatedReplace() { michael@0: do_log_info("test replaceFaviconDataFromDataURL to not make unrelated changes"); michael@0: michael@0: let pageURI = uri("http://test4.bar/"); michael@0: yield promiseAddVisits(pageURI); michael@0: michael@0: let favicon = createFavicon("favicon6.png"); michael@0: let unrelatedFavicon = createFavicon("favicon7.png"); michael@0: michael@0: iconsvc.replaceFaviconDataFromDataURL(unrelatedFavicon.uri, createDataURLForFavicon(unrelatedFavicon)); michael@0: michael@0: let deferSetAndFetchFavicon = Promise.defer(); michael@0: iconsvc.setAndFetchFaviconForPage( michael@0: pageURI, favicon.uri, true, michael@0: PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, michael@0: function test_replaceFaviconDataFromDataURL_unrelatedReplace_check(aURI, aDataLen, aData, aMimeType) { michael@0: checkCallbackSucceeded(aMimeType, aData, favicon.mimetype, favicon.data); michael@0: checkFaviconDataForPage( michael@0: pageURI, favicon.mimetype, favicon.data, michael@0: function test_replaceFaviconDataFromDataURL_unrelatedReplace_callback() { michael@0: favicon.file.remove(false); michael@0: unrelatedFavicon.file.remove(false); michael@0: deferSetAndFetchFavicon.resolve(); michael@0: }); michael@0: }); michael@0: yield deferSetAndFetchFavicon.promise; michael@0: michael@0: yield promiseClearHistory(); michael@0: }); michael@0: michael@0: add_task(function test_replaceFaviconDataFromDataURL_badInputs() { michael@0: do_log_info("test replaceFaviconDataFromDataURL to throw on bad inputs"); michael@0: michael@0: let favicon = createFavicon("favicon8.png"); michael@0: michael@0: let ex = null; michael@0: try { michael@0: iconsvc.replaceFaviconDataFromDataURL(favicon.uri, ""); michael@0: } catch (e) { michael@0: ex = e; michael@0: } finally { michael@0: do_check_true(!!ex); michael@0: } michael@0: michael@0: ex = null; michael@0: try { michael@0: iconsvc.replaceFaviconDataFromDataURL(null, createDataURLForFavicon(favicon)); michael@0: } catch (e) { michael@0: ex = e; michael@0: } finally { michael@0: do_check_true(!!ex); michael@0: } michael@0: michael@0: favicon.file.remove(false); michael@0: michael@0: yield promiseClearHistory(); michael@0: }); michael@0: michael@0: add_task(function test_replaceFaviconDataFromDataURL_twiceReplace() { michael@0: do_log_info("test replaceFaviconDataFromDataURL on multiple replacements"); michael@0: michael@0: let pageURI = uri("http://test5.bar/"); michael@0: yield promiseAddVisits(pageURI); michael@0: michael@0: let firstFavicon = createFavicon("favicon9.png"); michael@0: let secondFavicon = createFavicon("favicon10.png"); michael@0: michael@0: iconsvc.replaceFaviconDataFromDataURL(firstFavicon.uri, createDataURLForFavicon(firstFavicon)); michael@0: iconsvc.replaceFaviconDataFromDataURL(firstFavicon.uri, createDataURLForFavicon(secondFavicon)); michael@0: michael@0: let deferSetAndFetchFavicon = Promise.defer(); michael@0: iconsvc.setAndFetchFaviconForPage( michael@0: pageURI, firstFavicon.uri, true, michael@0: PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, michael@0: function test_replaceFaviconDataFromDataURL_twiceReplace_check(aURI, aDataLen, aData, aMimeType) { michael@0: checkCallbackSucceeded(aMimeType, aData, secondFavicon.mimetype, secondFavicon.data); michael@0: checkFaviconDataForPage( michael@0: pageURI, secondFavicon.mimetype, secondFavicon.data, michael@0: function test_replaceFaviconDataFromDataURL_twiceReplace_callback() { michael@0: firstFavicon.file.remove(false); michael@0: secondFavicon.file.remove(false); michael@0: deferSetAndFetchFavicon.resolve(); michael@0: }); michael@0: }); michael@0: yield deferSetAndFetchFavicon.promise; michael@0: michael@0: yield promiseClearHistory(); michael@0: }); michael@0: michael@0: add_task(function test_replaceFaviconDataFromDataURL_afterRegularAssign() { michael@0: do_log_info("test replaceFaviconDataFromDataURL after replaceFaviconData"); michael@0: michael@0: let pageURI = uri("http://test6.bar/"); michael@0: yield promiseAddVisits(pageURI); michael@0: michael@0: let firstFavicon = createFavicon("favicon11.png"); michael@0: let secondFavicon = createFavicon("favicon12.png"); michael@0: michael@0: iconsvc.replaceFaviconData( michael@0: firstFavicon.uri, firstFavicon.data, firstFavicon.data.length, michael@0: firstFavicon.mimetype); michael@0: iconsvc.replaceFaviconDataFromDataURL(firstFavicon.uri, createDataURLForFavicon(secondFavicon)); michael@0: michael@0: let deferSetAndFetchFavicon = Promise.defer(); michael@0: iconsvc.setAndFetchFaviconForPage( michael@0: pageURI, firstFavicon.uri, true, michael@0: PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, michael@0: function test_replaceFaviconDataFromDataURL_afterRegularAssign_check(aURI, aDataLen, aData, aMimeType) { michael@0: checkCallbackSucceeded(aMimeType, aData, secondFavicon.mimetype, secondFavicon.data); michael@0: checkFaviconDataForPage( michael@0: pageURI, secondFavicon.mimetype, secondFavicon.data, michael@0: function test_replaceFaviconDataFromDataURL_afterRegularAssign_callback() { michael@0: firstFavicon.file.remove(false); michael@0: secondFavicon.file.remove(false); michael@0: deferSetAndFetchFavicon.resolve(); michael@0: }); michael@0: }); michael@0: yield deferSetAndFetchFavicon.promise; michael@0: michael@0: yield promiseClearHistory(); michael@0: }); michael@0: michael@0: add_task(function test_replaceFaviconDataFromDataURL_beforeRegularAssign() { michael@0: do_log_info("test replaceFaviconDataFromDataURL before replaceFaviconData"); michael@0: michael@0: let pageURI = uri("http://test7.bar/"); michael@0: yield promiseAddVisits(pageURI); michael@0: michael@0: let firstFavicon = createFavicon("favicon13.png"); michael@0: let secondFavicon = createFavicon("favicon14.png"); michael@0: michael@0: iconsvc.replaceFaviconDataFromDataURL(firstFavicon.uri, createDataURLForFavicon(firstFavicon)); michael@0: iconsvc.replaceFaviconData( michael@0: firstFavicon.uri, secondFavicon.data, secondFavicon.data.length, michael@0: secondFavicon.mimetype); michael@0: michael@0: let deferSetAndFetchFavicon = Promise.defer(); michael@0: iconsvc.setAndFetchFaviconForPage( michael@0: pageURI, firstFavicon.uri, true, michael@0: PlacesUtils.favicons.FAVICON_LOAD_NON_PRIVATE, michael@0: function test_replaceFaviconDataFromDataURL_beforeRegularAssign_check(aURI, aDataLen, aData, aMimeType) { michael@0: checkCallbackSucceeded(aMimeType, aData, secondFavicon.mimetype, secondFavicon.data); michael@0: checkFaviconDataForPage( michael@0: pageURI, secondFavicon.mimetype, secondFavicon.data, michael@0: function test_replaceFaviconDataFromDataURL_beforeRegularAssign_callback() { michael@0: firstFavicon.file.remove(false); michael@0: secondFavicon.file.remove(false); michael@0: deferSetAndFetchFavicon.resolve(); michael@0: }); michael@0: }); michael@0: yield deferSetAndFetchFavicon.promise; michael@0: michael@0: yield promiseClearHistory(); michael@0: }); michael@0: michael@0: /* toBase64 copied from image/test/unit/test_encoder_png.js */ michael@0: michael@0: /* Convert data (an array of integers) to a Base64 string. */ michael@0: const toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' + michael@0: '0123456789+/'; michael@0: const base64Pad = '='; michael@0: function toBase64(data) { michael@0: let result = ''; michael@0: let length = data.length; michael@0: let i; michael@0: // Convert every three bytes to 4 ascii characters. michael@0: for (i = 0; i < (length - 2); i += 3) { michael@0: result += toBase64Table[data[i] >> 2]; michael@0: result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; michael@0: result += toBase64Table[((data[i+1] & 0x0f) << 2) + (data[i+2] >> 6)]; michael@0: result += toBase64Table[data[i+2] & 0x3f]; michael@0: } michael@0: michael@0: // Convert the remaining 1 or 2 bytes, pad out to 4 characters. michael@0: if (length%3) { michael@0: i = length - (length%3); michael@0: result += toBase64Table[data[i] >> 2]; michael@0: if ((length%3) == 2) { michael@0: result += toBase64Table[((data[i] & 0x03) << 4) + (data[i+1] >> 4)]; michael@0: result += toBase64Table[(data[i+1] & 0x0f) << 2]; michael@0: result += base64Pad; michael@0: } else { michael@0: result += toBase64Table[(data[i] & 0x03) << 4]; michael@0: result += base64Pad + base64Pad; michael@0: } michael@0: } michael@0: michael@0: return result; michael@0: }