зеркало из https://github.com/mozilla/gecko-dev.git
Merge fx-team to m-c
This commit is contained in:
Коммит
7312341e00
|
@ -293,7 +293,7 @@
|
|||
accesskey="&keywordfield.accesskey;"
|
||||
oncommand="AddKeywordForSearchField();"/>
|
||||
<menuitem id="context-searchselect"
|
||||
oncommand="BrowserSearch.loadSearchFromContext(getBrowserSelection());"/>
|
||||
oncommand="BrowserSearch.loadSearchFromContext(this.searchTerms);"/>
|
||||
<menuitem id="context-shareselect"
|
||||
label="&shareSelectCmd.label;"
|
||||
accesskey="&shareSelectCmd.accesskey;"
|
||||
|
|
|
@ -32,7 +32,7 @@ nsContextMenu.prototype = {
|
|||
this.ellipsis = gPrefService.getComplexValue("intl.ellipsis",
|
||||
Ci.nsIPrefLocalizedString).data;
|
||||
} catch (e) { }
|
||||
this.isTextSelected = this.isTextSelection();
|
||||
|
||||
this.isContentSelected = this.isContentSelection();
|
||||
this.onPlainTextLink = false;
|
||||
|
||||
|
@ -268,19 +268,22 @@ nsContextMenu.prototype = {
|
|||
},
|
||||
|
||||
initMiscItems: function CM_initMiscItems() {
|
||||
var isTextSelected = this.isTextSelected;
|
||||
|
||||
// Use "Bookmark This Link" if on a link.
|
||||
this.showItem("context-bookmarkpage",
|
||||
!(this.isContentSelected || this.onTextInput || this.onLink ||
|
||||
this.onImage || this.onVideo || this.onAudio || this.onSocial));
|
||||
this.showItem("context-bookmarklink", (this.onLink && !this.onMailtoLink &&
|
||||
!this.onSocial) || this.onPlainTextLink);
|
||||
this.showItem("context-searchselect", isTextSelected);
|
||||
this.showItem("context-keywordfield",
|
||||
this.onTextInput && this.onKeywordField);
|
||||
this.showItem("frame", this.inFrame);
|
||||
|
||||
let showSearchSelect = (this.isTextSelected || this.onLink) && !this.onImage;
|
||||
this.showItem("context-searchselect", showSearchSelect);
|
||||
if (showSearchSelect) {
|
||||
this.formatSearchContextItem();
|
||||
}
|
||||
|
||||
// srcdoc cannot be opened separately due to concerns about web
|
||||
// content with about:srcdoc in location bar masquerading as trusted
|
||||
// chrome/addon content.
|
||||
|
@ -292,7 +295,7 @@ nsContextMenu.prototype = {
|
|||
this.showItem("context-bookmarkframe", !this.inSrcdocFrame);
|
||||
this.showItem("open-frame-sep", !this.inSrcdocFrame);
|
||||
|
||||
this.showItem("frame-sep", this.inFrame && isTextSelected);
|
||||
this.showItem("frame-sep", this.inFrame && this.isTextSelected);
|
||||
|
||||
// Hide menu entries for images, show otherwise
|
||||
if (this.inFrame) {
|
||||
|
@ -540,6 +543,8 @@ nsContextMenu.prototype = {
|
|||
this.isDesignMode = false;
|
||||
this.onCTPPlugin = false;
|
||||
this.canSpellCheck = false;
|
||||
this.textSelected = getBrowserSelection();
|
||||
this.isTextSelected = this.textSelected.length != 0;
|
||||
|
||||
// Remember the node that was clicked.
|
||||
this.target = aNode;
|
||||
|
@ -1442,39 +1447,6 @@ nsContextMenu.prototype = {
|
|||
return text;
|
||||
},
|
||||
|
||||
// Get selected text. Only display the first 15 chars.
|
||||
isTextSelection: function() {
|
||||
// Get 16 characters, so that we can trim the selection if it's greater
|
||||
// than 15 chars
|
||||
var selectedText = getBrowserSelection(16);
|
||||
|
||||
if (!selectedText)
|
||||
return false;
|
||||
|
||||
if (selectedText.length > 15)
|
||||
selectedText = selectedText.substr(0,15) + this.ellipsis;
|
||||
|
||||
// Use the current engine if the search bar is visible, the default
|
||||
// engine otherwise.
|
||||
var engineName = "";
|
||||
var ss = Cc["@mozilla.org/browser/search-service;1"].
|
||||
getService(Ci.nsIBrowserSearchService);
|
||||
if (isElementVisible(BrowserSearch.searchBar))
|
||||
engineName = ss.currentEngine.name;
|
||||
else
|
||||
engineName = ss.defaultEngine.name;
|
||||
|
||||
// format "Search <engine> for <selection>" string to show in menu
|
||||
var menuLabel = gNavigatorBundle.getFormattedString("contextMenuSearch",
|
||||
[engineName,
|
||||
selectedText]);
|
||||
document.getElementById("context-searchselect").label = menuLabel;
|
||||
document.getElementById("context-searchselect").accessKey =
|
||||
gNavigatorBundle.getString("contextMenuSearch.accesskey");
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
// Returns true if anything is selected.
|
||||
isContentSelection: function() {
|
||||
return !document.commandDispatcher.focusedWindow.getSelection().isCollapsed;
|
||||
|
@ -1688,5 +1660,34 @@ nsContextMenu.prototype = {
|
|||
if (this.onImage)
|
||||
return this.mediaURL;
|
||||
return "";
|
||||
},
|
||||
|
||||
// Formats the 'Search <engine> for "<selection or link text>"' context menu.
|
||||
formatSearchContextItem: function() {
|
||||
var menuItem = document.getElementById("context-searchselect");
|
||||
var selectedText = this.onLink ? this.linkText() : this.textSelected;
|
||||
|
||||
// Store searchTerms in context menu item so we know what to search onclick
|
||||
menuItem.searchTerms = selectedText;
|
||||
|
||||
if (selectedText.length > 15)
|
||||
selectedText = selectedText.substr(0,15) + this.ellipsis;
|
||||
|
||||
// Use the current engine if the search bar is visible, the default
|
||||
// engine otherwise.
|
||||
var engineName = "";
|
||||
var ss = Cc["@mozilla.org/browser/search-service;1"].
|
||||
getService(Ci.nsIBrowserSearchService);
|
||||
if (isElementVisible(BrowserSearch.searchBar))
|
||||
engineName = ss.currentEngine.name;
|
||||
else
|
||||
engineName = ss.defaultEngine.name;
|
||||
|
||||
// format "Search <engine> for <selection>" string to show in menu
|
||||
var menuLabel = gNavigatorBundle.getFormattedString("contextMenuSearch",
|
||||
[engineName,
|
||||
selectedText]);
|
||||
menuItem.label = menuLabel;
|
||||
menuItem.accessKey = gNavigatorBundle.getString("contextMenuSearch.accesskey");
|
||||
}
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ support-files =
|
|||
browser_bug479408_sample.html
|
||||
browser_bug678392-1.html
|
||||
browser_bug678392-2.html
|
||||
browser_bug970746.xhtml
|
||||
browser_registerProtocolHandler_notification.html
|
||||
browser_star_hsts.sjs
|
||||
browser_tab_dragdrop2_frame1.xul
|
||||
|
@ -202,6 +203,7 @@ skip-if = os == "mac" # Intermittent failures, bug 925225
|
|||
[browser_bug882977.js]
|
||||
[browser_bug902156.js]
|
||||
[browser_bug906190.js]
|
||||
[browser_bug970746.js]
|
||||
[browser_canonizeURL.js]
|
||||
[browser_contentAreaClick.js]
|
||||
[browser_contextSearchTabPosition.js]
|
||||
|
|
|
@ -0,0 +1,104 @@
|
|||
/* Make sure context menu includes option to search hyperlink text on search engine */
|
||||
|
||||
function test() {
|
||||
waitForExplicitFinish();
|
||||
|
||||
gBrowser.selectedTab = gBrowser.addTab();
|
||||
|
||||
gBrowser.selectedBrowser.addEventListener("load", function() {
|
||||
gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
|
||||
|
||||
let doc = gBrowser.contentDocument;
|
||||
let contentAreaContextMenu = document.getElementById("contentAreaContextMenu");
|
||||
let ellipsis = "\u2026";
|
||||
|
||||
// Tests if the "Search <engine> for '<some terms>'" context menu item is shown for the
|
||||
// given query string of an element. Tests to make sure label includes the proper search terms.
|
||||
//
|
||||
// Options:
|
||||
//
|
||||
// id: The id of the element to test.
|
||||
// isSelected: Flag to enable selection (text hilight) the contents of the element
|
||||
// shouldBeShown: The display state of the menu item
|
||||
// expectedLabelContents: The menu item label should contain a portion of this string.
|
||||
// Will only be tested if shouldBeShown is true.
|
||||
|
||||
let testElement = function(opts) {
|
||||
let element = doc.getElementById(opts.id);
|
||||
document.popupNode = element;
|
||||
|
||||
let selection = content.getSelection();
|
||||
selection.removeAllRanges();
|
||||
|
||||
if(opts.isSelected) {
|
||||
selection.selectAllChildren(element);
|
||||
}
|
||||
|
||||
let contextMenu = new nsContextMenu(contentAreaContextMenu);
|
||||
let menuItem = document.getElementById("context-searchselect");
|
||||
|
||||
is(document.getElementById("context-searchselect").hidden, !opts.shouldBeShown, "search context menu item is shown for '#" + opts.id + "' and selected is '" + opts.isSelected + "'");
|
||||
|
||||
if(opts.shouldBeShown) {
|
||||
ok(menuItem.label.contains(opts.expectedLabelContents), "Menu item text '" + menuItem.label + "' contains the correct search terms '" + opts.expectedLabelContents + "'");
|
||||
}
|
||||
}
|
||||
|
||||
testElement({
|
||||
id: "link",
|
||||
isSelected: true,
|
||||
shouldBeShown: true,
|
||||
expectedLabelContents: "I'm a link!",
|
||||
});
|
||||
testElement({
|
||||
id: "link",
|
||||
isSelected: false,
|
||||
shouldBeShown: true,
|
||||
expectedLabelContents: "I'm a link!",
|
||||
});
|
||||
|
||||
testElement({
|
||||
id: "longLink",
|
||||
isSelected: true,
|
||||
shouldBeShown: true,
|
||||
expectedLabelContents: "I'm a really lo" + ellipsis,
|
||||
});
|
||||
testElement({
|
||||
id: "longLink",
|
||||
isSelected: false,
|
||||
shouldBeShown: true,
|
||||
expectedLabelContents: "I'm a really lo" + ellipsis,
|
||||
});
|
||||
|
||||
testElement({
|
||||
id: "plainText",
|
||||
isSelected: true,
|
||||
shouldBeShown: true,
|
||||
expectedLabelContents: "Right clicking " + ellipsis,
|
||||
});
|
||||
testElement({
|
||||
id: "plainText",
|
||||
isSelected: false,
|
||||
shouldBeShown: false,
|
||||
});
|
||||
|
||||
testElement({
|
||||
id: "mixedContent",
|
||||
isSelected: true,
|
||||
shouldBeShown: true,
|
||||
expectedLabelContents: "I'm some text, " + ellipsis,
|
||||
});
|
||||
testElement({
|
||||
id: "mixedContent",
|
||||
isSelected: false,
|
||||
shouldBeShown: false,
|
||||
});
|
||||
|
||||
// cleanup
|
||||
document.popupNode = null;
|
||||
gBrowser.removeCurrentTab();
|
||||
finish();
|
||||
}, true);
|
||||
|
||||
content.location = "http://mochi.test:8888/browser/browser/base/content/test/general/browser_bug970746.xhtml";
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<html xmlns="http://www.w3.org/1999/xhtml">
|
||||
<body>
|
||||
<a href="http://mozilla.org" id="link">I'm a link!</a>
|
||||
<a href="http://mozilla.org" id="longLink">I'm a really long link and I should be truncated.</a>
|
||||
|
||||
<span id="plainText">
|
||||
Right clicking me when I'm selected should show the menu item.
|
||||
</span>
|
||||
<span id="mixedContent">
|
||||
I'm some text, and <a href="http://mozilla.org">I'm a link!</a>
|
||||
</span>
|
||||
</body>
|
||||
</html>
|
|
@ -118,7 +118,8 @@ function runTest(testNum) {
|
|||
"---", null,
|
||||
"context-bookmarklink", true,
|
||||
"context-savelink", true,
|
||||
"context-copylink", true
|
||||
"context-copylink", true,
|
||||
"context-searchselect", true
|
||||
].concat(inspectItems));
|
||||
} else {
|
||||
checkContextMenu(["context-openlinkintab", true,
|
||||
|
@ -126,7 +127,8 @@ function runTest(testNum) {
|
|||
"---", null,
|
||||
"context-bookmarklink", true,
|
||||
"context-savelink", true,
|
||||
"context-copylink", true
|
||||
"context-copylink", true,
|
||||
"context-searchselect", true
|
||||
].concat(inspectItems));
|
||||
}
|
||||
closeContextMenu();
|
||||
|
@ -135,7 +137,9 @@ function runTest(testNum) {
|
|||
|
||||
case 4:
|
||||
// Context menu for text mailto-link
|
||||
checkContextMenu(["context-copyemail", true].concat(inspectItems));
|
||||
checkContextMenu(["context-copyemail", true,
|
||||
"context-searchselect", true
|
||||
].concat(inspectItems));
|
||||
closeContextMenu();
|
||||
openContextMenuFor(img); // Invoke context menu for next test.
|
||||
break;
|
||||
|
|
|
@ -167,7 +167,7 @@ DistributionCustomizer.prototype = {
|
|||
, index: index
|
||||
, feedURI: this._makeURI(items[iid]["feedLink"])
|
||||
, siteURI: this._makeURI(items[iid]["siteLink"])
|
||||
});
|
||||
}).then(null, Cu.reportError);
|
||||
break;
|
||||
|
||||
case "bookmark":
|
||||
|
|
|
@ -251,24 +251,20 @@ var BookmarkPropertiesPanel = {
|
|||
|
||||
case "folder":
|
||||
this._itemType = BOOKMARK_FOLDER;
|
||||
PlacesUtils.livemarks.getLivemark(
|
||||
{ id: this._itemId },
|
||||
(function (aStatus, aLivemark) {
|
||||
if (Components.isSuccessCode(aStatus)) {
|
||||
this._itemType = LIVEMARK_CONTAINER;
|
||||
this._feedURI = aLivemark.feedURI;
|
||||
this._siteURI = aLivemark.siteURI;
|
||||
this._fillEditProperties();
|
||||
PlacesUtils.livemarks.getLivemark({ id: this._itemId })
|
||||
.then(aLivemark => {
|
||||
this._itemType = LIVEMARK_CONTAINER;
|
||||
this._feedURI = aLivemark.feedURI;
|
||||
this._siteURI = aLivemark.siteURI;
|
||||
this._fillEditProperties();
|
||||
|
||||
let acceptButton = document.documentElement.getButton("accept");
|
||||
acceptButton.disabled = !this._inputIsValid();
|
||||
let acceptButton = document.documentElement.getButton("accept");
|
||||
acceptButton.disabled = !this._inputIsValid();
|
||||
|
||||
let newHeight = window.outerHeight +
|
||||
this._element("descriptionField").boxObject.height;
|
||||
window.resizeTo(window.outerWidth, newHeight);
|
||||
}
|
||||
}).bind(this)
|
||||
);
|
||||
let newHeight = window.outerHeight +
|
||||
this._element("descriptionField").boxObject.height;
|
||||
window.resizeTo(window.outerWidth, newHeight);
|
||||
}, () => undefined);
|
||||
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -317,21 +317,17 @@ PlacesViewBase.prototype = {
|
|||
element.setAttribute("hostContainer", "true");
|
||||
}
|
||||
else if (itemId != -1) {
|
||||
PlacesUtils.livemarks.getLivemark(
|
||||
{ id: itemId },
|
||||
function (aStatus, aLivemark) {
|
||||
if (Components.isSuccessCode(aStatus)) {
|
||||
element.setAttribute("livemark", "true");
|
||||
PlacesUtils.livemarks.getLivemark({ id: itemId })
|
||||
.then(aLivemark => {
|
||||
element.setAttribute("livemark", "true");
|
||||
#ifdef XP_MACOSX
|
||||
// OS X native menubar doesn't track list-style-images since
|
||||
// it doesn't have a frame (bug 733415). Thus enforce updating.
|
||||
element.setAttribute("image", "");
|
||||
element.removeAttribute("image");
|
||||
// OS X native menubar doesn't track list-style-images since
|
||||
// it doesn't have a frame (bug 733415). Thus enforce updating.
|
||||
element.setAttribute("image", "");
|
||||
element.removeAttribute("image");
|
||||
#endif
|
||||
this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
|
||||
}, () => undefined);
|
||||
}
|
||||
|
||||
let popup = document.createElement("menupopup");
|
||||
|
@ -509,16 +505,12 @@ PlacesViewBase.prototype = {
|
|||
#endif
|
||||
}
|
||||
|
||||
PlacesUtils.livemarks.getLivemark(
|
||||
{ id: aPlacesNode.itemId },
|
||||
function (aStatus, aLivemark) {
|
||||
if (Components.isSuccessCode(aStatus)) {
|
||||
// Controller will use this to build the meta data for the node.
|
||||
this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
|
||||
this.invalidateContainer(aPlacesNode);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
|
||||
.then(aLivemark => {
|
||||
// Controller will use this to build the meta data for the node.
|
||||
this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
|
||||
this.invalidateContainer(aPlacesNode);
|
||||
}, () => undefined);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -647,26 +639,23 @@ PlacesViewBase.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId },
|
||||
function (aStatus, aLivemark) {
|
||||
if (Components.isSuccessCode(aStatus)) {
|
||||
let shouldInvalidate =
|
||||
!this.controller.hasCachedLivemarkInfo(aPlacesNode);
|
||||
this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
|
||||
if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED) {
|
||||
aLivemark.registerForUpdates(aPlacesNode, this);
|
||||
// Prioritize the current livemark.
|
||||
aLivemark.reload();
|
||||
PlacesUtils.livemarks.reloadLivemarks();
|
||||
if (shouldInvalidate)
|
||||
this.invalidateContainer(aPlacesNode);
|
||||
}
|
||||
else {
|
||||
aLivemark.unregisterForUpdates(aPlacesNode);
|
||||
}
|
||||
PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
|
||||
.then(aLivemark => {
|
||||
let shouldInvalidate =
|
||||
!this.controller.hasCachedLivemarkInfo(aPlacesNode);
|
||||
this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
|
||||
if (aNewState == Ci.nsINavHistoryContainerResultNode.STATE_OPENED) {
|
||||
aLivemark.registerForUpdates(aPlacesNode, this);
|
||||
// Prioritize the current livemark.
|
||||
aLivemark.reload();
|
||||
PlacesUtils.livemarks.reloadLivemarks();
|
||||
if (shouldInvalidate)
|
||||
this.invalidateContainer(aPlacesNode);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
else {
|
||||
aLivemark.unregisterForUpdates(aPlacesNode);
|
||||
}
|
||||
}, () => undefined);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -678,10 +667,10 @@ PlacesViewBase.prototype = {
|
|||
if (aPopup._startMarker.nextSibling == aPopup._endMarker)
|
||||
this._setLivemarkStatusMenuItem(aPopup, Ci.mozILivemark.STATUS_LOADING);
|
||||
|
||||
PlacesUtils.livemarks.getLivemark({ id: aPopup._placesNode.itemId },
|
||||
function (aStatus, aLivemark) {
|
||||
PlacesUtils.livemarks.getLivemark({ id: aPopup._placesNode.itemId })
|
||||
.then(aLivemark => {
|
||||
let placesNode = aPopup._placesNode;
|
||||
if (!Components.isSuccessCode(aStatus) || !placesNode.containerOpen)
|
||||
if (!placesNode.containerOpen)
|
||||
return;
|
||||
|
||||
if (aLivemark.status != Ci.mozILivemark.STATUS_LOADING)
|
||||
|
@ -698,8 +687,7 @@ PlacesViewBase.prototype = {
|
|||
else
|
||||
this._getDOMNodeForPlacesNode(child).removeAttribute("visited");
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
}, Components.utils.reportError);
|
||||
},
|
||||
|
||||
invalidateContainer: function PVB_invalidateContainer(aPlacesNode) {
|
||||
|
@ -1008,15 +996,11 @@ PlacesToolbar.prototype = {
|
|||
button.setAttribute("tagContainer", "true");
|
||||
}
|
||||
else if (PlacesUtils.nodeIsFolder(aChild)) {
|
||||
PlacesUtils.livemarks.getLivemark(
|
||||
{ id: aChild.itemId },
|
||||
function (aStatus, aLivemark) {
|
||||
if (Components.isSuccessCode(aStatus)) {
|
||||
button.setAttribute("livemark", "true");
|
||||
this.controller.cacheLivemarkInfo(aChild, aLivemark);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
PlacesUtils.livemarks.getLivemark({ id: aChild.itemId })
|
||||
.then(aLivemark => {
|
||||
button.setAttribute("livemark", "true");
|
||||
this.controller.cacheLivemarkInfo(aChild, aLivemark);
|
||||
}, () => undefined);
|
||||
}
|
||||
|
||||
let popup = document.createElement("menupopup");
|
||||
|
@ -1268,15 +1252,11 @@ PlacesToolbar.prototype = {
|
|||
if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
|
||||
elt.setAttribute("livemark", true);
|
||||
|
||||
PlacesUtils.livemarks.getLivemark(
|
||||
{ id: aPlacesNode.itemId },
|
||||
function (aStatus, aLivemark) {
|
||||
if (Components.isSuccessCode(aStatus)) {
|
||||
this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
|
||||
this.invalidateContainer(aPlacesNode);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
|
||||
.then(aLivemark => {
|
||||
this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
|
||||
this.invalidateContainer(aPlacesNode);
|
||||
}, Components.utils.reportError);
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
@ -1840,15 +1820,11 @@ PlacesPanelMenuView.prototype = {
|
|||
button.setAttribute("tagContainer", "true");
|
||||
}
|
||||
else if (PlacesUtils.nodeIsFolder(aChild)) {
|
||||
PlacesUtils.livemarks.getLivemark(
|
||||
{ id: aChild.itemId },
|
||||
function (aStatus, aLivemark) {
|
||||
if (Components.isSuccessCode(aStatus)) {
|
||||
button.setAttribute("livemark", "true");
|
||||
this.controller.cacheLivemarkInfo(aChild, aLivemark);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
PlacesUtils.livemarks.getLivemark({ id: aChild.itemId })
|
||||
.then(aLivemark => {
|
||||
button.setAttribute("livemark", "true");
|
||||
this.controller.cacheLivemarkInfo(aChild, aLivemark);
|
||||
}, () => undefined);
|
||||
}
|
||||
}
|
||||
else if (PlacesUtils.nodeIsURI(aChild)) {
|
||||
|
@ -1912,15 +1888,11 @@ PlacesPanelMenuView.prototype = {
|
|||
if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
|
||||
elt.setAttribute("livemark", true);
|
||||
|
||||
PlacesUtils.livemarks.getLivemark(
|
||||
{ id: aPlacesNode.itemId },
|
||||
function (aStatus, aLivemark) {
|
||||
if (Components.isSuccessCode(aStatus)) {
|
||||
this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
|
||||
this.invalidateContainer(aPlacesNode);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
PlacesUtils.livemarks.getLivemark({ id: aPlacesNode.itemId })
|
||||
.then(aLivemark => {
|
||||
this.controller.cacheLivemarkInfo(aPlacesNode, aLivemark);
|
||||
this.invalidateContainer(aPlacesNode);
|
||||
}, Components.utils.reportError);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -694,14 +694,10 @@ PlacesController.prototype = {
|
|||
var selectedNode = this._view.selectedNode;
|
||||
if (selectedNode) {
|
||||
let itemId = selectedNode.itemId;
|
||||
PlacesUtils.livemarks.getLivemark(
|
||||
{ id: itemId },
|
||||
(function(aStatus, aLivemark) {
|
||||
if (Components.isSuccessCode(aStatus)) {
|
||||
aLivemark.reload(true);
|
||||
}
|
||||
}).bind(this)
|
||||
);
|
||||
PlacesUtils.livemarks.getLivemark({ id: itemId })
|
||||
.then(aLivemark => {
|
||||
aLivemark.reload(true);
|
||||
}, Components.utils.reportError);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -147,17 +147,13 @@ var gEditItemOverlay = {
|
|||
else {
|
||||
this._uri = null;
|
||||
this._isLivemark = false;
|
||||
PlacesUtils.livemarks.getLivemark(
|
||||
{id: this._itemId },
|
||||
(function (aStatus, aLivemark) {
|
||||
if (Components.isSuccessCode(aStatus)) {
|
||||
this._isLivemark = true;
|
||||
this._initTextField("feedLocationField", aLivemark.feedURI.spec, true);
|
||||
this._initTextField("siteLocationField", aLivemark.siteURI ? aLivemark.siteURI.spec : "", true);
|
||||
this._showHideRows();
|
||||
}
|
||||
}).bind(this)
|
||||
);
|
||||
PlacesUtils.livemarks.getLivemark({id: this._itemId })
|
||||
.then(aLivemark => {
|
||||
this._isLivemark = true;
|
||||
this._initTextField("feedLocationField", aLivemark.feedURI.spec, true);
|
||||
this._initTextField("siteLocationField", aLivemark.siteURI ? aLivemark.siteURI.spec : "", true);
|
||||
this._showHideRows();
|
||||
}, () => undefined);
|
||||
}
|
||||
|
||||
// folder picker
|
||||
|
|
|
@ -326,10 +326,13 @@ var PlacesOrganizer = {
|
|||
},
|
||||
|
||||
openFlatContainer: function PO_openFlatContainerFlatContainer(aContainer) {
|
||||
if (aContainer.itemId != -1)
|
||||
if (aContainer.itemId != -1) {
|
||||
PlacesUtils.asContainer(this._places.selectedNode).containerOpen = true;
|
||||
this._places.selectItems([aContainer.itemId], false);
|
||||
else if (PlacesUtils.nodeIsQuery(aContainer))
|
||||
}
|
||||
else if (PlacesUtils.nodeIsQuery(aContainer)) {
|
||||
this._places.selectPlaceURI(aContainer.uri);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -786,11 +786,11 @@ PlacesTreeView.prototype = {
|
|||
},
|
||||
|
||||
_populateLivemarkContainer: function PTV__populateLivemarkContainer(aNode) {
|
||||
PlacesUtils.livemarks.getLivemark({ id: aNode.itemId },
|
||||
function (aStatus, aLivemark) {
|
||||
PlacesUtils.livemarks.getLivemark({ id: aNode.itemId })
|
||||
.then(aLivemark => {
|
||||
let placesNode = aNode;
|
||||
// Need to check containerOpen since getLivemark is async.
|
||||
if (!Components.isSuccessCode(aStatus) || !placesNode.containerOpen)
|
||||
if (!placesNode.containerOpen)
|
||||
return;
|
||||
|
||||
let children = aLivemark.getNodesForContainer(placesNode);
|
||||
|
@ -798,7 +798,7 @@ PlacesTreeView.prototype = {
|
|||
let child = children[i];
|
||||
this.nodeInserted(placesNode, child, i);
|
||||
}
|
||||
}.bind(this));
|
||||
}, Components.utils.reportError);
|
||||
},
|
||||
|
||||
nodeTitleChanged: function PTV_nodeTitleChanged(aNode, aNewTitle) {
|
||||
|
@ -847,19 +847,15 @@ PlacesTreeView.prototype = {
|
|||
this._invalidateCellValue(aNode, this.COLUMN_TYPE_DESCRIPTION);
|
||||
}
|
||||
else if (aAnno == PlacesUtils.LMANNO_FEEDURI) {
|
||||
PlacesUtils.livemarks.getLivemark(
|
||||
{ id: aNode.itemId },
|
||||
function (aStatus, aLivemark) {
|
||||
if (Components.isSuccessCode(aStatus)) {
|
||||
this._controller.cacheLivemarkInfo(aNode, aLivemark);
|
||||
let properties = this._cellProperties.get(aNode);
|
||||
this._cellProperties.set(aNode, properties += " livemark ");
|
||||
PlacesUtils.livemarks.getLivemark({ id: aNode.itemId })
|
||||
.then(aLivemark => {
|
||||
this._controller.cacheLivemarkInfo(aNode, aLivemark);
|
||||
let properties = this._cellProperties.get(aNode);
|
||||
this._cellProperties.set(aNode, properties += " livemark ");
|
||||
|
||||
// The livemark attribute is set as a cell property on the title cell.
|
||||
this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
// The livemark attribute is set as a cell property on the title cell.
|
||||
this._invalidateCellValue(aNode, this.COLUMN_TYPE_TITLE);
|
||||
}, Components.utils.reportError);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -883,26 +879,23 @@ PlacesTreeView.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
PlacesUtils.livemarks.getLivemark({ id: aNode.itemId },
|
||||
function (aStatus, aLivemark) {
|
||||
if (Components.isSuccessCode(aStatus)) {
|
||||
let shouldInvalidate =
|
||||
!this._controller.hasCachedLivemarkInfo(aNode);
|
||||
this._controller.cacheLivemarkInfo(aNode, aLivemark);
|
||||
if (aNewState == Components.interfaces.nsINavHistoryContainerResultNode.STATE_OPENED) {
|
||||
aLivemark.registerForUpdates(aNode, this);
|
||||
// Prioritize the current livemark.
|
||||
aLivemark.reload();
|
||||
PlacesUtils.livemarks.reloadLivemarks();
|
||||
if (shouldInvalidate)
|
||||
this.invalidateContainer(aNode);
|
||||
}
|
||||
else {
|
||||
aLivemark.unregisterForUpdates(aNode);
|
||||
}
|
||||
PlacesUtils.livemarks.getLivemark({ id: aNode.itemId })
|
||||
.then(aLivemark => {
|
||||
let shouldInvalidate =
|
||||
!this._controller.hasCachedLivemarkInfo(aNode);
|
||||
this._controller.cacheLivemarkInfo(aNode, aLivemark);
|
||||
if (aNewState == Components.interfaces.nsINavHistoryContainerResultNode.STATE_OPENED) {
|
||||
aLivemark.registerForUpdates(aNode, this);
|
||||
// Prioritize the current livemark.
|
||||
aLivemark.reload();
|
||||
PlacesUtils.livemarks.reloadLivemarks();
|
||||
if (shouldInvalidate)
|
||||
this.invalidateContainer(aNode);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
else {
|
||||
aLivemark.unregisterForUpdates(aNode);
|
||||
}
|
||||
}, () => undefined);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -1174,17 +1167,13 @@ PlacesTreeView.prototype = {
|
|||
properties += " livemark";
|
||||
}
|
||||
else {
|
||||
PlacesUtils.livemarks.getLivemark(
|
||||
{ id: node.itemId },
|
||||
function (aStatus, aLivemark) {
|
||||
if (Components.isSuccessCode(aStatus)) {
|
||||
this._controller.cacheLivemarkInfo(node, aLivemark);
|
||||
properties += " livemark";
|
||||
// The livemark attribute is set as a cell property on the title cell.
|
||||
this._invalidateCellValue(node, this.COLUMN_TYPE_TITLE);
|
||||
}
|
||||
}.bind(this)
|
||||
);
|
||||
PlacesUtils.livemarks.getLivemark({ id: node.itemId })
|
||||
.then(aLivemark => {
|
||||
this._controller.cacheLivemarkInfo(node, aLivemark);
|
||||
properties += " livemark";
|
||||
// The livemark attribute is set as a cell property on the title cell.
|
||||
this._invalidateCellValue(node, this.COLUMN_TYPE_TITLE);
|
||||
}, () => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,3 +47,4 @@ skip-if = true
|
|||
[browser_library_left_pane_select_hierarchy.js]
|
||||
[browser_435851_copy_query.js]
|
||||
[browser_toolbarbutton_menu_context.js]
|
||||
[browser_library_openFlatContainer.js]
|
||||
|
|
|
@ -0,0 +1,42 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/
|
||||
*/
|
||||
|
||||
/**
|
||||
* Test opening a flat container in the right pane even if its parent in the
|
||||
* left pane is closed.
|
||||
*/
|
||||
|
||||
add_task(function* () {
|
||||
let folder = PlacesUtils.bookmarks
|
||||
.createFolder(PlacesUtils.unfiledBookmarksFolderId,
|
||||
"Folder",
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX);
|
||||
let bookmark = PlacesUtils.bookmarks
|
||||
.insertBookmark(folder, NetUtil.newURI("http://example.com/"),
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
"Bookmark");
|
||||
|
||||
let library = yield promiseLibrary("AllBookmarks");
|
||||
registerCleanupFunction(function () {
|
||||
library.close();
|
||||
PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
|
||||
});
|
||||
|
||||
// Select unfiled later, to ensure it's closed.
|
||||
library.PlacesOrganizer.selectLeftPaneQuery("UnfiledBookmarks");
|
||||
ok(!library.PlacesOrganizer._places.selectedNode.containerOpen,
|
||||
"Unfiled container is closed");
|
||||
|
||||
let folderNode = library.ContentTree.view.view.nodeForTreeIndex(0);
|
||||
is(folderNode.itemId, folder,
|
||||
"Found the expected folder in the right pane");
|
||||
// Select the folder node in the right pane.
|
||||
library.ContentTree.view.selectNode(folderNode);
|
||||
|
||||
synthesizeClickOnSelectedTreeCell(library.ContentTree.view,
|
||||
{ clickCount: 2 });
|
||||
|
||||
is(library.ContentTree.view.view.nodeForTreeIndex(0).itemId, bookmark,
|
||||
"Found the expected bookmark in the right pane");
|
||||
});
|
|
@ -119,27 +119,6 @@ function test() {
|
|||
}, true);
|
||||
}
|
||||
|
||||
function synthesizeClickOnSelectedTreeCell(aTree) {
|
||||
let tbo = aTree.treeBoxObject;
|
||||
is(tbo.view.selection.count, 1,
|
||||
"The test node should be successfully selected");
|
||||
// Get selection rowID.
|
||||
let min = {}, max = {};
|
||||
tbo.view.selection.getRangeAt(0, min, max);
|
||||
let rowID = min.value;
|
||||
tbo.ensureRowIsVisible(rowID);
|
||||
|
||||
// Calculate the click coordinates.
|
||||
let x = {}, y = {}, width = {}, height = {};
|
||||
tbo.getCoordsForCellItem(rowID, aTree.columns[0], "text",
|
||||
x, y, width, height);
|
||||
x = x.value + width.value / 2;
|
||||
y = y.value + height.value / 2;
|
||||
// Simulate the click.
|
||||
EventUtils.synthesizeMouse(aTree.body, x, y, {},
|
||||
aTree.ownerDocument.defaultView);
|
||||
}
|
||||
|
||||
function changeSidebarDirection(aDirection) {
|
||||
sidebar.contentDocument.documentElement.style.direction = aDirection;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
Components.utils.import("resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||
|
||||
// We need to cache this before test runs...
|
||||
let cachedLeftPaneFolderIdGetter;
|
||||
|
@ -182,6 +186,22 @@ function addVisits(aPlaceInfo, aWindow, aCallback, aStack) {
|
|||
);
|
||||
}
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/commonjs/sdk/core/promise.js");
|
||||
|
||||
function synthesizeClickOnSelectedTreeCell(aTree, aOptions) {
|
||||
let tbo = aTree.treeBoxObject;
|
||||
if (tbo.view.selection.count != 1)
|
||||
throw new Error("The test node should be successfully selected");
|
||||
// Get selection rowID.
|
||||
let min = {}, max = {};
|
||||
tbo.view.selection.getRangeAt(0, min, max);
|
||||
let rowID = min.value;
|
||||
tbo.ensureRowIsVisible(rowID);
|
||||
// Calculate the click coordinates.
|
||||
let x = {}, y = {}, width = {}, height = {};
|
||||
tbo.getCoordsForCellItem(rowID, aTree.columns[0], "text",
|
||||
x, y, width, height);
|
||||
x = x.value + width.value / 2;
|
||||
y = y.value + height.value / 2;
|
||||
// Simulate the click.
|
||||
EventUtils.synthesizeMouse(aTree.body, x, y, aOptions || {},
|
||||
aTree.ownerDocument.defaultView);
|
||||
}
|
||||
|
|
|
@ -11,8 +11,8 @@ Services.prefs.setBoolPref("devtools.debugger.log", true);
|
|||
let gProcess;
|
||||
|
||||
function test() {
|
||||
// Windows XP test slaves are terribly slow at this test.
|
||||
requestLongerTimeout(4);
|
||||
// Windows XP and 8.1 test slaves are terribly slow at this test.
|
||||
requestLongerTimeout(5);
|
||||
|
||||
initChromeDebugger(aOnClose).then(aProcess => {
|
||||
gProcess = aProcess;
|
||||
|
|
|
@ -217,10 +217,14 @@ toolbarbutton[sdk-button="true"][cui-areatype="menu-panel"] > .toolbarbutton-ico
|
|||
-moz-box-orient: vertical;
|
||||
width: calc(@menuPanelButtonWidth@ - 2px);
|
||||
height: calc(49px + 2.2em);
|
||||
margin-top: 3px; /* Hack needed to get type=menu-button to properly align vertically. */
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.panelUI-grid .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-text,
|
||||
.panelUI-grid .toolbarbutton-1 > .toolbarbutton-menubutton-button > .toolbarbutton-multiline-text {
|
||||
margin-top: 2px; /* Hack needed to get the label of type=menu-button aligned with other buttons */
|
||||
}
|
||||
|
||||
.panel-customization-placeholder-child {
|
||||
margin: 6px 0 0;
|
||||
padding: 2px 6px;
|
||||
|
|
|
@ -141,7 +141,7 @@ HTTP(..) == bug533251.html bug533251-ref.html
|
|||
HTTP(..) == font-familiy-whitespace-1.html font-familiy-whitespace-1-ref.html
|
||||
HTTP(..) != font-familiy-whitespace-1.html font-familiy-whitespace-1-notref.html
|
||||
|
||||
skip-if(B2G) fails-if(Android) HTTP(..) == ivs-1.html ivs-1-ref.html # bug 773482
|
||||
skip-if(B2G) HTTP(..) == ivs-1.html ivs-1-ref.html # bug 773482
|
||||
|
||||
skip-if(B2G) HTTP(..) == missing-names.html missing-names-ref.html # bug 773482
|
||||
|
||||
|
|
|
@ -846,3 +846,7 @@ pref("home.sync.updateMode", 0);
|
|||
|
||||
// How frequently to check if we should sync home provider data.
|
||||
pref("home.sync.checkIntervalSecs", 3600);
|
||||
|
||||
#ifdef NIGHTLY_BUILD
|
||||
pref("devtools.debugger.remote-enabled", true);
|
||||
#endif
|
||||
|
|
|
@ -42,7 +42,6 @@ import org.mozilla.gecko.home.SearchEngine;
|
|||
import org.mozilla.gecko.menu.GeckoMenu;
|
||||
import org.mozilla.gecko.preferences.GeckoPreferences;
|
||||
import org.mozilla.gecko.prompts.Prompt;
|
||||
import org.mozilla.gecko.prompts.PromptListItem;
|
||||
import org.mozilla.gecko.sync.setup.SyncAccounts;
|
||||
import org.mozilla.gecko.toolbar.AutocompleteHandler;
|
||||
import org.mozilla.gecko.toolbar.BrowserToolbar;
|
||||
|
@ -1447,7 +1446,7 @@ abstract public class BrowserApp extends GeckoApp
|
|||
// If we failed to load a favicon, we use the default favicon instead.
|
||||
Tabs.getInstance()
|
||||
.updateFaviconForURL(pageUrl,
|
||||
(favicon == null) ? Favicons.sDefaultFavicon : favicon);
|
||||
(favicon == null) ? Favicons.defaultFavicon : favicon);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1694,16 +1693,22 @@ abstract public class BrowserApp extends GeckoApp
|
|||
mHomePager = (HomePager) homePagerStub.inflate();
|
||||
|
||||
final HomeBanner homeBanner = (HomeBanner) findViewById(R.id.home_banner);
|
||||
mHomePager.setBanner(homeBanner);
|
||||
|
||||
// Remove the banner from the view hierarchy if it is dismissed.
|
||||
homeBanner.setOnDismissListener(new HomeBanner.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss() {
|
||||
mHomePager.setBanner(null);
|
||||
mHomePagerContainer.removeView(homeBanner);
|
||||
}
|
||||
});
|
||||
// Never show the home banner in guest mode.
|
||||
if (GeckoProfile.get(this).inGuestMode()) {
|
||||
mHomePagerContainer.removeView(homeBanner);
|
||||
} else {
|
||||
mHomePager.setBanner(homeBanner);
|
||||
|
||||
// Remove the banner from the view hierarchy if it is dismissed.
|
||||
homeBanner.setOnDismissListener(new HomeBanner.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss() {
|
||||
mHomePager.setBanner(null);
|
||||
mHomePagerContainer.removeView(homeBanner);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
mHomePagerContainer.setVisibility(View.VISIBLE);
|
||||
|
|
|
@ -1121,10 +1121,13 @@ public class GeckoAppShell
|
|||
/**
|
||||
* Given the inputs to <code>getOpenURIIntent</code>, plus an optional
|
||||
* package name and class name, create and fire an intent to open the
|
||||
* provided URI.
|
||||
* provided URI. If a class name is specified but a package name is not,
|
||||
* we will default to using the current fennec package.
|
||||
*
|
||||
* @param targetURI the string spec of the URI to open.
|
||||
* @param mimeType an optional MIME type string.
|
||||
* @param packageName an optional app package name.
|
||||
* @param className an optional intent class name.
|
||||
* @param action an Android action specifier, such as
|
||||
* <code>Intent.ACTION_SEND</code>.
|
||||
* @param title the title to use in <code>ACTION_SEND</code> intents.
|
||||
|
@ -1145,8 +1148,13 @@ public class GeckoAppShell
|
|||
return false;
|
||||
}
|
||||
|
||||
if (packageName.length() > 0 && className.length() > 0) {
|
||||
intent.setClassName(packageName, className);
|
||||
if (!TextUtils.isEmpty(className)) {
|
||||
if (!TextUtils.isEmpty(packageName)) {
|
||||
intent.setClassName(packageName, className);
|
||||
} else {
|
||||
// Default to using the fennec app context.
|
||||
intent.setClassName(context, className);
|
||||
}
|
||||
}
|
||||
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
|
|
|
@ -13,7 +13,6 @@ import org.mozilla.gecko.Tab;
|
|||
import org.mozilla.gecko.Tabs;
|
||||
import org.mozilla.gecko.db.BrowserDB;
|
||||
import org.mozilla.gecko.favicons.cache.FaviconCache;
|
||||
import org.mozilla.gecko.gfx.BitmapUtils;
|
||||
import org.mozilla.gecko.util.GeckoJarReader;
|
||||
import org.mozilla.gecko.util.NonEvictingLruCache;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
|
@ -29,12 +28,9 @@ import android.util.SparseArray;
|
|||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
public class Favicons {
|
||||
private static final String LOGTAG = "GeckoFavicons";
|
||||
|
@ -53,25 +49,25 @@ public class Favicons {
|
|||
public static final int FLAG_PERSIST = 2;
|
||||
public static final int FLAG_SCALE = 4;
|
||||
|
||||
protected static Context sContext;
|
||||
protected static Context context;
|
||||
|
||||
// The default Favicon to show if no other can be found.
|
||||
public static Bitmap sDefaultFavicon;
|
||||
public static Bitmap defaultFavicon;
|
||||
|
||||
// The density-adjusted default Favicon dimensions.
|
||||
public static int sDefaultFaviconSize;
|
||||
public static int defaultFaviconSize;
|
||||
|
||||
// The density-adjusted maximum Favicon dimensions.
|
||||
public static int sLargestFaviconSize;
|
||||
public static int largestFaviconSize;
|
||||
|
||||
private static final SparseArray<LoadFaviconTask> sLoadTasks = new SparseArray<LoadFaviconTask>();
|
||||
private static final SparseArray<LoadFaviconTask> loadTasks = new SparseArray<LoadFaviconTask>();
|
||||
|
||||
// Cache to hold mappings between page URLs and Favicon URLs. Used to avoid going to the DB when
|
||||
// doing so is not necessary.
|
||||
private static final NonEvictingLruCache<String, String> sPageURLMappings = new NonEvictingLruCache<String, String>(NUM_PAGE_URL_MAPPINGS_TO_STORE);
|
||||
private static final NonEvictingLruCache<String, String> pageURLMappings = new NonEvictingLruCache<String, String>(NUM_PAGE_URL_MAPPINGS_TO_STORE);
|
||||
|
||||
public static String getFaviconURLForPageURLFromCache(String pageURL) {
|
||||
return sPageURLMappings.get(pageURL);
|
||||
return pageURLMappings.get(pageURL);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -79,10 +75,10 @@ public class Favicons {
|
|||
* Useful for short-circuiting local database access.
|
||||
*/
|
||||
public static void putFaviconURLForPageURLInCache(String pageURL, String faviconURL) {
|
||||
sPageURLMappings.put(pageURL, faviconURL);
|
||||
pageURLMappings.put(pageURL, faviconURL);
|
||||
}
|
||||
|
||||
private static FaviconCache sFaviconsCache;
|
||||
private static FaviconCache faviconsCache;
|
||||
|
||||
/**
|
||||
* Returns either NOT_LOADING, or LOADED if the onFaviconLoaded call could
|
||||
|
@ -117,7 +113,7 @@ public class Favicons {
|
|||
* Returns null otherwise.
|
||||
*/
|
||||
public static Bitmap getSizedFaviconForPageFromCache(final String pageURL, int targetSize) {
|
||||
final String faviconURL = sPageURLMappings.get(pageURL);
|
||||
final String faviconURL = pageURLMappings.get(pageURL);
|
||||
if (faviconURL == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -143,7 +139,7 @@ public class Favicons {
|
|||
// Do we know the favicon URL for this page already?
|
||||
String cacheURL = faviconURL;
|
||||
if (cacheURL == null) {
|
||||
cacheURL = sPageURLMappings.get(pageURL);
|
||||
cacheURL = pageURLMappings.get(pageURL);
|
||||
}
|
||||
|
||||
// If there's no favicon URL given, try and hit the cache with the default one.
|
||||
|
@ -153,7 +149,7 @@ public class Favicons {
|
|||
|
||||
// If it's something we can't even figure out a default URL for, just give up.
|
||||
if (cacheURL == null) {
|
||||
return dispatchResult(pageURL, null, sDefaultFavicon, listener);
|
||||
return dispatchResult(pageURL, null, defaultFavicon, listener);
|
||||
}
|
||||
|
||||
Bitmap cachedIcon = getSizedFaviconFromCache(cacheURL, targetSize);
|
||||
|
@ -162,8 +158,8 @@ public class Favicons {
|
|||
}
|
||||
|
||||
// Check if favicon has failed.
|
||||
if (sFaviconsCache.isFailedFavicon(cacheURL)) {
|
||||
return dispatchResult(pageURL, cacheURL, sDefaultFavicon, listener);
|
||||
if (faviconsCache.isFailedFavicon(cacheURL)) {
|
||||
return dispatchResult(pageURL, cacheURL, defaultFavicon, listener);
|
||||
}
|
||||
|
||||
// Failing that, try and get one from the database or internet.
|
||||
|
@ -181,7 +177,7 @@ public class Favicons {
|
|||
* null if no applicable Favicon exists in the cache.
|
||||
*/
|
||||
public static Bitmap getSizedFaviconFromCache(String faviconURL, int targetSize) {
|
||||
return sFaviconsCache.getFaviconForDimensions(faviconURL, targetSize);
|
||||
return faviconsCache.getFaviconForDimensions(faviconURL, targetSize);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -201,10 +197,10 @@ public class Favicons {
|
|||
public static int getSizedFaviconForPageFromLocal(final String pageURL, final int targetSize, final OnFaviconLoadedListener callback) {
|
||||
// Firstly, try extremely hard to cheat.
|
||||
// Have we cached this favicon URL? If we did, we can consult the memcache right away.
|
||||
String targetURL = sPageURLMappings.get(pageURL);
|
||||
String targetURL = pageURLMappings.get(pageURL);
|
||||
if (targetURL != null) {
|
||||
// Check if favicon has failed.
|
||||
if (sFaviconsCache.isFailedFavicon(targetURL)) {
|
||||
if (faviconsCache.isFailedFavicon(targetURL)) {
|
||||
return dispatchResult(pageURL, targetURL, null, callback);
|
||||
}
|
||||
|
||||
|
@ -219,15 +215,15 @@ public class Favicons {
|
|||
// No joy using in-memory resources. Go to background thread and ask the database.
|
||||
LoadFaviconTask task = new LoadFaviconTask(ThreadUtils.getBackgroundHandler(), pageURL, targetURL, 0, callback, targetSize, true);
|
||||
int taskId = task.getId();
|
||||
synchronized(sLoadTasks) {
|
||||
sLoadTasks.put(taskId, task);
|
||||
synchronized(loadTasks) {
|
||||
loadTasks.put(taskId, task);
|
||||
}
|
||||
task.execute();
|
||||
return taskId;
|
||||
}
|
||||
|
||||
public static int getSizedFaviconForPageFromLocal(final String pageURL, final OnFaviconLoadedListener callback) {
|
||||
return getSizedFaviconForPageFromLocal(pageURL, sDefaultFaviconSize, callback);
|
||||
return getSizedFaviconForPageFromLocal(pageURL, defaultFaviconSize, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -250,7 +246,7 @@ public class Favicons {
|
|||
}
|
||||
}
|
||||
|
||||
targetURL = BrowserDB.getFaviconUrlForHistoryUrl(sContext.getContentResolver(), pageURL);
|
||||
targetURL = BrowserDB.getFaviconUrlForHistoryUrl(context.getContentResolver(), pageURL);
|
||||
if (targetURL == null) {
|
||||
// Nothing in the history database. Fall back to the default URL and hope for the best.
|
||||
targetURL = guessDefaultFaviconURL(pageURL);
|
||||
|
@ -286,8 +282,8 @@ public class Favicons {
|
|||
LoadFaviconTask task = new LoadFaviconTask(ThreadUtils.getBackgroundHandler(), pageUrl, faviconUrl, flags, listener, targetSize, false);
|
||||
|
||||
int taskId = task.getId();
|
||||
synchronized(sLoadTasks) {
|
||||
sLoadTasks.put(taskId, task);
|
||||
synchronized(loadTasks) {
|
||||
loadTasks.put(taskId, task);
|
||||
}
|
||||
|
||||
task.execute();
|
||||
|
@ -296,7 +292,7 @@ public class Favicons {
|
|||
}
|
||||
|
||||
public static void putFaviconInMemCache(String pageUrl, Bitmap image) {
|
||||
sFaviconsCache.putSingleFavicon(pageUrl, image);
|
||||
faviconsCache.putSingleFavicon(pageUrl, image);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -308,7 +304,7 @@ public class Favicons {
|
|||
* @param images An iterator over the new favicons to put in the cache.
|
||||
*/
|
||||
public static void putFaviconsInMemCache(String pageUrl, Iterator<Bitmap> images, boolean permanently) {
|
||||
sFaviconsCache.putFavicons(pageUrl, images, permanently);
|
||||
faviconsCache.putFavicons(pageUrl, images, permanently);
|
||||
}
|
||||
|
||||
public static void putFaviconsInMemCache(String pageUrl, Iterator<Bitmap> images) {
|
||||
|
@ -316,12 +312,12 @@ public class Favicons {
|
|||
}
|
||||
|
||||
public static void clearMemCache() {
|
||||
sFaviconsCache.evictAll();
|
||||
sPageURLMappings.evictAll();
|
||||
faviconsCache.evictAll();
|
||||
pageURLMappings.evictAll();
|
||||
}
|
||||
|
||||
public static void putFaviconInFailedCache(String faviconURL) {
|
||||
sFaviconsCache.putFailed(faviconURL);
|
||||
faviconsCache.putFailed(faviconURL);
|
||||
}
|
||||
|
||||
public static boolean cancelFaviconLoad(int taskId) {
|
||||
|
@ -330,13 +326,14 @@ public class Favicons {
|
|||
}
|
||||
|
||||
boolean cancelled;
|
||||
synchronized (sLoadTasks) {
|
||||
if (sLoadTasks.indexOfKey(taskId) < 0)
|
||||
synchronized (loadTasks) {
|
||||
if (loadTasks.indexOfKey(taskId) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.d(LOGTAG, "Cancelling favicon load (" + taskId + ")");
|
||||
|
||||
LoadFaviconTask task = sLoadTasks.get(taskId);
|
||||
LoadFaviconTask task = loadTasks.get(taskId);
|
||||
cancelled = task.cancel(false);
|
||||
}
|
||||
return cancelled;
|
||||
|
@ -346,12 +343,12 @@ public class Favicons {
|
|||
Log.d(LOGTAG, "Closing Favicons database");
|
||||
|
||||
// Cancel any pending tasks
|
||||
synchronized (sLoadTasks) {
|
||||
final int count = sLoadTasks.size();
|
||||
synchronized (loadTasks) {
|
||||
final int count = loadTasks.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
cancelFaviconLoad(sLoadTasks.keyAt(i));
|
||||
cancelFaviconLoad(loadTasks.keyAt(i));
|
||||
}
|
||||
sLoadTasks.clear();
|
||||
loadTasks.clear();
|
||||
}
|
||||
|
||||
LoadFaviconTask.closeHTTPClient();
|
||||
|
@ -364,7 +361,7 @@ public class Favicons {
|
|||
* @return The dominant colour of the provided Favicon.
|
||||
*/
|
||||
public static int getFaviconColor(String url) {
|
||||
return sFaviconsCache.getDominantColor(url);
|
||||
return faviconsCache.getDominantColor(url);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -376,24 +373,24 @@ public class Favicons {
|
|||
*/
|
||||
public static void attachToContext(Context context) throws Exception {
|
||||
final Resources res = context.getResources();
|
||||
sContext = context;
|
||||
Favicons.context = context;
|
||||
|
||||
// Decode the default Favicon ready for use.
|
||||
sDefaultFavicon = BitmapFactory.decodeResource(res, R.drawable.favicon);
|
||||
if (sDefaultFavicon == null) {
|
||||
defaultFavicon = BitmapFactory.decodeResource(res, R.drawable.favicon);
|
||||
if (defaultFavicon == null) {
|
||||
throw new Exception("Null default favicon was returned from the resources system!");
|
||||
}
|
||||
|
||||
sDefaultFaviconSize = res.getDimensionPixelSize(R.dimen.favicon_bg);
|
||||
defaultFaviconSize = res.getDimensionPixelSize(R.dimen.favicon_bg);
|
||||
|
||||
// Screen-density-adjusted upper limit on favicon size. Favicons larger than this are
|
||||
// downscaled to this size or discarded.
|
||||
sLargestFaviconSize = context.getResources().getDimensionPixelSize(R.dimen.favicon_largest_interesting_size);
|
||||
sFaviconsCache = new FaviconCache(FAVICON_CACHE_SIZE_BYTES, sLargestFaviconSize);
|
||||
largestFaviconSize = context.getResources().getDimensionPixelSize(R.dimen.favicon_largest_interesting_size);
|
||||
faviconsCache = new FaviconCache(FAVICON_CACHE_SIZE_BYTES, largestFaviconSize);
|
||||
|
||||
// Initialize page mappings for each of our special pages.
|
||||
for (String url : AboutPages.getDefaultIconPages()) {
|
||||
sPageURLMappings.putWithoutEviction(url, BUILT_IN_FAVICON_URL);
|
||||
pageURLMappings.putWithoutEviction(url, BUILT_IN_FAVICON_URL);
|
||||
}
|
||||
|
||||
// Load and cache the built-in favicon in each of its sizes.
|
||||
|
@ -453,8 +450,8 @@ public class Favicons {
|
|||
}
|
||||
|
||||
public static void removeLoadTask(int taskId) {
|
||||
synchronized(sLoadTasks) {
|
||||
sLoadTasks.delete(taskId);
|
||||
synchronized(loadTasks) {
|
||||
loadTasks.delete(taskId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -464,7 +461,7 @@ public class Favicons {
|
|||
* @param faviconURL Favicon URL to check for failure.
|
||||
*/
|
||||
static boolean isFailedFavicon(String faviconURL) {
|
||||
return sFaviconsCache.isFailedFavicon(faviconURL);
|
||||
return faviconsCache.isFailedFavicon(faviconURL);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -22,7 +22,7 @@ import org.mozilla.gecko.favicons.decoders.LoadFaviconResult;
|
|||
import org.mozilla.gecko.util.GeckoJarReader;
|
||||
import org.mozilla.gecko.util.ThreadUtils;
|
||||
import org.mozilla.gecko.util.UiAsyncTask;
|
||||
import static org.mozilla.gecko.favicons.Favicons.sContext;
|
||||
import static org.mozilla.gecko.favicons.Favicons.context;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -53,21 +53,21 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
|||
// by the server.
|
||||
private static final int DEFAULT_FAVICON_BUFFER_SIZE = 25000;
|
||||
|
||||
private static AtomicInteger mNextFaviconLoadId = new AtomicInteger(0);
|
||||
private int mId;
|
||||
private String mPageUrl;
|
||||
private String mFaviconUrl;
|
||||
private OnFaviconLoadedListener mListener;
|
||||
private int mFlags;
|
||||
private static AtomicInteger nextFaviconLoadId = new AtomicInteger(0);
|
||||
private int id;
|
||||
private String pageUrl;
|
||||
private String faviconURL;
|
||||
private OnFaviconLoadedListener listener;
|
||||
private int flags;
|
||||
|
||||
private final boolean mOnlyFromLocal;
|
||||
private final boolean onlyFromLocal;
|
||||
|
||||
// Assuming square favicons, judging by width only is acceptable.
|
||||
protected int mTargetWidth;
|
||||
private LinkedList<LoadFaviconTask> mChainees;
|
||||
private boolean mIsChaining;
|
||||
protected int targetWidth;
|
||||
private LinkedList<LoadFaviconTask> chainees;
|
||||
private boolean isChaining;
|
||||
|
||||
static AndroidHttpClient sHttpClient = AndroidHttpClient.newInstance(GeckoAppShell.getGeckoInterface().getDefaultUAString());
|
||||
static AndroidHttpClient httpClient = AndroidHttpClient.newInstance(GeckoAppShell.getGeckoInterface().getDefaultUAString());
|
||||
|
||||
public LoadFaviconTask(Handler backgroundThreadHandler,
|
||||
String pageUrl, String faviconUrl, int flags,
|
||||
|
@ -76,33 +76,33 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
|||
}
|
||||
public LoadFaviconTask(Handler backgroundThreadHandler,
|
||||
String pageUrl, String faviconUrl, int flags,
|
||||
OnFaviconLoadedListener aListener, int targetSize, boolean fromLocal) {
|
||||
OnFaviconLoadedListener listener, int targetWidth, boolean onlyFromLocal) {
|
||||
super(backgroundThreadHandler);
|
||||
|
||||
mId = mNextFaviconLoadId.incrementAndGet();
|
||||
id = nextFaviconLoadId.incrementAndGet();
|
||||
|
||||
mPageUrl = pageUrl;
|
||||
mFaviconUrl = faviconUrl;
|
||||
mListener = aListener;
|
||||
mFlags = flags;
|
||||
mTargetWidth = targetSize;
|
||||
mOnlyFromLocal = fromLocal;
|
||||
this.pageUrl = pageUrl;
|
||||
this.faviconURL = faviconUrl;
|
||||
this.listener = listener;
|
||||
this.flags = flags;
|
||||
this.targetWidth = targetWidth;
|
||||
this.onlyFromLocal = onlyFromLocal;
|
||||
}
|
||||
|
||||
// Runs in background thread
|
||||
private LoadFaviconResult loadFaviconFromDb() {
|
||||
ContentResolver resolver = sContext.getContentResolver();
|
||||
return BrowserDB.getFaviconForFaviconUrl(resolver, mFaviconUrl);
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
return BrowserDB.getFaviconForFaviconUrl(resolver, faviconURL);
|
||||
}
|
||||
|
||||
// Runs in background thread
|
||||
private void saveFaviconToDb(final byte[] encodedFavicon) {
|
||||
if ((mFlags & FLAG_PERSIST) == 0) {
|
||||
if ((flags & FLAG_PERSIST) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
ContentResolver resolver = sContext.getContentResolver();
|
||||
BrowserDB.updateFaviconForUrl(resolver, mPageUrl, encodedFavicon, mFaviconUrl);
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
BrowserDB.updateFaviconForUrl(resolver, pageUrl, encodedFavicon, faviconURL);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -121,7 +121,7 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
|||
}
|
||||
|
||||
HttpGet request = new HttpGet(faviconURI);
|
||||
HttpResponse response = sHttpClient.execute(request);
|
||||
HttpResponse response = httpClient.execute(request);
|
||||
if (response == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -172,7 +172,7 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
|||
if (uri.startsWith("jar:jar:")) {
|
||||
Log.d(LOGTAG, "Fetching favicon from JAR.");
|
||||
try {
|
||||
return GeckoJarReader.getBitmap(sContext.getResources(), uri);
|
||||
return GeckoJarReader.getBitmap(context.getResources(), uri);
|
||||
} catch (Exception e) {
|
||||
// Just about anything could happen here.
|
||||
Log.w(LOGTAG, "Error fetching favicon from JAR.", e);
|
||||
|
@ -287,27 +287,27 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
|||
|
||||
// Handle the case of malformed favicon URL.
|
||||
// If favicon is empty, fall back to the stored one.
|
||||
if (TextUtils.isEmpty(mFaviconUrl)) {
|
||||
if (TextUtils.isEmpty(faviconURL)) {
|
||||
// Try to get the favicon URL from the memory cache.
|
||||
storedFaviconUrl = Favicons.getFaviconURLForPageURLFromCache(mPageUrl);
|
||||
storedFaviconUrl = Favicons.getFaviconURLForPageURLFromCache(pageUrl);
|
||||
|
||||
// If that failed, try to get the URL from the database.
|
||||
if (storedFaviconUrl == null) {
|
||||
storedFaviconUrl = Favicons.getFaviconURLForPageURL(mPageUrl);
|
||||
storedFaviconUrl = Favicons.getFaviconURLForPageURL(pageUrl);
|
||||
if (storedFaviconUrl != null) {
|
||||
// If that succeeded, cache the URL loaded from the database in memory.
|
||||
Favicons.putFaviconURLForPageURLInCache(mPageUrl, storedFaviconUrl);
|
||||
Favicons.putFaviconURLForPageURLInCache(pageUrl, storedFaviconUrl);
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a faviconURL - use it.
|
||||
if (storedFaviconUrl != null) {
|
||||
mFaviconUrl = storedFaviconUrl;
|
||||
faviconURL = storedFaviconUrl;
|
||||
} else {
|
||||
// If we don't have a stored one, fall back to the default.
|
||||
mFaviconUrl = Favicons.guessDefaultFaviconURL(mPageUrl);
|
||||
faviconURL = Favicons.guessDefaultFaviconURL(pageUrl);
|
||||
|
||||
if (TextUtils.isEmpty(mFaviconUrl)) {
|
||||
if (TextUtils.isEmpty(faviconURL)) {
|
||||
return null;
|
||||
}
|
||||
isUsingDefaultURL = true;
|
||||
|
@ -316,7 +316,7 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
|||
|
||||
// Check if favicon has failed - if so, give up. We need this check because, sometimes, we
|
||||
// didn't know the real Favicon URL until we asked the database.
|
||||
if (Favicons.isFailedFavicon(mFaviconUrl)) {
|
||||
if (Favicons.isFailedFavicon(faviconURL)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -329,10 +329,10 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
|||
// If there is, just join the queue and wait for it to finish. If not, we carry on.
|
||||
synchronized(loadsInFlight) {
|
||||
// Another load of the current Favicon is already underway
|
||||
LoadFaviconTask existingTask = loadsInFlight.get(mFaviconUrl);
|
||||
LoadFaviconTask existingTask = loadsInFlight.get(faviconURL);
|
||||
if (existingTask != null && !existingTask.isCancelled()) {
|
||||
existingTask.chainTasks(this);
|
||||
mIsChaining = true;
|
||||
isChaining = true;
|
||||
|
||||
// If we are chaining, we want to keep the first task started to do this job as the one
|
||||
// in the hashmap so subsequent tasks will add themselves to its chaining list.
|
||||
|
@ -341,7 +341,7 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
|||
|
||||
// We do not want to update the hashmap if the task has chained - other tasks need to
|
||||
// chain onto the same parent task.
|
||||
loadsInFlight.put(mFaviconUrl, this);
|
||||
loadsInFlight.put(faviconURL, this);
|
||||
}
|
||||
|
||||
if (isCancelled()) {
|
||||
|
@ -354,20 +354,20 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
|||
return pushToCacheAndGetResult(loadedBitmaps);
|
||||
}
|
||||
|
||||
if (mOnlyFromLocal || isCancelled()) {
|
||||
if (onlyFromLocal || isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Let's see if it's in a JAR.
|
||||
image = fetchJARFavicon(mFaviconUrl);
|
||||
image = fetchJARFavicon(faviconURL);
|
||||
if (imageIsValid(image)) {
|
||||
// We don't want to put this into the DB.
|
||||
Favicons.putFaviconInMemCache(mFaviconUrl, image);
|
||||
Favicons.putFaviconInMemCache(faviconURL, image);
|
||||
return image;
|
||||
}
|
||||
|
||||
try {
|
||||
loadedBitmaps = downloadFavicon(new URI(mFaviconUrl));
|
||||
loadedBitmaps = downloadFavicon(new URI(faviconURL));
|
||||
} catch (URISyntaxException e) {
|
||||
Log.e(LOGTAG, "The provided favicon URL is not valid");
|
||||
return null;
|
||||
|
@ -381,7 +381,7 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
|||
}
|
||||
|
||||
if (isUsingDefaultURL) {
|
||||
Favicons.putFaviconInFailedCache(mFaviconUrl);
|
||||
Favicons.putFaviconInFailedCache(faviconURL);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
@ -390,16 +390,16 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
|||
}
|
||||
|
||||
// If we're not already trying the default URL, try it now.
|
||||
final String guessed = Favicons.guessDefaultFaviconURL(mPageUrl);
|
||||
final String guessed = Favicons.guessDefaultFaviconURL(pageUrl);
|
||||
if (guessed == null) {
|
||||
Favicons.putFaviconInFailedCache(mFaviconUrl);
|
||||
Favicons.putFaviconInFailedCache(faviconURL);
|
||||
return null;
|
||||
}
|
||||
|
||||
image = fetchJARFavicon(guessed);
|
||||
if (imageIsValid(image)) {
|
||||
// We don't want to put this into the DB.
|
||||
Favicons.putFaviconInMemCache(mFaviconUrl, image);
|
||||
Favicons.putFaviconInMemCache(faviconURL, image);
|
||||
return image;
|
||||
}
|
||||
|
||||
|
@ -428,8 +428,8 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
|||
* we are under extreme memory pressure and find ourselves dropping the cache immediately.
|
||||
*/
|
||||
private Bitmap pushToCacheAndGetResult(LoadFaviconResult loadedBitmaps) {
|
||||
Favicons.putFaviconsInMemCache(mFaviconUrl, loadedBitmaps.getBitmaps());
|
||||
Bitmap result = Favicons.getSizedFaviconFromCache(mFaviconUrl, mTargetWidth);
|
||||
Favicons.putFaviconsInMemCache(faviconURL, loadedBitmaps.getBitmaps());
|
||||
Bitmap result = Favicons.getSizedFaviconFromCache(faviconURL, targetWidth);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -441,7 +441,7 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
|||
|
||||
@Override
|
||||
protected void onPostExecute(Bitmap image) {
|
||||
if (mIsChaining) {
|
||||
if (isChaining) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -450,10 +450,10 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
|||
|
||||
synchronized (loadsInFlight) {
|
||||
// Prevent any other tasks from chaining on this one.
|
||||
loadsInFlight.remove(mFaviconUrl);
|
||||
loadsInFlight.remove(faviconURL);
|
||||
}
|
||||
|
||||
// Since any update to mChainees is done while holding the loadsInFlight lock, once we reach
|
||||
// Since any update to chainees is done while holding the loadsInFlight lock, once we reach
|
||||
// this point no further updates to that list can possibly take place (As far as other tasks
|
||||
// are concerned, there is no longer a task to chain from. The above block will have waited
|
||||
// for any tasks that were adding themselves to the list before reaching this point.)
|
||||
|
@ -463,8 +463,8 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
|||
// actually happens outside of the strange situations unit tests create.
|
||||
|
||||
// Share the result with all chained tasks.
|
||||
if (mChainees != null) {
|
||||
for (LoadFaviconTask t : mChainees) {
|
||||
if (chainees != null) {
|
||||
for (LoadFaviconTask t : chainees) {
|
||||
// In the case that we just decoded multiple favicons, either we're passing the right
|
||||
// image now, or the call into the cache in processResult will fetch the right one.
|
||||
t.processResult(image);
|
||||
|
@ -473,35 +473,35 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
|||
}
|
||||
|
||||
private void processResult(Bitmap image) {
|
||||
Favicons.removeLoadTask(mId);
|
||||
Favicons.removeLoadTask(id);
|
||||
Bitmap scaled = image;
|
||||
|
||||
// Notify listeners, scaling if required.
|
||||
if (mTargetWidth != -1 && image != null && image.getWidth() != mTargetWidth) {
|
||||
scaled = Favicons.getSizedFaviconFromCache(mFaviconUrl, mTargetWidth);
|
||||
if (targetWidth != -1 && image != null && image.getWidth() != targetWidth) {
|
||||
scaled = Favicons.getSizedFaviconFromCache(faviconURL, targetWidth);
|
||||
}
|
||||
|
||||
Favicons.dispatchResult(mPageUrl, mFaviconUrl, scaled, mListener);
|
||||
Favicons.dispatchResult(pageUrl, faviconURL, scaled, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCancelled() {
|
||||
Favicons.removeLoadTask(mId);
|
||||
Favicons.removeLoadTask(id);
|
||||
|
||||
synchronized(loadsInFlight) {
|
||||
// Only remove from the hashmap if the task there is the one that's being canceled.
|
||||
// Cancellation of a task that would have chained is not interesting to the hashmap.
|
||||
final LoadFaviconTask primary = loadsInFlight.get(mFaviconUrl);
|
||||
final LoadFaviconTask primary = loadsInFlight.get(faviconURL);
|
||||
if (primary == this) {
|
||||
loadsInFlight.remove(mFaviconUrl);
|
||||
loadsInFlight.remove(faviconURL);
|
||||
return;
|
||||
}
|
||||
if (primary == null) {
|
||||
// This shouldn't happen.
|
||||
return;
|
||||
}
|
||||
if (primary.mChainees != null) {
|
||||
primary.mChainees.remove(this);
|
||||
if (primary.chainees != null) {
|
||||
primary.chainees.remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -518,15 +518,15 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
|||
* @param aChainee LoadFaviconTask
|
||||
*/
|
||||
private void chainTasks(LoadFaviconTask aChainee) {
|
||||
if (mChainees == null) {
|
||||
mChainees = new LinkedList<LoadFaviconTask>();
|
||||
if (chainees == null) {
|
||||
chainees = new LinkedList<LoadFaviconTask>();
|
||||
}
|
||||
|
||||
mChainees.add(aChainee);
|
||||
chainees.add(aChainee);
|
||||
}
|
||||
|
||||
int getId() {
|
||||
return mId;
|
||||
return id;
|
||||
}
|
||||
|
||||
static void closeHTTPClient() {
|
||||
|
@ -534,8 +534,8 @@ public class LoadFaviconTask extends UiAsyncTask<Void, Void, Bitmap> {
|
|||
// the connection pool, which typically involves closing a connection --
|
||||
// which counts as network activity.
|
||||
if (ThreadUtils.isOnBackgroundThread()) {
|
||||
if (sHttpClient != null) {
|
||||
sHttpClient.close();
|
||||
if (httpClient != null) {
|
||||
httpClient.close();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
* When a favicon at a particular URL is decoded, it will yield one or more bitmaps.
|
||||
* While in memory, these bitmaps are stored in a list, sorted in ascending order of size, in a
|
||||
* FaviconsForURL object.
|
||||
* The collection of FaviconsForURL objects currently in the cache is stored in mBackingMap, keyed
|
||||
* The collection of FaviconsForURL objects currently in the cache is stored in backingMap, keyed
|
||||
* by favicon URL.
|
||||
*
|
||||
* A second map exists for permanent cache entries -- ones that are never expired. These entries
|
||||
|
@ -59,7 +59,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||
* as well as the bitmap, a pointer to the encapsulating FaviconsForURL object (Used by the LRU
|
||||
* culler), the size of the encapsulated image, a flag indicating if this is a primary favicon, and
|
||||
* a flag indicating if the entry is invalid.
|
||||
* All FaviconCacheElement objects are tracked in the mOrdering LinkedList. This is used to record
|
||||
* All FaviconCacheElement objects are tracked in the ordering LinkedList. This is used to record
|
||||
* LRU information about FaviconCacheElements. In particular, the most recently used FaviconCacheElement
|
||||
* will be at the start of the list, the least recently used at the end of the list.
|
||||
*
|
||||
|
@ -98,7 +98,7 @@ public class FaviconCache {
|
|||
private static final int NUM_FAVICON_SIZES = 4;
|
||||
|
||||
// Dimensions of the largest favicon to store in the cache. Everything is downscaled to this.
|
||||
public final int mMaxCachedWidth;
|
||||
public final int maxCachedWidth;
|
||||
|
||||
// Retry failed favicons after 20 minutes.
|
||||
public static final long FAILURE_RETRY_MILLISECONDS = 1000 * 60 * 20;
|
||||
|
@ -107,16 +107,16 @@ public class FaviconCache {
|
|||
// Since favicons may be container formats holding multiple icons, the underlying type holds a
|
||||
// sorted list of bitmap payloads in ascending order of size. The underlying type may be queried
|
||||
// for the least larger payload currently present.
|
||||
private final ConcurrentHashMap<String, FaviconsForURL> mBackingMap = new ConcurrentHashMap<String, FaviconsForURL>();
|
||||
private final ConcurrentHashMap<String, FaviconsForURL> backingMap = new ConcurrentHashMap<String, FaviconsForURL>();
|
||||
|
||||
// And the same, but never evicted.
|
||||
private final ConcurrentHashMap<String, FaviconsForURL> mPermanentBackingMap = new ConcurrentHashMap<String, FaviconsForURL>();
|
||||
private final ConcurrentHashMap<String, FaviconsForURL> permanentBackingMap = new ConcurrentHashMap<String, FaviconsForURL>();
|
||||
|
||||
// A linked list used to implement a queue, defining the LRU properties of the cache. Elements
|
||||
// contained within the various FaviconsForURL objects are held here, the least recently used
|
||||
// of which at the end of the list. When space needs to be reclaimed, the appropriate bitmap is
|
||||
// culled.
|
||||
private final LinkedList<FaviconCacheElement> mOrdering = new LinkedList<FaviconCacheElement>();
|
||||
private final LinkedList<FaviconCacheElement> ordering = new LinkedList<FaviconCacheElement>();
|
||||
|
||||
// The above structures, if used correctly, enable this cache to exhibit LRU semantics across all
|
||||
// favicon payloads in the system, as well as enabling the dynamic selection from the cache of
|
||||
|
@ -124,60 +124,48 @@ public class FaviconCache {
|
|||
// are provided by the underlying file format).
|
||||
|
||||
// Current size, in bytes, of the bitmap data present in the LRU cache.
|
||||
private final AtomicInteger mCurrentSize = new AtomicInteger(0);
|
||||
private final AtomicInteger currentSize = new AtomicInteger(0);
|
||||
|
||||
// The maximum quantity, in bytes, of bitmap data which may be stored in the cache.
|
||||
private final int mMaxSizeBytes;
|
||||
private final int maxSizeBytes;
|
||||
|
||||
// Tracks the number of ongoing read operations. Enables the first one in to lock writers out and
|
||||
// the last one out to let them in.
|
||||
private final AtomicInteger mOngoingReads = new AtomicInteger(0);
|
||||
private final AtomicInteger ongoingReads = new AtomicInteger(0);
|
||||
|
||||
// Used to ensure transaction fairness - each txn acquires and releases this as the first operation.
|
||||
// The effect is an orderly, inexpensive ordering enforced on txns to prevent writer starvation.
|
||||
private final Semaphore mTurnSemaphore = new Semaphore(1);
|
||||
private final Semaphore turnSemaphore = new Semaphore(1);
|
||||
|
||||
// A deviation from the usual MRSW solution - this semaphore is used to guard modification to the
|
||||
// ordering map. This allows for read transactions to update the most-recently-used value without
|
||||
// needing to take out the write lock.
|
||||
private final Semaphore mReorderingSemaphore = new Semaphore(1);
|
||||
private final Semaphore reorderingSemaphore = new Semaphore(1);
|
||||
|
||||
// The semaphore one must acquire in order to perform a write.
|
||||
private final Semaphore mWriteLock = new Semaphore(1);
|
||||
private final Semaphore writeLock = new Semaphore(1);
|
||||
|
||||
/**
|
||||
* Called by txns performing only reads as they start. Prevents writer starvation with a turn
|
||||
* semaphore and locks writers out if this is the first concurrent reader txn starting up.
|
||||
*/
|
||||
private void startRead() {
|
||||
mTurnSemaphore.acquireUninterruptibly();
|
||||
mTurnSemaphore.release();
|
||||
turnSemaphore.acquireUninterruptibly();
|
||||
turnSemaphore.release();
|
||||
|
||||
if (mOngoingReads.incrementAndGet() == 1) {
|
||||
if (ongoingReads.incrementAndGet() == 1) {
|
||||
// First one in. Wait for writers to finish and lock them out.
|
||||
mWriteLock.acquireUninterruptibly();
|
||||
writeLock.acquireUninterruptibly();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An alternative to startWrite to be used when in a read transaction and wanting to upgrade it
|
||||
* to a write transaction. Such a transaction should be terminated with finishWrite.
|
||||
*/
|
||||
private void upgradeReadToWrite() {
|
||||
mTurnSemaphore.acquireUninterruptibly();
|
||||
if (mOngoingReads.decrementAndGet() == 0) {
|
||||
mWriteLock.release();
|
||||
}
|
||||
mWriteLock.acquireUninterruptibly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by transactions performing only reads as they finish. Ensures that if this is the last
|
||||
* concluding read transaction then then writers are subsequently allowed in.
|
||||
*/
|
||||
private void finishRead() {
|
||||
if (mOngoingReads.decrementAndGet() == 0) {
|
||||
mWriteLock.release();
|
||||
if (ongoingReads.decrementAndGet() == 0) {
|
||||
writeLock.release();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -186,21 +174,21 @@ public class FaviconCache {
|
|||
* Upon return, no other txns will be executing concurrently.
|
||||
*/
|
||||
private void startWrite() {
|
||||
mTurnSemaphore.acquireUninterruptibly();
|
||||
mWriteLock.acquireUninterruptibly();
|
||||
turnSemaphore.acquireUninterruptibly();
|
||||
writeLock.acquireUninterruptibly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by a concluding write transaction - unlocks the structure.
|
||||
*/
|
||||
private void finishWrite() {
|
||||
mTurnSemaphore.release();
|
||||
mWriteLock.release();
|
||||
turnSemaphore.release();
|
||||
writeLock.release();
|
||||
}
|
||||
|
||||
public FaviconCache(int maxSize, int maxWidthToCache) {
|
||||
mMaxSizeBytes = maxSize;
|
||||
mMaxCachedWidth = maxWidthToCache;
|
||||
maxSizeBytes = maxSize;
|
||||
maxCachedWidth = maxWidthToCache;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -217,57 +205,42 @@ public class FaviconCache {
|
|||
|
||||
startRead();
|
||||
|
||||
boolean isExpired = false;
|
||||
boolean isAborting = false;
|
||||
|
||||
try {
|
||||
// If we don't have it in the cache, it certainly isn't a known failure.
|
||||
// Non-evictable favicons are never failed, so we don't need to
|
||||
// check mPermanentBackingMap.
|
||||
if (!mBackingMap.containsKey(faviconURL)) {
|
||||
// check permanentBackingMap.
|
||||
if (!backingMap.containsKey(faviconURL)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
FaviconsForURL container = mBackingMap.get(faviconURL);
|
||||
FaviconsForURL container = backingMap.get(faviconURL);
|
||||
|
||||
// If the has failed flag is not set, it's certainly not a known failure.
|
||||
if (!container.mHasFailed) {
|
||||
if (!container.hasFailed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final long failureTimestamp = container.mDownloadTimestamp;
|
||||
final long failureTimestamp = container.downloadTimestamp;
|
||||
|
||||
// Calculate elapsed time since the failing download.
|
||||
final long failureDiff = System.currentTimeMillis() - failureTimestamp;
|
||||
|
||||
// If long enough has passed, mark it as no longer a failure.
|
||||
if (failureDiff > FAILURE_RETRY_MILLISECONDS) {
|
||||
isExpired = true;
|
||||
} else {
|
||||
// If the expiry is still in effect, return. Otherwise, continue and unmark the failure.
|
||||
if (failureDiff < FAILURE_RETRY_MILLISECONDS) {
|
||||
return true;
|
||||
}
|
||||
} catch (Exception unhandled) {
|
||||
// Handle any exception thrown and return the locks to a sensible state.
|
||||
finishRead();
|
||||
|
||||
// Flag to prevent finally from doubly-unlocking.
|
||||
isAborting = true;
|
||||
Log.e(LOGTAG, "FaviconCache exception!", unhandled);
|
||||
return true;
|
||||
} finally {
|
||||
if (!isAborting) {
|
||||
if (isExpired) {
|
||||
// No longer expired.
|
||||
upgradeReadToWrite();
|
||||
} else {
|
||||
finishRead();
|
||||
}
|
||||
}
|
||||
finishRead();
|
||||
}
|
||||
|
||||
startWrite();
|
||||
|
||||
// If the entry is no longer failed, remove the record of it from the cache.
|
||||
try {
|
||||
recordRemoved(mBackingMap.get(faviconURL));
|
||||
mBackingMap.remove(faviconURL);
|
||||
recordRemoved(backingMap.remove(faviconURL));
|
||||
return false;
|
||||
} finally {
|
||||
finishWrite();
|
||||
|
@ -282,14 +255,12 @@ public class FaviconCache {
|
|||
public void putFailed(String faviconURL) {
|
||||
startWrite();
|
||||
|
||||
if (mBackingMap.containsKey(faviconURL)) {
|
||||
recordRemoved(mBackingMap.get(faviconURL));
|
||||
try {
|
||||
FaviconsForURL container = new FaviconsForURL(0, true);
|
||||
recordRemoved(backingMap.put(faviconURL, container));
|
||||
} finally {
|
||||
finishWrite();
|
||||
}
|
||||
|
||||
FaviconsForURL container = new FaviconsForURL(0, true);
|
||||
mBackingMap.put(faviconURL, container);
|
||||
|
||||
finishWrite();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -309,9 +280,7 @@ public class FaviconCache {
|
|||
return null;
|
||||
}
|
||||
|
||||
boolean doingWrites = false;
|
||||
boolean shouldComputeColour = false;
|
||||
boolean isAborting = false;
|
||||
boolean wasPermanent = false;
|
||||
FaviconsForURL container;
|
||||
final Bitmap newBitmap;
|
||||
|
@ -319,9 +288,9 @@ public class FaviconCache {
|
|||
startRead();
|
||||
|
||||
try {
|
||||
container = mPermanentBackingMap.get(faviconURL);
|
||||
container = permanentBackingMap.get(faviconURL);
|
||||
if (container == null) {
|
||||
container = mBackingMap.get(faviconURL);
|
||||
container = backingMap.get(faviconURL);
|
||||
if (container == null) {
|
||||
// We don't have it!
|
||||
return null;
|
||||
|
@ -338,22 +307,22 @@ public class FaviconCache {
|
|||
// cacheElementIndex now holds either the index of the next least largest bitmap from
|
||||
// targetSize, or -1 if targetSize > all bitmaps.
|
||||
if (cacheElementIndex != -1) {
|
||||
// If cacheElementIndex is not the sentinel value, then it is a valid index into mFavicons.
|
||||
cacheElement = container.mFavicons.get(cacheElementIndex);
|
||||
// If cacheElementIndex is not the sentinel value, then it is a valid index into favicons.
|
||||
cacheElement = container.favicons.get(cacheElementIndex);
|
||||
|
||||
if (cacheElement.mInvalidated) {
|
||||
if (cacheElement.invalidated) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If we found exactly what we wanted - we're done.
|
||||
if (cacheElement.mImageSize == targetSize) {
|
||||
setMostRecentlyUsed(cacheElement);
|
||||
return cacheElement.mFaviconPayload;
|
||||
if (cacheElement.imageSize == targetSize) {
|
||||
setMostRecentlyUsedWithinRead(cacheElement);
|
||||
return cacheElement.faviconPayload;
|
||||
}
|
||||
} else {
|
||||
// We requested an image larger than all primaries. Set the element to start the search
|
||||
// from to the element beyond the end of the array, so the search runs backwards.
|
||||
cacheElementIndex = container.mFavicons.size();
|
||||
cacheElementIndex = container.favicons.size();
|
||||
}
|
||||
|
||||
// We did not find exactly what we wanted, but now have set cacheElementIndex to the index
|
||||
|
@ -370,17 +339,12 @@ public class FaviconCache {
|
|||
|
||||
if (targetSize == -1) {
|
||||
// We got the biggest primary, so that's what we'll return.
|
||||
return cacheElement.mFaviconPayload;
|
||||
return cacheElement.faviconPayload;
|
||||
}
|
||||
|
||||
// Having got this far, we'll be needing to write the new secondary to the cache, which
|
||||
// involves us falling through to the next try block. This flag lets us do this (Other
|
||||
// paths prior to this end in returns.)
|
||||
doingWrites = true;
|
||||
|
||||
// Scaling logic...
|
||||
Bitmap largestElementBitmap = cacheElement.mFaviconPayload;
|
||||
int largestSize = cacheElement.mImageSize;
|
||||
Bitmap largestElementBitmap = cacheElement.faviconPayload;
|
||||
int largestSize = cacheElement.imageSize;
|
||||
|
||||
if (largestSize >= targetSize) {
|
||||
// The largest we have is larger than the target - downsize to target.
|
||||
|
@ -401,24 +365,16 @@ public class FaviconCache {
|
|||
}
|
||||
}
|
||||
} catch (Exception unhandled) {
|
||||
isAborting = true;
|
||||
|
||||
// Handle any exception thrown and return the locks to a sensible state.
|
||||
finishRead();
|
||||
|
||||
// Flag to prevent finally from doubly-unlocking.
|
||||
Log.e(LOGTAG, "FaviconCache exception!", unhandled);
|
||||
return null;
|
||||
} finally {
|
||||
if (!isAborting) {
|
||||
if (doingWrites) {
|
||||
upgradeReadToWrite();
|
||||
} else {
|
||||
finishRead();
|
||||
}
|
||||
}
|
||||
finishRead();
|
||||
}
|
||||
|
||||
startWrite();
|
||||
try {
|
||||
if (shouldComputeColour) {
|
||||
// And since we failed, we'll need the dominant colour.
|
||||
|
@ -432,8 +388,9 @@ public class FaviconCache {
|
|||
FaviconCacheElement newElement = container.addSecondary(newBitmap, targetSize);
|
||||
|
||||
if (!wasPermanent) {
|
||||
setMostRecentlyUsed(newElement);
|
||||
mCurrentSize.addAndGet(newElement.sizeOf());
|
||||
if (setMostRecentlyUsedWithinWrite(newElement)) {
|
||||
currentSize.addAndGet(newElement.sizeOf());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
finishWrite();
|
||||
|
@ -452,19 +409,18 @@ public class FaviconCache {
|
|||
startRead();
|
||||
|
||||
try {
|
||||
FaviconsForURL element = mPermanentBackingMap.get(key);
|
||||
FaviconsForURL element = permanentBackingMap.get(key);
|
||||
if (element == null) {
|
||||
element = mBackingMap.get(key);
|
||||
element = backingMap.get(key);
|
||||
}
|
||||
|
||||
if (element == null) {
|
||||
Log.w(LOGTAG, "Cannot compute dominant color of non-cached favicon. Cache fullness " +
|
||||
mCurrentSize.get() + '/' + mMaxSizeBytes);
|
||||
currentSize.get() + '/' + maxSizeBytes);
|
||||
finishRead();
|
||||
return 0xFFFFFF;
|
||||
}
|
||||
|
||||
|
||||
return element.ensureDominantColor();
|
||||
} finally {
|
||||
finishRead();
|
||||
|
@ -472,8 +428,8 @@ public class FaviconCache {
|
|||
}
|
||||
|
||||
/**
|
||||
* Remove all payloads stored in the given container from the LRU cache. Must be called while
|
||||
* holding the write lock.
|
||||
* Remove all payloads stored in the given container from the LRU cache.
|
||||
* Must be called while holding the write lock.
|
||||
*
|
||||
* @param wasRemoved The container to purge from the cache.
|
||||
*/
|
||||
|
@ -485,40 +441,59 @@ public class FaviconCache {
|
|||
|
||||
int sizeRemoved = 0;
|
||||
|
||||
for (FaviconCacheElement e : wasRemoved.mFavicons) {
|
||||
for (FaviconCacheElement e : wasRemoved.favicons) {
|
||||
sizeRemoved += e.sizeOf();
|
||||
mOrdering.remove(e);
|
||||
ordering.remove(e);
|
||||
}
|
||||
|
||||
mCurrentSize.addAndGet(-sizeRemoved);
|
||||
currentSize.addAndGet(-sizeRemoved);
|
||||
}
|
||||
|
||||
private Bitmap produceCacheableBitmap(Bitmap favicon) {
|
||||
// Never cache the default Favicon, or the null Favicon.
|
||||
if (favicon == Favicons.sDefaultFavicon || favicon == null) {
|
||||
if (favicon == Favicons.defaultFavicon || favicon == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Some sites serve up insanely huge Favicons (Seen 512x512 ones...)
|
||||
// While we want to cache nice big icons, we apply a limit based on screen density for the
|
||||
// sake of space.
|
||||
if (favicon.getWidth() > mMaxCachedWidth) {
|
||||
return Bitmap.createScaledBitmap(favicon, mMaxCachedWidth, mMaxCachedWidth, true);
|
||||
if (favicon.getWidth() > maxCachedWidth) {
|
||||
return Bitmap.createScaledBitmap(favicon, maxCachedWidth, maxCachedWidth, true);
|
||||
}
|
||||
|
||||
return favicon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an existing element as the most recently used element. May be called from either type of
|
||||
* transaction.
|
||||
* Set an existing element as the most recently used element. Intended for use from read transactions. While
|
||||
* write transactions may safely use this method, it will perform slightly worse than its unsafe counterpart below.
|
||||
*
|
||||
* @param element The element that is to become the most recently used one.
|
||||
* @return true if this element already existed in the list, false otherwise. (Useful for preventing multiple-insertion.)
|
||||
*/
|
||||
private void setMostRecentlyUsed(FaviconCacheElement element) {
|
||||
mReorderingSemaphore.acquireUninterruptibly();
|
||||
mOrdering.remove(element);
|
||||
mOrdering.offer(element);
|
||||
mReorderingSemaphore.release();
|
||||
private boolean setMostRecentlyUsedWithinRead(FaviconCacheElement element) {
|
||||
reorderingSemaphore.acquireUninterruptibly();
|
||||
try {
|
||||
boolean contained = ordering.remove(element);
|
||||
ordering.offer(element);
|
||||
return contained;
|
||||
} finally {
|
||||
reorderingSemaphore.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Functionally equivalent to setMostRecentlyUsedWithinRead, but operates without taking the reordering semaphore.
|
||||
* Only safe for use when called from a write transaction, or there is a risk of concurrent modification.
|
||||
*
|
||||
* @param element The element that is to become the most recently used one.
|
||||
* @return true if this element already existed in the list, false otherwise. (Useful for preventing multiple-insertion.)
|
||||
*/
|
||||
private boolean setMostRecentlyUsedWithinWrite(FaviconCacheElement element) {
|
||||
boolean contained = ordering.remove(element);
|
||||
ordering.offer(element);
|
||||
return contained;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -546,13 +521,13 @@ public class FaviconCache {
|
|||
startWrite();
|
||||
try {
|
||||
// Set the new element as the most recently used one.
|
||||
setMostRecentlyUsed(newElement);
|
||||
setMostRecentlyUsedWithinWrite(newElement);
|
||||
|
||||
mCurrentSize.addAndGet(newElement.sizeOf());
|
||||
currentSize.addAndGet(newElement.sizeOf());
|
||||
|
||||
// Update the value in the LruCache...
|
||||
FaviconsForURL wasRemoved;
|
||||
wasRemoved = mBackingMap.put(faviconURL, toInsert);
|
||||
wasRemoved = backingMap.put(faviconURL, toInsert);
|
||||
|
||||
recordRemoved(wasRemoved);
|
||||
} finally {
|
||||
|
@ -584,42 +559,23 @@ public class FaviconCache {
|
|||
sizeGained += newElement.sizeOf();
|
||||
}
|
||||
|
||||
startRead();
|
||||
|
||||
boolean abortingRead = false;
|
||||
|
||||
// Not using setMostRecentlyUsed, because the elements are known to be new. This can be done
|
||||
// without taking the write lock, via the magic of the reordering semaphore.
|
||||
mReorderingSemaphore.acquireUninterruptibly();
|
||||
try {
|
||||
if (!permanently) {
|
||||
for (FaviconCacheElement newElement : toInsert.mFavicons) {
|
||||
mOrdering.offer(newElement);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
abortingRead = true;
|
||||
mReorderingSemaphore.release();
|
||||
finishRead();
|
||||
|
||||
Log.e(LOGTAG, "Favicon cache exception!", e);
|
||||
return;
|
||||
} finally {
|
||||
if (!abortingRead) {
|
||||
mReorderingSemaphore.release();
|
||||
upgradeReadToWrite();
|
||||
}
|
||||
}
|
||||
|
||||
startWrite();
|
||||
try {
|
||||
if (permanently) {
|
||||
mPermanentBackingMap.put(faviconURL, toInsert);
|
||||
} else {
|
||||
mCurrentSize.addAndGet(sizeGained);
|
||||
|
||||
// Update the value in the LruCache...
|
||||
recordRemoved(mBackingMap.put(faviconURL, toInsert));
|
||||
permanentBackingMap.put(faviconURL, toInsert);
|
||||
return;
|
||||
}
|
||||
|
||||
for (FaviconCacheElement newElement : toInsert.favicons) {
|
||||
setMostRecentlyUsedWithinWrite(newElement);
|
||||
}
|
||||
|
||||
// In the event this insertion is being made to a key that already held a value, the subsequent recordRemoved
|
||||
// call will subtract the size of the old value, preventing double-counting.
|
||||
currentSize.addAndGet(sizeGained);
|
||||
|
||||
// Update the value in the LruCache...
|
||||
recordRemoved(backingMap.put(faviconURL, toInsert));
|
||||
} finally {
|
||||
finishWrite();
|
||||
}
|
||||
|
@ -632,24 +588,24 @@ public class FaviconCache {
|
|||
* Otherwise, do nothing.
|
||||
*/
|
||||
private void cullIfRequired() {
|
||||
Log.d(LOGTAG, "Favicon cache fullness: " + mCurrentSize.get() + '/' + mMaxSizeBytes);
|
||||
Log.d(LOGTAG, "Favicon cache fullness: " + currentSize.get() + '/' + maxSizeBytes);
|
||||
|
||||
if (mCurrentSize.get() <= mMaxSizeBytes) {
|
||||
if (currentSize.get() <= maxSizeBytes) {
|
||||
return;
|
||||
}
|
||||
|
||||
startWrite();
|
||||
try {
|
||||
while (mCurrentSize.get() > mMaxSizeBytes) {
|
||||
while (currentSize.get() > maxSizeBytes) {
|
||||
// Cull the least recently used element.
|
||||
|
||||
FaviconCacheElement victim;
|
||||
victim = mOrdering.poll();
|
||||
victim = ordering.poll();
|
||||
|
||||
mCurrentSize.addAndGet(-victim.sizeOf());
|
||||
currentSize.addAndGet(-victim.sizeOf());
|
||||
victim.onEvictedFromCache();
|
||||
|
||||
Log.d(LOGTAG, "After cull: " + mCurrentSize.get() + '/' + mMaxSizeBytes);
|
||||
Log.d(LOGTAG, "After cull: " + currentSize.get() + '/' + maxSizeBytes);
|
||||
}
|
||||
} finally {
|
||||
finishWrite();
|
||||
|
@ -664,9 +620,9 @@ public class FaviconCache {
|
|||
|
||||
// Note that we neither clear, nor track the size of, the permanent map.
|
||||
try {
|
||||
mCurrentSize.set(0);
|
||||
mBackingMap.clear();
|
||||
mOrdering.clear();
|
||||
currentSize.set(0);
|
||||
backingMap.clear();
|
||||
ordering.clear();
|
||||
|
||||
} finally {
|
||||
finishWrite();
|
||||
|
|
|
@ -12,49 +12,49 @@ import android.graphics.Bitmap;
|
|||
*/
|
||||
public class FaviconCacheElement implements Comparable<FaviconCacheElement> {
|
||||
// Was this Favicon computed via scaling another primary Favicon, or is this a primary Favicon?
|
||||
final boolean mIsPrimary;
|
||||
final boolean isPrimary;
|
||||
|
||||
// The Favicon bitmap.
|
||||
Bitmap mFaviconPayload;
|
||||
Bitmap faviconPayload;
|
||||
|
||||
// If set, mFaviconPayload is absent. Since the underlying ICO may contain multiple primary
|
||||
// If set, faviconPayload is absent. Since the underlying ICO may contain multiple primary
|
||||
// payloads, primary payloads are never truly deleted from the cache, but instead have their
|
||||
// payload deleted and this flag set on their FaviconCacheElement. That way, the cache always
|
||||
// has a record of the existence of a primary payload, even if it is no longer in the cache.
|
||||
// This means that when a request comes in that will be best served using a primary that is in
|
||||
// the database but no longer cached, we know that it exists and can go get it (Useful when ICO
|
||||
// support is added).
|
||||
volatile boolean mInvalidated;
|
||||
volatile boolean invalidated;
|
||||
|
||||
final int mImageSize;
|
||||
final int imageSize;
|
||||
|
||||
// Used for LRU pruning.
|
||||
final FaviconsForURL mBackpointer;
|
||||
final FaviconsForURL backpointer;
|
||||
|
||||
public FaviconCacheElement(Bitmap payload, boolean isPrimary, int imageSize, FaviconsForURL backpointer) {
|
||||
mFaviconPayload = payload;
|
||||
mIsPrimary = isPrimary;
|
||||
mImageSize = imageSize;
|
||||
mBackpointer = backpointer;
|
||||
public FaviconCacheElement(Bitmap payload, boolean primary, int size, FaviconsForURL backpointer) {
|
||||
this.faviconPayload = payload;
|
||||
this.isPrimary = primary;
|
||||
this.imageSize = size;
|
||||
this.backpointer = backpointer;
|
||||
}
|
||||
|
||||
public FaviconCacheElement(Bitmap payload, boolean isPrimary, FaviconsForURL backpointer) {
|
||||
mFaviconPayload = payload;
|
||||
mIsPrimary = isPrimary;
|
||||
mBackpointer = backpointer;
|
||||
public FaviconCacheElement(Bitmap faviconPayload, boolean isPrimary, FaviconsForURL backpointer) {
|
||||
this.faviconPayload = faviconPayload;
|
||||
this.isPrimary = isPrimary;
|
||||
this.backpointer = backpointer;
|
||||
|
||||
if (payload != null) {
|
||||
mImageSize = payload.getWidth();
|
||||
if (faviconPayload != null) {
|
||||
imageSize = faviconPayload.getWidth();
|
||||
} else {
|
||||
mImageSize = 0;
|
||||
imageSize = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public int sizeOf() {
|
||||
if (mInvalidated) {
|
||||
if (invalidated) {
|
||||
return 0;
|
||||
}
|
||||
return mFaviconPayload.getRowBytes() * mFaviconPayload.getHeight();
|
||||
return faviconPayload.getRowBytes() * faviconPayload.getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,20 +68,20 @@ public class FaviconCacheElement implements Comparable<FaviconCacheElement> {
|
|||
*/
|
||||
@Override
|
||||
public int compareTo(FaviconCacheElement another) {
|
||||
if (mInvalidated && !another.mInvalidated) {
|
||||
if (invalidated && !another.invalidated) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!mInvalidated && another.mInvalidated) {
|
||||
if (!invalidated && another.invalidated) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (mInvalidated) {
|
||||
if (invalidated) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
final int w1 = mImageSize;
|
||||
final int w2 = another.mImageSize;
|
||||
final int w1 = imageSize;
|
||||
final int w2 = another.imageSize;
|
||||
if (w1 > w2) {
|
||||
return 1;
|
||||
} else if (w2 > w1) {
|
||||
|
@ -96,20 +96,20 @@ public class FaviconCacheElement implements Comparable<FaviconCacheElement> {
|
|||
* If primary, drop the payload and set invalid. If secondary, just unlink from parent node.
|
||||
*/
|
||||
public void onEvictedFromCache() {
|
||||
if (mIsPrimary) {
|
||||
if (isPrimary) {
|
||||
// So we keep a record of which primaries exist in the database for this URL, we
|
||||
// don't actually delete the entry for primaries. Instead, we delete their payload
|
||||
// and flag them as invalid. This way, we can later figure out that what a request
|
||||
// really want is one of the primaries that have been dropped from the cache, and we
|
||||
// can go get it.
|
||||
mInvalidated = true;
|
||||
mFaviconPayload = null;
|
||||
invalidated = true;
|
||||
faviconPayload = null;
|
||||
} else {
|
||||
// Secondaries don't matter - just delete them.
|
||||
if (mBackpointer == null) {
|
||||
if (backpointer == null) {
|
||||
return;
|
||||
}
|
||||
mBackpointer.mFavicons.remove(this);
|
||||
backpointer.favicons.remove(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,21 +14,21 @@ import java.util.Collections;
|
|||
public class FaviconsForURL {
|
||||
private static final String LOGTAG = "FaviconForURL";
|
||||
|
||||
private volatile int mDominantColor = -1;
|
||||
private volatile int dominantColor = -1;
|
||||
|
||||
final long mDownloadTimestamp;
|
||||
final ArrayList<FaviconCacheElement> mFavicons;
|
||||
final long downloadTimestamp;
|
||||
final ArrayList<FaviconCacheElement> favicons;
|
||||
|
||||
public final boolean mHasFailed;
|
||||
public final boolean hasFailed;
|
||||
|
||||
public FaviconsForURL(int size) {
|
||||
this(size, false);
|
||||
}
|
||||
|
||||
public FaviconsForURL(int size, boolean hasFailed) {
|
||||
mHasFailed = hasFailed;
|
||||
mDownloadTimestamp = System.currentTimeMillis();
|
||||
mFavicons = new ArrayList<FaviconCacheElement>(size);
|
||||
public FaviconsForURL(int size, boolean failed) {
|
||||
hasFailed = failed;
|
||||
downloadTimestamp = System.currentTimeMillis();
|
||||
favicons = new ArrayList<FaviconCacheElement>(size);
|
||||
}
|
||||
|
||||
public FaviconCacheElement addSecondary(Bitmap favicon, int imageSize) {
|
||||
|
@ -42,15 +42,19 @@ public class FaviconsForURL {
|
|||
private FaviconCacheElement addInternal(Bitmap favicon, boolean isPrimary, int imageSize) {
|
||||
FaviconCacheElement c = new FaviconCacheElement(favicon, isPrimary, imageSize, this);
|
||||
|
||||
int index = Collections.binarySearch(mFavicons, c);
|
||||
int index = Collections.binarySearch(favicons, c);
|
||||
|
||||
// We've already got an equivalent one. We don't care about this new one. This only occurs in certain obscure
|
||||
// case conditions.
|
||||
if (index >= 0) {
|
||||
return favicons.get(index);
|
||||
}
|
||||
|
||||
// binarySearch returns -x - 1 where x is the insertion point of the element. Convert
|
||||
// this to the actual insertion point..
|
||||
if (index < 0) {
|
||||
index++;
|
||||
index = -index;
|
||||
}
|
||||
mFavicons.add(index, c);
|
||||
index++;
|
||||
index = -index;
|
||||
favicons.add(index, c);
|
||||
|
||||
return c;
|
||||
}
|
||||
|
@ -66,7 +70,7 @@ public class FaviconsForURL {
|
|||
// Create a dummy object to hold the target value for comparable.
|
||||
FaviconCacheElement dummy = new FaviconCacheElement(null, false, targetSize, null);
|
||||
|
||||
int index = Collections.binarySearch(mFavicons, dummy);
|
||||
int index = Collections.binarySearch(favicons, dummy);
|
||||
|
||||
// The search routine returns the index of an element equal to dummy, if present.
|
||||
// Otherwise, it returns -x - 1, where x is the index in the ArrayList where dummy would be
|
||||
|
@ -78,11 +82,11 @@ public class FaviconsForURL {
|
|||
|
||||
// index is now 'x', as described above.
|
||||
|
||||
// The routine will return mFavicons.size() as the index iff dummy is larger than all elements
|
||||
// The routine will return favicons.size() as the index iff dummy is larger than all elements
|
||||
// present (So the "index at which it should be inserted" is the index after the end.
|
||||
// In this case, we set the sentinel value -1 to indicate that we just requested something
|
||||
// larger than all primaries.
|
||||
if (index == mFavicons.size()) {
|
||||
if (index == favicons.size()) {
|
||||
index = -1;
|
||||
}
|
||||
|
||||
|
@ -97,19 +101,19 @@ public class FaviconsForURL {
|
|||
* primary at all (The input index may be a secondary which is larger than the actual available
|
||||
* primary.)
|
||||
*
|
||||
* @param fromIndex The index into mFavicons from which to start the search.
|
||||
* @param fromIndex The index into favicons from which to start the search.
|
||||
* @return The FaviconCacheElement of the next valid primary from the given index. If none exists,
|
||||
* then returns the previous valid primary. If none exists, returns null (Insanity.).
|
||||
*/
|
||||
public FaviconCacheElement getNextPrimary(final int fromIndex) {
|
||||
final int numIcons = mFavicons.size();
|
||||
final int numIcons = favicons.size();
|
||||
|
||||
int searchIndex = fromIndex;
|
||||
while (searchIndex < numIcons) {
|
||||
FaviconCacheElement element = mFavicons.get(searchIndex);
|
||||
FaviconCacheElement element = favicons.get(searchIndex);
|
||||
|
||||
if (element.mIsPrimary) {
|
||||
if (element.mInvalidated) {
|
||||
if (element.isPrimary) {
|
||||
if (element.invalidated) {
|
||||
// We return null here, despite the possible existence of other primaries,
|
||||
// because we know the most suitable primary for this request exists, but is
|
||||
// no longer in the cache. By returning null, we cause the caller to load the
|
||||
|
@ -124,10 +128,10 @@ public class FaviconsForURL {
|
|||
// No larger primary available. Let's look for smaller ones...
|
||||
searchIndex = fromIndex - 1;
|
||||
while (searchIndex >= 0) {
|
||||
FaviconCacheElement element = mFavicons.get(searchIndex);
|
||||
FaviconCacheElement element = favicons.get(searchIndex);
|
||||
|
||||
if (element.mIsPrimary) {
|
||||
if (element.mInvalidated) {
|
||||
if (element.isPrimary) {
|
||||
if (element.invalidated) {
|
||||
return null;
|
||||
}
|
||||
return element;
|
||||
|
@ -144,17 +148,17 @@ public class FaviconsForURL {
|
|||
* Ensure the dominant colour field is populated for this favicon.
|
||||
*/
|
||||
public int ensureDominantColor() {
|
||||
if (mDominantColor == -1) {
|
||||
if (dominantColor == -1) {
|
||||
// Find a payload, any payload, that is not invalidated.
|
||||
for (FaviconCacheElement element : mFavicons) {
|
||||
if (!element.mInvalidated) {
|
||||
mDominantColor = BitmapUtils.getDominantColor(element.mFaviconPayload);
|
||||
return mDominantColor;
|
||||
for (FaviconCacheElement element : favicons) {
|
||||
if (!element.invalidated) {
|
||||
dominantColor = BitmapUtils.getDominantColor(element.faviconPayload);
|
||||
return dominantColor;
|
||||
}
|
||||
}
|
||||
mDominantColor = 0xFFFFFF;
|
||||
dominantColor = 0xFFFFFF;
|
||||
}
|
||||
|
||||
return mDominantColor;
|
||||
return dominantColor;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -89,14 +89,14 @@ public class FaviconDecoder {
|
|||
LoadFaviconResult result;
|
||||
if (isDecodableByAndroid(buffer, offset)) {
|
||||
result = new LoadFaviconResult();
|
||||
result.mOffset = offset;
|
||||
result.mLength = length;
|
||||
result.mIsICO = false;
|
||||
result.offset = offset;
|
||||
result.length = length;
|
||||
result.isICO = false;
|
||||
|
||||
// We assume here that decodeByteArray doesn't hold on to the entire supplied
|
||||
// buffer -- worst case, each of our buffers will be twice the necessary size.
|
||||
result.mBitmapsDecoded = new SingleBitmapIterator(BitmapUtils.decodeByteArray(buffer, offset, length));
|
||||
result.mFaviconBytes = buffer;
|
||||
result.bitmapsDecoded = new SingleBitmapIterator(BitmapUtils.decodeByteArray(buffer, offset, length));
|
||||
result.faviconBytes = buffer;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -193,10 +193,10 @@ public class FaviconDecoder {
|
|||
* Iterator to hold a single bitmap.
|
||||
*/
|
||||
static class SingleBitmapIterator implements Iterator<Bitmap> {
|
||||
private Bitmap mBitmap;
|
||||
private Bitmap bitmap;
|
||||
|
||||
public SingleBitmapIterator(Bitmap b) {
|
||||
mBitmap = b;
|
||||
bitmap = b;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -207,22 +207,22 @@ public class FaviconDecoder {
|
|||
* @return The bitmap carried by this SingleBitmapIterator.
|
||||
*/
|
||||
public Bitmap peek() {
|
||||
return mBitmap;
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return mBitmap != null;
|
||||
return bitmap != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bitmap next() {
|
||||
if (mBitmap == null) {
|
||||
if (bitmap == null) {
|
||||
throw new NoSuchElementException("Element already returned from SingleBitmapIterator.");
|
||||
}
|
||||
|
||||
Bitmap ret = mBitmap;
|
||||
mBitmap = null;
|
||||
Bitmap ret = bitmap;
|
||||
bitmap = null;
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ import org.mozilla.gecko.gfx.BitmapUtils;
|
|||
|
||||
import android.util.SparseArray;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.NoSuchElementException;
|
||||
|
||||
|
@ -79,55 +78,55 @@ public class ICODecoder implements Iterable<Bitmap> {
|
|||
public static final int ICO_ICONDIRENTRY_LENGTH_BYTES = 16;
|
||||
|
||||
// The buffer containing bytes to attempt to decode.
|
||||
private byte[] mDecodand;
|
||||
private byte[] decodand;
|
||||
|
||||
// The region of the decodand to decode.
|
||||
private int mOffset;
|
||||
private int mLen;
|
||||
private int offset;
|
||||
private int len;
|
||||
|
||||
private IconDirectoryEntry[] mIconDirectory;
|
||||
private boolean mIsValid;
|
||||
private boolean mHasDecoded;
|
||||
private IconDirectoryEntry[] iconDirectory;
|
||||
private boolean isValid;
|
||||
private boolean hasDecoded;
|
||||
|
||||
public ICODecoder(byte[] buffer, int offset, int len) {
|
||||
mDecodand = buffer;
|
||||
mOffset = offset;
|
||||
mLen = len;
|
||||
public ICODecoder(byte[] decodand, int offset, int len) {
|
||||
this.decodand = decodand;
|
||||
this.offset = offset;
|
||||
this.len = len;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decode the Icon Directory for this ICO and store the result in mIconDirectory.
|
||||
* Decode the Icon Directory for this ICO and store the result in iconDirectory.
|
||||
*
|
||||
* @return true if ICO decoding was considered to probably be a success, false if it certainly
|
||||
* was a failure.
|
||||
*/
|
||||
private boolean decodeIconDirectoryAndPossiblyPrune() {
|
||||
mHasDecoded = true;
|
||||
hasDecoded = true;
|
||||
|
||||
// Fail if the end of the described range is out of bounds.
|
||||
if (mOffset + mLen > mDecodand.length) {
|
||||
if (offset + len > decodand.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fail if we don't have enough space for the header.
|
||||
if (mLen < ICO_HEADER_LENGTH_BYTES) {
|
||||
if (len < ICO_HEADER_LENGTH_BYTES) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that the reserved fields in the header are indeed zero, and that the type field
|
||||
// specifies ICO. If not, we've probably been given something that isn't really an ICO.
|
||||
if (mDecodand[mOffset] != 0 ||
|
||||
mDecodand[mOffset + 1] != 0 ||
|
||||
mDecodand[mOffset + 2] != 1 ||
|
||||
mDecodand[mOffset + 3] != 0) {
|
||||
if (decodand[offset] != 0 ||
|
||||
decodand[offset + 1] != 0 ||
|
||||
decodand[offset + 2] != 1 ||
|
||||
decodand[offset + 3] != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Here, and in many other places, byte values are ANDed with 0xFF. This is because Java
|
||||
// bytes are signed - to obtain a numerical value of a longer type which holds the unsigned
|
||||
// interpretation of the byte of interest, we do this.
|
||||
int numEncodedImages = (mDecodand[mOffset + 4] & 0xFF) |
|
||||
(mDecodand[mOffset + 5] & 0xFF) << 8;
|
||||
int numEncodedImages = (decodand[offset + 4] & 0xFF) |
|
||||
(decodand[offset + 5] & 0xFF) << 8;
|
||||
|
||||
|
||||
// Fail if there are no images or the field is corrupt.
|
||||
|
@ -139,12 +138,12 @@ public class ICODecoder implements Iterable<Bitmap> {
|
|||
|
||||
// Fail if there is not enough space in the buffer for the stated number of icondir entries,
|
||||
// let alone the data.
|
||||
if (mLen < headerAndDirectorySize) {
|
||||
if (len < headerAndDirectorySize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Put the pointer on the first byte of the first Icon Directory Entry.
|
||||
int bufferIndex = mOffset + ICO_HEADER_LENGTH_BYTES;
|
||||
int bufferIndex = offset + ICO_HEADER_LENGTH_BYTES;
|
||||
|
||||
// We now iterate over the Icon Directory, decoding each entry as we go. We also need to
|
||||
// discard all entries except one >= the maximum interesting size.
|
||||
|
@ -157,35 +156,35 @@ public class ICODecoder implements Iterable<Bitmap> {
|
|||
|
||||
for (int i = 0; i < numEncodedImages; i++, bufferIndex += ICO_ICONDIRENTRY_LENGTH_BYTES) {
|
||||
// Decode the Icon Directory Entry at this offset.
|
||||
IconDirectoryEntry newEntry = IconDirectoryEntry.createFromBuffer(mDecodand, mOffset, mLen, bufferIndex);
|
||||
newEntry.mIndex = i;
|
||||
IconDirectoryEntry newEntry = IconDirectoryEntry.createFromBuffer(decodand, offset, len, bufferIndex);
|
||||
newEntry.index = i;
|
||||
|
||||
if (newEntry.mIsErroneous) {
|
||||
if (newEntry.isErroneous) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (newEntry.mWidth > Favicons.sLargestFaviconSize) {
|
||||
if (newEntry.width > Favicons.largestFaviconSize) {
|
||||
// If we already have a smaller image larger than the maximum size of interest, we
|
||||
// don't care about the new one which is larger than the smallest image larger than
|
||||
// the maximum size.
|
||||
if (newEntry.mWidth >= minimumMaximum) {
|
||||
if (newEntry.width >= minimumMaximum) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove the previous minimum-maximum.
|
||||
preferenceArray.delete(minimumMaximum);
|
||||
|
||||
minimumMaximum = newEntry.mWidth;
|
||||
minimumMaximum = newEntry.width;
|
||||
}
|
||||
|
||||
IconDirectoryEntry oldEntry = preferenceArray.get(newEntry.mWidth);
|
||||
IconDirectoryEntry oldEntry = preferenceArray.get(newEntry.width);
|
||||
if (oldEntry == null) {
|
||||
preferenceArray.put(newEntry.mWidth, newEntry);
|
||||
preferenceArray.put(newEntry.width, newEntry);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (oldEntry.compareTo(newEntry) < 0) {
|
||||
preferenceArray.put(newEntry.mWidth, newEntry);
|
||||
preferenceArray.put(newEntry.width, newEntry);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,24 +196,24 @@ public class ICODecoder implements Iterable<Bitmap> {
|
|||
}
|
||||
|
||||
// Allocate space for the icon directory entries in the decoded directory.
|
||||
mIconDirectory = new IconDirectoryEntry[count];
|
||||
iconDirectory = new IconDirectoryEntry[count];
|
||||
|
||||
// The size of the data in the buffer that we find useful.
|
||||
int retainedSpace = ICO_HEADER_LENGTH_BYTES;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
IconDirectoryEntry e = preferenceArray.valueAt(i);
|
||||
retainedSpace += ICO_ICONDIRENTRY_LENGTH_BYTES + e.mPayloadSize;
|
||||
mIconDirectory[i] = e;
|
||||
retainedSpace += ICO_ICONDIRENTRY_LENGTH_BYTES + e.payloadSize;
|
||||
iconDirectory[i] = e;
|
||||
}
|
||||
|
||||
mIsValid = true;
|
||||
isValid = true;
|
||||
|
||||
// Set the number of images field in the buffer to reflect the number of retained entries.
|
||||
mDecodand[mOffset + 4] = (byte) mIconDirectory.length;
|
||||
mDecodand[mOffset + 5] = (byte) (mIconDirectory.length >>> 8);
|
||||
decodand[offset + 4] = (byte) iconDirectory.length;
|
||||
decodand[offset + 5] = (byte) (iconDirectory.length >>> 8);
|
||||
|
||||
if ((mLen - retainedSpace) > COMPACT_THRESHOLD) {
|
||||
if ((len - retainedSpace) > COMPACT_THRESHOLD) {
|
||||
compactingCopy(retainedSpace);
|
||||
}
|
||||
|
||||
|
@ -228,19 +227,19 @@ public class ICODecoder implements Iterable<Bitmap> {
|
|||
byte[] buf = new byte[spaceRetained];
|
||||
|
||||
// Copy the header.
|
||||
System.arraycopy(mDecodand, mOffset, buf, 0, ICO_HEADER_LENGTH_BYTES);
|
||||
System.arraycopy(decodand, offset, buf, 0, ICO_HEADER_LENGTH_BYTES);
|
||||
|
||||
int headerPtr = ICO_HEADER_LENGTH_BYTES;
|
||||
|
||||
int payloadPtr = ICO_HEADER_LENGTH_BYTES + (mIconDirectory.length * ICO_ICONDIRENTRY_LENGTH_BYTES);
|
||||
int payloadPtr = ICO_HEADER_LENGTH_BYTES + (iconDirectory.length * ICO_ICONDIRENTRY_LENGTH_BYTES);
|
||||
|
||||
int ind = 0;
|
||||
for (IconDirectoryEntry entry : mIconDirectory) {
|
||||
for (IconDirectoryEntry entry : iconDirectory) {
|
||||
// Copy this entry.
|
||||
System.arraycopy(mDecodand, mOffset + entry.getOffset(), buf, headerPtr, ICO_ICONDIRENTRY_LENGTH_BYTES);
|
||||
System.arraycopy(decodand, offset + entry.getOffset(), buf, headerPtr, ICO_ICONDIRENTRY_LENGTH_BYTES);
|
||||
|
||||
// Copy its payload.
|
||||
System.arraycopy(mDecodand, mOffset + entry.mPayloadOffset, buf, payloadPtr, entry.mPayloadSize);
|
||||
System.arraycopy(decodand, offset + entry.payloadOffset, buf, payloadPtr, entry.payloadSize);
|
||||
|
||||
// Update the offset field.
|
||||
buf[headerPtr + 12] = (byte) payloadPtr;
|
||||
|
@ -248,17 +247,17 @@ public class ICODecoder implements Iterable<Bitmap> {
|
|||
buf[headerPtr + 14] = (byte) (payloadPtr >>> 16);
|
||||
buf[headerPtr + 15] = (byte) (payloadPtr >>> 24);
|
||||
|
||||
entry.mPayloadOffset = payloadPtr;
|
||||
entry.mIndex = ind;
|
||||
entry.payloadOffset = payloadPtr;
|
||||
entry.index = ind;
|
||||
|
||||
payloadPtr += entry.mPayloadSize;
|
||||
payloadPtr += entry.payloadSize;
|
||||
headerPtr += ICO_ICONDIRENTRY_LENGTH_BYTES;
|
||||
ind++;
|
||||
}
|
||||
|
||||
mDecodand = buf;
|
||||
mOffset = 0;
|
||||
mLen = spaceRetained;
|
||||
decodand = buf;
|
||||
offset = 0;
|
||||
len = spaceRetained;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -269,16 +268,16 @@ public class ICODecoder implements Iterable<Bitmap> {
|
|||
* fails.
|
||||
*/
|
||||
public Bitmap decodeBitmapAtIndex(int index) {
|
||||
final IconDirectoryEntry iconDirEntry = mIconDirectory[index];
|
||||
final IconDirectoryEntry iconDirEntry = iconDirectory[index];
|
||||
|
||||
if (iconDirEntry.mPayloadIsPNG) {
|
||||
if (iconDirEntry.payloadIsPNG) {
|
||||
// PNG payload. Simply extract it and decode it.
|
||||
return BitmapUtils.decodeByteArray(mDecodand, mOffset + iconDirEntry.mPayloadOffset, iconDirEntry.mPayloadSize);
|
||||
return BitmapUtils.decodeByteArray(decodand, offset + iconDirEntry.payloadOffset, iconDirEntry.payloadSize);
|
||||
}
|
||||
|
||||
// The payload is a BMP, so we need to do some magic to get the decoder to do what we want.
|
||||
// We construct an ICO containing just the image we want, and let Android do the rest.
|
||||
byte[] decodeTarget = new byte[ICO_HEADER_LENGTH_BYTES + ICO_ICONDIRENTRY_LENGTH_BYTES + iconDirEntry.mPayloadSize];
|
||||
byte[] decodeTarget = new byte[ICO_HEADER_LENGTH_BYTES + ICO_ICONDIRENTRY_LENGTH_BYTES + iconDirEntry.payloadSize];
|
||||
|
||||
// Set the type field in the ICO header.
|
||||
decodeTarget[2] = 1;
|
||||
|
@ -287,11 +286,11 @@ public class ICODecoder implements Iterable<Bitmap> {
|
|||
decodeTarget[4] = 1;
|
||||
|
||||
// Copy the ICONDIRENTRY we need into the new buffer.
|
||||
System.arraycopy(mDecodand, mOffset + iconDirEntry.getOffset(), decodeTarget, ICO_HEADER_LENGTH_BYTES, ICO_ICONDIRENTRY_LENGTH_BYTES);
|
||||
System.arraycopy(decodand, offset + iconDirEntry.getOffset(), decodeTarget, ICO_HEADER_LENGTH_BYTES, ICO_ICONDIRENTRY_LENGTH_BYTES);
|
||||
|
||||
// Copy the payload into the new buffer.
|
||||
final int singlePayloadOffset = ICO_HEADER_LENGTH_BYTES + ICO_ICONDIRENTRY_LENGTH_BYTES;
|
||||
System.arraycopy(mDecodand, mOffset + iconDirEntry.mPayloadOffset, decodeTarget, singlePayloadOffset, iconDirEntry.mPayloadSize);
|
||||
System.arraycopy(decodand, offset + iconDirEntry.payloadOffset, decodeTarget, singlePayloadOffset, iconDirEntry.payloadSize);
|
||||
|
||||
// Update the offset field of the ICONDIRENTRY to make the new ICO valid.
|
||||
decodeTarget[ICO_HEADER_LENGTH_BYTES + 12] = (byte) singlePayloadOffset;
|
||||
|
@ -311,12 +310,12 @@ public class ICODecoder implements Iterable<Bitmap> {
|
|||
@Override
|
||||
public ICOIterator iterator() {
|
||||
// If a previous call to decode concluded this ICO is invalid, abort.
|
||||
if (mHasDecoded && !mIsValid) {
|
||||
if (hasDecoded && !isValid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If we've not been decoded before, but now fail to make any sense of the ICO, abort.
|
||||
if (!mHasDecoded) {
|
||||
if (!hasDecoded) {
|
||||
if (!decodeIconDirectoryAndPossiblyPrune()) {
|
||||
return null;
|
||||
}
|
||||
|
@ -339,11 +338,11 @@ public class ICODecoder implements Iterable<Bitmap> {
|
|||
|
||||
LoadFaviconResult result = new LoadFaviconResult();
|
||||
|
||||
result.mBitmapsDecoded = bitmaps;
|
||||
result.mFaviconBytes = mDecodand;
|
||||
result.mOffset = mOffset;
|
||||
result.mLength = mLen;
|
||||
result.mIsICO = true;
|
||||
result.bitmapsDecoded = bitmaps;
|
||||
result.faviconBytes = decodand;
|
||||
result.offset = offset;
|
||||
result.length = len;
|
||||
result.isICO = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
@ -356,12 +355,12 @@ public class ICODecoder implements Iterable<Bitmap> {
|
|||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return mIndex < mIconDirectory.length;
|
||||
return mIndex < iconDirectory.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bitmap next() {
|
||||
if (mIndex > mIconDirectory.length) {
|
||||
if (mIndex > iconDirectory.length) {
|
||||
throw new NoSuchElementException("No more elements in this ICO.");
|
||||
}
|
||||
return decodeBitmapAtIndex(mIndex++);
|
||||
|
@ -369,10 +368,10 @@ public class ICODecoder implements Iterable<Bitmap> {
|
|||
|
||||
@Override
|
||||
public void remove() {
|
||||
if (mIconDirectory[mIndex] == null) {
|
||||
if (iconDirectory[mIndex] == null) {
|
||||
throw new IllegalStateException("Remove already called for element " + mIndex);
|
||||
}
|
||||
mIconDirectory[mIndex] = null;
|
||||
iconDirectory[mIndex] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,28 +9,28 @@ package org.mozilla.gecko.favicons.decoders;
|
|||
*/
|
||||
public class IconDirectoryEntry implements Comparable<IconDirectoryEntry> {
|
||||
|
||||
public static int sMaxBPP;
|
||||
public static int maxBPP;
|
||||
|
||||
int mWidth;
|
||||
int mHeight;
|
||||
int mPaletteSize;
|
||||
int mBitsPerPixel;
|
||||
int mPayloadSize;
|
||||
int mPayloadOffset;
|
||||
boolean mPayloadIsPNG;
|
||||
int width;
|
||||
int height;
|
||||
int paletteSize;
|
||||
int bitsPerPixel;
|
||||
int payloadSize;
|
||||
int payloadOffset;
|
||||
boolean payloadIsPNG;
|
||||
|
||||
// Tracks the index in the Icon Directory of this entry. Useful only for pruning.
|
||||
int mIndex;
|
||||
boolean mIsErroneous;
|
||||
int index;
|
||||
boolean isErroneous;
|
||||
|
||||
public IconDirectoryEntry(int width, int height, int paletteSize, int bitsPerPixel, int payloadSize, int payloadOffset, boolean payloadIsPNG) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mPaletteSize = paletteSize;
|
||||
mBitsPerPixel = bitsPerPixel;
|
||||
mPayloadSize = payloadSize;
|
||||
mPayloadOffset = payloadOffset;
|
||||
mPayloadIsPNG = payloadIsPNG;
|
||||
this.width = width;
|
||||
this.height = height;
|
||||
this.paletteSize = paletteSize;
|
||||
this.bitsPerPixel = bitsPerPixel;
|
||||
this.payloadSize = payloadSize;
|
||||
this.payloadOffset = payloadOffset;
|
||||
this.payloadIsPNG = payloadIsPNG;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,7 +40,7 @@ public class IconDirectoryEntry implements Comparable<IconDirectoryEntry> {
|
|||
*/
|
||||
public static IconDirectoryEntry getErroneousEntry() {
|
||||
IconDirectoryEntry ret = new IconDirectoryEntry(-1, -1, -1, -1, -1, -1, false);
|
||||
ret.mIsErroneous = true;
|
||||
ret.isErroneous = true;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -118,63 +118,63 @@ public class IconDirectoryEntry implements Comparable<IconDirectoryEntry> {
|
|||
* Get the number of bytes from the start of the ICO file to the beginning of this entry.
|
||||
*/
|
||||
public int getOffset() {
|
||||
return ICODecoder.ICO_HEADER_LENGTH_BYTES + (mIndex * ICODecoder.ICO_ICONDIRENTRY_LENGTH_BYTES);
|
||||
return ICODecoder.ICO_HEADER_LENGTH_BYTES + (index * ICODecoder.ICO_ICONDIRENTRY_LENGTH_BYTES);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(IconDirectoryEntry another) {
|
||||
if (mWidth > another.mWidth) {
|
||||
if (width > another.width) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (mWidth < another.mWidth) {
|
||||
if (width < another.width) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Where both images exceed the max BPP, take the smaller of the two BPP values.
|
||||
if (mBitsPerPixel >= sMaxBPP && another.mBitsPerPixel >= sMaxBPP) {
|
||||
if (mBitsPerPixel < another.mBitsPerPixel) {
|
||||
if (bitsPerPixel >= maxBPP && another.bitsPerPixel >= maxBPP) {
|
||||
if (bitsPerPixel < another.bitsPerPixel) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (mBitsPerPixel > another.mBitsPerPixel) {
|
||||
if (bitsPerPixel > another.bitsPerPixel) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, take the larger of the BPP values.
|
||||
if (mBitsPerPixel > another.mBitsPerPixel) {
|
||||
if (bitsPerPixel > another.bitsPerPixel) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (mBitsPerPixel < another.mBitsPerPixel) {
|
||||
if (bitsPerPixel < another.bitsPerPixel) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Prefer large palettes.
|
||||
if (mPaletteSize > another.mPaletteSize) {
|
||||
if (paletteSize > another.paletteSize) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (mPaletteSize < another.mPaletteSize) {
|
||||
if (paletteSize < another.paletteSize) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Prefer smaller payloads.
|
||||
if (mPayloadSize < another.mPayloadSize) {
|
||||
if (payloadSize < another.payloadSize) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (mPayloadSize > another.mPayloadSize) {
|
||||
if (payloadSize > another.payloadSize) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// If all else fails, prefer PNGs over BMPs. They tend to be smaller.
|
||||
if (mPayloadIsPNG && !another.mPayloadIsPNG) {
|
||||
if (payloadIsPNG && !another.payloadIsPNG) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!mPayloadIsPNG && another.mPayloadIsPNG) {
|
||||
if (!payloadIsPNG && another.payloadIsPNG) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -182,20 +182,20 @@ public class IconDirectoryEntry implements Comparable<IconDirectoryEntry> {
|
|||
}
|
||||
|
||||
public static void setMaxBPP(int maxBPP) {
|
||||
sMaxBPP = maxBPP;
|
||||
IconDirectoryEntry.maxBPP = maxBPP;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IconDirectoryEntry{" +
|
||||
"\nmWidth=" + mWidth +
|
||||
", \nmHeight=" + mHeight +
|
||||
", \nmPaletteSize=" + mPaletteSize +
|
||||
", \nmBitsPerPixel=" + mBitsPerPixel +
|
||||
", \nmPayloadSize=" + mPayloadSize +
|
||||
", \nmPayloadOffset=" + mPayloadOffset +
|
||||
", \nmPayloadIsPNG=" + mPayloadIsPNG +
|
||||
", \nmIndex=" + mIndex +
|
||||
"\nwidth=" + width +
|
||||
", \nheight=" + height +
|
||||
", \npaletteSize=" + paletteSize +
|
||||
", \nbitsPerPixel=" + bitsPerPixel +
|
||||
", \npayloadSize=" + payloadSize +
|
||||
", \npayloadOffset=" + payloadOffset +
|
||||
", \npayloadIsPNG=" + payloadIsPNG +
|
||||
", \nindex=" + index +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,15 +21,15 @@ import java.util.Iterator;
|
|||
public class LoadFaviconResult {
|
||||
private static final String LOGTAG = "LoadFaviconResult";
|
||||
|
||||
byte[] mFaviconBytes;
|
||||
int mOffset;
|
||||
int mLength;
|
||||
byte[] faviconBytes;
|
||||
int offset;
|
||||
int length;
|
||||
|
||||
boolean mIsICO;
|
||||
Iterator<Bitmap> mBitmapsDecoded;
|
||||
boolean isICO;
|
||||
Iterator<Bitmap> bitmapsDecoded;
|
||||
|
||||
public Iterator<Bitmap> getBitmaps() {
|
||||
return mBitmapsDecoded;
|
||||
return bitmapsDecoded;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -40,17 +40,17 @@ public class LoadFaviconResult {
|
|||
*/
|
||||
public byte[] getBytesForDatabaseStorage() {
|
||||
// Begin by normalising the buffer.
|
||||
if (mOffset != 0 || mLength != mFaviconBytes.length) {
|
||||
final byte[] normalised = new byte[mLength];
|
||||
System.arraycopy(mFaviconBytes, mOffset, normalised, 0, mLength);
|
||||
mOffset = 0;
|
||||
mFaviconBytes = normalised;
|
||||
if (offset != 0 || length != faviconBytes.length) {
|
||||
final byte[] normalised = new byte[length];
|
||||
System.arraycopy(faviconBytes, offset, normalised, 0, length);
|
||||
offset = 0;
|
||||
faviconBytes = normalised;
|
||||
}
|
||||
|
||||
// For results containing a single image, we re-encode the result as a PNG in an effort to
|
||||
// save space.
|
||||
if (!mIsICO) {
|
||||
Bitmap favicon = ((FaviconDecoder.SingleBitmapIterator) mBitmapsDecoded).peek();
|
||||
if (!isICO) {
|
||||
Bitmap favicon = ((FaviconDecoder.SingleBitmapIterator) bitmapsDecoded).peek();
|
||||
byte[] data = null;
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
|
||||
|
@ -68,7 +68,7 @@ public class LoadFaviconResult {
|
|||
// We may instead want to consider re-encoding the entire ICO as a collection of efficiently
|
||||
// encoded PNGs. This may not be worth the CPU time (Indeed, the encoding of single-image
|
||||
// favicons may also not be worth the time/space tradeoff.).
|
||||
return mFaviconBytes;
|
||||
return faviconBytes;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -568,7 +568,7 @@ public class BrowserSearch extends HomeFragment
|
|||
mSuggestionsOptInPrompt = ((ViewStub) mView.findViewById(R.id.suggestions_opt_in_prompt)).inflate();
|
||||
|
||||
TextView promptText = (TextView) mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_title);
|
||||
promptText.setText(getResources().getString(R.string.suggestions_prompt, mSearchEngines.get(0).name));
|
||||
promptText.setText(getResources().getString(R.string.suggestions_prompt));
|
||||
|
||||
final View yesButton = mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_yes);
|
||||
final View noButton = mSuggestionsOptInPrompt.findViewById(R.id.suggestions_prompt_no);
|
||||
|
|
|
@ -378,9 +378,7 @@ just addresses the organization to follow, e.g. "This site is run by " -->
|
|||
from Android">
|
||||
<!ENTITY bookmarkhistory_import_wait "Please wait...">
|
||||
|
||||
<!-- Localization note (suggestions_prompt2): The placeholder &formatS; will be
|
||||
replaced with the name of the search engine. -->
|
||||
<!ENTITY suggestions_prompt2 "Would you like to turn on &formatS; search suggestions?">
|
||||
<!ENTITY suggestions_prompt3 "Would you like to turn on search suggestions?">
|
||||
|
||||
<!-- Localization note (suggestion_for_engine): The placeholder &formatS1; will be
|
||||
replaced with the name of the search engine. The placeholder &formatS2; will be
|
||||
|
|
|
@ -133,13 +133,13 @@ public class SearchEnginePreference extends CustomListPreference {
|
|||
if (mFaviconView != null) {
|
||||
desiredWidth = mFaviconView.getWidth();
|
||||
} else {
|
||||
// sLargestFaviconSize is initialized when Favicons is attached to a
|
||||
// largestFaviconSize is initialized when Favicons is attached to a
|
||||
// context, which occurs during GeckoApp.onCreate. That might not
|
||||
// ever happen (leaving it at 0), so we fall back.
|
||||
if (Favicons.sLargestFaviconSize == 0) {
|
||||
if (Favicons.largestFaviconSize == 0) {
|
||||
desiredWidth = 128;
|
||||
} else {
|
||||
desiredWidth = Favicons.sLargestFaviconSize;
|
||||
desiredWidth = Favicons.largestFaviconSize;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -370,7 +370,7 @@
|
|||
<string name="updater_apply_select">&updater_apply_select2;</string>
|
||||
|
||||
<!-- Search suggestions opt-in -->
|
||||
<string name="suggestions_prompt">&suggestions_prompt2;</string>
|
||||
<string name="suggestions_prompt">&suggestions_prompt3;</string>
|
||||
|
||||
<string name="suggestion_for_engine">&suggestion_for_engine;</string>
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ Cu.import("resource://gre/modules/Task.jsm");
|
|||
|
||||
const TEST_DATASET_ID = "test-dataset-id";
|
||||
const TEST_URL = "http://test.com";
|
||||
const TEST_TITLE = "Test";
|
||||
|
||||
const PREF_SYNC_CHECK_INTERVAL_SECS = "home.sync.checkIntervalSecs";
|
||||
const TEST_INTERVAL_SECS = 1;
|
||||
|
@ -47,7 +48,7 @@ add_test(function test_periodic_sync() {
|
|||
add_task(function test_save_and_delete() {
|
||||
// Use the HomeProvider API to save some data.
|
||||
let storage = HomeProvider.getStorage(TEST_DATASET_ID);
|
||||
yield storage.save([{ url: TEST_URL }]);
|
||||
yield storage.save([{ title: TEST_TITLE, url: TEST_URL }]);
|
||||
|
||||
// Peek in the DB to make sure we have the right data.
|
||||
let db = yield Sqlite.openConnection({ path: DB_PATH });
|
||||
|
@ -71,4 +72,61 @@ add_task(function test_save_and_delete() {
|
|||
db.close();
|
||||
});
|
||||
|
||||
add_task(function test_row_validation() {
|
||||
// Use the HomeProvider API to save some data.
|
||||
let storage = HomeProvider.getStorage(TEST_DATASET_ID);
|
||||
|
||||
let invalidRows = [
|
||||
{ url: "url" },
|
||||
{ title: "title" },
|
||||
{ description: "description" },
|
||||
{ image_url: "image_url" }
|
||||
];
|
||||
|
||||
// None of these save calls should save anything
|
||||
for (let row of invalidRows) {
|
||||
try {
|
||||
yield storage.save([row]);
|
||||
} catch (e if e instanceof HomeProvider.ValidationError) {
|
||||
// Just catch and ignore validation errors
|
||||
}
|
||||
}
|
||||
|
||||
// Peek in the DB to make sure we have the right data.
|
||||
let db = yield Sqlite.openConnection({ path: DB_PATH });
|
||||
|
||||
// Make sure no data has been saved.
|
||||
let result = yield db.execute("SELECT * FROM items");
|
||||
do_check_eq(result.length, 0);
|
||||
|
||||
db.close();
|
||||
});
|
||||
|
||||
add_task(function test_save_transaction() {
|
||||
// Use the HomeProvider API to save some data.
|
||||
let storage = HomeProvider.getStorage(TEST_DATASET_ID);
|
||||
|
||||
// One valid, one invalid
|
||||
let rows = [
|
||||
{ title: TEST_TITLE, url: TEST_URL },
|
||||
{ image_url: "image_url" }
|
||||
];
|
||||
|
||||
// Try to save all the rows at once
|
||||
try {
|
||||
yield storage.save(rows);
|
||||
} catch (e if e instanceof HomeProvider.ValidationError) {
|
||||
// Just catch and ignore validation errors
|
||||
}
|
||||
|
||||
// Peek in the DB to make sure we have the right data.
|
||||
let db = yield Sqlite.openConnection({ path: DB_PATH });
|
||||
|
||||
// Make sure no data has been saved.
|
||||
let result = yield db.execute("SELECT * FROM items");
|
||||
do_check_eq(result.length, 0);
|
||||
|
||||
db.close();
|
||||
});
|
||||
|
||||
run_next_test();
|
||||
|
|
|
@ -43,12 +43,10 @@ HelperAppLauncherDialog.prototype = {
|
|||
* Returns true otherwise.
|
||||
*/
|
||||
_canDownload: function (url, alreadyResolved=false) {
|
||||
Services.console.logStringMessage("_canDownload: " + url);
|
||||
// The common case.
|
||||
if (url.schemeIs("http") ||
|
||||
url.schemeIs("https") ||
|
||||
url.schemeIs("ftp")) {
|
||||
Services.console.logStringMessage("_canDownload: true\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -57,7 +55,6 @@ HelperAppLauncherDialog.prototype = {
|
|||
url.schemeIs("jar") ||
|
||||
url.schemeIs("resource") ||
|
||||
url.schemeIs("wyciwyg")) {
|
||||
Services.console.logStringMessage("_canDownload: false\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -66,7 +63,6 @@ HelperAppLauncherDialog.prototype = {
|
|||
let ioSvc = Cc["@mozilla.org/network/io-service;1"].getService(Components.interfaces.nsIIOService);
|
||||
let innerURI = ioSvc.newChannelFromURI(url).URI;
|
||||
if (!url.equals(innerURI)) {
|
||||
Services.console.logStringMessage("_canDownload: recursing.\n");
|
||||
return this._canDownload(innerURI, true);
|
||||
}
|
||||
}
|
||||
|
@ -81,17 +77,14 @@ HelperAppLauncherDialog.prototype = {
|
|||
|
||||
let appRoot = FileUtils.getFile("XREExeF", []);
|
||||
if (appRoot.contains(file, true)) {
|
||||
Services.console.logStringMessage("_canDownload: appRoot.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
let profileRoot = FileUtils.getFile("ProfD", []);
|
||||
if (profileRoot.contains(file, true)) {
|
||||
Services.console.logStringMessage("_canDownload: prof dir.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
Services.console.logStringMessage("_canDownload: safe.\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -382,6 +382,7 @@
|
|||
@BINPATH@/components/nsINIProcessor.js
|
||||
@BINPATH@/components/nsPrompter.manifest
|
||||
@BINPATH@/components/nsPrompter.js
|
||||
@BINPATH@/components/servicesComponents.manifest
|
||||
@BINPATH@/components/TelemetryStartup.js
|
||||
@BINPATH@/components/TelemetryStartup.manifest
|
||||
@BINPATH@/components/Webapps.js
|
||||
|
|
|
@ -112,7 +112,20 @@ function syncTimerCallback(timer) {
|
|||
}
|
||||
}
|
||||
|
||||
this.HomeStorage = function(datasetId) {
|
||||
this.datasetId = datasetId;
|
||||
};
|
||||
|
||||
this.ValidationError = function(message) {
|
||||
this.name = "ValidationError";
|
||||
this.message = message;
|
||||
};
|
||||
ValidationError.prototype = new Error();
|
||||
ValidationError.prototype.constructor = ValidationError;
|
||||
|
||||
this.HomeProvider = Object.freeze({
|
||||
ValidationError: ValidationError,
|
||||
|
||||
/**
|
||||
* Returns a storage associated with a given dataset identifer.
|
||||
*
|
||||
|
@ -249,9 +262,23 @@ function getDatabaseConnection() {
|
|||
});
|
||||
}
|
||||
|
||||
this.HomeStorage = function(datasetId) {
|
||||
this.datasetId = datasetId;
|
||||
};
|
||||
/**
|
||||
* Validates an item to be saved to the DB.
|
||||
*
|
||||
* @param item
|
||||
* (object) item object to be validated.
|
||||
*/
|
||||
function validateItem(datasetId, item) {
|
||||
if (!item.url) {
|
||||
throw new ValidationError('HomeStorage: All rows must have an URL: datasetId = ' +
|
||||
datasetId);
|
||||
}
|
||||
|
||||
if (!item.image_url && !item.title && !item.description) {
|
||||
throw new ValidationError('HomeStorage: All rows must have at least an image URL, ' +
|
||||
'or a title or a description: datasetId = ' + datasetId);
|
||||
}
|
||||
}
|
||||
|
||||
HomeStorage.prototype = {
|
||||
/**
|
||||
|
@ -267,20 +294,24 @@ HomeStorage.prototype = {
|
|||
return Task.spawn(function save_task() {
|
||||
let db = yield getDatabaseConnection();
|
||||
try {
|
||||
// Insert data into DB.
|
||||
for (let item of data) {
|
||||
// XXX: Directly pass item as params? More validation for item? Batch insert?
|
||||
let params = {
|
||||
dataset_id: this.datasetId,
|
||||
url: item.url,
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
image_url: item.image_url,
|
||||
filter: item.filter,
|
||||
created: Date.now()
|
||||
};
|
||||
yield db.executeCached(SQL.insertItem, params);
|
||||
}
|
||||
yield db.executeTransaction(function save_transaction() {
|
||||
// Insert data into DB.
|
||||
for (let item of data) {
|
||||
validateItem(this.datasetId, item);
|
||||
|
||||
// XXX: Directly pass item as params? More validation for item?
|
||||
let params = {
|
||||
dataset_id: this.datasetId,
|
||||
url: item.url,
|
||||
title: item.title,
|
||||
description: item.description,
|
||||
image_url: item.image_url,
|
||||
filter: item.filter,
|
||||
created: Date.now()
|
||||
};
|
||||
yield db.executeCached(SQL.insertItem, params);
|
||||
}
|
||||
}.bind(this));
|
||||
} finally {
|
||||
yield db.close();
|
||||
}
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -21,57 +21,26 @@ class nsIThread;
|
|||
namespace mozilla {
|
||||
namespace net {
|
||||
|
||||
class Dashboard:
|
||||
public nsIDashboard,
|
||||
public nsIDashboardEventNotifier,
|
||||
public nsITransportEventSink,
|
||||
public nsITimerCallback,
|
||||
public nsIDNSListener
|
||||
class SocketData;
|
||||
class HttpData;
|
||||
class DnsData;
|
||||
class WebSocketRequest;
|
||||
class ConnectionData;
|
||||
|
||||
class Dashboard
|
||||
: public nsIDashboard
|
||||
, public nsIDashboardEventNotifier
|
||||
{
|
||||
public:
|
||||
NS_DECL_THREADSAFE_ISUPPORTS
|
||||
NS_DECL_NSIDASHBOARD
|
||||
NS_DECL_NSIDASHBOARDEVENTNOTIFIER
|
||||
NS_DECL_NSITRANSPORTEVENTSINK
|
||||
NS_DECL_NSITIMERCALLBACK
|
||||
NS_DECL_NSIDNSLISTENER
|
||||
|
||||
Dashboard();
|
||||
friend class DashConnStatusRunnable;
|
||||
static const char *GetErrorString(nsresult rv);
|
||||
private:
|
||||
virtual ~Dashboard();
|
||||
|
||||
void GetSocketsDispatch();
|
||||
void GetHttpDispatch();
|
||||
void GetDnsInfoDispatch();
|
||||
void StartTimer(uint32_t aTimeout);
|
||||
void StopTimer();
|
||||
nsresult TestNewConnection(const nsACString& aHost, uint32_t aPort,
|
||||
const char *aProtocol, uint32_t aTimeout);
|
||||
|
||||
/* Helper methods that pass the JSON to the callback function. */
|
||||
nsresult GetSockets();
|
||||
nsresult GetHttpConnections();
|
||||
nsresult GetWebSocketConnections();
|
||||
nsresult GetDNSCacheEntries();
|
||||
nsresult GetConnectionStatus(struct ConnStatus aStatus);
|
||||
nsresult GetConnectionStatus(ConnectionData *aConnectionData);
|
||||
|
||||
private:
|
||||
struct SocketData
|
||||
{
|
||||
uint64_t totalSent;
|
||||
uint64_t totalRecv;
|
||||
nsTArray<SocketInfo> data;
|
||||
nsCOMPtr<NetDashboardCallback> cb;
|
||||
nsIThread* thread;
|
||||
};
|
||||
|
||||
struct HttpData {
|
||||
nsTArray<HttpRetParams> data;
|
||||
nsCOMPtr<NetDashboardCallback> cb;
|
||||
nsIThread* thread;
|
||||
};
|
||||
|
||||
struct LogData
|
||||
{
|
||||
|
@ -109,42 +78,27 @@ private:
|
|||
}
|
||||
nsTArray<LogData> data;
|
||||
mozilla::Mutex lock;
|
||||
nsCOMPtr<NetDashboardCallback> cb;
|
||||
nsIThread* thread;
|
||||
};
|
||||
|
||||
struct DnsData
|
||||
{
|
||||
nsCOMPtr<nsIDNSService> serv;
|
||||
nsTArray<DNSCacheEntries> data;
|
||||
nsCOMPtr<NetDashboardCallback> cb;
|
||||
nsIThread* thread;
|
||||
};
|
||||
|
||||
struct DnsLookup
|
||||
{
|
||||
nsCOMPtr<nsIDNSService> serv;
|
||||
nsCOMPtr<nsICancelable> cancel;
|
||||
nsCOMPtr<NetDashboardCallback> cb;
|
||||
};
|
||||
|
||||
struct ConnectionData
|
||||
{
|
||||
nsCOMPtr<nsISocketTransport> socket;
|
||||
nsCOMPtr<nsIInputStream> streamIn;
|
||||
nsCOMPtr<nsITimer> timer;
|
||||
nsCOMPtr<NetDashboardCallback> cb;
|
||||
nsIThread* thread;
|
||||
};
|
||||
|
||||
bool mEnableLogging;
|
||||
WebSocketData mWs;
|
||||
|
||||
struct SocketData mSock;
|
||||
struct HttpData mHttp;
|
||||
struct WebSocketData mWs;
|
||||
struct DnsData mDns;
|
||||
struct DnsLookup mDnsup;
|
||||
struct ConnectionData mConn;
|
||||
private:
|
||||
virtual ~Dashboard();
|
||||
|
||||
nsresult GetSocketsDispatch(SocketData *);
|
||||
nsresult GetHttpDispatch(HttpData *);
|
||||
nsresult GetDnsInfoDispatch(DnsData *);
|
||||
nsresult TestNewConnection(ConnectionData *);
|
||||
|
||||
/* Helper methods that pass the JSON to the callback function. */
|
||||
nsresult GetSockets(SocketData *);
|
||||
nsresult GetHttpConnections(HttpData *);
|
||||
nsresult GetDNSCacheEntries(DnsData *);
|
||||
nsresult GetWebSocketConnections(WebSocketRequest *);
|
||||
|
||||
nsCOMPtr<nsIDNSService> mDnsService;
|
||||
};
|
||||
|
||||
} } // namespace mozilla::net
|
||||
|
|
|
@ -731,10 +731,10 @@ BookmarksStore.prototype = {
|
|||
feedURI: Utils.makeURI(record.feedUri),
|
||||
siteURI: siteURI,
|
||||
guid: record.id};
|
||||
PlacesUtils.livemarks.addLivemark(livemarkObj,
|
||||
function (aStatus, aLivemark) {
|
||||
spinningCb(null, [aStatus, aLivemark]);
|
||||
});
|
||||
PlacesUtils.livemarks.addLivemark(livemarkObj).then(
|
||||
aLivemark => { spinningCb(null, [Components.results.NS_OK, aLivemark]) },
|
||||
() => { spinningCb(null, [Components.results.NS_ERROR_UNEXPECTED, aLivemark]) }
|
||||
);
|
||||
|
||||
let [status, livemark] = spinningCb.wait();
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
|
|
|
@ -38,7 +38,14 @@ SyncScheduler.prototype = {
|
|||
setDefaults: function setDefaults() {
|
||||
this._log.trace("Setting SyncScheduler policy values to defaults.");
|
||||
|
||||
this.singleDeviceInterval = Svc.Prefs.get("scheduler.singleDeviceInterval") * 1000;
|
||||
let service = Cc["@mozilla.org/weave/service;1"]
|
||||
.getService(Ci.nsISupports)
|
||||
.wrappedJSObject;
|
||||
|
||||
let part = service.fxAccountsEnabled ? "fxa" : "sync11";
|
||||
let prefSDInterval = "scheduler." + part + ".singleDeviceInterval";
|
||||
this.singleDeviceInterval = Svc.Prefs.get(prefSDInterval) * 1000;
|
||||
|
||||
this.idleInterval = Svc.Prefs.get("scheduler.idleInterval") * 1000;
|
||||
this.activeInterval = Svc.Prefs.get("scheduler.activeInterval") * 1000;
|
||||
this.immediateInterval = Svc.Prefs.get("scheduler.immediateInterval") * 1000;
|
||||
|
|
|
@ -14,12 +14,14 @@ pref("services.sync.lastversion", "firstrun");
|
|||
pref("services.sync.sendVersionInfo", true);
|
||||
|
||||
pref("services.sync.scheduler.eolInterval", 604800); // 1 week
|
||||
pref("services.sync.scheduler.singleDeviceInterval", 86400); // 1 day
|
||||
pref("services.sync.scheduler.idleInterval", 3600); // 1 hour
|
||||
pref("services.sync.scheduler.activeInterval", 600); // 10 minutes
|
||||
pref("services.sync.scheduler.immediateInterval", 90); // 1.5 minutes
|
||||
pref("services.sync.scheduler.idleTime", 300); // 5 minutes
|
||||
|
||||
pref("services.sync.scheduler.fxa.singleDeviceInterval", 3600); // 1 hour
|
||||
pref("services.sync.scheduler.sync11.singleDeviceInterval", 86400); // 1 day
|
||||
|
||||
pref("services.sync.errorhandler.networkFailureReportTimeout", 1209600); // 2 weeks
|
||||
|
||||
pref("services.sync.engine.addons", true);
|
||||
|
|
|
@ -125,7 +125,7 @@ add_test(function test_prefAttributes() {
|
|||
|
||||
_("Intervals correspond to default preferences.");
|
||||
do_check_eq(scheduler.singleDeviceInterval,
|
||||
Svc.Prefs.get("scheduler.singleDeviceInterval") * 1000);
|
||||
Svc.Prefs.get("scheduler.sync11.singleDeviceInterval") * 1000);
|
||||
do_check_eq(scheduler.idleInterval,
|
||||
Svc.Prefs.get("scheduler.idleInterval") * 1000);
|
||||
do_check_eq(scheduler.activeInterval,
|
||||
|
@ -134,7 +134,7 @@ add_test(function test_prefAttributes() {
|
|||
Svc.Prefs.get("scheduler.immediateInterval") * 1000);
|
||||
|
||||
_("Custom values for prefs will take effect after a restart.");
|
||||
Svc.Prefs.set("scheduler.singleDeviceInterval", 42);
|
||||
Svc.Prefs.set("scheduler.sync11.singleDeviceInterval", 42);
|
||||
Svc.Prefs.set("scheduler.idleInterval", 23);
|
||||
Svc.Prefs.set("scheduler.activeInterval", 18);
|
||||
Svc.Prefs.set("scheduler.immediateInterval", 31415);
|
||||
|
|
|
@ -784,10 +784,10 @@ Livemark.prototype = {
|
|||
// Until this can handle asynchronous creation, we need to spin.
|
||||
let spinningCb = Async.makeSpinningCallback();
|
||||
|
||||
PlacesUtils.livemarks.addLivemark(livemarkObj,
|
||||
function (aStatus, aLivemark) {
|
||||
spinningCb(null, [aStatus, aLivemark]);
|
||||
});
|
||||
PlacesUtils.livemarks.addLivemark(livemarkObj).then(
|
||||
aLivemark => { spinningCb(null, [Components.results.NS_OK, aLivemark]) },
|
||||
() => { spinningCb(null, [Components.results.NS_ERROR_UNEXPECTED, aLivemark]) }
|
||||
);
|
||||
|
||||
let [status, livemark] = spinningCb.wait();
|
||||
if (!Components.isSuccessCode(status)) {
|
||||
|
|
|
@ -669,7 +669,7 @@ BookmarkImporter.prototype = {
|
|||
"index": PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
"feedURI": frame.previousFeed,
|
||||
"siteURI": frame.previousLink,
|
||||
});
|
||||
}).then(null, Cu.reportError);
|
||||
} else if (frame.previousLink) {
|
||||
// This is a common bookmark.
|
||||
PlacesUtils.bookmarks.setItemTitle(frame.previousId,
|
||||
|
|
|
@ -386,15 +386,13 @@ BookmarkImporter.prototype = {
|
|||
index: aIndex,
|
||||
lastModified: aData.lastModified,
|
||||
siteURI: siteURI
|
||||
}, function(aStatus, aLivemark) {
|
||||
if (Components.isSuccessCode(aStatus)) {
|
||||
let id = aLivemark.id;
|
||||
if (aData.dateAdded)
|
||||
PlacesUtils.bookmarks.setItemDateAdded(id, aData.dateAdded);
|
||||
if (aData.annos && aData.annos.length)
|
||||
PlacesUtils.setAnnotationsForItem(id, aData.annos);
|
||||
}
|
||||
});
|
||||
}).then(function (aLivemark) {
|
||||
let id = aLivemark.id;
|
||||
if (aData.dateAdded)
|
||||
PlacesUtils.bookmarks.setItemDateAdded(id, aData.dateAdded);
|
||||
if (aData.annos && aData.annos.length)
|
||||
PlacesUtils.setAnnotationsForItem(id, aData.annos);
|
||||
}, Cu.reportError);
|
||||
}
|
||||
} else {
|
||||
id = PlacesUtils.bookmarks.createFolder(
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -1523,7 +1523,31 @@ this.PlacesUtils = {
|
|||
}
|
||||
});
|
||||
return deferred.promise;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the unique id for an item (a bookmark, a folder or a separator) given
|
||||
* its item id.
|
||||
*
|
||||
* @param aItemId
|
||||
* an item id
|
||||
* @return {Promise}
|
||||
* @resolves to the GUID.
|
||||
* @rejects if aItemId is invalid.
|
||||
*/
|
||||
promiseItemGUID: function (aItemId) GUIDHelper.getItemGUID(aItemId),
|
||||
|
||||
/**
|
||||
* Get the item id for an item (a bookmark, a folder or a separator) given
|
||||
* its unique id.
|
||||
*
|
||||
* @param aGUID
|
||||
* an item GUID
|
||||
* @retrun {Promise}
|
||||
* @resolves to the GUID.
|
||||
* @rejects if there's no item for the given GUID.
|
||||
*/
|
||||
promiseItemId: function (aGUID) GUIDHelper.getItemId(aGUID)
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -1640,6 +1664,138 @@ XPCOMUtils.defineLazyServiceGetter(this, "focusManager",
|
|||
"@mozilla.org/focus-manager;1",
|
||||
"nsIFocusManager");
|
||||
|
||||
// Sometime soon, likely as part of the transition to mozIAsyncBookmarks,
|
||||
// itemIds will be deprecated in favour of GUIDs, which play much better
|
||||
// with multiple undo/redo operations. Because these GUIDs are already stored,
|
||||
// and because we don't want to revise the transactions API once more when this
|
||||
// happens, transactions are set to work with GUIDs exclusively, in the sense
|
||||
// that they may never expose itemIds, nor do they accept them as input.
|
||||
// More importantly, transactions which add or remove items guarantee to
|
||||
// restore the guids on undo/redo, so that the following transactions that may
|
||||
// done or undo can assume the items they're interested in are stil accessible
|
||||
// through the same GUID.
|
||||
// The current bookmarks API, however, doesn't expose the necessary means for
|
||||
// working with GUIDs. So, until it does, this helper object accesses the
|
||||
// Places database directly in order to switch between GUIDs and itemIds, and
|
||||
// "restore" GUIDs on items re-created items.
|
||||
const REASON_FINISHED = Ci.mozIStorageStatementCallback.REASON_FINISHED;
|
||||
let GUIDHelper = {
|
||||
// Cache for guid<->itemId paris.
|
||||
GUIDsForIds: new Map(),
|
||||
idsForGUIDs: new Map(),
|
||||
|
||||
getItemId: function (aGUID) {
|
||||
if (this.idsForGUIDs.has(aGUID))
|
||||
return Promise.resolve(this.idsForGUIDs.get(aGUID));
|
||||
|
||||
let deferred = Promise.defer();
|
||||
let itemId = -1;
|
||||
|
||||
this._getIDStatement.params.guid = aGUID;
|
||||
this._getIDStatement.executeAsync({
|
||||
handleResult: function (aResultSet) {
|
||||
let row = aResultSet.getNextRow();
|
||||
if (row)
|
||||
itemId = row.getResultByIndex(0);
|
||||
},
|
||||
handleCompletion: function (aReason) {
|
||||
if (aReason == REASON_FINISHED && itemId != -1) {
|
||||
deferred.resolve(itemId);
|
||||
|
||||
this.ensureObservingRemovedItems();
|
||||
this.idsForGUIDs.set(aGUID, itemId);
|
||||
}
|
||||
else if (itemId != -1) {
|
||||
deferred.reject("no item found for the given guid");
|
||||
}
|
||||
else {
|
||||
deferred.reject("SQLite Error: " + aReason);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
getItemGUID: function (aItemId) {
|
||||
if (this.GUIDsForIds.has(aItemId))
|
||||
return Promise.resolve(this.GUIDsForIds.has(aItemId));
|
||||
|
||||
let deferred = Promise.defer();
|
||||
let guid = "";
|
||||
|
||||
this._getGUIDStatement.params.id = aItemId;
|
||||
this._getGUIDStatement.executeAsync({
|
||||
handleResult: function (aResultSet) {
|
||||
let row = aResultSet.getNextRow();
|
||||
if (row) {
|
||||
guid = row.getResultByIndex(1);
|
||||
}
|
||||
},
|
||||
handleCompletion: function (aReason) {
|
||||
if (aReason == REASON_FINISHED && guid) {
|
||||
deferred.resolve(guid);
|
||||
|
||||
this.ensureObservingRemovedItems();
|
||||
this.GUIDsForIds.set(aItemId, guid);
|
||||
}
|
||||
else if (!guid) {
|
||||
deferred.reject("no item found for the given itemId");
|
||||
}
|
||||
else {
|
||||
deferred.reject("SQLite Error: " + aReason);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
ensureObservingRemovedItems: function () {
|
||||
if (!("observer" in this)) {
|
||||
/**
|
||||
* This observers serves two purposes:
|
||||
* (1) Invalidate cached id<->guid paris on when items are removed.
|
||||
* (2) Cache GUIDs given us free of charge by onItemAdded/onItemRemoved.
|
||||
* So, for exmaple, when the NewBookmark needs the new GUID, we already
|
||||
* have it cached.
|
||||
*/
|
||||
this.observer = {
|
||||
onItemAdded: (aItemId, aParentId, aIndex, aItemType, aURI, aTitle,
|
||||
aDateAdded, aGUID, aParentGUID) => {
|
||||
this.GUIDsForIds.set(aItemId, aGUID);
|
||||
this.GUIDsForIds.set(aParentId, aParentGUID);
|
||||
},
|
||||
onItemRemoved:
|
||||
(aItemId, aParentId, aIndex, aItemTyep, aURI, aGUID, aParentGUID) => {
|
||||
this.GUIDsForIds.delete(aItemId);
|
||||
this.idsForGUIDs.delete(aGUID);
|
||||
this.GUIDsForIds.set(aParentId, aParentGUID);
|
||||
},
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI(Ci.nsINavBookmarkObserver),
|
||||
__noSuchMethod__: () => {}, // Catch all all onItem* methods.
|
||||
};
|
||||
PlacesUtils.bookmarks.addObserver(this.observer, false);
|
||||
PlacesUtils.registerShutdownFunction(() => {
|
||||
PlacesUtils.bookmarks.removeObserver(this.observer);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
XPCOMUtils.defineLazyGetter(GUIDHelper, "_getIDStatement", () => {
|
||||
let s = PlacesUtils.history.DBConnection.createAsyncStatement(
|
||||
"SELECT b.id, b.guid from moz_bookmarks b WHERE b.guid = :guid");
|
||||
PlacesUtils.registerShutdownFunction( () => s.finalize() );
|
||||
return s;
|
||||
});
|
||||
XPCOMUtils.defineLazyGetter(GUIDHelper, "_getGUIDStatement", () => {
|
||||
let s = PlacesUtils.history.DBConnection.createAsyncStatement(
|
||||
"SELECT b.id, b.guid from moz_bookmarks b WHERE b.id = :id");
|
||||
PlacesUtils.registerShutdownFunction( () => s.finalize() );
|
||||
return s;
|
||||
});
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Transactions handlers.
|
||||
|
||||
|
@ -2039,29 +2195,23 @@ PlacesCreateLivemarkTransaction.prototype = {
|
|||
, parentId: this.item.parentId
|
||||
, index: this.item.index
|
||||
, siteURI: this.item.siteURI
|
||||
},
|
||||
(function(aStatus, aLivemark) {
|
||||
if (Components.isSuccessCode(aStatus)) {
|
||||
this.item.id = aLivemark.id;
|
||||
if (this.item.annotations && this.item.annotations.length > 0) {
|
||||
PlacesUtils.setAnnotationsForItem(this.item.id,
|
||||
this.item.annotations);
|
||||
}
|
||||
}).then(aLivemark => {
|
||||
this.item.id = aLivemark.id;
|
||||
if (this.item.annotations && this.item.annotations.length > 0) {
|
||||
PlacesUtils.setAnnotationsForItem(this.item.id,
|
||||
this.item.annotations);
|
||||
}
|
||||
}).bind(this)
|
||||
);
|
||||
}, Cu.reportError);
|
||||
},
|
||||
|
||||
undoTransaction: function CLTXN_undoTransaction()
|
||||
{
|
||||
// The getLivemark callback is expected to receive a failure status but it
|
||||
// is used just to serialize, so doesn't matter.
|
||||
PlacesUtils.livemarks.getLivemark(
|
||||
{ id: this.item.id },
|
||||
(function (aStatus, aLivemark) {
|
||||
PlacesUtils.livemarks.getLivemark({ id: this.item.id })
|
||||
.then(null, () => {
|
||||
PlacesUtils.bookmarks.removeItem(this.item.id);
|
||||
}).bind(this)
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -2099,17 +2249,12 @@ PlacesRemoveLivemarkTransaction.prototype = {
|
|||
|
||||
doTransaction: function RLTXN_doTransaction()
|
||||
{
|
||||
PlacesUtils.livemarks.getLivemark(
|
||||
{ id: this.item.id },
|
||||
(function (aStatus, aLivemark) {
|
||||
if (Components.isSuccessCode(aStatus)) {
|
||||
this.item.feedURI = aLivemark.feedURI;
|
||||
this.item.siteURI = aLivemark.siteURI;
|
||||
|
||||
PlacesUtils.bookmarks.removeItem(this.item.id);
|
||||
}
|
||||
}).bind(this)
|
||||
);
|
||||
PlacesUtils.livemarks.getLivemark({ id: this.item.id })
|
||||
.then(aLivemark => {
|
||||
this.item.feedURI = aLivemark.feedURI;
|
||||
this.item.siteURI = aLivemark.siteURI;
|
||||
PlacesUtils.bookmarks.removeItem(this.item.id);
|
||||
}, Cu.reportError);
|
||||
},
|
||||
|
||||
undoTransaction: function RLTXN_undoTransaction()
|
||||
|
@ -2118,26 +2263,21 @@ PlacesRemoveLivemarkTransaction.prototype = {
|
|||
// feedURI and siteURI of the livemark.
|
||||
// The getLivemark callback is expected to receive a failure status but it
|
||||
// is used just to serialize, so doesn't matter.
|
||||
PlacesUtils.livemarks.getLivemark(
|
||||
{ id: this.item.id },
|
||||
(function () {
|
||||
let addLivemarkCallback = (function(aStatus, aLivemark) {
|
||||
if (Components.isSuccessCode(aStatus)) {
|
||||
let itemId = aLivemark.id;
|
||||
PlacesUtils.bookmarks.setItemDateAdded(itemId, this.item.dateAdded);
|
||||
PlacesUtils.setAnnotationsForItem(itemId, this.item.annotations);
|
||||
}
|
||||
}).bind(this);
|
||||
PlacesUtils.livemarks.getLivemark({ id: this.item.id })
|
||||
.then(null, () => {
|
||||
PlacesUtils.livemarks.addLivemark({ parentId: this.item.parentId
|
||||
, title: this.item.title
|
||||
, siteURI: this.item.siteURI
|
||||
, feedURI: this.item.feedURI
|
||||
, index: this.item.index
|
||||
, lastModified: this.item.lastModified
|
||||
},
|
||||
addLivemarkCallback);
|
||||
}).bind(this)
|
||||
);
|
||||
}).then(
|
||||
aLivemark => {
|
||||
let itemId = aLivemark.id;
|
||||
PlacesUtils.bookmarks.setItemDateAdded(itemId, this.item.dateAdded);
|
||||
PlacesUtils.setAnnotationsForItem(itemId, this.item.annotations);
|
||||
}, Cu.reportError);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -67,6 +67,7 @@ if CONFIG['MOZ_PLACES']:
|
|||
'ColorConversion.js',
|
||||
'PlacesBackups.jsm',
|
||||
'PlacesDBUtils.jsm',
|
||||
'PlacesTransactions.jsm',
|
||||
]
|
||||
|
||||
EXTRA_PP_JS_MODULES += [
|
||||
|
|
|
@ -27,6 +27,9 @@ interface mozIAsyncLivemarks : nsISupports
|
|||
* @return {Promise}
|
||||
* @throws NS_ERROR_INVALID_ARG if the supplied information is insufficient
|
||||
* for the creation.
|
||||
* @deprecated passing a callback is deprecated. Moreover, for backwards
|
||||
* compatibility reasons, when a callback is provided this method
|
||||
* won't return a promise.
|
||||
*/
|
||||
jsval addLivemark(in jsval aLivemarkInfo,
|
||||
[optional] in mozILivemarkCallback aCallback);
|
||||
|
@ -43,6 +46,9 @@ interface mozIAsyncLivemarks : nsISupports
|
|||
*
|
||||
* @return {Promise}
|
||||
* @throws NS_ERROR_INVALID_ARG if the id/guid is invalid.
|
||||
* @deprecated passing a callback is deprecated. Moreover, for backwards
|
||||
* compatibility reasons, when a callback is provided this method
|
||||
* won't return a promise.
|
||||
*/
|
||||
jsval removeLivemark(in jsval aLivemarkInfo,
|
||||
[optional] in mozILivemarkCallback aCallback);
|
||||
|
@ -60,6 +66,9 @@ interface mozIAsyncLivemarks : nsISupports
|
|||
* @return {Promise}
|
||||
* @throws NS_ERROR_INVALID_ARG if the id/guid is invalid or an invalid
|
||||
* callback is provided.
|
||||
* @deprecated passing a callback is deprecated. Moreover, for backwards
|
||||
* compatibility reasons, when a callback is provided this method
|
||||
* won't return a promise.
|
||||
*/
|
||||
jsval getLivemark(in jsval aLivemarkInfo,
|
||||
[optional] in mozILivemarkCallback aCallback);
|
||||
|
|
|
@ -18,6 +18,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
|||
"resource://gre/modules/NetUtil.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Promise",
|
||||
"resource://gre/modules/Promise.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
|
||||
"resource://gre/modules/Deprecated.jsm");
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// Services
|
||||
|
@ -126,9 +128,9 @@ LivemarkService.prototype = {
|
|||
stmt.finalize();
|
||||
},
|
||||
|
||||
_onCacheReady: function LS__onCacheReady(aCallback, aWaitForAsyncWrites)
|
||||
_onCacheReady: function LS__onCacheReady(aCallback)
|
||||
{
|
||||
if (this._pendingStmt || aWaitForAsyncWrites) {
|
||||
if (this._pendingStmt) {
|
||||
// The cache is still being populated, so enqueue the job to the Storage
|
||||
// async thread. Ideally this should just dispatch a runnable to it,
|
||||
// that would call back on the main thread, but bug 608142 made that
|
||||
|
@ -212,6 +214,12 @@ LivemarkService.prototype = {
|
|||
throw Cr.NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (aLivemarkCallback) {
|
||||
Deprecated.warning("Passing a callback to Livermarks methods is deprecated. " +
|
||||
"Please use the returned promise instead.",
|
||||
"https://developer.mozilla.org/docs/Mozilla/JavaScript_code_modules/Promise.jsm");
|
||||
}
|
||||
|
||||
// The addition is done synchronously due to the fact importExport service
|
||||
// and JSON backups require that. The notification is async though.
|
||||
// Once bookmarks are async, this may be properly fixed.
|
||||
|
@ -235,9 +243,7 @@ LivemarkService.prototype = {
|
|||
});
|
||||
if (this._itemAdded && this._itemAdded.id == livemark.id) {
|
||||
livemark.index = this._itemAdded.index;
|
||||
if (!aLivemarkInfo.guid) {
|
||||
livemark.guid = this._itemAdded.guid;
|
||||
}
|
||||
livemark.guid = this._itemAdded.guid;
|
||||
if (!aLivemarkInfo.lastModified) {
|
||||
livemark.lastModified = this._itemAdded.lastModified;
|
||||
}
|
||||
|
@ -246,7 +252,7 @@ LivemarkService.prototype = {
|
|||
// Updating the cache even if it has not yet been populated doesn't
|
||||
// matter since it will just be overwritten.
|
||||
this._livemarks[livemark.id] = livemark;
|
||||
this._guids[aLivemarkInfo.guid] = livemark.id;
|
||||
this._guids[livemark.guid] = livemark.id;
|
||||
}
|
||||
catch (ex) {
|
||||
addLivemarkEx = ex;
|
||||
|
@ -260,8 +266,9 @@ LivemarkService.prototype = {
|
|||
aLivemarkCallback.onCompletion(addLivemarkEx.result, livemark);
|
||||
}
|
||||
catch(ex2) { }
|
||||
} else {
|
||||
deferred.reject(addLivemarkEx);
|
||||
}
|
||||
deferred.reject(addLivemarkEx);
|
||||
}
|
||||
else {
|
||||
if (aLivemarkCallback) {
|
||||
|
@ -269,13 +276,14 @@ LivemarkService.prototype = {
|
|||
aLivemarkCallback.onCompletion(Cr.NS_OK, livemark);
|
||||
}
|
||||
catch(ex2) { }
|
||||
} else {
|
||||
deferred.resolve(livemark);
|
||||
}
|
||||
deferred.resolve(livemark);
|
||||
}
|
||||
}, true);
|
||||
});
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
return aLivemarkCallback ? null : deferred.promise;
|
||||
},
|
||||
|
||||
removeLivemark: function LS_removeLivemark(aLivemarkInfo, aLivemarkCallback)
|
||||
|
@ -292,6 +300,12 @@ LivemarkService.prototype = {
|
|||
throw Cr.NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (aLivemarkCallback) {
|
||||
Deprecated.warning("Passing a callback to Livermarks methods is deprecated. " +
|
||||
"Please use the returned promise instead.",
|
||||
"https://developer.mozilla.org/docs/Mozilla/JavaScript_code_modules/Promise.jsm");
|
||||
}
|
||||
|
||||
// Convert the guid to an id.
|
||||
if (id in this._guids) {
|
||||
id = this._guids[id];
|
||||
|
@ -316,8 +330,9 @@ LivemarkService.prototype = {
|
|||
aLivemarkCallback.onCompletion(removeLivemarkEx.result, null);
|
||||
}
|
||||
catch(ex2) { }
|
||||
} else {
|
||||
deferred.reject(removeLivemarkEx);
|
||||
}
|
||||
deferred.reject(removeLivemarkEx);
|
||||
}
|
||||
else {
|
||||
if (aLivemarkCallback) {
|
||||
|
@ -325,13 +340,14 @@ LivemarkService.prototype = {
|
|||
aLivemarkCallback.onCompletion(Cr.NS_OK, null);
|
||||
}
|
||||
catch(ex2) { }
|
||||
} else {
|
||||
deferred.resolve();
|
||||
}
|
||||
deferred.resolve();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
return aLivemarkCallback ? null : deferred.promise;
|
||||
},
|
||||
|
||||
_reloaded: [],
|
||||
|
@ -382,6 +398,12 @@ LivemarkService.prototype = {
|
|||
throw Cr.NS_ERROR_INVALID_ARG;
|
||||
}
|
||||
|
||||
if (aLivemarkCallback) {
|
||||
Deprecated.warning("Passing a callback to Livermarks methods is deprecated. " +
|
||||
"Please use the returned promise instead.",
|
||||
"https://developer.mozilla.org/docs/Mozilla/JavaScript_code_modules/Promise.jsm");
|
||||
}
|
||||
|
||||
let deferred = Promise.defer();
|
||||
this._onCacheReady( () => {
|
||||
// Convert the guid to an id.
|
||||
|
@ -393,20 +415,22 @@ LivemarkService.prototype = {
|
|||
try {
|
||||
aLivemarkCallback.onCompletion(Cr.NS_OK, this._livemarks[id]);
|
||||
} catch (ex) {}
|
||||
} else {
|
||||
deferred.resolve(this._livemarks[id]);
|
||||
}
|
||||
deferred.resolve(this._livemarks[id]);
|
||||
}
|
||||
else {
|
||||
if (aLivemarkCallback) {
|
||||
try {
|
||||
aLivemarkCallback.onCompletion(Cr.NS_ERROR_INVALID_ARG, null);
|
||||
} catch (ex) { }
|
||||
} else {
|
||||
deferred.reject(Components.Exception("", Cr.NS_ERROR_INVALID_ARG));
|
||||
}
|
||||
deferred.reject(Components.Exception("", Cr.NS_ERROR_INVALID_ARG));
|
||||
}
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
return aLivemarkCallback ? null : deferred.promise;
|
||||
},
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -558,11 +582,9 @@ function Livemark(aLivemarkInfo)
|
|||
// Create a new livemark.
|
||||
this.id = PlacesUtils.bookmarks.createFolder(aLivemarkInfo.parentId,
|
||||
aLivemarkInfo.title,
|
||||
aLivemarkInfo.index);
|
||||
aLivemarkInfo.index,
|
||||
aLivemarkInfo.guid);
|
||||
PlacesUtils.bookmarks.setFolderReadonly(this.id, true);
|
||||
if (aLivemarkInfo.guid) {
|
||||
this.writeGuid(aLivemarkInfo.guid);
|
||||
}
|
||||
this.writeFeedURI(aLivemarkInfo.feedURI);
|
||||
if (aLivemarkInfo.siteURI) {
|
||||
this.writeSiteURI(aLivemarkInfo.siteURI);
|
||||
|
@ -630,31 +652,6 @@ Livemark.prototype = {
|
|||
this.siteURI = aSiteURI;
|
||||
},
|
||||
|
||||
writeGuid: function LM_writeGuid(aGUID)
|
||||
{
|
||||
// There isn't a way to create a bookmark with a given guid yet, nor to
|
||||
// set a guid on an existing one. So, for now, just go the dirty way.
|
||||
let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
|
||||
.DBConnection;
|
||||
let stmt = db.createAsyncStatement("UPDATE moz_bookmarks " +
|
||||
"SET guid = :guid " +
|
||||
"WHERE id = :item_id");
|
||||
stmt.params.guid = aGUID;
|
||||
stmt.params.item_id = this.id;
|
||||
let livemark = this;
|
||||
stmt.executeAsync({
|
||||
handleError: function () {},
|
||||
handleResult: function () {},
|
||||
handleCompletion: function ETAT_handleCompletion(aReason)
|
||||
{
|
||||
if (aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED) {
|
||||
livemark._guid = aGUID;
|
||||
}
|
||||
}
|
||||
});
|
||||
stmt.finalize();
|
||||
},
|
||||
|
||||
set guid(aGUID) {
|
||||
this._guid = aGUID;
|
||||
return aGUID;
|
||||
|
|
|
@ -55,10 +55,8 @@ function runTest()
|
|||
, index: PlacesUtils.bookmarks.DEFAULT_INDEX
|
||||
, feedURI: aLivemarkData.feedURI
|
||||
, siteURI: aLivemarkData.siteURI
|
||||
},
|
||||
function (aStatus, aLivemark) {
|
||||
ok(Components.isSuccessCode(aStatus), "Get livemark");
|
||||
|
||||
})
|
||||
.then(function (aLivemark) {
|
||||
is (aLivemark.feedURI.spec, aLivemarkData.feedURI.spec,
|
||||
"Get correct feedURI");
|
||||
if (aLivemarkData.siteURI) {
|
||||
|
@ -84,8 +82,10 @@ function runTest()
|
|||
SimpleTest.finish();
|
||||
}
|
||||
});
|
||||
}, function () {
|
||||
is(true, false, "Should not fail adding a livemark");
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
LIVEMARKS.forEach(testLivemark);
|
||||
|
|
|
@ -37,9 +37,8 @@ function runTest() {
|
|||
, index: PlacesUtils.bookmarks.DEFAULT_INDEX
|
||||
, feedURI: NetUtil.newURI(FEEDSPEC)
|
||||
, siteURI: NetUtil.newURI(INITIALSITESPEC)
|
||||
},
|
||||
function (aStatus, aLivemark) {
|
||||
ok(Components.isSuccessCode(aStatus), "Get livemark");
|
||||
})
|
||||
.then(function (aLivemark) {
|
||||
is(aLivemark.siteURI.spec, INITIALSITESPEC,
|
||||
"Has correct initial livemark site URI");
|
||||
|
||||
|
@ -50,6 +49,8 @@ function runTest() {
|
|||
PlacesUtils.bookmarks.removeItem(aLivemark.id);
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}, function () {
|
||||
is(true, false, "Should not fail adding a livemark");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -35,9 +35,8 @@ function runTest() {
|
|||
, parentId: PlacesUtils.toolbarFolderId
|
||||
, index: PlacesUtils.bookmarks.DEFAULT_INDEX
|
||||
, feedURI: NetUtil.newURI(FEEDSPEC)
|
||||
},
|
||||
function (aStatus, aLivemark) {
|
||||
ok(Components.isSuccessCode(aStatus), "Get livemark");
|
||||
})
|
||||
.then(function (aLivemark) {
|
||||
is(aLivemark.siteURI, null, "Has null livemark site URI");
|
||||
|
||||
waitForLivemarkLoad(aLivemark, function (aLivemark) {
|
||||
|
@ -47,6 +46,8 @@ function runTest() {
|
|||
PlacesUtils.bookmarks.removeItem(aLivemark.id);
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}, function () {
|
||||
is(true, false, "Should not fail adding a livemark");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -36,10 +36,8 @@ function runTest() {
|
|||
, index: PlacesUtils.bookmarks.DEFAULT_INDEX
|
||||
, feedURI: NetUtil.newURI(FEEDSPEC)
|
||||
, siteURI: NetUtil.newURI("http:/mochi.test/")
|
||||
},
|
||||
function (aStatus, aLivemark) {
|
||||
ok(Components.isSuccessCode(aStatus), "Get livemark");
|
||||
|
||||
})
|
||||
.then(function (aLivemark) {
|
||||
waitForLivemarkLoad(aLivemark, function (aLivemark) {
|
||||
let nodes = aLivemark.getNodesForContainer({});
|
||||
|
||||
|
@ -52,6 +50,8 @@ function runTest() {
|
|||
PlacesUtils.bookmarks.removeItem(aLivemark.id);
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}, function () {
|
||||
is(true, false, "Should not fail adding a livemark");
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -35,13 +35,10 @@ function runTest() {
|
|||
, index: PlacesUtils.bookmarks.DEFAULT_INDEX
|
||||
, feedURI: NetUtil.newURI(FEEDSPEC)
|
||||
, siteURI: NetUtil.newURI("http:/mochi.test/")
|
||||
},
|
||||
function (aStatus, aLivemark) {
|
||||
ok(Components.isSuccessCode(aStatus), "Get livemark");
|
||||
|
||||
})
|
||||
.then(function (aLivemark) {
|
||||
waitForLivemarkLoad(aLivemark, function (aLivemark) {
|
||||
let nodes = aLivemark.getNodesForContainer({});
|
||||
ok(Components.isSuccessCode(aStatus), "Get livemark entries");
|
||||
|
||||
is(nodes[0].title, "The First Title",
|
||||
"livemark site URI set to value in feed");
|
||||
|
@ -49,6 +46,9 @@ function runTest() {
|
|||
PlacesUtils.bookmarks.removeItem(aLivemark.id);
|
||||
SimpleTest.finish();
|
||||
});
|
||||
}, function () {
|
||||
is(true, false, "Should not fail adding a livemark");
|
||||
SimpleTest.finish();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -57,15 +57,18 @@ function addLivemarks(aCallback) {
|
|||
info("Adding livemarks");
|
||||
let count = gLivemarks.length;
|
||||
gLivemarks.forEach(function(aLivemarkData) {
|
||||
PlacesUtils.livemarks.addLivemark(aLivemarkData,
|
||||
function (aStatus, aLivemark) {
|
||||
ok(Components.isSuccessCode(aStatus), "Add livemark should succeed");
|
||||
PlacesUtils.livemarks.addLivemark(aLivemarkData)
|
||||
.then(function (aLivemark) {
|
||||
ok(aLivemark.feedURI.equals(aLivemarkData.feedURI), "Livemark added");
|
||||
aLivemarkData.id = aLivemark.id;
|
||||
if (--count == 0) {
|
||||
aCallback();
|
||||
}
|
||||
}
|
||||
);
|
||||
},
|
||||
function () {
|
||||
is(true, false, "Should not fail adding a livemark.");
|
||||
aCallback();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -73,9 +76,9 @@ function reloadLivemarks(aForceUpdate, aCallback) {
|
|||
info("Reloading livemarks with forceUpdate: " + aForceUpdate);
|
||||
let count = gLivemarks.length;
|
||||
gLivemarks.forEach(function(aLivemarkData) {
|
||||
PlacesUtils.livemarks.getLivemark(aLivemarkData,
|
||||
function (aStatus, aLivemark) {
|
||||
ok(Components.isSuccessCode(aStatus), "Get livemark should succeed");
|
||||
PlacesUtils.livemarks.getLivemark(aLivemarkData)
|
||||
.then(aLivemark => {
|
||||
ok(aLivemark.feedURI.equals(aLivemarkData.feedURI), "Livemark found");
|
||||
aLivemarkData._observer = new resultObserver(aLivemark, function() {
|
||||
if (++count == gLivemarks.length) {
|
||||
aCallback();
|
||||
|
@ -84,6 +87,10 @@ function reloadLivemarks(aForceUpdate, aCallback) {
|
|||
if (--count == 0) {
|
||||
PlacesUtils.livemarks.reloadLivemarks(aForceUpdate);
|
||||
}
|
||||
},
|
||||
function() {
|
||||
is(true, false, "Should not fail getting a livemark.");
|
||||
aCallback();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -93,12 +100,15 @@ function removeLivemarks(aCallback) {
|
|||
info("Removing livemarks");
|
||||
let count = gLivemarks.length;
|
||||
gLivemarks.forEach(function(aLivemarkData) {
|
||||
PlacesUtils.livemarks.removeLivemark(aLivemarkData,
|
||||
function (aStatus, aLivemark) {
|
||||
ok(Components.isSuccessCode(aStatus), "Remove livemark should succeed");
|
||||
PlacesUtils.livemarks.removeLivemark(aLivemarkData).then(
|
||||
function (aLivemark) {
|
||||
if (--count == 0) {
|
||||
aCallback();
|
||||
}
|
||||
},
|
||||
function() {
|
||||
is(true, false, "Should not fail adding a livemark.");
|
||||
aCallback();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
@ -36,6 +36,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "BookmarkJSONUtils",
|
|||
"resource://gre/modules/BookmarkJSONUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesBackups",
|
||||
"resource://gre/modules/PlacesBackups.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PlacesTransactions",
|
||||
"resource://gre/modules/PlacesTransactions.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "OS",
|
||||
"resource://gre/modules/osfile.jsm");
|
||||
|
||||
|
|
|
@ -162,7 +162,7 @@ function task_populateDB(aArray)
|
|||
, index: qdata.index
|
||||
, feedURI: uri(qdata.feedURI)
|
||||
, siteURI: uri(qdata.uri)
|
||||
});
|
||||
}).then(null, do_throw);
|
||||
}
|
||||
|
||||
if (qdata.isBookmark) {
|
||||
|
|
|
@ -113,7 +113,7 @@ function populate() {
|
|||
|
||||
function validate() {
|
||||
yield testCanonicalBookmarks();
|
||||
testToolbarFolder();
|
||||
yield testToolbarFolder();
|
||||
testUnfiledBookmarks();
|
||||
testTags();
|
||||
}
|
||||
|
@ -217,16 +217,11 @@ function testToolbarFolder() {
|
|||
// title
|
||||
do_check_eq("Latest Headlines", livemark.title);
|
||||
|
||||
PlacesUtils.livemarks.getLivemark(
|
||||
{ id: livemark.itemId },
|
||||
function (aStatus, aLivemark) {
|
||||
do_check_true(Components.isSuccessCode(aStatus));
|
||||
do_check_eq("http://en-us.fxfeeds.mozilla.com/en-US/firefox/livebookmarks/",
|
||||
aLivemark.siteURI.spec);
|
||||
do_check_eq("http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml",
|
||||
aLivemark.feedURI.spec);
|
||||
}
|
||||
);
|
||||
let foundLivemark = yield PlacesUtils.livemarks.getLivemark({ id: livemark.itemId });
|
||||
do_check_eq("http://en-us.fxfeeds.mozilla.com/en-US/firefox/livebookmarks/",
|
||||
foundLivemark.siteURI.spec);
|
||||
do_check_eq("http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml",
|
||||
foundLivemark.feedURI.spec);
|
||||
|
||||
// test added bookmark data
|
||||
var child = toolbar.getChild(2);
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -363,13 +363,9 @@ function checkItem(aExpected, aNode)
|
|||
do_check_eq((yield PlacesUtils.getCharsetForURI(testURI)), aExpected.charset);
|
||||
break;
|
||||
case "feedUrl":
|
||||
yield PlacesUtils.livemarks.getLivemark(
|
||||
{ id: id },
|
||||
(aStatus, aLivemark) => {
|
||||
do_check_true(Components.isSuccessCode(aStatus));
|
||||
do_check_eq(aLivemark.siteURI.spec, aExpected.url);
|
||||
do_check_eq(aLivemark.feedURI.spec, aExpected.feedUrl);
|
||||
});
|
||||
let livemark = yield PlacesUtils.livemarks.getLivemark({ id: id });
|
||||
do_check_eq(livemark.siteURI.spec, aExpected.url);
|
||||
do_check_eq(livemark.feedURI.spec, aExpected.feedUrl);
|
||||
break;
|
||||
case "children":
|
||||
let folder = aNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
|
||||
|
|
|
@ -154,19 +154,11 @@ function database_check() {
|
|||
// title
|
||||
do_check_eq("Latest Headlines", livemark.title);
|
||||
|
||||
let deferGetLivemark = Promise.defer();
|
||||
PlacesUtils.livemarks.getLivemark(
|
||||
{ id: livemark.itemId },
|
||||
function (aStatus, aLivemark) {
|
||||
do_check_true(Components.isSuccessCode(aStatus));
|
||||
do_check_eq("http://en-us.fxfeeds.mozilla.com/en-US/firefox/livebookmarks/",
|
||||
aLivemark.siteURI.spec);
|
||||
do_check_eq("http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml",
|
||||
aLivemark.feedURI.spec);
|
||||
deferGetLivemark.resolve();
|
||||
}
|
||||
);
|
||||
yield deferGetLivemark.promise;
|
||||
let foundLivemark = yield PlacesUtils.livemarks.getLivemark({ id: livemark.itemId });
|
||||
do_check_eq("http://en-us.fxfeeds.mozilla.com/en-US/firefox/livebookmarks/",
|
||||
foundLivemark.siteURI.spec);
|
||||
do_check_eq("http://en-us.fxfeeds.mozilla.com/en-US/firefox/headlines.xml",
|
||||
foundLivemark.feedURI.spec);
|
||||
|
||||
// cleanup
|
||||
toolbar.containerOpen = false;
|
||||
|
|
|
@ -192,13 +192,9 @@ function checkItem(aExpected, aNode) {
|
|||
do_check_eq((yield PlacesUtils.getCharsetForURI(testURI)), aExpected.charset);
|
||||
break;
|
||||
case "feedUrl":
|
||||
yield PlacesUtils.livemarks.getLivemark(
|
||||
{ id: id },
|
||||
(aStatus, aLivemark) => {
|
||||
do_check_true(Components.isSuccessCode(aStatus));
|
||||
do_check_eq(aLivemark.siteURI.spec, aExpected.url);
|
||||
do_check_eq(aLivemark.feedURI.spec, aExpected.feedUrl);
|
||||
});
|
||||
let livemark = yield PlacesUtils.livemarks.getLivemark({ id: id });
|
||||
do_check_eq(livemark.siteURI.spec, aExpected.url);
|
||||
do_check_eq(livemark.feedURI.spec, aExpected.feedUrl);
|
||||
break;
|
||||
case "children":
|
||||
let folder = aNode.QueryInterface(Ci.nsINavHistoryContainerResultNode);
|
||||
|
|
|
@ -13,14 +13,11 @@ function run_test()
|
|||
{
|
||||
if (aAnnotationName == PlacesUtils.LMANNO_FEEDURI) {
|
||||
PlacesUtils.annotations.removeObserver(this);
|
||||
PlacesUtils.livemarks.getLivemark(
|
||||
{ id: aItemId },
|
||||
function (aStatus, aLivemark) {
|
||||
do_check_true(Components.isSuccessCode(aStatus));
|
||||
PlacesUtils.livemarks.getLivemark({ id: aItemId })
|
||||
.then(aLivemark => {
|
||||
PlacesUtils.bookmarks.removeItem(aItemId);
|
||||
do_test_finished();
|
||||
}
|
||||
);
|
||||
}, do_throw);
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -39,5 +36,5 @@ function run_test()
|
|||
, siteURI: uri("http://example.com/")
|
||||
, feedURI: uri("http://example.com/rdf")
|
||||
}
|
||||
);
|
||||
).then(null, do_throw);
|
||||
}
|
||||
|
|
|
@ -178,82 +178,52 @@ add_task(function test_addLivemark_noCallback_succeeds()
|
|||
});
|
||||
|
||||
|
||||
add_task(function test_addLivemark_noSiteURI_callback_succeeds()
|
||||
add_task(function test_addLivemark_noSiteURI_succeeds()
|
||||
{
|
||||
let checkLivemark = aLivemark => {
|
||||
do_check_true(aLivemark.id > 0);
|
||||
do_check_valid_places_guid(aLivemark.guid);
|
||||
do_check_eq(aLivemark.title, "test");
|
||||
do_check_eq(aLivemark.parentId, PlacesUtils.unfiledBookmarksFolderId);
|
||||
do_check_eq(aLivemark.index, PlacesUtils.bookmarks.getItemIndex(aLivemark.id));
|
||||
do_check_eq(aLivemark.lastModified, PlacesUtils.bookmarks.getItemLastModified(aLivemark.id));
|
||||
do_check_true(aLivemark.feedURI.equals(FEED_URI));
|
||||
do_check_eq(aLivemark.siteURI, null);
|
||||
};
|
||||
|
||||
// The deprecated callback is called before resolving the promise.
|
||||
let callbackCalled = false;
|
||||
let livemark = yield PlacesUtils.livemarks.addLivemark(
|
||||
{ title: "test"
|
||||
, parentId: PlacesUtils.unfiledBookmarksFolderId
|
||||
, index: PlacesUtils.bookmarks.DEFAULT_INDEX
|
||||
, feedURI: FEED_URI
|
||||
},
|
||||
(aStatus, aLivemark) => {
|
||||
callbackCalled = true;
|
||||
do_check_true(Components.isSuccessCode(aStatus));
|
||||
checkLivemark(aLivemark);
|
||||
} );
|
||||
do_check_true(callbackCalled);
|
||||
checkLivemark(livemark);
|
||||
});
|
||||
do_check_true(livemark.id > 0);
|
||||
do_check_valid_places_guid(livemark.guid);
|
||||
do_check_eq(livemark.title, "test");
|
||||
do_check_eq(livemark.parentId, PlacesUtils.unfiledBookmarksFolderId);
|
||||
do_check_eq(livemark.index, PlacesUtils.bookmarks.getItemIndex(livemark.id));
|
||||
do_check_eq(livemark.lastModified, PlacesUtils.bookmarks.getItemLastModified(livemark.id));
|
||||
do_check_true(livemark.feedURI.equals(FEED_URI));
|
||||
do_check_eq(livemark.siteURI, null);
|
||||
});
|
||||
|
||||
add_task(function test_addLivemark_callback_succeeds()
|
||||
add_task(function test_addLivemark_succeeds()
|
||||
{
|
||||
let checkLivemark = aLivemark => {
|
||||
do_check_true(aLivemark.id > 0);
|
||||
do_check_valid_places_guid(aLivemark.guid);
|
||||
do_check_eq(aLivemark.title, "test");
|
||||
do_check_eq(aLivemark.parentId, PlacesUtils.unfiledBookmarksFolderId);
|
||||
do_check_eq(aLivemark.index, PlacesUtils.bookmarks.getItemIndex(aLivemark.id));
|
||||
do_check_eq(aLivemark.lastModified, PlacesUtils.bookmarks.getItemLastModified(aLivemark.id));
|
||||
do_check_true(aLivemark.feedURI.equals(FEED_URI));
|
||||
do_check_true(aLivemark.siteURI.equals(SITE_URI));
|
||||
do_check_true(PlacesUtils.annotations
|
||||
.itemHasAnnotation(aLivemark.id,
|
||||
PlacesUtils.LMANNO_FEEDURI));
|
||||
do_check_true(PlacesUtils.annotations
|
||||
.itemHasAnnotation(aLivemark.id,
|
||||
PlacesUtils.LMANNO_SITEURI));
|
||||
};
|
||||
|
||||
// The deprecated callback is called before resolving the promise.
|
||||
let callbackCalled = false;
|
||||
let livemark = yield PlacesUtils.livemarks.addLivemark(
|
||||
{ title: "test"
|
||||
, parentId: PlacesUtils.unfiledBookmarksFolderId
|
||||
, index: PlacesUtils.bookmarks.DEFAULT_INDEX
|
||||
, feedURI: FEED_URI
|
||||
, siteURI: SITE_URI
|
||||
},
|
||||
(aStatus, aLivemark) => {
|
||||
callbackCalled = true;
|
||||
do_check_true(Components.isSuccessCode(aStatus));
|
||||
checkLivemark(aLivemark);
|
||||
} );
|
||||
do_check_true(callbackCalled);
|
||||
checkLivemark(livemark);
|
||||
});
|
||||
|
||||
do_check_true(livemark.id > 0);
|
||||
do_check_valid_places_guid(livemark.guid);
|
||||
do_check_eq(livemark.title, "test");
|
||||
do_check_eq(livemark.parentId, PlacesUtils.unfiledBookmarksFolderId);
|
||||
do_check_eq(livemark.index, PlacesUtils.bookmarks.getItemIndex(livemark.id));
|
||||
do_check_eq(livemark.lastModified, PlacesUtils.bookmarks.getItemLastModified(livemark.id));
|
||||
do_check_true(livemark.feedURI.equals(FEED_URI));
|
||||
do_check_true(livemark.siteURI.equals(SITE_URI));
|
||||
do_check_true(PlacesUtils.annotations
|
||||
.itemHasAnnotation(livemark.id,
|
||||
PlacesUtils.LMANNO_FEEDURI));
|
||||
do_check_true(PlacesUtils.annotations
|
||||
.itemHasAnnotation(livemark.id,
|
||||
PlacesUtils.LMANNO_SITEURI));
|
||||
});
|
||||
|
||||
add_task(function test_addLivemark_bogusid_callback_succeeds()
|
||||
add_task(function test_addLivemark_bogusid_succeeds()
|
||||
{
|
||||
let checkLivemark = aLivemark => {
|
||||
do_check_true(aLivemark.id > 0);
|
||||
do_check_neq(aLivemark.id, 100);
|
||||
};
|
||||
|
||||
// The deprecated callback is called before resolving the promise.
|
||||
let callbackCalled = false;
|
||||
let livemark = yield PlacesUtils.livemarks.addLivemark(
|
||||
{ id: 100 // Should be ignored.
|
||||
, title: "test"
|
||||
|
@ -261,118 +231,72 @@ add_task(function test_addLivemark_bogusid_callback_succeeds()
|
|||
, index: PlacesUtils.bookmarks.DEFAULT_INDEX
|
||||
, feedURI: FEED_URI
|
||||
, siteURI: SITE_URI
|
||||
},
|
||||
(aStatus, aLivemark) => {
|
||||
callbackCalled = true;
|
||||
do_check_true(Components.isSuccessCode(aStatus));
|
||||
checkLivemark(aLivemark);
|
||||
} );
|
||||
do_check_true(callbackCalled);
|
||||
checkLivemark(livemark);
|
||||
});
|
||||
do_check_true(livemark.id > 0);
|
||||
do_check_neq(livemark.id, 100);
|
||||
});
|
||||
|
||||
add_task(function test_addLivemark_bogusParent_callback_fails()
|
||||
add_task(function test_addLivemark_bogusParent_fails()
|
||||
{
|
||||
// The deprecated callback is called before resolving the promise.
|
||||
let callbackCalled = false;
|
||||
try {
|
||||
yield PlacesUtils.livemarks.addLivemark(
|
||||
{ title: "test"
|
||||
, parentId: 187
|
||||
, index: PlacesUtils.bookmarks.DEFAULT_INDEX
|
||||
, feedURI: FEED_URI
|
||||
},
|
||||
(aStatus, aLivemark) => {
|
||||
callbackCalled = true;
|
||||
do_check_false(Components.isSuccessCode(aStatus));
|
||||
do_check_eq(aLivemark, null);
|
||||
} );
|
||||
});
|
||||
do_throw("Adding a livemark with a bogus parent should fail");
|
||||
}
|
||||
catch(ex) {
|
||||
do_check_true(callbackCalled);
|
||||
}
|
||||
} catch(ex) {}
|
||||
});
|
||||
|
||||
add_task(function test_addLivemark_intoLivemark_callback_fails()
|
||||
add_task(function test_addLivemark_intoLivemark_fails()
|
||||
{
|
||||
// The deprecated callback is called before resolving the promise.
|
||||
let callbackCalled = false;
|
||||
let livemark = yield PlacesUtils.livemarks.addLivemark(
|
||||
{ title: "test"
|
||||
, parentId: PlacesUtils.unfiledBookmarksFolderId
|
||||
, index: PlacesUtils.bookmarks.DEFAULT_INDEX
|
||||
, feedURI: FEED_URI
|
||||
},
|
||||
(aStatus, aLivemark) => {
|
||||
callbackCalled = true;
|
||||
do_check_true(Components.isSuccessCode(aStatus));
|
||||
} );
|
||||
do_check_true(callbackCalled);
|
||||
});
|
||||
do_check_true(Boolean(livemark));
|
||||
|
||||
callbackCalled = false;
|
||||
try {
|
||||
yield PlacesUtils.livemarks.addLivemark(
|
||||
{ title: "test"
|
||||
, parentId: livemark.id
|
||||
, index: PlacesUtils.bookmarks.DEFAULT_INDEX
|
||||
, feedURI: FEED_URI
|
||||
},
|
||||
(aStatus, aLivemark) => {
|
||||
callbackCalled = true;
|
||||
do_check_false(Components.isSuccessCode(aStatus));
|
||||
do_check_eq(aLivemark, null);
|
||||
} );
|
||||
});
|
||||
do_throw("Adding a livemark into a livemark should fail");
|
||||
}
|
||||
catch(ex) {
|
||||
do_check_true(callbackCalled);
|
||||
}
|
||||
} catch(ex) {}
|
||||
});
|
||||
|
||||
add_task(function test_addLivemark_forceGuid_callback_succeeds()
|
||||
add_task(function test_addLivemark_forceGuid_succeeds()
|
||||
{
|
||||
let checkLivemark = aLivemark => {
|
||||
do_check_eq(aLivemark.guid, "1234567890AB");
|
||||
do_check_guid_for_bookmark(aLivemark.id, "1234567890AB");
|
||||
};
|
||||
|
||||
// The deprecated callback is called before resolving the promise.
|
||||
let callbackCalled = false;
|
||||
let livemark = yield PlacesUtils.livemarks.addLivemark(
|
||||
{ title: "test"
|
||||
, parentId: PlacesUtils.unfiledBookmarksFolderId
|
||||
, index: PlacesUtils.bookmarks.DEFAULT_INDEX
|
||||
, feedURI: FEED_URI
|
||||
, guid: "1234567890AB"
|
||||
},
|
||||
(aStatus, aLivemark) => {
|
||||
callbackCalled = true;
|
||||
do_check_true(Components.isSuccessCode(aStatus));
|
||||
checkLivemark(aLivemark);
|
||||
} );
|
||||
do_check_true(callbackCalled);
|
||||
});
|
||||
checkLivemark(livemark);
|
||||
});
|
||||
|
||||
add_task(function test_addLivemark_lastModified_callback_succeeds()
|
||||
add_task(function test_addLivemark_lastModified_succeeds()
|
||||
{
|
||||
let now = Date.now() * 1000;
|
||||
let callbackCalled = false;
|
||||
let livemark = yield PlacesUtils.livemarks.addLivemark(
|
||||
{ title: "test"
|
||||
, parentId: PlacesUtils.unfiledBookmarksFolderId
|
||||
, index: PlacesUtils.bookmarks.DEFAULT_INDEX
|
||||
, feedURI: FEED_URI
|
||||
, lastModified: now
|
||||
},
|
||||
(aStatus, aLivemark) => {
|
||||
callbackCalled = true;
|
||||
do_check_true(Components.isSuccessCode(aStatus));
|
||||
do_check_eq(aLivemark.lastModified, now);
|
||||
} );
|
||||
do_check_true(callbackCalled);
|
||||
});
|
||||
do_check_eq(livemark.lastModified, now);
|
||||
});
|
||||
|
||||
|
@ -398,19 +322,11 @@ add_task(function test_removeLivemark_noValidId_throws()
|
|||
|
||||
add_task(function test_removeLivemark_nonExistent_fails()
|
||||
{
|
||||
let callbackCalled = false;
|
||||
try {
|
||||
yield PlacesUtils.livemarks.removeLivemark(
|
||||
{ id: 1337 },
|
||||
(aStatus, aLivemark) => {
|
||||
callbackCalled = true;
|
||||
do_check_false(Components.isSuccessCode(aStatus));
|
||||
do_check_eq(aLivemark, null);
|
||||
} );
|
||||
yield PlacesUtils.livemarks.removeLivemark({ id: 1337 });
|
||||
do_throw("Removing a non-existent livemark should fail");
|
||||
}
|
||||
catch(ex) {
|
||||
do_check_true(callbackCalled);
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -469,36 +385,20 @@ add_task(function test_getLivemark_noValidId_throws()
|
|||
|
||||
add_task(function test_getLivemark_nonExistentId_fails()
|
||||
{
|
||||
let callbackCalled = false;
|
||||
try {
|
||||
yield PlacesUtils.livemarks.getLivemark({ id: 1234 },
|
||||
(aStatus, aLivemark) => {
|
||||
callbackCalled = true;
|
||||
do_check_false(Components.isSuccessCode(aStatus));
|
||||
do_check_eq(aLivemark, null);
|
||||
} );
|
||||
yield PlacesUtils.livemarks.getLivemark({ id: 1234 });
|
||||
do_throw("getLivemark for a non existent id should fail");
|
||||
}
|
||||
catch(ex) {
|
||||
do_check_true(callbackCalled);
|
||||
}
|
||||
catch(ex) {}
|
||||
});
|
||||
|
||||
add_task(function test_getLivemark_nonExistentGUID_fails()
|
||||
{
|
||||
let callbackCalled = false;
|
||||
try {
|
||||
yield PlacesUtils.livemarks.getLivemark({ guid: "34567890ABCD" },
|
||||
(aStatus, aLivemark) => {
|
||||
callbackCalled = true;
|
||||
do_check_false(Components.isSuccessCode(aStatus));
|
||||
do_check_eq(aLivemark, null);
|
||||
} );
|
||||
yield PlacesUtils.livemarks.getLivemark({ guid: "34567890ABCD" });
|
||||
do_throw("getLivemark for a non-existent guid should fail");
|
||||
}
|
||||
catch(ex) {
|
||||
do_check_true(callbackCalled);
|
||||
}
|
||||
catch(ex) {}
|
||||
});
|
||||
|
||||
add_task(function test_getLivemark_guid_succeeds()
|
||||
|
@ -510,26 +410,16 @@ add_task(function test_getLivemark_guid_succeeds()
|
|||
, feedURI: FEED_URI
|
||||
, guid: "34567890ABCD" });
|
||||
|
||||
let checkLivemark = aLivemark => {
|
||||
do_check_eq(aLivemark.title, "test");
|
||||
do_check_eq(aLivemark.parentId, PlacesUtils.unfiledBookmarksFolderId);
|
||||
do_check_eq(aLivemark.index, PlacesUtils.bookmarks.getItemIndex(aLivemark.id));
|
||||
do_check_true(aLivemark.feedURI.equals(FEED_URI));
|
||||
do_check_eq(aLivemark.siteURI, null);
|
||||
do_check_eq(aLivemark.guid, "34567890ABCD");
|
||||
};
|
||||
|
||||
// invalid id to check the guid wins.
|
||||
let livemark =
|
||||
yield PlacesUtils.livemarks.getLivemark({ id: 789, guid: "34567890ABCD" },
|
||||
(aStatus, aLivemark) => {
|
||||
callbackCalled = true;
|
||||
do_check_true(Components.isSuccessCode(aStatus));
|
||||
checkLivemark(aLivemark)
|
||||
} );
|
||||
yield PlacesUtils.livemarks.getLivemark({ id: 789, guid: "34567890ABCD" });
|
||||
|
||||
do_check_true(callbackCalled);
|
||||
checkLivemark(livemark);
|
||||
do_check_eq(livemark.title, "test");
|
||||
do_check_eq(livemark.parentId, PlacesUtils.unfiledBookmarksFolderId);
|
||||
do_check_eq(livemark.index, PlacesUtils.bookmarks.getItemIndex(livemark.id));
|
||||
do_check_true(livemark.feedURI.equals(FEED_URI));
|
||||
do_check_eq(livemark.siteURI, null);
|
||||
do_check_eq(livemark.guid, "34567890ABCD");
|
||||
});
|
||||
|
||||
add_task(function test_getLivemark_id_succeeds()
|
||||
|
@ -541,26 +431,14 @@ add_task(function test_getLivemark_id_succeeds()
|
|||
, feedURI: FEED_URI
|
||||
});
|
||||
|
||||
let checkLivemark = aLivemark => {
|
||||
do_check_eq(aLivemark.title, "test");
|
||||
do_check_eq(aLivemark.parentId, PlacesUtils.unfiledBookmarksFolderId);
|
||||
do_check_eq(aLivemark.index, PlacesUtils.bookmarks.getItemIndex(aLivemark.id));
|
||||
do_check_true(aLivemark.feedURI.equals(FEED_URI));
|
||||
do_check_eq(aLivemark.siteURI, null);
|
||||
do_check_guid_for_bookmark(aLivemark.id, aLivemark.guid);
|
||||
};
|
||||
livemark = yield PlacesUtils.livemarks.getLivemark({ id: livemark.id });
|
||||
|
||||
let callbackCalled = false;
|
||||
livemark = yield PlacesUtils.livemarks.getLivemark(
|
||||
{ id: livemark.id },
|
||||
(aStatus, aLivemark) => {
|
||||
callbackCalled = true;
|
||||
do_check_true(Components.isSuccessCode(aStatus));
|
||||
checkLivemark(aLivemark);
|
||||
} );
|
||||
|
||||
do_check_true(callbackCalled);
|
||||
checkLivemark(livemark);
|
||||
do_check_eq(livemark.title, "test");
|
||||
do_check_eq(livemark.parentId, PlacesUtils.unfiledBookmarksFolderId);
|
||||
do_check_eq(livemark.index, PlacesUtils.bookmarks.getItemIndex(livemark.id));
|
||||
do_check_true(livemark.feedURI.equals(FEED_URI));
|
||||
do_check_eq(livemark.siteURI, null);
|
||||
do_check_guid_for_bookmark(livemark.id, livemark.guid);
|
||||
});
|
||||
|
||||
add_task(function test_getLivemark_removeItem_contention()
|
||||
|
@ -579,26 +457,14 @@ add_task(function test_getLivemark_removeItem_contention()
|
|||
let id = PlacesUtils.bookmarks.getIdForItemAt(PlacesUtils.unfiledBookmarksFolderId,
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX);
|
||||
|
||||
let checkLivemark = (aLivemark) => {
|
||||
do_check_eq(aLivemark.title, "test");
|
||||
do_check_eq(aLivemark.parentId, PlacesUtils.unfiledBookmarksFolderId);
|
||||
do_check_eq(aLivemark.index, PlacesUtils.bookmarks.getItemIndex(aLivemark.id));
|
||||
do_check_true(aLivemark.feedURI.equals(FEED_URI));
|
||||
do_check_eq(aLivemark.siteURI, null);
|
||||
do_check_guid_for_bookmark(aLivemark.id, aLivemark.guid);
|
||||
};
|
||||
let livemark = yield PlacesUtils.livemarks.getLivemark({ id: id });
|
||||
|
||||
let callbackCalled = false;
|
||||
let livemark = yield PlacesUtils.livemarks.getLivemark(
|
||||
{ id: id },
|
||||
(aStatus, aLivemark) => {
|
||||
callbackCalled = true;
|
||||
do_check_true(Components.isSuccessCode(aStatus));
|
||||
checkLivemark(aLivemark);
|
||||
} );
|
||||
|
||||
do_check_true(callbackCalled);
|
||||
checkLivemark(livemark);
|
||||
do_check_eq(livemark.title, "test");
|
||||
do_check_eq(livemark.parentId, PlacesUtils.unfiledBookmarksFolderId);
|
||||
do_check_eq(livemark.index, PlacesUtils.bookmarks.getItemIndex(livemark.id));
|
||||
do_check_true(livemark.feedURI.equals(FEED_URI));
|
||||
do_check_eq(livemark.siteURI, null);
|
||||
do_check_guid_for_bookmark(livemark.id, livemark.guid);
|
||||
});
|
||||
|
||||
add_task(function test_title_change()
|
||||
|
|
|
@ -138,3 +138,4 @@ skip-if = os == "android"
|
|||
[test_telemetry.js]
|
||||
[test_getPlacesInfo.js]
|
||||
[test_pageGuid_bookmarkGuid.js]
|
||||
[test_async_transactions.js]
|
||||
|
|
Загрузка…
Ссылка в новой задаче