Merge fx-team to m-c

This commit is contained in:
Wes Kocher 2014-03-12 20:01:45 -07:00
commit c317000cea
70 changed files with 4106 additions and 1501 deletions

View File

@ -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;"

View File

@ -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");
}
};

View File

@ -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]

View File

@ -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";
}

View File

@ -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>

View File

@ -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;

View File

@ -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":

View File

@ -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;
}

View File

@ -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);
}
},

View File

@ -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);
}
},

View File

@ -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

View File

@ -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);
}
},
/**

View File

@ -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);
}
}

View File

@ -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]

View File

@ -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");
});

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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);
}
/**

View File

@ -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;
}

View File

@ -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();

View File

@ -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);
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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;
}
}
}

View File

@ -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 +
'}';
}
}

View File

@ -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;
}
}

View File

@ -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);

View File

@ -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

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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();

View File

@ -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;
}

View File

@ -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

View File

@ -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();
}

File diff suppressed because it is too large Load Diff

View File

@ -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

View File

@ -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)) {

View File

@ -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;

View File

@ -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);

View File

@ -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);

View File

@ -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)) {

View File

@ -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,

View File

@ -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(

File diff suppressed because it is too large Load Diff

View File

@ -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);
});
}
};

View File

@ -67,6 +67,7 @@ if CONFIG['MOZ_PLACES']:
'ColorConversion.js',
'PlacesBackups.jsm',
'PlacesDBUtils.jsm',
'PlacesTransactions.jsm',
]
EXTRA_PP_JS_MODULES += [

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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");
}
);
}

View File

@ -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");
}
);
}

View File

@ -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");
}
);
}

View File

@ -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();
}
);
}

View File

@ -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();
}
);
});

View File

@ -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");

View File

@ -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) {

View File

@ -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);

File diff suppressed because it is too large Load Diff

View File

@ -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);

View File

@ -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;

View File

@ -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);

View File

@ -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);
}

View File

@ -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()

View File

@ -138,3 +138,4 @@ skip-if = os == "android"
[test_telemetry.js]
[test_getPlacesInfo.js]
[test_pageGuid_bookmarkGuid.js]
[test_async_transactions.js]