Skip to content
This repository has been archived by the owner on Feb 26, 2022. It is now read-only.

Commit

Permalink
Bug 861955: getFavicon async method in io/data
Browse files Browse the repository at this point in the history
  • Loading branch information
jsantell committed Apr 30, 2013
1 parent f7188bb commit de3984b
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 2 deletions.
29 changes: 27 additions & 2 deletions lib/sdk/io/data.js
Expand Up @@ -10,27 +10,51 @@ module.metadata = {

const { Cc, Ci, Cu } = require("chrome");
const base64 = require("../base64");
const { defer } = require("../core/promise");
const { newURI } = require("../url/utils");

const IOService = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);

const { deprecateFunction } = require('../util/deprecate');
const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm");
const FaviconService = Cc["@mozilla.org/browser/favicon-service;1"].
getService(Ci.nsIFaviconService);
const { deprecateFunction } = require("../util/deprecate");
const AsyncFavicons = FaviconService.QueryInterface(Ci.mozIAsyncFavicons);

const PNG_B64 = "data:image/png;base64,";
const DEF_FAVICON_URI = "chrome://mozapps/skin/places/defaultFavicon.png";
let DEF_FAVICON = null;

/**
* Takes URI of the page and returns a promise that resolves
* to the page's favicon URI.
* @param {String} uri
* @param {Function} (callback)
* @returns {Promise}
*/

exports.getFavicon = function getFavicon(uri, callback) {
let pageURI = newURI(uri);
let deferred = defer();
AsyncFavicons.getFaviconURLForPage(pageURI, function (aURI) {
if (aURI && aURI.spec)
deferred.resolve(aURI.spec.toString());
else
deferred.reject(null);
});
if (callback) deferred.promise.then(callback, callback);
return deferred.promise;
};

/**
* Takes URI of the page and returns associated favicon URI.
* If page under passed uri has no favicon then base64 encoded data URI of
* default faveicon is returned.
* @param {String} uri
* @returns {String}
*/
exports.getFaviconURIForLocation = function getFaviconURIForLocation(uri) {
function getFaviconURIForLocation(uri) {
let pageURI = NetUtil.newURI(uri);
try {
return FaviconService.getFaviconDataAsDataURL(
Expand All @@ -44,6 +68,7 @@ exports.getFaviconURIForLocation = function getFaviconURIForLocation(uri) {
return DEF_FAVICON;
}
}
exports.getFaviconURIForLocation = getFaviconURIForLocation;

/**
* Takes chrome URI and returns content under that URI.
Expand Down
21 changes: 21 additions & 0 deletions lib/sdk/url/utils.js
@@ -0,0 +1,21 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

module.metadata = {
"stability": "experimental"
};

const { Cc, Ci, Cr } = require("chrome");
const IOService = Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
const { isValidURI } = require("../url");

function newURI (uri) {
if (!isValidURI(uri))
throw new Error("malformed URI: " + uri);
return IOService.newURI(uri, null, null);
}
exports.newURI = newURI;
74 changes: 74 additions & 0 deletions test/favicon-helpers.js
@@ -0,0 +1,74 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { Cc, Ci, Cu } = require('chrome');
const { Loader } = require('sdk/test/loader');
const loader = Loader(module);
const file = require('sdk/io/file');
const httpd = loader.require('sdk/test/httpd');
const { pathFor } = require('sdk/system');
const { startServerAsync } = httpd;
const basePath = pathFor('ProfD');
const { atob } = Cu.import("resource://gre/modules/Services.jsm", {});
const historyService = Cc["@mozilla.org/browser/nav-history-service;1"]
.getService(Ci.nsINavHistoryService);
Cu.import('resource://gre/modules/XPCOMUtils.jsm');
const ObserverShimMethods = ['onBeginUpdateBatch', 'onEndUpdateBatch',
'onVisit', 'onTitleChanged', 'onDeleteURI', 'onClearHistory',
'onPageChanged', 'onDeleteVisits'];

/*
* Shims NavHistoryObserver
*/

let noop = function () {}
let NavHistoryObserver = function () {};
ObserverShimMethods.forEach(function (method) {
NavHistoryObserver.prototype[method] = noop;
});
NavHistoryObserver.prototype.QueryInterface = XPCOMUtils.generateQI([
Ci.nsINavHistoryObserver
]);

/*
* Uses history observer to watch for an onPageChanged event,
* which detects when a favicon is updated in the registry.
*/
function onFaviconChange (uri, callback) {
let observer = Object.create(NavHistoryObserver.prototype, {
onPageChanged: {
value: function onPageChanged(aURI, aWhat, aValue, aGUID) {
if (aWhat !== Ci.nsINavHistoryObserver.ATTRIBUTE_FAVICON)
return;
if (aURI.spec !== uri)
return;
historyService.removeObserver(this);
callback(aValue);
}
}
});
historyService.addObserver(observer, false);
}
exports.onFaviconChange = onFaviconChange;

/*
* Takes page content, a page path, and favicon binary data
*/
function serve ({name, favicon, port, host}) {
let faviconTag = '<link rel="icon" type="image/x-icon" href="/'+ name +'.ico"/>';
let content = '<html><head>' + faviconTag + '<title>'+name+'</title></head><body></body></html>';
let srv = startServerAsync(port, basePath);
let pagePath = file.join(basePath, name + '.html');
let iconPath = file.join(basePath, name + '.ico');
let pageStream = file.open(pagePath, 'w');
let iconStream = file.open(iconPath, 'wb');
iconStream.write(favicon);
iconStream.close();
pageStream.write(content);
pageStream.close();
return srv;
}
exports.serve = serve;

let binFavicon = exports.binFavicon = atob('AAABAAEAEBAAAAAAAABoBQAAFgAAACgAAAAQAAAAIAAAAAEACAAAAAAAAAEAAAAAAAAAAAAAAAEAAAABAAAAAAAAAACAAACAAAAAgIAAgAAAAIAAgACAgAAAwMDAAMDcwADwyqYABAQEAAgICAAMDAwAERERABYWFgAcHBwAIiIiACkpKQBVVVUATU1NAEJCQgA5OTkAgHz/AFBQ/wCTANYA/+zMAMbW7wDW5+cAkKmtAAAAMwAAAGYAAACZAAAAzAAAMwAAADMzAAAzZgAAM5kAADPMAAAz/wAAZgAAAGYzAABmZgAAZpkAAGbMAABm/wAAmQAAAJkzAACZZgAAmZkAAJnMAACZ/wAAzAAAAMwzAADMZgAAzJkAAMzMAADM/wAA/2YAAP+ZAAD/zAAzAAAAMwAzADMAZgAzAJkAMwDMADMA/wAzMwAAMzMzADMzZgAzM5kAMzPMADMz/wAzZgAAM2YzADNmZgAzZpkAM2bMADNm/wAzmQAAM5kzADOZZgAzmZkAM5nMADOZ/wAzzAAAM8wzADPMZgAzzJkAM8zMADPM/wAz/zMAM/9mADP/mQAz/8wAM///AGYAAABmADMAZgBmAGYAmQBmAMwAZgD/AGYzAABmMzMAZjNmAGYzmQBmM8wAZjP/AGZmAABmZjMAZmZmAGZmmQBmZswAZpkAAGaZMwBmmWYAZpmZAGaZzABmmf8AZswAAGbMMwBmzJkAZszMAGbM/wBm/wAAZv8zAGb/mQBm/8wAzAD/AP8AzACZmQAAmTOZAJkAmQCZAMwAmQAAAJkzMwCZAGYAmTPMAJkA/wCZZgAAmWYzAJkzZgCZZpkAmWbMAJkz/wCZmTMAmZlmAJmZmQCZmcwAmZn/AJnMAACZzDMAZsxmAJnMmQCZzMwAmcz/AJn/AACZ/zMAmcxmAJn/mQCZ/8wAmf//AMwAAACZADMAzABmAMwAmQDMAMwAmTMAAMwzMwDMM2YAzDOZAMwzzADMM/8AzGYAAMxmMwCZZmYAzGaZAMxmzACZZv8AzJkAAMyZMwDMmWYAzJmZAMyZzADMmf8AzMwAAMzMMwDMzGYAzMyZAMzMzADMzP8AzP8AAMz/MwCZ/2YAzP+ZAMz/zADM//8AzAAzAP8AZgD/AJkAzDMAAP8zMwD/M2YA/zOZAP8zzAD/M/8A/2YAAP9mMwDMZmYA/2aZAP9mzADMZv8A/5kAAP+ZMwD/mWYA/5mZAP+ZzAD/mf8A/8wAAP/MMwD/zGYA/8yZAP/MzAD/zP8A//8zAMz/ZgD//5kA///MAGZm/wBm/2YAZv//AP9mZgD/Zv8A//9mACEApQBfX18Ad3d3AIaGhgCWlpYAy8vLALKysgDX19cA3d3dAOPj4wDq6uoA8fHxAPj4+ADw+/8ApKCgAICAgAAAAP8AAP8AAAD//wD/AAAA/wD/AP//AAD///8ACgoKCgoKCgoKCgoKCgoKCgoKCgoHAQEMbQoKCgoKCgoAAAdDH/kgHRIAAAAAAAAAAADrHfn5ASQQAAAAAAAAAArsBx0B+fkgHesAAAAAAAD/Cgwf+fn5IA4dEus/IvcACgcMAfkg+QEB+SABHushbf8QHR/5HQH5+QEdHetEHx4K7B/5+QH5+fkdDBL5+SBE/wwdJfkf+fn5AR8g+fkfEArsCh/5+QEeJR/5+SAeBwAACgoe+SAlHwFAEhAfAAAAAPcKHh8eASYBHhAMAAAAAAAA9EMdIB8gHh0dBwAAAAAAAAAA7BAdQ+wHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAP//AADwfwAAwH8AAMB/AAAAPwAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAQAAgAcAAIAPAADADwAA8D8AAP//AAA');
112 changes: 112 additions & 0 deletions test/test-io-data.js
@@ -0,0 +1,112 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

const { Cc, Ci, Cu } = require('chrome');
const { getFavicon, getFaviconURIForLocation } = require('sdk/io/data');
const tabs = require('sdk/tabs');
const open = tabs.open;
const port = 8099;
const host = 'http://localhost:' + port;
const { onFaviconChange, serve, binFavicon } = require('./favicon-helpers');
const { once } = require('sdk/system/events');
const faviconService = Cc["@mozilla.org/browser/favicon-service;1"].
getService(Ci.nsIFaviconService);

exports.testGetFaviconCallbackSuccess = function (assert, done) {
let name = 'callbacksuccess'
let srv = serve({name: name, favicon: binFavicon, port: port, host: host});
let url = host + '/' + name + '.html';
let favicon = host + '/' + name + '.ico';
let tab;

onFaviconChange(url, function (faviconUrl) {
getFavicon(url, function (url) {
assert.equal(favicon, url, 'Callback returns correct favicon url');
complete(tab, srv, done);
});
});

open({
url: url,
onOpen: function (newTab) tab = newTab,
inBackground: true
});
};

exports.testGetFaviconCallbackFailure = function (assert, done) {
let name = 'callbackfailure';
let srv = serve({name: name, favicon: binFavicon, port: port, host: host});
let url = host + '/' + name + '.html';
let tab;

onFaviconChange(url, function (faviconUrl) {
once('places-favicons-expired', function () {
getFavicon(url, function (url) {
assert.equal(url, null, 'Callback returns null');
complete(tab, srv, done);
});
});
faviconService.expireAllFavicons();
});

open({
url: url,
onOpen: function (newTab) tab = newTab,
inBackground: true
});
};

exports.testGetFaviconPromiseSuccess = function (assert, done) {
let name = 'promisesuccess'
let srv = serve({name: name, favicon: binFavicon, port: port, host: host});
let url = host + '/' + name + '.html';
let favicon = host + '/' + name + '.ico';
let tab;

onFaviconChange(url, function (faviconUrl) {
getFavicon(url).then(function (url) {
assert.equal(url, favicon, 'Callback returns null');
}, function (err) {
assert.fail('Reject should not be called');
}).then(complete.bind(null, tab, srv, done));
});

open({
url: url,
onOpen: function (newTab) tab = newTab,
inBackground: true
});
};

exports.testGetFaviconPromiseFailure = function (assert, done) {
let name = 'promisefailure'
let srv = serve({name: name, favicon: binFavicon, port: port, host: host});
let url = host + '/' + name + '.html';
let tab;

onFaviconChange(url, function (faviconUrl) {
once('places-favicons-expired', function () {
getFavicon(url).then(function (url) {
assert.fail('success should not be called');
}, function (err) {
assert.equal(err, null, 'should call reject');
}).then(complete.bind(null, tab, srv, done));
});
faviconService.expireAllFavicons();
});

open({
url: url,
onOpen: function (newTab) tab = newTab,
inBackground: true
});
};

function complete(tab, srv, done) {
tab.close(function () {
srv.stop(done);
})
}

require("test").run(exports);

0 comments on commit de3984b

Please sign in to comment.