mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:18:48 +00:00
import changes from `dev' branch of rmottola/Arctic-Fox:
- Bug 951651 - Make bookmarkProperties, Star UI and Library info pane work with PlacesTransactions. r=mak (6d0127aa5) - Bug 547623 - Add a button to about:support to enter safe mode. r=adw (1a0481412) - Bug 728813 - Switch Help menu item to allow leaving Safe Mode when it's enabled. r=MattN (cfeae5bf8) - Bug 1125115 - Write a new keywords pseudo-API in PlacesUtils. r=ttaubert (afc6f00c9)
This commit is contained in:
@@ -65,7 +65,9 @@
|
||||
<menuitem id="helpSafeMode"
|
||||
accesskey="&helpSafeMode.accesskey;"
|
||||
label="&helpSafeMode.label;"
|
||||
oncommand="restart(true);"/>
|
||||
stopaccesskey="&helpSafeMode.stop.accesskey;"
|
||||
stoplabel="&helpSafeMode.stop.label;"
|
||||
oncommand="safeModeRestart();"/>
|
||||
<menuseparator id="aboutSeparator"/>
|
||||
<menuitem id="aboutName"
|
||||
accesskey="&aboutProduct.accesskey;"
|
||||
|
||||
@@ -56,7 +56,7 @@ var StarUI = {
|
||||
},
|
||||
|
||||
// nsIDOMEventListener
|
||||
handleEvent: function SU_handleEvent(aEvent) {
|
||||
handleEvent(aEvent) {
|
||||
switch (aEvent.type) {
|
||||
case "popuphidden":
|
||||
if (aEvent.originalTarget == this.panel) {
|
||||
@@ -65,26 +65,32 @@ var StarUI = {
|
||||
|
||||
this._restoreCommandsState();
|
||||
this._itemId = -1;
|
||||
if (this._batching) {
|
||||
PlacesUtils.transactionManager.endBatch(false);
|
||||
this._batching = false;
|
||||
}
|
||||
if (this._batching)
|
||||
this.endBatch();
|
||||
|
||||
switch (this._actionOnHide) {
|
||||
case "cancel": {
|
||||
PlacesUtils.transactionManager.undoTransaction();
|
||||
if (!PlacesUIUtils.useAsyncTransactions) {
|
||||
PlacesUtils.transactionManager.undoTransaction();
|
||||
break;
|
||||
}
|
||||
PlacesTransactions.undo().catch(Cu.reportError);
|
||||
break;
|
||||
}
|
||||
case "remove": {
|
||||
// Remove all bookmarks for the bookmark's url, this also removes
|
||||
// the tags for the url.
|
||||
PlacesUtils.transactionManager.beginBatch(null);
|
||||
let itemIds = PlacesUtils.getBookmarksForURI(this._uriForRemoval);
|
||||
for (let i = 0; i < itemIds.length; i++) {
|
||||
let txn = new PlacesRemoveItemTransaction(itemIds[i]);
|
||||
PlacesUtils.transactionManager.doTransaction(txn);
|
||||
if (!PlacesUIUtils.useAsyncTransactions) {
|
||||
let itemIds = PlacesUtils.getBookmarksForURI(this._uriForRemoval);
|
||||
for (let itemId of itemIds) {
|
||||
let txn = new PlacesRemoveItemTransaction(itemId);
|
||||
PlacesUtils.transactionManager.doTransaction(txn);
|
||||
}
|
||||
break;
|
||||
}
|
||||
PlacesUtils.transactionManager.endBatch(false);
|
||||
|
||||
PlacesTransactions.RemoveBookmarksForUrls(this._uriForRemoval)
|
||||
.transact().catch(Cu.reportError);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -118,15 +124,30 @@ var StarUI = {
|
||||
|
||||
_overlayLoaded: false,
|
||||
_overlayLoading: false,
|
||||
showEditBookmarkPopup:
|
||||
function SU_showEditBookmarkPopup(aItemId, aAnchorElement, aPosition) {
|
||||
showEditBookmarkPopup: Task.async(function* (aNode, aAnchorElement, aPosition) {
|
||||
// TODO: Deprecate this once async transactions are enabled and the legacy
|
||||
// transactions code is gone (bug 1131491) - we don't want addons to to use
|
||||
// the completeNodeLikeObjectForItemId, so it's better if they keep passing
|
||||
// the item-id for now).
|
||||
if (typeof(aNode) == "number") {
|
||||
let itemId = aNode;
|
||||
if (PlacesUIUtils.useAsyncTransactions) {
|
||||
let guid = yield PlacesUtils.promiseItemGuid(itemId);
|
||||
aNode = yield PlacesUIUtils.promiseNodeLike(guid);
|
||||
}
|
||||
else {
|
||||
aNode = { itemId };
|
||||
yield PlacesUIUtils.completeNodeLikeObjectForItemId(aNode);
|
||||
}
|
||||
}
|
||||
|
||||
// Performance: load the overlay the first time the panel is opened
|
||||
// (see bug 392443).
|
||||
if (this._overlayLoading)
|
||||
return;
|
||||
|
||||
if (this._overlayLoaded) {
|
||||
this._doShowEditBookmarkPanel(aItemId, aAnchorElement, aPosition);
|
||||
this._doShowEditBookmarkPanel(aNode, aAnchorElement, aPosition);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -146,13 +167,12 @@ var StarUI = {
|
||||
|
||||
this._overlayLoading = false;
|
||||
this._overlayLoaded = true;
|
||||
this._doShowEditBookmarkPanel(aItemId, aAnchorElement, aPosition);
|
||||
this._doShowEditBookmarkPanel(aNode, aAnchorElement, aPosition);
|
||||
}).bind(this)
|
||||
);
|
||||
},
|
||||
}),
|
||||
|
||||
_doShowEditBookmarkPanel:
|
||||
function SU__doShowEditBookmarkPanel(aItemId, aAnchorElement, aPosition) {
|
||||
_doShowEditBookmarkPanel: Task.async(function* (aNode, aAnchorElement, aPosition) {
|
||||
if (this.panel.state != "closed")
|
||||
return;
|
||||
|
||||
@@ -178,23 +198,23 @@ var StarUI = {
|
||||
|
||||
// The label of the remove button differs if the URI is bookmarked
|
||||
// multiple times.
|
||||
var bookmarks = PlacesUtils.getBookmarksForURI(gBrowser.currentURI);
|
||||
var forms = gNavigatorBundle.getString("editBookmark.removeBookmarks.label");
|
||||
var label = PluralForm.get(bookmarks.length, forms).replace("#1", bookmarks.length);
|
||||
let bookmarks = PlacesUtils.getBookmarksForURI(gBrowser.currentURI);
|
||||
let forms = gNavigatorBundle.getString("editBookmark.removeBookmarks.label");
|
||||
let label = PluralForm.get(bookmarks.length, forms).replace("#1", bookmarks.length);
|
||||
this._element("editBookmarkPanelRemoveButton").label = label;
|
||||
|
||||
// unset the unstarred state, if set
|
||||
this._element("editBookmarkPanelStarIcon").removeAttribute("unstarred");
|
||||
|
||||
this._itemId = aItemId !== undefined ? aItemId : this._itemId;
|
||||
this._itemId = aNode.itemId;
|
||||
this.beginBatch();
|
||||
|
||||
this.panel.openPopup(aAnchorElement, aPosition);
|
||||
|
||||
gEditItemOverlay.initPanel(this._itemId,
|
||||
{ hiddenRows: ["description", "location",
|
||||
gEditItemOverlay.initPanel({ node: aNode
|
||||
, hiddenRows: ["description", "location",
|
||||
"loadInSidebar", "keyword"] });
|
||||
},
|
||||
}),
|
||||
|
||||
panelShown:
|
||||
function SU_panelShown(aEvent) {
|
||||
@@ -231,13 +251,46 @@ var StarUI = {
|
||||
this.panel.hidePopup();
|
||||
},
|
||||
|
||||
beginBatch: function SU_beginBatch() {
|
||||
if (!this._batching) {
|
||||
PlacesUtils.transactionManager.beginBatch(null);
|
||||
this._batching = true;
|
||||
// Matching the way it is used in the Library, editBookmarkOverlay implements
|
||||
// an instant-apply UI, having no batched-Undo/Redo support.
|
||||
// However, in this context (the Star UI) we have a Cancel button whose
|
||||
// expected behavior is to undo all the operations done in the panel.
|
||||
// Sometime in the future this needs to be reimplemented using a
|
||||
// non-instant apply code path, but for the time being, we patch-around
|
||||
// editBookmarkOverlay so that all of the actions done in the panel
|
||||
// are treated by PlacesTransactions as a single batch. To do so,
|
||||
// we start a PlacesTransactions batch when the star UI panel is shown, and
|
||||
// we keep the batch ongoing until the panel is hidden.
|
||||
_batchBlockingDeferred: null,
|
||||
beginBatch() {
|
||||
if (this._batching)
|
||||
return;
|
||||
if (PlacesUIUtils.useAsyncTransactions) {
|
||||
this._batchBlockingDeferred = PromiseUtils.defer();
|
||||
PlacesTransactions.batch(function* () {
|
||||
yield this._batchBlockingDeferred.promise;
|
||||
}.bind(this));
|
||||
}
|
||||
else {
|
||||
PlacesUtils.transactionManager.beginBatch(null);
|
||||
}
|
||||
this._batching = true;
|
||||
},
|
||||
|
||||
endBatch() {
|
||||
if (!this._batching)
|
||||
return;
|
||||
|
||||
if (PlacesUIUtils.useAsyncTransactions) {
|
||||
this._batchBlockingDeferred.resolve();
|
||||
this._batchBlockingDeferred = null;
|
||||
}
|
||||
else {
|
||||
PlacesUtils.transactionManager.endBatch(false);
|
||||
}
|
||||
this._batching = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//// PlacesCommandHook
|
||||
@@ -253,8 +306,11 @@ var PlacesCommandHook = {
|
||||
* aBrowser isn't bookmarked yet, defaults to the unfiled root.
|
||||
* @param [optional] aShowEditUI
|
||||
* whether or not to show the edit-bookmark UI for the bookmark item
|
||||
*/
|
||||
bookmarkPage: function PCH_bookmarkPage(aBrowser, aParent, aShowEditUI) {
|
||||
*/
|
||||
bookmarkPage: Task.async(function* (aBrowser, aParent, aShowEditUI) {
|
||||
if (PlacesUIUtils.useAsyncTransactions)
|
||||
return (yield this._bookmarkPagePT(aBrowser, aParent, aShowEditUI));
|
||||
|
||||
var uri = aBrowser.currentURI;
|
||||
var itemId = PlacesUtils.getMostRecentBookmarkForURI(uri);
|
||||
if (itemId == -1) {
|
||||
@@ -287,7 +343,7 @@ var PlacesCommandHook = {
|
||||
StarUI.beginBatch();
|
||||
}
|
||||
|
||||
var parent = aParent != undefined ?
|
||||
var parent = aParent !== undefined ?
|
||||
aParent : PlacesUtils.unfiledBookmarksFolderId;
|
||||
var descAnno = { name: PlacesUIUtils.DESCRIPTION_ANNO, value: description };
|
||||
var txn = new PlacesCreateBookmarkTransaction(uri, parent,
|
||||
@@ -295,7 +351,7 @@ var PlacesCommandHook = {
|
||||
title, null, [descAnno]);
|
||||
PlacesUtils.transactionManager.doTransaction(txn);
|
||||
itemId = txn.item.id;
|
||||
// Set the character-set
|
||||
// Set the character-set.
|
||||
if (charset && !PrivateBrowsingUtils.isBrowserPrivate(aBrowser))
|
||||
PlacesUtils.setCharsetForURI(uri, charset);
|
||||
}
|
||||
@@ -325,7 +381,82 @@ var PlacesCommandHook = {
|
||||
} else {
|
||||
StarUI.showEditBookmarkPopup(itemId, aBrowser, "overlap");
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
// TODO: Replace bookmarkPage code with this function once legacy
|
||||
// transactions are removed.
|
||||
_bookmarkPagePT: Task.async(function* (aBrowser, aParentId, aShowEditUI) {
|
||||
let url = new URL(aBrowser.currentURI.spec);
|
||||
let info = yield PlacesUtils.bookmarks.fetch({ url });
|
||||
if (!info) {
|
||||
let parentGuid = aParentId !== undefined ?
|
||||
yield PlacesUtils.promiseItemGuid(aParentId) :
|
||||
PlacesUtils.bookmarks.unfiledGuid;
|
||||
info = { url, parentGuid };
|
||||
// Bug 1148838 - Make this code work for full page plugins.
|
||||
let description = null;
|
||||
let charset = null;
|
||||
try {
|
||||
let isErrorPage = /^about:(neterror|certerror|blocked)/
|
||||
.test(aBrowser.contentDocumentAsCPOW.documentURI);
|
||||
info.title = isErrorPage ?
|
||||
(yield PlacesUtils.promisePlaceInfo(aBrowser.currentURI)).title :
|
||||
aBrowser.contentTitle;
|
||||
info.title = info.title || url.href;
|
||||
description = PlacesUIUtils.getDescriptionFromDocument(aBrowser.contentDocumentAsCPOW);
|
||||
charset = aBrowser.characterSet;
|
||||
}
|
||||
catch (e) {
|
||||
Components.utils.reportError(e);
|
||||
}
|
||||
|
||||
if (aShowEditUI) {
|
||||
// If we bookmark the page here (i.e. page was not "starred" already)
|
||||
// but open right into the "edit" state, start batching here, so
|
||||
// "Cancel" in that state removes the bookmark.
|
||||
StarUI.beginBatch();
|
||||
}
|
||||
|
||||
if (description) {
|
||||
info.annotations = [{ name: PlacesUIUtils.DESCRIPTION_ANNO
|
||||
, value: description }];
|
||||
}
|
||||
|
||||
info.guid = yield PlacesTransactions.NewBookmark(info).transact();
|
||||
|
||||
// Set the character-set
|
||||
if (charset && !PrivateBrowsingUtils.isBrowserPrivate(aBrowser))
|
||||
PlacesUtils.setCharsetForURI(makeURI(url.href), charset);
|
||||
}
|
||||
|
||||
// Revert the contents of the location bar
|
||||
if (gURLBar)
|
||||
gURLBar.handleRevert();
|
||||
|
||||
// If it was not requested to open directly in "edit" mode, we are done.
|
||||
if (!aShowEditUI)
|
||||
return;
|
||||
|
||||
let node = yield PlacesUIUtils.promiseNodeLikeFromFetchInfo(info);
|
||||
|
||||
// Try to dock the panel to:
|
||||
// 1. the bookmarks menu button
|
||||
// 2. the page-proxy-favicon
|
||||
// 3. the content area
|
||||
if (BookmarkingUI.anchor) {
|
||||
StarUI.showEditBookmarkPopup(node, BookmarkingUI.anchor,
|
||||
"bottomcenter topright");
|
||||
return;
|
||||
}
|
||||
|
||||
let pageProxyFavicon = document.getElementById("page-proxy-favicon");
|
||||
if (isElementVisible(pageProxyFavicon)) {
|
||||
StarUI.showEditBookmarkPopup(node, pageProxyFavicon,
|
||||
"bottomcenter topright");
|
||||
} else {
|
||||
StarUI.showEditBookmarkPopup(node, aBrowser, "overlap");
|
||||
}
|
||||
}),
|
||||
|
||||
/**
|
||||
* Adds a bookmark to the page loaded in the current tab.
|
||||
@@ -344,10 +475,27 @@ var PlacesCommandHook = {
|
||||
* @param aTitle
|
||||
* The link text
|
||||
*/
|
||||
bookmarkLink: function PCH_bookmarkLink(aParent, aURL, aTitle) {
|
||||
var linkURI = makeURI(aURL);
|
||||
var itemId = PlacesUtils.getMostRecentBookmarkForURI(linkURI);
|
||||
if (itemId == -1) {
|
||||
bookmarkLink: Task.async(function* (aParentId, aURL, aTitle) {
|
||||
let node = null;
|
||||
if (PlacesUIUtils.useAsyncTransactions) {
|
||||
node = yield PlacesUIUtils.fetchNodeLike({ url: aURL });
|
||||
}
|
||||
else {
|
||||
let linkURI = makeURI(aURL);
|
||||
let itemId = PlacesUtils.getMostRecentBookmarkForURI(linkURI);
|
||||
if (itemId != -1) {
|
||||
node = { itemId, uri: aURL };
|
||||
PlacesUIUtils.completeNodeLikeObjectForItemId(node);
|
||||
}
|
||||
}
|
||||
|
||||
if (node) {
|
||||
PlacesUIUtils.showBookmarkDialog({ action: "edit"
|
||||
, type: "bookmark"
|
||||
, node
|
||||
}, window);
|
||||
}
|
||||
else {
|
||||
PlacesUIUtils.showBookmarkDialog({ action: "add"
|
||||
, type: "bookmark"
|
||||
, uri: linkURI
|
||||
@@ -358,13 +506,7 @@ var PlacesCommandHook = {
|
||||
, "keyword" ]
|
||||
}, window);
|
||||
}
|
||||
else {
|
||||
PlacesUIUtils.showBookmarkDialog({ action: "edit"
|
||||
, type: "bookmark"
|
||||
, itemId: itemId
|
||||
}, window);
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
/**
|
||||
* List of nsIURI objects characterizing the tabs currently open in the
|
||||
|
||||
@@ -9,6 +9,8 @@ let Cu = Components.utils;
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource:///modules/RecentWindow.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "CharsetMenu",
|
||||
"resource:///modules/CharsetMenu.jsm");
|
||||
|
||||
@@ -1047,6 +1049,12 @@ var gBrowserInit = {
|
||||
gHomeButton.updateTooltip(homeButton);
|
||||
gHomeButton.updatePersonalToolbarStyle(homeButton);
|
||||
|
||||
let safeMode = document.getElementById("helpSafeMode");
|
||||
if (Services.appinfo.inSafeMode) {
|
||||
safeMode.label = safeMode.getAttribute("stoplabel");
|
||||
safeMode.accesskey = safeMode.getAttribute("stopaccesskey");
|
||||
}
|
||||
|
||||
// BiDi UI
|
||||
gBidiUI = isBidiEnabled();
|
||||
if (gBidiUI) {
|
||||
@@ -6968,57 +6976,20 @@ Object.defineProperty(this, "HUDService", {
|
||||
#endif
|
||||
|
||||
// Prompt user to restart the browser in safe mode or normally
|
||||
function restart(safeMode)
|
||||
{
|
||||
let promptTitleString = null;
|
||||
let promptMessageString = null;
|
||||
let restartTextString = null;
|
||||
if (safeMode) {
|
||||
promptTitleString = "safeModeRestartPromptTitle";
|
||||
promptMessageString = "safeModeRestartPromptMessage";
|
||||
restartTextString = "safeModeRestartButton";
|
||||
} else {
|
||||
promptTitleString = "restartPromptTitle";
|
||||
promptMessageString = "restartPromptMessage";
|
||||
restartTextString = "restartButton";
|
||||
}
|
||||
|
||||
let flags = Ci.nsIAppStartup.eAttemptQuit;
|
||||
|
||||
// Prompt the user to confirm
|
||||
let promptTitle = gNavigatorBundle.getString(promptTitleString);
|
||||
let brandBundle = document.getElementById("bundle_brand");
|
||||
let brandShortName = brandBundle.getString("brandShortName");
|
||||
let promptMessage =
|
||||
gNavigatorBundle.getFormattedString(promptMessageString, [brandShortName]);
|
||||
let restartText = gNavigatorBundle.getString(restartTextString);
|
||||
let buttonFlags = (Services.prompt.BUTTON_POS_0 *
|
||||
Services.prompt.BUTTON_TITLE_IS_STRING) +
|
||||
(Services.prompt.BUTTON_POS_1 *
|
||||
Services.prompt.BUTTON_TITLE_CANCEL) +
|
||||
Services.prompt.BUTTON_POS_0_DEFAULT;
|
||||
|
||||
let rv = Services.prompt.confirmEx(window, promptTitle, promptMessage,
|
||||
buttonFlags, restartText, null, null,
|
||||
null, {});
|
||||
|
||||
if (rv == 0) {
|
||||
// Notify all windows that an application quit has been requested.
|
||||
let cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]
|
||||
.createInstance(Ci.nsISupportsPRBool);
|
||||
function safeModeRestart() {
|
||||
if (Services.appinfo.inSafeMode) {
|
||||
let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
|
||||
createInstance(Ci.nsISupportsPRBool);
|
||||
Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
|
||||
|
||||
// Something aborted the quit process.
|
||||
if (cancelQuit.data) {
|
||||
if (cancelQuit.data)
|
||||
return;
|
||||
}
|
||||
|
||||
if (safeMode) {
|
||||
Services.startup.restartInSafeMode(flags);
|
||||
} else {
|
||||
Services.startup.quit(flags | Ci.nsIAppStartup.eRestart);
|
||||
}
|
||||
Services.startup.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
|
||||
return;
|
||||
}
|
||||
|
||||
Services.obs.notifyObservers(null, "restart-in-safe-mode", "");
|
||||
}
|
||||
|
||||
/* duplicateTabIn duplicates tab in a place specified by the parameter |where|.
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
let {Weave} = Cu.import("resource://services-sync/main.js", {});
|
||||
let {Notifications} = Cu.import("resource://services-sync/notifications.js", {});
|
||||
|
||||
let stringBundle = Cc["@mozilla.org/intl/stringbundle;1"]
|
||||
.getService(Ci.nsIStringBundleService)
|
||||
.createBundle("chrome://weave/locale/services/sync.properties");
|
||||
|
||||
function promiseObserver(topic) {
|
||||
return new Promise(resolve => {
|
||||
let obs = (subject, topic, data) => {
|
||||
Services.obs.removeObserver(obs, topic);
|
||||
resolve(subject);
|
||||
}
|
||||
Services.obs.addObserver(obs, topic, false);
|
||||
});
|
||||
}
|
||||
|
||||
add_task(function* prepare() {
|
||||
let xps = Components.classes["@mozilla.org/weave/service;1"]
|
||||
.getService(Components.interfaces.nsISupports)
|
||||
.wrappedJSObject;
|
||||
yield xps.whenLoaded();
|
||||
// mock out the "_needsSetup()" function so we don't short-circuit.
|
||||
let oldNeedsSetup = window.gSyncUI._needsSetup;
|
||||
window.gSyncUI._needsSetup = () => false;
|
||||
registerCleanupFunction(() => {
|
||||
window.gSyncUI._needsSetup = oldNeedsSetup;
|
||||
// this test leaves the tab focused which can cause browser_tabopen_reflows
|
||||
// to fail, so re-select the URLBar.
|
||||
gURLBar.select();
|
||||
});
|
||||
});
|
||||
|
||||
add_task(function* testProlongedError() {
|
||||
let promiseNotificationAdded = promiseObserver("weave:notification:added");
|
||||
Assert.equal(Notifications.notifications.length, 0, "start with no notifications");
|
||||
|
||||
// Pretend we are in the "prolonged error" state.
|
||||
Weave.Status.sync = Weave.PROLONGED_SYNC_FAILURE;
|
||||
Weave.Status.login = Weave.LOGIN_SUCCEEDED;
|
||||
Services.obs.notifyObservers(null, "weave:ui:sync:error", null);
|
||||
|
||||
let subject = yield promiseNotificationAdded;
|
||||
let notification = subject.wrappedJSObject.object; // sync's observer abstraction is abstract!
|
||||
Assert.equal(notification.title, stringBundle.GetStringFromName("error.sync.title"));
|
||||
Assert.equal(Notifications.notifications.length, 1, "exactly 1 notification");
|
||||
|
||||
// Now pretend we just had a successful sync - the error notification should go away.
|
||||
let promiseNotificationRemoved = promiseObserver("weave:notification:removed");
|
||||
Weave.Status.sync = Weave.STATUS_OK;
|
||||
Services.obs.notifyObservers(null, "weave:ui:sync:finish", null);
|
||||
yield promiseNotificationRemoved;
|
||||
Assert.equal(Notifications.notifications.length, 0, "no notifications left");
|
||||
});
|
||||
|
||||
add_task(function* testLoginError() {
|
||||
let promiseNotificationAdded = promiseObserver("weave:notification:added");
|
||||
Assert.equal(Notifications.notifications.length, 0, "start with no notifications");
|
||||
|
||||
// Pretend we are in the "prolonged error" state.
|
||||
Weave.Status.sync = Weave.LOGIN_FAILED;
|
||||
Weave.Status.login = Weave.LOGIN_FAILED_LOGIN_REJECTED;
|
||||
Services.obs.notifyObservers(null, "weave:ui:sync:error", null);
|
||||
|
||||
let subject = yield promiseNotificationAdded;
|
||||
let notification = subject.wrappedJSObject.object; // sync's observer abstraction is abstract!
|
||||
Assert.equal(notification.title, stringBundle.GetStringFromName("error.login.title"));
|
||||
Assert.equal(Notifications.notifications.length, 1, "exactly 1 notification");
|
||||
// Now pretend we just had a successful login - the error notification should go away.
|
||||
Weave.Status.sync = Weave.STATUS_OK;
|
||||
Weave.Status.login = Weave.LOGIN_SUCCEEDED;
|
||||
let promiseNotificationRemoved = promiseObserver("weave:notification:removed");
|
||||
Services.obs.notifyObservers(null, "weave:service:login:start", null);
|
||||
Services.obs.notifyObservers(null, "weave:service:login:finish", null);
|
||||
yield promiseNotificationRemoved;
|
||||
Assert.equal(Notifications.notifications.length, 0, "no notifications left");
|
||||
});
|
||||
|
||||
function testButtonActions(startNotification, endNotification) {
|
||||
let button = document.getElementById("sync-button");
|
||||
Assert.ok(button, "button exists");
|
||||
let panelbutton = document.getElementById("PanelUI-fxa-status");
|
||||
Assert.ok(panelbutton, "panel button exists");
|
||||
|
||||
// pretend a sync is starting.
|
||||
Services.obs.notifyObservers(null, startNotification, null);
|
||||
Assert.equal(button.getAttribute("status"), "active");
|
||||
Assert.equal(panelbutton.getAttribute("syncstatus"), "active");
|
||||
|
||||
Services.obs.notifyObservers(null, endNotification, null);
|
||||
Assert.ok(!button.hasAttribute("status"));
|
||||
Assert.ok(!panelbutton.hasAttribute("syncstatus"));
|
||||
}
|
||||
|
||||
add_task(function* testButtonActivities() {
|
||||
// add the Sync button to the panel so we can get it!
|
||||
CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
|
||||
// check the button's functionality
|
||||
yield PanelUI.show();
|
||||
try {
|
||||
testButtonActions("weave:service:login:start", "weave:service:login:finish");
|
||||
testButtonActions("weave:service:sync:start", "weave:ui:sync:finish");
|
||||
} finally {
|
||||
PanelUI.hide();
|
||||
CustomizableUI.removeWidgetFromArea("sync-button");
|
||||
}
|
||||
});
|
||||
+1
-1
@@ -102,7 +102,7 @@ browser.jar:
|
||||
content/browser/openLocation.xul (content/openLocation.xul)
|
||||
content/browser/safeMode.css (content/safeMode.css)
|
||||
content/browser/safeMode.js (content/safeMode.js)
|
||||
content/browser/safeMode.xul (content/safeMode.xul)
|
||||
* content/browser/safeMode.xul (content/safeMode.xul)
|
||||
* content/browser/sanitize.js (content/sanitize.js)
|
||||
* content/browser/sanitize.xul (content/sanitize.xul)
|
||||
* content/browser/sanitizeDialog.js (content/sanitizeDialog.js)
|
||||
|
||||
@@ -192,6 +192,9 @@ BrowserGlue.prototype = {
|
||||
Services.console.logStringMessage(null); // clear the console (in case it's open)
|
||||
Services.console.reset();
|
||||
break;
|
||||
case "restart-in-safe-mode":
|
||||
this._onSafeModeRestart();
|
||||
break;
|
||||
case "quit-application-requested":
|
||||
this._onQuitRequest(subject, data);
|
||||
break;
|
||||
@@ -368,6 +371,7 @@ BrowserGlue.prototype = {
|
||||
os.addObserver(this, "profile-before-change", false);
|
||||
os.addObserver(this, "browser-search-engine-modified", false);
|
||||
os.addObserver(this, "browser-search-service", false);
|
||||
os.addObserver(this, "restart-in-safe-mode", false);
|
||||
},
|
||||
|
||||
// cleanup (called on application shutdown)
|
||||
@@ -379,6 +383,7 @@ BrowserGlue.prototype = {
|
||||
os.removeObserver(this, "browser:purge-session-history");
|
||||
os.removeObserver(this, "quit-application-requested");
|
||||
os.removeObserver(this, "quit-application-granted");
|
||||
os.removeObserver(this, "restart-in-safe-mode");
|
||||
#ifdef OBSERVE_LASTWINDOW_CLOSE_TOPICS
|
||||
os.removeObserver(this, "browser-lastwindow-close-requested");
|
||||
os.removeObserver(this, "browser-lastwindow-close-granted");
|
||||
@@ -542,6 +547,33 @@ BrowserGlue.prototype = {
|
||||
}
|
||||
},
|
||||
|
||||
_onSafeModeRestart: function BG_onSafeModeRestart() {
|
||||
// prompt the user to confirm
|
||||
let strings = Services.strings.createBundle("chrome://browser/locale/browser.properties");
|
||||
let promptTitle = strings.GetStringFromName("safeModeRestartPromptTitle");
|
||||
let promptMessage = strings.GetStringFromName("safeModeRestartPromptMessage");
|
||||
let restartText = strings.GetStringFromName("safeModeRestartButton");
|
||||
let buttonFlags = (Services.prompt.BUTTON_POS_0 *
|
||||
Services.prompt.BUTTON_TITLE_IS_STRING) +
|
||||
(Services.prompt.BUTTON_POS_1 *
|
||||
Services.prompt.BUTTON_TITLE_CANCEL) +
|
||||
Services.prompt.BUTTON_POS_0_DEFAULT;
|
||||
|
||||
let rv = Services.prompt.confirmEx(null, promptTitle, promptMessage,
|
||||
buttonFlags, restartText, null, null,
|
||||
null, {});
|
||||
if (rv != 0)
|
||||
return;
|
||||
|
||||
let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
|
||||
.createInstance(Ci.nsISupportsPRBool);
|
||||
Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
|
||||
|
||||
if (!cancelQuit.data) {
|
||||
Services.startup.restartInSafeMode(Ci.nsIAppStartup.eAttemptQuit);
|
||||
}
|
||||
},
|
||||
|
||||
_trackSlowStartup: function () {
|
||||
if (Services.startup.interrupted ||
|
||||
Services.prefs.getBoolPref("browser.slowStartup.notificationDisabled"))
|
||||
|
||||
@@ -18,6 +18,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
|
||||
"resource:///modules/RecentWindow.jsm");
|
||||
|
||||
@@ -1153,6 +1155,155 @@ this.PlacesUIUtils = {
|
||||
Weave.Service.engineManager.get("tabs") &&
|
||||
Weave.Service.engineManager.get("tabs").enabled;
|
||||
},
|
||||
|
||||
/**
|
||||
* WARNING TO ADDON AUTHORS: DO NOT USE THIS METHOD. IT'S LIKELY TO BE REMOVED IN A
|
||||
* FUTURE RELEASE.
|
||||
*
|
||||
* Checks if a place: href represents a folder shortcut.
|
||||
*
|
||||
* @param queryString
|
||||
* the query string to check (a place: href)
|
||||
* @return whether or not queryString represents a folder shortcut.
|
||||
* @throws if queryString is malformed.
|
||||
*/
|
||||
isFolderShortcutQueryString(queryString) {
|
||||
// Based on GetSimpleBookmarksQueryFolder in nsNavHistory.cpp.
|
||||
|
||||
let queriesParam = { }, optionsParam = { };
|
||||
PlacesUtils.history.queryStringToQueries(queryString,
|
||||
queriesParam,
|
||||
{ },
|
||||
optionsParam);
|
||||
let queries = queries.value;
|
||||
if (queries.length == 0)
|
||||
throw new Error(`Invalid place: uri: ${queryString}`);
|
||||
return queries.length == 1 &&
|
||||
queries[0].folderCount == 1 &&
|
||||
!queries[0].hasBeginTime &&
|
||||
!queries[0].hasEndTime &&
|
||||
!queries[0].hasDomain &&
|
||||
!queries[0].hasURI &&
|
||||
!queries[0].hasSearchTerms &&
|
||||
!queries[0].tags.length == 0 &&
|
||||
optionsParam.value.maxResults == 0;
|
||||
},
|
||||
|
||||
/**
|
||||
* WARNING TO ADDON AUTHORS: DO NOT USE THIS METHOD. IT"S LIKELY TO BE REMOVED IN A
|
||||
* FUTURE RELEASE.
|
||||
*
|
||||
* Helpers for consumers of editBookmarkOverlay which don't have a node as their input.
|
||||
* Given a partial node-like object, having at least the itemId property set, this
|
||||
* method completes the rest of the properties necessary for initialising the edit
|
||||
* overlay with it.
|
||||
*
|
||||
* @param aNodeLike
|
||||
* an object having at least the itemId nsINavHistoryResultNode property set,
|
||||
* along with any other properties available.
|
||||
*/
|
||||
completeNodeLikeObjectForItemId(aNodeLike) {
|
||||
if (this.useAsyncTransactions) {
|
||||
// When async-transactions are enabled, node-likes must have
|
||||
// bookmarkGuid set, and we cannot set it synchronously.
|
||||
throw new Error("completeNodeLikeObjectForItemId cannot be used when " +
|
||||
"async transactions are enabled");
|
||||
}
|
||||
if (!("itemId" in aNodeLike))
|
||||
throw new Error("itemId missing in aNodeLike");
|
||||
|
||||
let itemId = aNodeLike.itemId;
|
||||
let defGetter = XPCOMUtils.defineLazyGetter.bind(XPCOMUtils, aNodeLike);
|
||||
|
||||
if (!("title" in aNodeLike))
|
||||
defGetter("title", () => PlacesUtils.bookmarks.getItemTitle(itemId));
|
||||
|
||||
if (!("uri" in aNodeLike)) {
|
||||
defGetter("uri", () => {
|
||||
let uri = null;
|
||||
try {
|
||||
uri = PlacesUtils.bookmarks.getBookmarkURI(itemId);
|
||||
}
|
||||
catch(ex) { }
|
||||
return uri ? uri.spec : "";
|
||||
});
|
||||
}
|
||||
|
||||
if (!("type" in aNodeLike)) {
|
||||
defGetter("type", () => {
|
||||
if (aNodeLike.uri.length > 0) {
|
||||
if (/^place:/.test(aNodeLike.uri)) {
|
||||
if (this.isFolderShortcutQueryString(aNodeLike.uri))
|
||||
return Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
|
||||
|
||||
return Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY;
|
||||
}
|
||||
|
||||
return Ci.nsINavHistoryResultNode.RESULT_TYPE_URI;
|
||||
}
|
||||
|
||||
let itemType = PlacesUtils.bookmarks.getItemType(itemId);
|
||||
if (itemType == PlacesUtils.bookmarks.TYPE_FOLDER)
|
||||
return Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER;
|
||||
|
||||
throw new Error("Unexpected item type");
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Helpers for consumers of editBookmarkOverlay which don't have a node as their input.
|
||||
*
|
||||
* Given a bookmark object for either a url bookmark or a folder, returned by
|
||||
* Bookmarks.fetch (see Bookmark.jsm), this creates a node-like object suitable for
|
||||
* initialising the edit overlay with it.
|
||||
*
|
||||
* @param aFetchInfo
|
||||
* a bookmark object returned by Bookmarks.fetch.
|
||||
* @return a node-like object suitable for initialising editBookmarkOverlay.
|
||||
* @throws if aFetchInfo is representing a separator.
|
||||
*/
|
||||
promiseNodeLikeFromFetchInfo: Task.async(function* (aFetchInfo) {
|
||||
if (aFetchInfo.itemType == PlacesUtils.bookmarks.TYPE_SEPARATOR)
|
||||
throw new Error("promiseNodeLike doesn't support separators");
|
||||
|
||||
return Object.freeze({
|
||||
itemId: yield PlacesUtils.promiseItemId(aFetchInfo.guid),
|
||||
bookmarkGuid: aFetchInfo.guid,
|
||||
title: aFetchInfo.title,
|
||||
uri: aFetchInfo.url !== undefined ? aFetchInfo.url.href : "",
|
||||
|
||||
get type() {
|
||||
if (aFetchInfo.itemType == PlacesUtils.bookmarks.TYPE_FOLDER)
|
||||
return Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER;
|
||||
|
||||
if (this.uri.length == 0)
|
||||
throw new Error("Unexpected item type");
|
||||
|
||||
if (/^place:/.test(this.uri)) {
|
||||
if (this.isFolderShortcutQueryString(this.uri))
|
||||
return Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT;
|
||||
|
||||
return Ci.nsINavHistoryResultNode.RESULT_TYPE_QUERY;
|
||||
}
|
||||
|
||||
return Ci.nsINavHistoryResultNode.RESULT_TYPE_URI;
|
||||
}
|
||||
});
|
||||
}),
|
||||
|
||||
/**
|
||||
* Shortcut for calling promiseNodeLikeFromFetchInfo on the result of
|
||||
* Bookmarks.fetch for the given guid/info object.
|
||||
*
|
||||
* @see promiseNodeLikeFromFetchInfo above and Bookmarks.fetch in Bookmarks.jsm.
|
||||
*/
|
||||
fetchNodeLike: Task.async(function* (aGuidOrInfo) {
|
||||
let info = yield PlacesUtils.bookmarks.fetch(aGuidOrInfo);
|
||||
if (!info)
|
||||
return null;
|
||||
return (yield this.promiseNodeLikeFromFetchInfo(info));
|
||||
})
|
||||
};
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(PlacesUIUtils, "RDF",
|
||||
|
||||
@@ -37,9 +37,11 @@
|
||||
* - "edit" - for editing a bookmark item or a folder.
|
||||
* @ type (String). Possible values:
|
||||
* - "bookmark"
|
||||
* @ itemId (Integer) - the id of the bookmark item.
|
||||
* @ node (an nsINavHistoryResultNode object) - a node representing
|
||||
* the bookmark.
|
||||
* - "folder" (also applies to livemarks)
|
||||
* @ itemId (Integer) - the id of the folder.
|
||||
* @ node (an nsINavHistoryResultNode object) - a node representing
|
||||
* the folder.
|
||||
* @ hiddenRows (Strings array) - optional, list of rows to be hidden
|
||||
* regardless of the item edited or added by the dialog.
|
||||
* Possible values:
|
||||
@@ -49,10 +51,7 @@
|
||||
* - "keyword"
|
||||
* - "tags"
|
||||
* - "loadInSidebar"
|
||||
* - "feedLocation"
|
||||
* - "siteLocation"
|
||||
* - "folderPicker" - hides both the tree and the menu.
|
||||
* @ readOnly (Boolean) - optional, states if the panel should be read-only
|
||||
*
|
||||
* window.arguments[0].performed is set to true if any transaction has
|
||||
* been performed by the dialog.
|
||||
@@ -61,6 +60,10 @@
|
||||
Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
|
||||
"resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Task",
|
||||
"resource://gre/modules/Task.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
|
||||
"resource://gre/modules/PromiseUtils.jsm");
|
||||
|
||||
const BOOKMARK_ITEM = 0;
|
||||
const BOOKMARK_FOLDER = 1;
|
||||
@@ -99,7 +102,6 @@ var BookmarkPropertiesPanel = {
|
||||
_defaultInsertionPoint: null,
|
||||
_hiddenRows: [],
|
||||
_batching: false,
|
||||
_readOnly: false,
|
||||
|
||||
/**
|
||||
* This method returns the correct label for the dialog's "accept"
|
||||
@@ -148,8 +150,8 @@ var BookmarkPropertiesPanel = {
|
||||
/**
|
||||
* Determines the initial data for the item edited or added by this dialog
|
||||
*/
|
||||
_determineItemInfo: function BPP__determineItemInfo() {
|
||||
var dialogInfo = window.arguments[0];
|
||||
_determineItemInfo() {
|
||||
let dialogInfo = window.arguments[0];
|
||||
this._action = dialogInfo.action == "add" ? ACTION_ADD : ACTION_EDIT;
|
||||
this._hiddenRows = dialogInfo.hiddenRows ? dialogInfo.hiddenRows : [];
|
||||
if (this._action == ACTION_ADD) {
|
||||
@@ -161,11 +163,12 @@ var BookmarkPropertiesPanel = {
|
||||
if ("defaultInsertionPoint" in dialogInfo) {
|
||||
this._defaultInsertionPoint = dialogInfo.defaultInsertionPoint;
|
||||
}
|
||||
else
|
||||
else {
|
||||
this._defaultInsertionPoint =
|
||||
new InsertionPoint(PlacesUtils.bookmarksMenuFolderId,
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
Ci.nsITreeView.DROP_ON);
|
||||
}
|
||||
|
||||
switch (dialogInfo.type) {
|
||||
case "bookmark":
|
||||
@@ -232,52 +235,15 @@ var BookmarkPropertiesPanel = {
|
||||
this._description = dialogInfo.description;
|
||||
}
|
||||
else { // edit
|
||||
NS_ASSERT("itemId" in dialogInfo);
|
||||
this._itemId = dialogInfo.itemId;
|
||||
this._title = PlacesUtils.bookmarks.getItemTitle(this._itemId);
|
||||
this._readOnly = !!dialogInfo.readOnly;
|
||||
|
||||
this._node = dialogInfo.node;
|
||||
switch (dialogInfo.type) {
|
||||
case "bookmark":
|
||||
this._itemType = BOOKMARK_ITEM;
|
||||
|
||||
this._uri = PlacesUtils.bookmarks.getBookmarkURI(this._itemId);
|
||||
// keyword
|
||||
this._keyword = PlacesUtils.bookmarks
|
||||
.getKeywordForBookmark(this._itemId);
|
||||
// Load In Sidebar
|
||||
this._loadInSidebar = PlacesUtils.annotations
|
||||
.itemHasAnnotation(this._itemId,
|
||||
PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO);
|
||||
break;
|
||||
|
||||
case "folder":
|
||||
this._itemType = BOOKMARK_FOLDER;
|
||||
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 newHeight = window.outerHeight +
|
||||
this._element("descriptionField").boxObject.height;
|
||||
window.resizeTo(window.outerWidth, newHeight);
|
||||
}, () => undefined);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// Description
|
||||
if (PlacesUtils.annotations
|
||||
.itemHasAnnotation(this._itemId, PlacesUIUtils.DESCRIPTION_ANNO)) {
|
||||
this._description = PlacesUtils.annotations
|
||||
.getItemAnnotation(this._itemId,
|
||||
PlacesUIUtils.DESCRIPTION_ANNO);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -303,7 +269,7 @@ var BookmarkPropertiesPanel = {
|
||||
* This method should be called by the onload of the Bookmark Properties
|
||||
* dialog to initialize the state of the panel.
|
||||
*/
|
||||
onDialogLoad: function BPP_onDialogLoad() {
|
||||
onDialogLoad: Task.async(function* () {
|
||||
this._determineItemInfo();
|
||||
|
||||
document.title = this._getDialogTitle();
|
||||
@@ -351,11 +317,23 @@ var BookmarkPropertiesPanel = {
|
||||
|
||||
switch (this._action) {
|
||||
case ACTION_EDIT:
|
||||
this._fillEditProperties();
|
||||
acceptButton.disabled = this._readOnly;
|
||||
gEditItemOverlay.initPanel({ node: this._node
|
||||
, hiddenRows: this._hiddenRows });
|
||||
acceptButton.disabled = gEditItemOverlay.readOnly;
|
||||
break;
|
||||
case ACTION_ADD:
|
||||
this._fillAddProperties();
|
||||
this._node = yield this._promiseNewItem();
|
||||
// Edit the new item
|
||||
gEditItemOverlay.initPanel({ node: this._node
|
||||
, hiddenRows: this._hiddenRows });
|
||||
|
||||
// Empty location field if the uri is about:blank, this way inserting a new
|
||||
// url will be easier for the user, Accept button will be automatically
|
||||
// disabled by the input listener until the user fills the field.
|
||||
let locationField = this._element("locationField");
|
||||
if (locationField.value == "about:blank")
|
||||
locationField.value = "";
|
||||
|
||||
// if this is an uri related dialog disable accept button until
|
||||
// the user fills an uri value.
|
||||
if (this._itemType == BOOKMARK_ITEM)
|
||||
@@ -363,7 +341,12 @@ var BookmarkPropertiesPanel = {
|
||||
break;
|
||||
}
|
||||
|
||||
if (!this._readOnly) {
|
||||
// Adjust the dialog size to the changes done by initPanel. This is necessary because
|
||||
// initPanel, which shows and hides elements, may run after some async work was done
|
||||
// here - i.e. after the DOM load event was processed.
|
||||
window.sizeToContent();
|
||||
|
||||
if (!gEditItemOverlay.readOnly) {
|
||||
// Listen on uri fields to enable accept button if input is valid
|
||||
if (this._itemType == BOOKMARK_ITEM) {
|
||||
this._element("locationField")
|
||||
@@ -373,14 +356,8 @@ var BookmarkPropertiesPanel = {
|
||||
.addEventListener("input", this, false);
|
||||
}
|
||||
}
|
||||
else if (this._itemType == LIVEMARK_CONTAINER) {
|
||||
this._element("feedLocationField")
|
||||
.addEventListener("input", this, false);
|
||||
this._element("siteLocationField")
|
||||
.addEventListener("input", this, false);
|
||||
}
|
||||
}
|
||||
},
|
||||
}),
|
||||
|
||||
// nsIDOMEventListener
|
||||
handleEvent: function BPP_handleEvent(aEvent) {
|
||||
@@ -388,8 +365,6 @@ var BookmarkPropertiesPanel = {
|
||||
switch (aEvent.type) {
|
||||
case "input":
|
||||
if (target.id == "editBMPanel_locationField" ||
|
||||
target.id == "editBMPanel_feedLocationField" ||
|
||||
target.id == "editBMPanel_siteLocationField" ||
|
||||
target.id == "editBMPanel_keywordField") {
|
||||
// Check uri fields to enable accept button if input is valid
|
||||
document.documentElement
|
||||
@@ -406,41 +381,39 @@ var BookmarkPropertiesPanel = {
|
||||
}
|
||||
},
|
||||
|
||||
_beginBatch: function BPP__beginBatch() {
|
||||
// Hack for implementing batched-Undo around the editBookmarkOverlay
|
||||
// instant-apply code. For all the details see the comment above beginBatch
|
||||
// in browser-places.js
|
||||
_batchBlockingDeferred: null,
|
||||
_beginBatch() {
|
||||
if (this._batching)
|
||||
return;
|
||||
|
||||
PlacesUtils.transactionManager.beginBatch(null);
|
||||
if (PlacesUIUtils.useAsyncTransactions) {
|
||||
this._batchBlockingDeferred = PromiseUtils.defer();
|
||||
PlacesTransactions.batch(function* () {
|
||||
yield this._batchBlockingDeferred.promise;
|
||||
}.bind(this));
|
||||
}
|
||||
else {
|
||||
PlacesUtils.transactionManager.beginBatch(null);
|
||||
}
|
||||
this._batching = true;
|
||||
},
|
||||
|
||||
_endBatch: function BPP__endBatch() {
|
||||
_endBatch() {
|
||||
if (!this._batching)
|
||||
return;
|
||||
|
||||
PlacesUtils.transactionManager.endBatch(false);
|
||||
if (PlacesUIUtils.useAsyncTransactions) {
|
||||
this._batchBlockingDeferred.resolve();
|
||||
this._batchBlockingDeferred = null;
|
||||
}
|
||||
else {
|
||||
PlacesUtils.transactionManager.endBatch(false);
|
||||
}
|
||||
this._batching = false;
|
||||
},
|
||||
|
||||
_fillEditProperties: function BPP__fillEditProperties() {
|
||||
gEditItemOverlay.initPanel(this._itemId,
|
||||
{ hiddenRows: this._hiddenRows,
|
||||
forceReadOnly: this._readOnly });
|
||||
},
|
||||
|
||||
_fillAddProperties: function BPP__fillAddProperties() {
|
||||
this._createNewItem();
|
||||
// Edit the new item
|
||||
gEditItemOverlay.initPanel(this._itemId,
|
||||
{ hiddenRows: this._hiddenRows });
|
||||
// Empty location field if the uri is about:blank, this way inserting a new
|
||||
// url will be easier for the user, Accept button will be automatically
|
||||
// disabled by the input listener until the user fills the field.
|
||||
var locationField = this._element("locationField");
|
||||
if (locationField.value == "about:blank")
|
||||
locationField.value = "";
|
||||
},
|
||||
|
||||
// nsISupports
|
||||
QueryInterface: function BPP_QueryInterface(aIID) {
|
||||
if (aIID.equals(Ci.nsIDOMEventListener) ||
|
||||
@@ -454,7 +427,7 @@ var BookmarkPropertiesPanel = {
|
||||
return document.getElementById("editBMPanel_" + aID);
|
||||
},
|
||||
|
||||
onDialogUnload: function BPP_onDialogUnload() {
|
||||
onDialogUnload() {
|
||||
// gEditItemOverlay does not exist anymore here, so don't rely on it.
|
||||
this._mutationObserver.disconnect();
|
||||
delete this._mutationObserver;
|
||||
@@ -465,13 +438,9 @@ var BookmarkPropertiesPanel = {
|
||||
// currently registered EventListener on the EventTarget has no effect.
|
||||
this._element("locationField")
|
||||
.removeEventListener("input", this, false);
|
||||
this._element("feedLocationField")
|
||||
.removeEventListener("input", this, false);
|
||||
this._element("siteLocationField")
|
||||
.removeEventListener("input", this, false);
|
||||
},
|
||||
|
||||
onDialogAccept: function BPP_onDialogAccept() {
|
||||
onDialogAccept() {
|
||||
// We must blur current focused element to save its changes correctly
|
||||
document.commandDispatcher.focusedElement.blur();
|
||||
// The order here is important! We have to uninit the panel first, otherwise
|
||||
@@ -481,13 +450,16 @@ var BookmarkPropertiesPanel = {
|
||||
window.arguments[0].performed = true;
|
||||
},
|
||||
|
||||
onDialogCancel: function BPP_onDialogCancel() {
|
||||
onDialogCancel() {
|
||||
// The order here is important! We have to uninit the panel first, otherwise
|
||||
// changes done as part of Undo may change the panel contents and by
|
||||
// that force it to commit more transactions.
|
||||
gEditItemOverlay.uninitPanel(true);
|
||||
this._endBatch();
|
||||
PlacesUtils.transactionManager.undoTransaction();
|
||||
if (PlacesUIUtils.useAsyncTransactions)
|
||||
PlacesTransactions.undo().catch(Components.utils.reportError);
|
||||
else
|
||||
PlacesUtils.transactionManager.undoTransaction();
|
||||
window.arguments[0].performed = false;
|
||||
},
|
||||
|
||||
@@ -593,15 +565,14 @@ var BookmarkPropertiesPanel = {
|
||||
*/
|
||||
_getTransactionsForURIList: function BPP__getTransactionsForURIList() {
|
||||
var transactions = [];
|
||||
for (var i = 0; i < this._URIs.length; ++i) {
|
||||
var uri = this._URIs[i];
|
||||
var title = this._getURITitleFromHistory(uri);
|
||||
var createTxn = new PlacesCreateBookmarkTransaction(uri, -1,
|
||||
for (let uri of this._URIs) {
|
||||
let title = this._getURITitleFromHistory(uri);
|
||||
let createTxn = new PlacesCreateBookmarkTransaction(uri, -1,
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
title);
|
||||
transactions.push(createTxn);
|
||||
}
|
||||
return transactions;
|
||||
return transactions;
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -634,9 +605,6 @@ var BookmarkPropertiesPanel = {
|
||||
aContainer, aIndex);
|
||||
},
|
||||
|
||||
/**
|
||||
* Dialog-accept code-path for creating a new item (any type)
|
||||
*/
|
||||
_createNewItem: function BPP__getCreateItemTransaction() {
|
||||
var [container, index] = this._getInsertionPointDetails();
|
||||
var txn;
|
||||
@@ -647,12 +615,93 @@ var BookmarkPropertiesPanel = {
|
||||
break;
|
||||
case LIVEMARK_CONTAINER:
|
||||
txn = this._getCreateNewLivemarkTransaction(container, index);
|
||||
break;
|
||||
break;
|
||||
default: // BOOKMARK_ITEM
|
||||
txn = this._getCreateNewBookmarkTransaction(container, index);
|
||||
}
|
||||
|
||||
PlacesUtils.transactionManager.doTransaction(txn);
|
||||
this._itemId = PlacesUtils.bookmarks.getIdForItemAt(container, index);
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
itemId: this._itemId,
|
||||
get bookmarkGuid() {
|
||||
throw new Error("Node-like bookmarkGuid getter called even though " +
|
||||
"async transactions are disabled");
|
||||
},
|
||||
title: this._title,
|
||||
uri: this._uri ? this._uri.spec : "",
|
||||
type: this._itemType == BOOKMARK_ITEM ?
|
||||
Ci.nsINavHistoryResultNode.RESULT_TYPE_URI :
|
||||
Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER
|
||||
});
|
||||
},
|
||||
|
||||
_promiseNewItem: Task.async(function* () {
|
||||
if (!PlacesUIUtils.useAsyncTransactions)
|
||||
return this._createNewItem();
|
||||
|
||||
let txnFunc =
|
||||
{ [BOOKMARK_FOLDER]: PlacesTransactions.NewFolder,
|
||||
[LIVEMARK_CONTAINER]: PlacesTransactions.NewLivemark,
|
||||
[BOOKMARK_ITEM]: PlacesTransactions.NewBookmark
|
||||
}[this._itemType];
|
||||
|
||||
let [containerId, index] = this._getInsertionPointDetails();
|
||||
let parentGuid = yield PlacesUtils.promiseItemGuid(containerId);
|
||||
let annotations = [];
|
||||
if (this._description) {
|
||||
annotations.push({ name: PlacesUIUtils.DESCRIPTION_ANNO
|
||||
, value: this._description });
|
||||
}
|
||||
if (this._loadInSidebar) {
|
||||
annotations.push({ name: PlacesUIUtils.LOAD_IN_SIDEBAR_ANNO
|
||||
, value: true });
|
||||
}
|
||||
|
||||
let itemGuid;
|
||||
let info = { parentGuid, index, title: this._title, annotations };
|
||||
if (this._itemType == BOOKMARK_ITEM) {
|
||||
info.url = this._uri;
|
||||
if (this._keyword)
|
||||
info.keyword = this._keyword;
|
||||
if (this._postData)
|
||||
info.postData = this._postData;
|
||||
|
||||
if (this._charSet && !PrivateBrowsingUtils.isWindowPrivate(window))
|
||||
PlacesUtils.setCharsetForURI(this._uri, this._charSet);
|
||||
|
||||
itemGuid = yield PlacesTransactions.NewBookmark(info).transact();
|
||||
}
|
||||
else if (this._itemType == LIVEMARK_CONTAINER) {
|
||||
info.feedUrl = this._feedURI;
|
||||
if (this._siteURI)
|
||||
info.siteUrl = this._siteURI;
|
||||
|
||||
itemGuid = yield PlacesTransactions.NewLivemark(info).transact();
|
||||
}
|
||||
else if (this._itemType == BOOKMARK_FOLDER) {
|
||||
itemGuid = yield PlacesTransactions.NewFolder(info).transact();
|
||||
for (let uri of this._URIs) {
|
||||
let placeInfo = yield PlacesUtils.promisePlaceInfo(uri);
|
||||
let title = placeInfo ? placeInfo.title : "";
|
||||
yield PlacesTransactions.transact({ parentGuid: itemGuid, uri, title });
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new Error(`unexpected value for _itemType: ${this._itemType}`);
|
||||
}
|
||||
|
||||
this._itemGuid = itemGuid;
|
||||
this._itemId = yield PlacesUtils.promiseItemId(itemGuid);
|
||||
return Object.freeze({
|
||||
itemId: this._itemId,
|
||||
bookmarkGuid: this._itemGuid,
|
||||
title: this._title,
|
||||
uri: this._uri ? this._uri.spec : "",
|
||||
type: this._itemType == BOOKMARK_ITEM ?
|
||||
Ci.nsINavHistoryResultNode.RESULT_TYPE_URI :
|
||||
Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER
|
||||
});
|
||||
})
|
||||
};
|
||||
|
||||
@@ -659,27 +659,13 @@ PlacesController.prototype = {
|
||||
/**
|
||||
* Opens the bookmark properties for the selected URI Node.
|
||||
*/
|
||||
showBookmarkPropertiesForSelection:
|
||||
function PC_showBookmarkPropertiesForSelection() {
|
||||
var node = this._view.selectedNode;
|
||||
showBookmarkPropertiesForSelection() {
|
||||
let node = this._view.selectedNode;
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
var itemType = PlacesUtils.nodeIsFolder(node) ||
|
||||
PlacesUtils.nodeIsTagQuery(node) ? "folder" : "bookmark";
|
||||
var concreteId = PlacesUtils.getConcreteItemId(node);
|
||||
var isRootItem = PlacesUtils.isRootItem(concreteId);
|
||||
var itemId = node.itemId;
|
||||
if (isRootItem || PlacesUtils.nodeIsTagQuery(node)) {
|
||||
// If this is a root or the Tags query we use the concrete itemId to catch
|
||||
// the correct title for the node.
|
||||
itemId = concreteId;
|
||||
}
|
||||
|
||||
PlacesUIUtils.showBookmarkDialog({ action: "edit"
|
||||
, type: itemType
|
||||
, itemId: itemId
|
||||
, readOnly: isRootItem
|
||||
, node
|
||||
, hiddenRows: [ "folderPicker" ]
|
||||
}, window.top);
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -32,10 +32,9 @@
|
||||
<label value="&editBookmarkOverlay.name.label;"
|
||||
class="editBMPanel_rowLabel"
|
||||
accesskey="&editBookmarkOverlay.name.accesskey;"
|
||||
control="editBMPanel_namePicker"
|
||||
observes="paneElementsBroadcaster"/>
|
||||
control="editBMPanel_namePicker"/>
|
||||
<textbox id="editBMPanel_namePicker"
|
||||
observes="paneElementsBroadcaster"/>
|
||||
onchange="gEditItemOverlay.onNamePickerChange();"/>
|
||||
</row>
|
||||
|
||||
<row id="editBMPanel_locationRow"
|
||||
@@ -48,33 +47,7 @@
|
||||
observes="paneElementsBroadcaster"/>
|
||||
<textbox id="editBMPanel_locationField"
|
||||
class="uri-element"
|
||||
observes="paneElementsBroadcaster"/>
|
||||
</row>
|
||||
|
||||
<row id="editBMPanel_feedLocationRow"
|
||||
align="center"
|
||||
collapsed="true">
|
||||
<label value="&editBookmarkOverlay.feedLocation.label;"
|
||||
class="editBMPanel_rowLabel"
|
||||
accesskey="&editBookmarkOverlay.feedLocation.accesskey;"
|
||||
control="editBMPanel_feedLocationField"
|
||||
observes="paneElementsBroadcaster"/>
|
||||
<textbox id="editBMPanel_feedLocationField"
|
||||
class="uri-element"
|
||||
observes="paneElementsBroadcaster"/>
|
||||
</row>
|
||||
|
||||
<row id="editBMPanel_siteLocationRow"
|
||||
align="center"
|
||||
collapsed="true">
|
||||
<label value="&editBookmarkOverlay.siteLocation.label;"
|
||||
class="editBMPanel_rowLabel"
|
||||
accesskey="&editBookmarkOverlay.siteLocation.accesskey;"
|
||||
control="editBMPanel_siteLocationField"
|
||||
observes="paneElementsBroadcaster"/>
|
||||
<textbox id="editBMPanel_siteLocationField"
|
||||
class="uri-element"
|
||||
observes="paneElementsBroadcaster"/>
|
||||
onchange="gEditItemOverlay.onLocationFieldChange();"/>
|
||||
</row>
|
||||
|
||||
<row id="editBMPanel_folderRow"
|
||||
@@ -140,7 +113,7 @@
|
||||
<button label="&editBookmarkOverlay.newFolderButton.label;"
|
||||
id="editBMPanel_newFolderButton"
|
||||
accesskey="&editBookmarkOverlay.newFolderButton.accesskey;"
|
||||
oncommand="gEditItemOverlay.newFolder();"/>
|
||||
oncommand="gEditItemOverlay.newFolder().catch(Components.utils.reportError);"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</row>
|
||||
@@ -163,7 +136,8 @@
|
||||
tabscrolling="true"
|
||||
showcommentcolumn="true"
|
||||
observes="paneElementsBroadcaster"
|
||||
placeholder="&editBookmarkOverlay.tagsEmptyDesc.label;"/>
|
||||
placeholder="&editBookmarkOverlay.tagsEmptyDesc.label;"
|
||||
onchange="gEditItemOverlay.onTagsFieldChange();"/>
|
||||
<button id="editBMPanel_tagsSelectorExpander"
|
||||
class="expander-down"
|
||||
tooltiptext="&editBookmarkOverlay.tagsExpanderDown.tooltip;"
|
||||
@@ -193,7 +167,7 @@
|
||||
control="editBMPanel_keywordField"
|
||||
observes="paneElementsBroadcaster"/>
|
||||
<textbox id="editBMPanel_keywordField"
|
||||
observes="paneElementsBroadcaster"/>
|
||||
onchange="gEditItemOverlay.onKeywordFieldChange();"/>
|
||||
</row>
|
||||
|
||||
<row id="editBMPanel_descriptionRow"
|
||||
@@ -206,7 +180,8 @@
|
||||
observes="paneElementsBroadcaster"/>
|
||||
<textbox id="editBMPanel_descriptionField"
|
||||
multiline="true"
|
||||
observes="paneElementsBroadcaster"/>
|
||||
rows="4"
|
||||
onchange="gEditItemOverlay.onDescriptionFieldChange();"/>
|
||||
</row>
|
||||
</rows>
|
||||
</grid>
|
||||
|
||||
@@ -608,7 +608,8 @@ var PlacesOrganizer = {
|
||||
// Make sure the infoBox UI is visible if we need to use it, we hide it
|
||||
// below when we don't.
|
||||
infoBox.hidden = false;
|
||||
var aSelectedNode = aNodeList.length == 1 ? aNodeList[0] : null;
|
||||
let selectedNode = aNodeList.length == 1 ? aNodeList[0] : null;
|
||||
|
||||
// If a textbox within a panel is focused, force-blur it so its contents
|
||||
// are saved
|
||||
if (gEditItemOverlay.itemId != -1) {
|
||||
@@ -620,12 +621,12 @@ var PlacesOrganizer = {
|
||||
|
||||
// don't update the panel if we are already editing this node unless we're
|
||||
// in multi-edit mode
|
||||
if (aSelectedNode) {
|
||||
var concreteId = PlacesUtils.getConcreteItemId(aSelectedNode);
|
||||
var nodeIsSame = gEditItemOverlay.itemId == aSelectedNode.itemId ||
|
||||
if (selectedNode) {
|
||||
var concreteId = PlacesUtils.getConcreteItemId(selectedNode);
|
||||
var nodeIsSame = gEditItemOverlay.itemId == selectedNode.itemId ||
|
||||
gEditItemOverlay.itemId == concreteId ||
|
||||
(aSelectedNode.itemId == -1 && gEditItemOverlay.uri &&
|
||||
gEditItemOverlay.uri == aSelectedNode.uri);
|
||||
(selectedNode.itemId == -1 && gEditItemOverlay.uri &&
|
||||
gEditItemOverlay.uri == selectedNode.uri);
|
||||
if (nodeIsSame && detailsDeck.selectedIndex == 1 &&
|
||||
!gEditItemOverlay.multiEdit)
|
||||
return;
|
||||
@@ -635,70 +636,54 @@ var PlacesOrganizer = {
|
||||
// Clean up the panel before initing it again.
|
||||
gEditItemOverlay.uninitPanel(false);
|
||||
|
||||
if (aSelectedNode && !PlacesUtils.nodeIsSeparator(aSelectedNode)) {
|
||||
if (selectedNode && !PlacesUtils.nodeIsSeparator(selectedNode)) {
|
||||
detailsDeck.selectedIndex = 1;
|
||||
// Using the concrete itemId is arguably wrong. The bookmarks API
|
||||
// does allow setting properties for folder shortcuts as well, but since
|
||||
// the UI does not distinct between the couple, we better just show
|
||||
// the concrete item properties for shortcuts to root nodes.
|
||||
var concreteId = PlacesUtils.getConcreteItemId(aSelectedNode);
|
||||
var concreteId = PlacesUtils.getConcreteItemId(selectedNode);
|
||||
var isRootItem = concreteId != -1 && PlacesUtils.isRootItem(concreteId);
|
||||
var readOnly = isRootItem ||
|
||||
aSelectedNode.parent.itemId == PlacesUIUtils.leftPaneFolderId;
|
||||
selectedNode.parent.itemId == PlacesUIUtils.leftPaneFolderId;
|
||||
var useConcreteId = isRootItem ||
|
||||
PlacesUtils.nodeIsTagQuery(aSelectedNode);
|
||||
PlacesUtils.nodeIsTagQuery(selectedNode);
|
||||
var itemId = -1;
|
||||
if (concreteId != -1 && useConcreteId)
|
||||
itemId = concreteId;
|
||||
else if (aSelectedNode.itemId != -1)
|
||||
itemId = aSelectedNode.itemId;
|
||||
else if (selectedNode.itemId != -1)
|
||||
itemId = selectedNode.itemId;
|
||||
else
|
||||
itemId = PlacesUtils._uri(aSelectedNode.uri);
|
||||
itemId = PlacesUtils._uri(selectedNode.uri);
|
||||
|
||||
gEditItemOverlay.initPanel(itemId, { hiddenRows: ["folderPicker"]
|
||||
, forceReadOnly: readOnly
|
||||
, titleOverride: aSelectedNode.title
|
||||
});
|
||||
gEditItemOverlay.initPanel({ node: selectedNode
|
||||
, hiddenRows: ["folderPicker"] });
|
||||
|
||||
// Dynamically generated queries, like history date containers, have
|
||||
// itemId !=0 and do not exist in history. For them the panel is
|
||||
// read-only, but empty, since it can't get a valid title for the object.
|
||||
// In such a case we force the title using the selectedNode one, for UI
|
||||
// polishness.
|
||||
if (aSelectedNode.itemId == -1 &&
|
||||
(PlacesUtils.nodeIsDay(aSelectedNode) ||
|
||||
PlacesUtils.nodeIsHost(aSelectedNode)))
|
||||
gEditItemOverlay._element("namePicker").value = aSelectedNode.title;
|
||||
|
||||
this._detectAndSetDetailsPaneMinimalState(aSelectedNode);
|
||||
this._detectAndSetDetailsPaneMinimalState(selectedNode);
|
||||
}
|
||||
else if (!aSelectedNode && aNodeList[0]) {
|
||||
var itemIds = [];
|
||||
for (var i = 0; i < aNodeList.length; i++) {
|
||||
if (!PlacesUtils.nodeIsBookmark(aNodeList[i]) &&
|
||||
!PlacesUtils.nodeIsURI(aNodeList[i])) {
|
||||
detailsDeck.selectedIndex = 0;
|
||||
var selectItemDesc = document.getElementById("selectItemDescription");
|
||||
var itemsCountLabel = document.getElementById("itemsCountText");
|
||||
selectItemDesc.hidden = false;
|
||||
itemsCountLabel.value =
|
||||
PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel",
|
||||
aNodeList.length, [aNodeList.length]);
|
||||
infoBox.hidden = true;
|
||||
return;
|
||||
}
|
||||
itemIds[i] = aNodeList[i].itemId != -1 ? aNodeList[i].itemId :
|
||||
PlacesUtils._uri(aNodeList[i].uri);
|
||||
else if (!selectedNode && aNodeList[0]) {
|
||||
if (aNodeList.every(PlacesUtils.nodeIsURI)) {
|
||||
let uris = [for (node of aNodeList) PlacesUtils._uri(node.uri)];
|
||||
detailsDeck.selectedIndex = 1;
|
||||
gEditItemOverlay.initPanel({ uris
|
||||
, hiddenRows: ["folderPicker",
|
||||
"loadInSidebar",
|
||||
"location",
|
||||
"keyword",
|
||||
"description",
|
||||
"name"]});
|
||||
this._detectAndSetDetailsPaneMinimalState(selectedNode);
|
||||
}
|
||||
else {
|
||||
detailsDeck.selectedIndex = 0;
|
||||
let selectItemDesc = document.getElementById("selectItemDescription");
|
||||
let itemsCountLabel = document.getElementById("itemsCountText");
|
||||
selectItemDesc.hidden = false;
|
||||
itemsCountLabel.value =
|
||||
PlacesUIUtils.getPluralString("detailsPane.itemsCountLabel",
|
||||
aNodeList.length, [aNodeList.length]);
|
||||
infoBox.hidden = true;
|
||||
}
|
||||
detailsDeck.selectedIndex = 1;
|
||||
gEditItemOverlay.initPanel(itemIds,
|
||||
{ hiddenRows: ["folderPicker",
|
||||
"loadInSidebar",
|
||||
"location",
|
||||
"keyword",
|
||||
"description",
|
||||
"name"]});
|
||||
this._detectAndSetDetailsPaneMinimalState(aSelectedNode);
|
||||
}
|
||||
else {
|
||||
detailsDeck.selectedIndex = 0;
|
||||
|
||||
@@ -1739,8 +1739,13 @@ PlacesTreeView.prototype = {
|
||||
// We may only get here if the cell is editable.
|
||||
let node = this._rows[aRow];
|
||||
if (node.title != aText) {
|
||||
let txn = new PlacesEditItemTitleTransaction(node.itemId, aText);
|
||||
PlacesUtils.transactionManager.doTransaction(txn);
|
||||
if (!PlacesUIUtils.useAsyncTransactions) {
|
||||
let txn = new PlacesEditItemTitleTransaction(node.itemId, aText);
|
||||
PlacesUtils.transactionManager.doTransaction(txn);
|
||||
return;
|
||||
}
|
||||
PlacesTransactions.EditTitle({ guid: node.bookmarkGuid, title: aText })
|
||||
.transact().catch(Cu.reportError);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
<!ENTITY helpMac.commandkey "?">
|
||||
<!ENTITY helpSafeMode.label "Restart in Safe Mode…">
|
||||
<!ENTITY helpSafeMode.accesskey "R">
|
||||
<!ENTITY helpSafeMode.stop.label "Restart with Add-ons Enabled">
|
||||
<!ENTITY helpSafeMode.stop.accesskey "R">
|
||||
|
||||
<!ENTITY helpTroubleshootingInfo.label "Troubleshooting Information">
|
||||
<!ENTITY helpTroubleshootingInfo.accesskey "T">
|
||||
|
||||
@@ -735,6 +735,13 @@ Database::InitSchema(bool* aDatabaseMigrated)
|
||||
|
||||
// Firefox 37 uses schema version 26.
|
||||
|
||||
if (currentSchemaVersion < 27) {
|
||||
rv = MigrateV27Up();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
// Firefox 38 uses schema version 27.
|
||||
|
||||
// Schema Upgrades must add migration code here.
|
||||
|
||||
rv = UpdateBookmarkRootTitles();
|
||||
@@ -801,6 +808,8 @@ Database::InitSchema(bool* aDatabaseMigrated)
|
||||
// moz_keywords.
|
||||
rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_KEYWORDS);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_KEYWORDS_PLACEPOSTDATA);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// moz_favicons.
|
||||
rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_FAVICONS);
|
||||
@@ -951,11 +960,18 @@ Database::InitTempTriggers()
|
||||
rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERUPDATE_TYPED_TRIGGER);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = mMainConn->ExecuteSimpleSQL(CREATE_FOREIGNCOUNT_AFTERDELETE_TRIGGER);
|
||||
rv = mMainConn->ExecuteSimpleSQL(CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERDELETE_TRIGGER);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = mMainConn->ExecuteSimpleSQL(CREATE_FOREIGNCOUNT_AFTERINSERT_TRIGGER);
|
||||
rv = mMainConn->ExecuteSimpleSQL(CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERINSERT_TRIGGER);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = mMainConn->ExecuteSimpleSQL(CREATE_FOREIGNCOUNT_AFTERUPDATE_TRIGGER);
|
||||
rv = mMainConn->ExecuteSimpleSQL(CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERUPDATE_TRIGGER);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = mMainConn->ExecuteSimpleSQL(CREATE_KEYWORDS_FOREIGNCOUNT_AFTERDELETE_TRIGGER);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = mMainConn->ExecuteSimpleSQL(CREATE_KEYWORDS_FOREIGNCOUNT_AFTERINSERT_TRIGGER);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = mMainConn->ExecuteSimpleSQL(CREATE_KEYWORDS_FOREIGNCOUNT_AFTERUPDATE_TRIGGER);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
@@ -1487,6 +1503,66 @@ Database::MigrateV26Up() {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
Database::MigrateV27Up() {
|
||||
MOZ_ASSERT(NS_IsMainThread());
|
||||
|
||||
// Change keywords store, moving their relation from bookmarks to urls.
|
||||
nsCOMPtr<mozIStorageStatement> stmt;
|
||||
nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
|
||||
"SELECT place_id FROM moz_keywords"
|
||||
), getter_AddRefs(stmt));
|
||||
if (NS_FAILED(rv)) {
|
||||
// Even if these 2 columns have a unique constraint, we allow NULL values
|
||||
// for backwards compatibility. NULL never breaks a unique constraint.
|
||||
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
||||
"ALTER TABLE moz_keywords ADD COLUMN place_id INTEGER"));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
||||
"ALTER TABLE moz_keywords ADD COLUMN post_data TEXT"));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_KEYWORDS_PLACEPOSTDATA);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
// Associate keywords with uris. A keyword could be associated to multiple
|
||||
// bookmarks uris, or multiple keywords could be associated to the same uri.
|
||||
// The new system only allows multiple uris per keyword, provided they have
|
||||
// a different post_data value.
|
||||
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
||||
"INSERT OR REPLACE INTO moz_keywords (id, keyword, place_id, post_data) "
|
||||
"SELECT k.id, k.keyword, h.id, MAX(a.content) "
|
||||
"FROM moz_places h "
|
||||
"JOIN moz_bookmarks b ON b.fk = h.id "
|
||||
"JOIN moz_keywords k ON k.id = b.keyword_id "
|
||||
"LEFT JOIN moz_items_annos a ON a.item_id = b.id "
|
||||
"LEFT JOIN moz_anno_attributes n ON a.anno_attribute_id = n.id "
|
||||
"AND n.name = 'bookmarkProperties/POSTData'"
|
||||
"WHERE k.place_id ISNULL "
|
||||
"GROUP BY keyword"));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Remove any keyword that points to a non-existing place id.
|
||||
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
||||
"DELETE FROM moz_keywords "
|
||||
"WHERE NOT EXISTS (SELECT 1 FROM moz_places WHERE id = moz_keywords.place_id)"));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
||||
"UPDATE moz_bookmarks SET keyword_id = NULL "
|
||||
"WHERE NOT EXISTS (SELECT 1 FROM moz_keywords WHERE id = moz_bookmarks.keyword_id)"));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Adjust foreign_count for all the rows.
|
||||
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
||||
"UPDATE moz_places SET foreign_count = "
|
||||
"(SELECT count(*) FROM moz_bookmarks WHERE fk = moz_places.id) + "
|
||||
"(SELECT count(*) FROM moz_keywords WHERE place_id = moz_places.id) "
|
||||
));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
void
|
||||
Database::Shutdown()
|
||||
{
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
// This is the schema version. Update it at any schema change and add a
|
||||
// corresponding migrateVxx method below.
|
||||
#define DATABASE_SCHEMA_VERSION 26
|
||||
#define DATABASE_SCHEMA_VERSION 27
|
||||
|
||||
// Fired after Places inited.
|
||||
#define TOPIC_PLACES_INIT_COMPLETE "places-init-complete"
|
||||
@@ -273,6 +273,7 @@ protected:
|
||||
nsresult MigrateV24Up();
|
||||
nsresult MigrateV25Up();
|
||||
nsresult MigrateV26Up();
|
||||
nsresult MigrateV27Up();
|
||||
|
||||
nsresult UpdateBookmarkRootTitles();
|
||||
|
||||
|
||||
@@ -474,23 +474,6 @@ this.PlacesDBUtils = {
|
||||
fixOrphanItems.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
|
||||
cleanupStatements.push(fixOrphanItems);
|
||||
|
||||
// D.5 fix wrong keywords
|
||||
let fixInvalidKeywords = DBConn.createAsyncStatement(
|
||||
`UPDATE moz_bookmarks SET keyword_id = NULL WHERE guid NOT IN (
|
||||
:rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid /* skip roots */
|
||||
) AND id IN (
|
||||
SELECT id FROM moz_bookmarks b
|
||||
WHERE keyword_id NOT NULL
|
||||
AND NOT EXISTS
|
||||
(SELECT id FROM moz_keywords WHERE id = b.keyword_id LIMIT 1)
|
||||
)`);
|
||||
fixInvalidKeywords.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
|
||||
fixInvalidKeywords.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
|
||||
fixInvalidKeywords.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
|
||||
fixInvalidKeywords.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
|
||||
fixInvalidKeywords.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
|
||||
cleanupStatements.push(fixInvalidKeywords);
|
||||
|
||||
// D.6 fix wrong item types
|
||||
// Folders and separators should not have an fk.
|
||||
// If they have a valid fk convert them to bookmarks. Later in D.9 we
|
||||
@@ -681,7 +664,7 @@ this.PlacesDBUtils = {
|
||||
`DELETE FROM moz_keywords WHERE id IN (
|
||||
SELECT id FROM moz_keywords k
|
||||
WHERE NOT EXISTS
|
||||
(SELECT id FROM moz_bookmarks WHERE keyword_id = k.id LIMIT 1)
|
||||
(SELECT 1 FROM moz_places h WHERE k.place_id = h.id)
|
||||
)`);
|
||||
cleanupStatements.push(deleteUnusedKeywords);
|
||||
|
||||
|
||||
@@ -386,6 +386,23 @@ this.PlacesUtils = {
|
||||
return aNode.itemId;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the concrete item-guid for the given node. For everything but folder
|
||||
* shortcuts, this is just node.bookmarkGuid. For folder shortcuts, this is
|
||||
* node.targetFolderGuid (see nsINavHistoryService.idl for the semantics).
|
||||
*
|
||||
* @param aNode
|
||||
* a result node.
|
||||
* @return the concrete item-guid for aNode.
|
||||
* @note unlike getConcreteItemId, this doesn't allow retrieving the guid of a
|
||||
* ta container.
|
||||
*/
|
||||
getConcreteItemGuid(aNode) {
|
||||
if (aNode.type == Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT)
|
||||
return asQuery(aNode).targetFolderGuid;
|
||||
return aNode.bookmarkGuid;
|
||||
},
|
||||
|
||||
/**
|
||||
* Reverse a host based on the moz_places algorithm, that is reverse the host
|
||||
* string and add a trailing period. For example "google.com" becomes
|
||||
@@ -814,13 +831,26 @@ this.PlacesUtils = {
|
||||
* @param aBookmarkId
|
||||
* @returns string of POST data
|
||||
*/
|
||||
setPostDataForBookmark: function PU_setPostDataForBookmark(aBookmarkId, aPostData) {
|
||||
const annos = this.annotations;
|
||||
if (aPostData)
|
||||
annos.setItemAnnotation(aBookmarkId, this.POST_DATA_ANNO, aPostData,
|
||||
0, Ci.nsIAnnotationService.EXPIRE_NEVER);
|
||||
else if (annos.itemHasAnnotation(aBookmarkId, this.POST_DATA_ANNO))
|
||||
annos.removeItemAnnotation(aBookmarkId, this.POST_DATA_ANNO);
|
||||
setPostDataForBookmark(aBookmarkId, aPostData) {
|
||||
// For now we don't have a unified API to create a keyword with postData,
|
||||
// thus here we can just try to complete a keyword that should already exist
|
||||
// without any post data.
|
||||
let nullPostDataFragment = aPostData ? "AND post_data ISNULL" : "";
|
||||
let stmt = PlacesUtils.history.DBConnection.createStatement(
|
||||
`UPDATE moz_keywords SET post_data = :post_data
|
||||
WHERE id = (SELECT k.id FROM moz_keywords k
|
||||
JOIN moz_bookmarks b ON b.fk = k.place_id
|
||||
WHERE b.id = :item_id
|
||||
${nullPostDataFragment}
|
||||
LIMIT 1)`);
|
||||
stmt.params.item_id = aBookmarkId;
|
||||
stmt.params.post_data = aPostData;
|
||||
try {
|
||||
stmt.execute();
|
||||
}
|
||||
finally {
|
||||
stmt.finalize();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -828,12 +858,22 @@ this.PlacesUtils = {
|
||||
* @param aBookmarkId
|
||||
* @returns string of POST data if set for aBookmarkId. null otherwise.
|
||||
*/
|
||||
getPostDataForBookmark: function PU_getPostDataForBookmark(aBookmarkId) {
|
||||
const annos = this.annotations;
|
||||
if (annos.itemHasAnnotation(aBookmarkId, this.POST_DATA_ANNO))
|
||||
return annos.getItemAnnotation(aBookmarkId, this.POST_DATA_ANNO);
|
||||
|
||||
return null;
|
||||
getPostDataForBookmark(aBookmarkId) {
|
||||
let stmt = PlacesUtils.history.DBConnection.createStatement(
|
||||
`SELECT k.post_data
|
||||
FROM moz_keywords k
|
||||
JOIN moz_places h ON h.id = k.place_id
|
||||
JOIN moz_bookmarks b ON b.fk = h.id
|
||||
WHERE b.id = :item_id`);
|
||||
stmt.params.item_id = aBookmarkId;
|
||||
try {
|
||||
if (!stmt.executeStep())
|
||||
return null;
|
||||
return stmt.row.post_data;
|
||||
}
|
||||
finally {
|
||||
stmt.finalize();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -841,24 +881,21 @@ this.PlacesUtils = {
|
||||
* @param aKeyword string keyword
|
||||
* @returns an array containing a string URL and a string of POST data
|
||||
*/
|
||||
getURLAndPostDataForKeyword: function PU_getURLAndPostDataForKeyword(aKeyword) {
|
||||
var url = null, postdata = null;
|
||||
getURLAndPostDataForKeyword(aKeyword) {
|
||||
let stmt = PlacesUtils.history.DBConnection.createStatement(
|
||||
`SELECT h.url, k.post_data
|
||||
FROM moz_keywords k
|
||||
JOIN moz_places h ON h.id = k.place_id
|
||||
WHERE k.keyword = :keyword`);
|
||||
stmt.params.keyword = aKeyword;
|
||||
try {
|
||||
var uri = this.bookmarks.getURIForKeyword(aKeyword);
|
||||
if (uri) {
|
||||
url = uri.spec;
|
||||
var bookmarks = this.bookmarks.getBookmarkIdsForURI(uri);
|
||||
for (let i = 0; i < bookmarks.length; i++) {
|
||||
var bookmark = bookmarks[i];
|
||||
var kw = this.bookmarks.getKeywordForBookmark(bookmark);
|
||||
if (kw == aKeyword) {
|
||||
postdata = this.getPostDataForBookmark(bookmark);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(ex) {}
|
||||
return [url, postdata];
|
||||
if (!stmt.executeStep())
|
||||
return [ null, null ];
|
||||
return [ stmt.row.url, stmt.row.post_data ];
|
||||
}
|
||||
finally {
|
||||
stmt.finalize();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -3064,7 +3101,7 @@ PlacesSortFolderByNameTransaction.prototype = {
|
||||
let callback = {
|
||||
_self: this,
|
||||
runBatched: function() {
|
||||
for (item in this._self._oldOrder)
|
||||
for (let item in this._self._oldOrder)
|
||||
PlacesUtils.bookmarks.setItemIndex(item, this._self._oldOrder[item]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -152,24 +152,20 @@ const SQL_ADAPTIVE_QUERY =
|
||||
const SQL_KEYWORD_QUERY =
|
||||
`/* do not warn (bug 487787) */
|
||||
SELECT :query_type,
|
||||
(SELECT REPLACE(url, '%s', :query_string) FROM moz_places WHERE id = b.fk)
|
||||
AS search_url, h.title,
|
||||
REPLACE(h.url, '%s', :query_string) AS search_url, h.title,
|
||||
IFNULL(f.url, (SELECT f.url
|
||||
FROM moz_places
|
||||
JOIN moz_favicons f ON f.id = favicon_id
|
||||
WHERE rev_host = (SELECT rev_host FROM moz_places WHERE id = b.fk)
|
||||
WHERE rev_host = h.rev_host
|
||||
ORDER BY frecency DESC
|
||||
LIMIT 1)
|
||||
),
|
||||
1, b.title, NULL, h.visit_count, h.typed, IFNULL(h.id, b.fk),
|
||||
t.open_count, h.frecency
|
||||
1, NULL, NULL, h.visit_count, h.typed, h.id, t.open_count, h.frecency
|
||||
FROM moz_keywords k
|
||||
JOIN moz_bookmarks b ON b.keyword_id = k.id
|
||||
LEFT JOIN moz_places h ON h.url = search_url
|
||||
JOIN moz_places h ON k.place_id = h.id
|
||||
LEFT JOIN moz_favicons f ON f.id = h.favicon_id
|
||||
LEFT JOIN moz_openpages_temp t ON t.url = search_url
|
||||
WHERE LOWER(k.keyword) = LOWER(:keyword)
|
||||
ORDER BY h.frecency DESC`;
|
||||
WHERE k.keyword = LOWER(:keyword)`;
|
||||
|
||||
function hostQuery(conditions = "") {
|
||||
let query =
|
||||
@@ -1241,24 +1237,13 @@ Search.prototype = {
|
||||
let title = bookmarkTitle || historyTitle;
|
||||
|
||||
if (queryType == QUERYTYPE_KEYWORD) {
|
||||
match.style = "keyword";
|
||||
if (this._enableActions) {
|
||||
match.style = "keyword";
|
||||
url = makeActionURL("keyword", {
|
||||
url: escapedURL,
|
||||
input: this._originalSearchString,
|
||||
});
|
||||
action = "keyword";
|
||||
} else {
|
||||
// If we do not have a title, then we must have a keyword, so let the UI
|
||||
// know it is a keyword. Otherwise, we found an exact page match, so just
|
||||
// show the page like a regular result. Because the page title is likely
|
||||
// going to be more specific than the bookmark title (keyword title).
|
||||
if (!historyTitle) {
|
||||
match.style = "keyword"
|
||||
}
|
||||
else {
|
||||
title = historyTitle;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -222,7 +222,7 @@ interface nsINavBookmarkObserver : nsISupports
|
||||
* folders. A URI in history can be contained in one or more such folders.
|
||||
*/
|
||||
|
||||
[scriptable, uuid(b0f9a80a-d7f0-4421-8513-444125f0d828)]
|
||||
[scriptable, uuid(24533891-afa6-4663-b72d-3143d03f1b04)]
|
||||
interface nsINavBookmarksService : nsISupports
|
||||
{
|
||||
/**
|
||||
@@ -536,12 +536,6 @@ interface nsINavBookmarksService : nsISupports
|
||||
*/
|
||||
void setKeywordForBookmark(in long long aItemId, in AString aKeyword);
|
||||
|
||||
/**
|
||||
* Retrieves the keyword for the given URI. Will be void string
|
||||
* (null in JS) if no such keyword is found.
|
||||
*/
|
||||
AString getKeywordForURI(in nsIURI aURI);
|
||||
|
||||
/**
|
||||
* Retrieves the keyword for the given bookmark. Will be void string
|
||||
* (null in JS) if no such keyword is found.
|
||||
|
||||
@@ -19,8 +19,6 @@
|
||||
|
||||
#include "GeckoProfiler.h"
|
||||
|
||||
#define BOOKMARKS_TO_KEYWORDS_INITIAL_CACHE_LENGTH 32
|
||||
|
||||
using namespace mozilla;
|
||||
|
||||
// These columns sit to the right of the kGetInfoIndex_* columns.
|
||||
@@ -40,25 +38,6 @@ PLACES_FACTORY_SINGLETON_IMPLEMENTATION(nsNavBookmarks, gBookmarksService)
|
||||
|
||||
namespace {
|
||||
|
||||
struct keywordSearchData
|
||||
{
|
||||
int64_t itemId;
|
||||
nsString keyword;
|
||||
};
|
||||
|
||||
PLDHashOperator
|
||||
SearchBookmarkForKeyword(nsTrimInt64HashKey::KeyType aKey,
|
||||
const nsString aValue,
|
||||
void* aUserArg)
|
||||
{
|
||||
keywordSearchData* data = reinterpret_cast<keywordSearchData*>(aUserArg);
|
||||
if (data->keyword.Equals(aValue)) {
|
||||
data->itemId = aKey;
|
||||
return PL_DHASH_STOP;
|
||||
}
|
||||
return PL_DHASH_NEXT;
|
||||
}
|
||||
|
||||
template<typename Method, typename DataType>
|
||||
class AsyncGetBookmarksForURI : public AsyncStatementCallback
|
||||
{
|
||||
@@ -143,8 +122,6 @@ nsNavBookmarks::nsNavBookmarks()
|
||||
, mCanNotify(false)
|
||||
, mCacheObservers("bookmark-observers")
|
||||
, mBatching(false)
|
||||
, mBookmarkToKeywordHash(BOOKMARKS_TO_KEYWORDS_INITIAL_CACHE_LENGTH)
|
||||
, mBookmarkToKeywordHashInitialized(false)
|
||||
{
|
||||
NS_ASSERTION(!gBookmarksService,
|
||||
"Attempting to create two instances of the service!");
|
||||
@@ -646,11 +623,12 @@ nsNavBookmarks::RemoveItem(int64_t aItemId)
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
rv = UpdateKeywordsHashForRemovedBookmark(aItemId);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// A broken url should not interrupt the removal process.
|
||||
(void)NS_NewURI(getter_AddRefs(uri), bookmark.url);
|
||||
rv = NS_NewURI(getter_AddRefs(uri), bookmark.url);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
rv = UpdateKeywordsForRemovedBookmark(bookmark);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
}
|
||||
|
||||
NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
|
||||
@@ -1108,8 +1086,14 @@ nsNavBookmarks::RemoveFolderChildren(int64_t aFolderId)
|
||||
rv = SetItemDateInternal(LAST_MODIFIED, folder.id, RoundedPRNow());
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
for (uint32_t i = 0; i < folderChildrenArray.Length(); i++) {
|
||||
rv = transaction.Commit();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Call observers in reverse order to serve children before their parent.
|
||||
for (int32_t i = folderChildrenArray.Length() - 1; i >= 0; --i) {
|
||||
BookmarkData& child = folderChildrenArray[i];
|
||||
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
if (child.type == TYPE_BOOKMARK) {
|
||||
// If not a tag, recalculate frecency for this entry, since it changed.
|
||||
if (child.grandParentId != mTagsRoot) {
|
||||
@@ -1119,21 +1103,12 @@ nsNavBookmarks::RemoveFolderChildren(int64_t aFolderId)
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
rv = UpdateKeywordsHashForRemovedBookmark(child.id);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
}
|
||||
|
||||
rv = transaction.Commit();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Call observers in reverse order to serve children before their parent.
|
||||
for (int32_t i = folderChildrenArray.Length() - 1; i >= 0; --i) {
|
||||
BookmarkData& child = folderChildrenArray[i];
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
if (child.type == TYPE_BOOKMARK) {
|
||||
// A broken url should not interrupt the removal process.
|
||||
(void)NS_NewURI(getter_AddRefs(uri), child.url);
|
||||
rv = NS_NewURI(getter_AddRefs(uri), child.url);
|
||||
if (NS_SUCCEEDED(rv)) {
|
||||
rv = UpdateKeywordsForRemovedBookmark(child);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
}
|
||||
|
||||
NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
|
||||
@@ -2264,39 +2239,69 @@ nsNavBookmarks::SetItemIndex(int64_t aItemId, int32_t aNewIndex)
|
||||
|
||||
|
||||
nsresult
|
||||
nsNavBookmarks::UpdateKeywordsHashForRemovedBookmark(int64_t aItemId)
|
||||
nsNavBookmarks::UpdateKeywordsForRemovedBookmark(const BookmarkData& aBookmark)
|
||||
{
|
||||
nsAutoString keyword;
|
||||
if (NS_SUCCEEDED(GetKeywordForBookmark(aItemId, keyword)) &&
|
||||
!keyword.IsEmpty()) {
|
||||
nsresult rv = EnsureKeywordsHash();
|
||||
// If there are no keywords for this URI, there's nothing to do.
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
nsresult rv = NS_NewURI(getter_AddRefs(uri), aBookmark.url);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsTArray<nsString> keywords;
|
||||
{
|
||||
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
|
||||
"SELECT keyword FROM moz_keywords WHERE place_id = :place_id "
|
||||
);
|
||||
NS_ENSURE_STATE(stmt);
|
||||
mozStorageStatementScoper scoper(stmt);
|
||||
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("place_id"), aBookmark.placeId);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
mBookmarkToKeywordHash.Remove(aItemId);
|
||||
|
||||
// If the keyword is unused, remove it from the database.
|
||||
keywordSearchData searchData;
|
||||
searchData.keyword.Assign(keyword);
|
||||
searchData.itemId = -1;
|
||||
mBookmarkToKeywordHash.EnumerateRead(SearchBookmarkForKeyword, &searchData);
|
||||
if (searchData.itemId == -1) {
|
||||
nsCOMPtr<mozIStorageAsyncStatement> stmt = mDB->GetAsyncStatement(
|
||||
"DELETE FROM moz_keywords "
|
||||
"WHERE keyword = :keyword "
|
||||
"AND NOT EXISTS ( "
|
||||
"SELECT id "
|
||||
"FROM moz_bookmarks "
|
||||
"WHERE keyword_id = moz_keywords.id "
|
||||
")"
|
||||
);
|
||||
NS_ENSURE_STATE(stmt);
|
||||
|
||||
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCOMPtr<mozIStoragePendingStatement> pendingStmt;
|
||||
rv = stmt->ExecuteAsync(nullptr, getter_AddRefs(pendingStmt));
|
||||
bool hasMore;
|
||||
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
|
||||
nsAutoString keyword;
|
||||
rv = stmt->GetString(0, keyword);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
keywords.AppendElement(keyword);
|
||||
}
|
||||
}
|
||||
|
||||
if (keywords.Length() == 0) {
|
||||
// This uri has no keywords associated, so there's nothing to do.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// If the uri is not bookmarked anymore, we can remove its keywords.
|
||||
nsTArray<BookmarkData> bookmarks;
|
||||
rv = GetBookmarksForURI(uri, bookmarks);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
if (bookmarks.Length() == 0) {
|
||||
for (uint32_t i = 0; i < keywords.Length(); ++i) {
|
||||
nsString keyword = keywords[i];
|
||||
|
||||
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
|
||||
"DELETE FROM moz_keywords WHERE keyword = :keyword "
|
||||
);
|
||||
NS_ENSURE_STATE(stmt);
|
||||
mozStorageStatementScoper scoper(stmt);
|
||||
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = stmt->Execute();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
|
||||
nsINavBookmarkObserver,
|
||||
OnItemChanged(aBookmark.id,
|
||||
NS_LITERAL_CSTRING("keyword"),
|
||||
false,
|
||||
EmptyCString(),
|
||||
aBookmark.lastModified,
|
||||
TYPE_BOOKMARK,
|
||||
aBookmark.parentId,
|
||||
aBookmark.guid,
|
||||
aBookmark.parentGuid));
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@@ -2311,121 +2316,163 @@ nsNavBookmarks::SetKeywordForBookmark(int64_t aBookmarkId,
|
||||
BookmarkData bookmark;
|
||||
nsresult rv = FetchItemInfo(aBookmarkId, bookmark);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = EnsureKeywordsHash();
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
rv = NS_NewURI(getter_AddRefs(uri), bookmark.url);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Shortcuts are always lowercased internally.
|
||||
nsAutoString keyword(aUserCasedKeyword);
|
||||
ToLowerCase(keyword);
|
||||
|
||||
// Check if bookmark was already associated to a keyword.
|
||||
nsAutoString oldKeyword;
|
||||
rv = GetKeywordForBookmark(bookmark.id, oldKeyword);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
// The same URI can be associated to more than one keyword, provided the post
|
||||
// data differs. Check if there are already keywords associated to this uri.
|
||||
nsTArray<nsString> oldKeywords;
|
||||
{
|
||||
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
|
||||
"SELECT keyword FROM moz_keywords WHERE place_id = :place_id"
|
||||
);
|
||||
NS_ENSURE_STATE(stmt);
|
||||
mozStorageStatementScoper scoper(stmt);
|
||||
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("place_id"), bookmark.placeId);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Trying to set the same value or to remove a nonexistent keyword is a no-op.
|
||||
if (keyword.Equals(oldKeyword) || (keyword.IsEmpty() && oldKeyword.IsEmpty()))
|
||||
bool hasMore;
|
||||
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
|
||||
nsString oldKeyword;
|
||||
rv = stmt->GetString(0, oldKeyword);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
oldKeywords.AppendElement(oldKeyword);
|
||||
}
|
||||
}
|
||||
|
||||
// Trying to remove a non-existent keyword is a no-op.
|
||||
if (keyword.IsEmpty() && oldKeywords.Length() == 0) {
|
||||
return NS_OK;
|
||||
|
||||
mozStorageTransaction transaction(mDB->MainConn(), false);
|
||||
|
||||
nsCOMPtr<mozIStorageStatement> updateBookmarkStmt = mDB->GetStatement(
|
||||
"UPDATE moz_bookmarks "
|
||||
"SET keyword_id = (SELECT id FROM moz_keywords WHERE keyword = :keyword), "
|
||||
"lastModified = :date "
|
||||
"WHERE id = :item_id "
|
||||
);
|
||||
NS_ENSURE_STATE(updateBookmarkStmt);
|
||||
mozStorageStatementScoper updateBookmarkScoper(updateBookmarkStmt);
|
||||
}
|
||||
|
||||
if (keyword.IsEmpty()) {
|
||||
// Remove keyword association from the hash.
|
||||
mBookmarkToKeywordHash.Remove(bookmark.id);
|
||||
rv = updateBookmarkStmt->BindNullByName(NS_LITERAL_CSTRING("keyword"));
|
||||
// We are removing the existing keywords.
|
||||
for (uint32_t i = 0; i < oldKeywords.Length(); ++i) {
|
||||
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
|
||||
"DELETE FROM moz_keywords WHERE keyword = :old_keyword"
|
||||
);
|
||||
NS_ENSURE_STATE(stmt);
|
||||
mozStorageStatementScoper scoper(stmt);
|
||||
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("old_keyword"),
|
||||
oldKeywords[i]);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = stmt->Execute();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
|
||||
nsTArray<BookmarkData> bookmarks;
|
||||
rv = GetBookmarksForURI(uri, bookmarks);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
|
||||
NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
|
||||
nsINavBookmarkObserver,
|
||||
OnItemChanged(bookmarks[i].id,
|
||||
NS_LITERAL_CSTRING("keyword"),
|
||||
false,
|
||||
EmptyCString(),
|
||||
bookmarks[i].lastModified,
|
||||
TYPE_BOOKMARK,
|
||||
bookmarks[i].parentId,
|
||||
bookmarks[i].guid,
|
||||
bookmarks[i].parentGuid));
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
else {
|
||||
// We are associating bookmark to a new keyword. Create a new keyword
|
||||
// record if needed.
|
||||
nsCOMPtr<mozIStorageStatement> newKeywordStmt = mDB->GetStatement(
|
||||
"INSERT OR IGNORE INTO moz_keywords (keyword) VALUES (:keyword)"
|
||||
|
||||
// A keyword can only be associated to a single URI. Check if the requested
|
||||
// keyword was already associated, in such a case we will need to notify about
|
||||
// the change.
|
||||
nsCOMPtr<nsIURI> oldUri;
|
||||
{
|
||||
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
|
||||
"SELECT url "
|
||||
"FROM moz_keywords "
|
||||
"JOIN moz_places h ON h.id = place_id "
|
||||
"WHERE keyword = :keyword"
|
||||
);
|
||||
NS_ENSURE_STATE(newKeywordStmt);
|
||||
mozStorageStatementScoper newKeywordScoper(newKeywordStmt);
|
||||
|
||||
rv = newKeywordStmt->BindStringByName(NS_LITERAL_CSTRING("keyword"),
|
||||
keyword);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = newKeywordStmt->Execute();
|
||||
NS_ENSURE_STATE(stmt);
|
||||
mozStorageStatementScoper scoper(stmt);
|
||||
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Add new keyword association to the hash, removing the old one if needed.
|
||||
if (!oldKeyword.IsEmpty())
|
||||
mBookmarkToKeywordHash.Remove(bookmark.id);
|
||||
mBookmarkToKeywordHash.Put(bookmark.id, keyword);
|
||||
rv = updateBookmarkStmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword);
|
||||
bool hasMore;
|
||||
if (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
|
||||
nsAutoCString spec;
|
||||
rv = stmt->GetUTF8String(0, spec);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = NS_NewURI(getter_AddRefs(oldUri), spec);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
}
|
||||
}
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
bookmark.lastModified = RoundedPRNow();
|
||||
rv = updateBookmarkStmt->BindInt64ByName(NS_LITERAL_CSTRING("date"),
|
||||
bookmark.lastModified);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = updateBookmarkStmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"),
|
||||
bookmark.id);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = updateBookmarkStmt->Execute();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
rv = transaction.Commit();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
// If another uri is using the new keyword, we must update the keyword entry.
|
||||
// Note we cannot use INSERT OR REPLACE cause it wouldn't invoke the delete
|
||||
// trigger.
|
||||
nsCOMPtr<mozIStorageStatement> stmt;
|
||||
if (oldUri) {
|
||||
// In both cases, notify about the change.
|
||||
nsTArray<BookmarkData> bookmarks;
|
||||
rv = GetBookmarksForURI(oldUri, bookmarks);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
|
||||
NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
|
||||
nsINavBookmarkObserver,
|
||||
OnItemChanged(bookmarks[i].id,
|
||||
NS_LITERAL_CSTRING("keyword"),
|
||||
false,
|
||||
EmptyCString(),
|
||||
bookmarks[i].lastModified,
|
||||
TYPE_BOOKMARK,
|
||||
bookmarks[i].parentId,
|
||||
bookmarks[i].guid,
|
||||
bookmarks[i].parentGuid));
|
||||
}
|
||||
|
||||
NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
|
||||
nsINavBookmarkObserver,
|
||||
OnItemChanged(bookmark.id,
|
||||
NS_LITERAL_CSTRING("keyword"),
|
||||
false,
|
||||
NS_ConvertUTF16toUTF8(keyword),
|
||||
bookmark.lastModified,
|
||||
bookmark.type,
|
||||
bookmark.parentId,
|
||||
bookmark.guid,
|
||||
bookmark.parentGuid));
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
NS_IMETHODIMP
|
||||
nsNavBookmarks::GetKeywordForURI(nsIURI* aURI, nsAString& aKeyword)
|
||||
{
|
||||
PLACES_WARN_DEPRECATED();
|
||||
|
||||
NS_ENSURE_ARG(aURI);
|
||||
aKeyword.Truncate(0);
|
||||
|
||||
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(
|
||||
"SELECT k.keyword "
|
||||
"FROM moz_places h "
|
||||
"JOIN moz_bookmarks b ON b.fk = h.id "
|
||||
"JOIN moz_keywords k ON k.id = b.keyword_id "
|
||||
"WHERE h.url = :page_url "
|
||||
);
|
||||
stmt = mDB->GetStatement(
|
||||
"UPDATE moz_keywords SET place_id = :place_id WHERE keyword = :keyword"
|
||||
);
|
||||
NS_ENSURE_STATE(stmt);
|
||||
}
|
||||
else {
|
||||
stmt = mDB->GetStatement(
|
||||
"INSERT INTO moz_keywords (keyword, place_id) "
|
||||
"VALUES (:keyword, :place_id)"
|
||||
);
|
||||
}
|
||||
NS_ENSURE_STATE(stmt);
|
||||
mozStorageStatementScoper scoper(stmt);
|
||||
|
||||
nsresult rv = URIBinder::Bind(stmt, NS_LITERAL_CSTRING("page_url"), aURI);
|
||||
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("place_id"), bookmark.placeId);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = stmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = stmt->Execute();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
bool hasMore = false;
|
||||
rv = stmt->ExecuteStep(&hasMore);
|
||||
if (NS_FAILED(rv) || !hasMore) {
|
||||
aKeyword.SetIsVoid(true);
|
||||
return NS_OK; // not found: return void keyword string
|
||||
// In both cases, notify about the change.
|
||||
nsTArray<BookmarkData> bookmarks;
|
||||
rv = GetBookmarksForURI(uri, bookmarks);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
for (uint32_t i = 0; i < bookmarks.Length(); ++i) {
|
||||
NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
|
||||
nsINavBookmarkObserver,
|
||||
OnItemChanged(bookmarks[i].id,
|
||||
NS_LITERAL_CSTRING("keyword"),
|
||||
false,
|
||||
NS_ConvertUTF16toUTF8(keyword),
|
||||
bookmarks[i].lastModified,
|
||||
TYPE_BOOKMARK,
|
||||
bookmarks[i].parentId,
|
||||
bookmarks[i].guid,
|
||||
bookmarks[i].parentGuid));
|
||||
}
|
||||
|
||||
// found, get the keyword
|
||||
rv = stmt->GetString(0, aKeyword);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@@ -2436,17 +2483,34 @@ nsNavBookmarks::GetKeywordForBookmark(int64_t aBookmarkId, nsAString& aKeyword)
|
||||
NS_ENSURE_ARG_MIN(aBookmarkId, 1);
|
||||
aKeyword.Truncate(0);
|
||||
|
||||
nsresult rv = EnsureKeywordsHash();
|
||||
// We can have multiple keywords for the same uri, here we'll just return the
|
||||
// last created one.
|
||||
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING(
|
||||
"SELECT k.keyword "
|
||||
"FROM moz_bookmarks b "
|
||||
"JOIN moz_keywords k ON k.place_id = b.fk "
|
||||
"WHERE b.id = :item_id "
|
||||
"ORDER BY k.ROWID DESC "
|
||||
"LIMIT 1"
|
||||
));
|
||||
NS_ENSURE_STATE(stmt);
|
||||
mozStorageStatementScoper scoper(stmt);
|
||||
|
||||
nsresult rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"),
|
||||
aBookmarkId);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsAutoString keyword;
|
||||
if (!mBookmarkToKeywordHash.Get(aBookmarkId, &keyword)) {
|
||||
aKeyword.SetIsVoid(true);
|
||||
}
|
||||
else {
|
||||
aKeyword.Assign(keyword);
|
||||
bool hasMore;
|
||||
if (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
|
||||
nsAutoString keyword;
|
||||
rv = stmt->GetString(0, keyword);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
aKeyword = keyword;
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
aKeyword.SetIsVoid(true);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
@@ -2463,51 +2527,27 @@ nsNavBookmarks::GetURIForKeyword(const nsAString& aUserCasedKeyword,
|
||||
nsAutoString keyword(aUserCasedKeyword);
|
||||
ToLowerCase(keyword);
|
||||
|
||||
nsresult rv = EnsureKeywordsHash();
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsCOMPtr<mozIStorageStatement> stmt = mDB->GetStatement(NS_LITERAL_CSTRING(
|
||||
"SELECT h.url "
|
||||
"FROM moz_places h "
|
||||
"JOIN moz_keywords k ON k.place_id = h.id "
|
||||
"WHERE k.keyword = :keyword"
|
||||
));
|
||||
NS_ENSURE_STATE(stmt);
|
||||
mozStorageStatementScoper scoper(stmt);
|
||||
|
||||
keywordSearchData searchData;
|
||||
searchData.keyword.Assign(keyword);
|
||||
searchData.itemId = -1;
|
||||
mBookmarkToKeywordHash.EnumerateRead(SearchBookmarkForKeyword, &searchData);
|
||||
|
||||
if (searchData.itemId == -1) {
|
||||
// Not found.
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
rv = GetBookmarkURI(searchData.itemId, aURI);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
|
||||
nsresult
|
||||
nsNavBookmarks::EnsureKeywordsHash() {
|
||||
if (mBookmarkToKeywordHashInitialized) {
|
||||
return NS_OK;
|
||||
}
|
||||
mBookmarkToKeywordHashInitialized = true;
|
||||
|
||||
nsCOMPtr<mozIStorageStatement> stmt;
|
||||
nsresult rv = mDB->MainConn()->CreateStatement(NS_LITERAL_CSTRING(
|
||||
"SELECT b.id, k.keyword "
|
||||
"FROM moz_bookmarks b "
|
||||
"JOIN moz_keywords k ON k.id = b.keyword_id "
|
||||
), getter_AddRefs(stmt));
|
||||
nsresult rv = stmt->BindStringByName(NS_LITERAL_CSTRING("keyword"), keyword);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
bool hasMore;
|
||||
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
|
||||
int64_t itemId;
|
||||
rv = stmt->GetInt64(0, &itemId);
|
||||
if (NS_SUCCEEDED(stmt->ExecuteStep(&hasMore)) && hasMore) {
|
||||
nsAutoCString spec;
|
||||
rv = stmt->GetUTF8String(0, spec);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
nsAutoString keyword;
|
||||
rv = stmt->GetString(1, keyword);
|
||||
nsCOMPtr<nsIURI> uri;
|
||||
rv = NS_NewURI(getter_AddRefs(uri), spec);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
mBookmarkToKeywordHash.Put(itemId, keyword);
|
||||
uri.forget(aURI);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
|
||||
@@ -420,21 +420,13 @@ private:
|
||||
// Note: this is only tracking bookmarks batches, not history ones.
|
||||
bool mBatching;
|
||||
|
||||
/**
|
||||
* Always call EnsureKeywordsHash() and check it for errors before actually
|
||||
* using the hash. Internal keyword methods are already doing that.
|
||||
*/
|
||||
nsresult EnsureKeywordsHash();
|
||||
nsDataHashtable<nsTrimInt64HashKey, nsString> mBookmarkToKeywordHash;
|
||||
bool mBookmarkToKeywordHashInitialized;
|
||||
|
||||
/**
|
||||
* This function must be called every time a bookmark is removed.
|
||||
*
|
||||
* @param aURI
|
||||
* Uri to test.
|
||||
*/
|
||||
nsresult UpdateKeywordsHashForRemovedBookmark(int64_t aItemId);
|
||||
nsresult UpdateKeywordsForRemovedBookmark(const BookmarkData& aBookmark);
|
||||
};
|
||||
|
||||
#endif // nsNavBookmarks_h_
|
||||
|
||||
@@ -414,24 +414,20 @@ function nsPlacesAutoComplete()
|
||||
XPCOMUtils.defineLazyGetter(this, "_keywordQuery", function() {
|
||||
return this._db.createAsyncStatement(
|
||||
`/* do not warn (bug 487787) */
|
||||
SELECT
|
||||
(SELECT REPLACE(url, '%s', :query_string) FROM moz_places WHERE id = b.fk)
|
||||
AS search_url, h.title,
|
||||
SELECT REPLACE(h.url, '%s', :query_string) AS search_url, h.title,
|
||||
IFNULL(f.url, (SELECT f.url
|
||||
FROM moz_places
|
||||
JOIN moz_favicons f ON f.id = favicon_id
|
||||
WHERE rev_host = (SELECT rev_host FROM moz_places WHERE id = b.fk)
|
||||
WHERE rev_host = h.rev_host
|
||||
ORDER BY frecency DESC
|
||||
LIMIT 1)
|
||||
), 1, b.title, NULL, h.visit_count, h.typed, IFNULL(h.id, b.fk),
|
||||
), 1, NULL, NULL, h.visit_count, h.typed, h.id,
|
||||
:query_type, t.open_count
|
||||
FROM moz_keywords k
|
||||
JOIN moz_bookmarks b ON b.keyword_id = k.id
|
||||
LEFT JOIN moz_places h ON h.url = search_url
|
||||
JOIN moz_places h ON k.place_id = h.id
|
||||
LEFT JOIN moz_favicons f ON f.id = h.favicon_id
|
||||
LEFT JOIN moz_openpages_temp t ON t.url = search_url
|
||||
WHERE LOWER(k.keyword) = LOWER(:keyword)
|
||||
ORDER BY h.frecency DESC`
|
||||
WHERE k.keyword = LOWER(:keyword)`
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -121,4 +121,13 @@
|
||||
"guid_uniqueindex", "moz_favicons", "guid", "UNIQUE" \
|
||||
)
|
||||
|
||||
/**
|
||||
* moz_keywords
|
||||
*/
|
||||
|
||||
#define CREATE_IDX_MOZ_KEYWORDS_PLACEPOSTDATA \
|
||||
CREATE_PLACES_IDX( \
|
||||
"placepostdata_uniqueindex", "moz_keywords", "place_id, post_data", "UNIQUE" \
|
||||
)
|
||||
|
||||
#endif // nsPlacesIndexes_h__
|
||||
|
||||
@@ -121,6 +121,8 @@
|
||||
"CREATE TABLE moz_keywords (" \
|
||||
" id INTEGER PRIMARY KEY AUTOINCREMENT" \
|
||||
", keyword TEXT UNIQUE" \
|
||||
", place_id INTEGER" \
|
||||
", post_data TEXT" \
|
||||
")" \
|
||||
)
|
||||
|
||||
|
||||
@@ -175,7 +175,7 @@
|
||||
"END" \
|
||||
)
|
||||
|
||||
#define CREATE_FOREIGNCOUNT_AFTERDELETE_TRIGGER NS_LITERAL_CSTRING( \
|
||||
#define CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERDELETE_TRIGGER NS_LITERAL_CSTRING( \
|
||||
"CREATE TEMP TRIGGER moz_bookmarks_foreign_count_afterdelete_trigger " \
|
||||
"AFTER DELETE ON moz_bookmarks FOR EACH ROW " \
|
||||
"BEGIN " \
|
||||
@@ -185,7 +185,7 @@
|
||||
"END" \
|
||||
)
|
||||
|
||||
#define CREATE_FOREIGNCOUNT_AFTERINSERT_TRIGGER NS_LITERAL_CSTRING( \
|
||||
#define CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERINSERT_TRIGGER NS_LITERAL_CSTRING( \
|
||||
"CREATE TEMP TRIGGER moz_bookmarks_foreign_count_afterinsert_trigger " \
|
||||
"AFTER INSERT ON moz_bookmarks FOR EACH ROW " \
|
||||
"BEGIN " \
|
||||
@@ -195,7 +195,7 @@
|
||||
"END" \
|
||||
)
|
||||
|
||||
#define CREATE_FOREIGNCOUNT_AFTERUPDATE_TRIGGER NS_LITERAL_CSTRING( \
|
||||
#define CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERUPDATE_TRIGGER NS_LITERAL_CSTRING( \
|
||||
"CREATE TEMP TRIGGER moz_bookmarks_foreign_count_afterupdate_trigger " \
|
||||
"AFTER UPDATE OF fk ON moz_bookmarks FOR EACH ROW " \
|
||||
"BEGIN " \
|
||||
@@ -207,4 +207,38 @@
|
||||
"WHERE id = OLD.fk;" \
|
||||
"END" \
|
||||
)
|
||||
|
||||
#define CREATE_KEYWORDS_FOREIGNCOUNT_AFTERDELETE_TRIGGER NS_LITERAL_CSTRING( \
|
||||
"CREATE TEMP TRIGGER moz_keywords_foreign_count_afterdelete_trigger " \
|
||||
"AFTER DELETE ON moz_keywords FOR EACH ROW " \
|
||||
"BEGIN " \
|
||||
"UPDATE moz_places " \
|
||||
"SET foreign_count = foreign_count - 1 " \
|
||||
"WHERE id = OLD.place_id;" \
|
||||
"END" \
|
||||
)
|
||||
|
||||
#define CREATE_KEYWORDS_FOREIGNCOUNT_AFTERINSERT_TRIGGER NS_LITERAL_CSTRING( \
|
||||
"CREATE TEMP TRIGGER moz_keyords_foreign_count_afterinsert_trigger " \
|
||||
"AFTER INSERT ON moz_keywords FOR EACH ROW " \
|
||||
"BEGIN " \
|
||||
"UPDATE moz_places " \
|
||||
"SET foreign_count = foreign_count + 1 " \
|
||||
"WHERE id = NEW.place_id;" \
|
||||
"END" \
|
||||
)
|
||||
|
||||
#define CREATE_KEYWORDS_FOREIGNCOUNT_AFTERUPDATE_TRIGGER NS_LITERAL_CSTRING( \
|
||||
"CREATE TEMP TRIGGER moz_keywords_foreign_count_afterupdate_trigger " \
|
||||
"AFTER UPDATE OF place_id ON moz_keywords FOR EACH ROW " \
|
||||
"BEGIN " \
|
||||
"UPDATE moz_places " \
|
||||
"SET foreign_count = foreign_count + 1 " \
|
||||
"WHERE id = NEW.place_id; " \
|
||||
"UPDATE moz_places " \
|
||||
"SET foreign_count = foreign_count - 1 " \
|
||||
"WHERE id = OLD.place_id; " \
|
||||
"END" \
|
||||
)
|
||||
|
||||
#endif // __nsPlacesTriggers_h__
|
||||
|
||||
@@ -43,13 +43,13 @@ let kTitles = [
|
||||
// Add the keyword bookmark
|
||||
addPageBook(0, 0, 1, [], keyKey);
|
||||
// Add in the "fake pages" for keyword searches
|
||||
gPages[1] = [1,1];
|
||||
gPages[2] = [2,1];
|
||||
gPages[3] = [3,1];
|
||||
gPages[4] = [4,1];
|
||||
gPages[1] = [1,0];
|
||||
gPages[2] = [2,0];
|
||||
gPages[3] = [3,0];
|
||||
gPages[4] = [4,0];
|
||||
// Add a page into history
|
||||
addPageBook(5, 0);
|
||||
gPages[6] = [6,1];
|
||||
gPages[6] = [6,0];
|
||||
|
||||
// Provide for each test: description; search terms; array of gPages indices of
|
||||
// pages that should match; optional function to be run before the test
|
||||
@@ -68,14 +68,4 @@ let gTests = [
|
||||
keyKey, [6]],
|
||||
["6: Keyword without query (with space)",
|
||||
keyKey + " ", [6]],
|
||||
|
||||
// This adds a second keyword so anything after this will match 2 keywords
|
||||
["7: Two keywords matched",
|
||||
keyKey + " twoKey", [8,9],
|
||||
function() {
|
||||
// Add the keyword search as well as search results
|
||||
addPageBook(7, 0, 1, [], keyKey);
|
||||
gPages[8] = [8,1];
|
||||
gPages[9] = [9,1];
|
||||
}]
|
||||
];
|
||||
|
||||
@@ -1,169 +1,307 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
const URI1 = NetUtil.newURI("http://test1.mozilla.org/");
|
||||
const URI2 = NetUtil.newURI("http://test2.mozilla.org/");
|
||||
const URI3 = NetUtil.newURI("http://test3.mozilla.org/");
|
||||
|
||||
function check_bookmark_keyword(aItemId, aKeyword)
|
||||
{
|
||||
let keyword = aKeyword ? aKeyword.toLowerCase() : null;
|
||||
do_check_eq(PlacesUtils.bookmarks.getKeywordForBookmark(aItemId),
|
||||
keyword);
|
||||
}
|
||||
|
||||
function check_uri_keyword(aURI, aKeyword)
|
||||
{
|
||||
let keyword = aKeyword ? aKeyword.toLowerCase() : null;
|
||||
function check_keyword(aURI, aKeyword) {
|
||||
if (aKeyword)
|
||||
aKeyword = aKeyword.toLowerCase();
|
||||
|
||||
for (let bm of PlacesUtils.getBookmarksForURI(aURI)) {
|
||||
let kid = PlacesUtils.bookmarks.getKeywordForBookmark(bm);
|
||||
if (kid && !keyword) {
|
||||
Assert.ok(false, `${aURI.spec} should not have a keyword`);
|
||||
} else if (keyword && kid == keyword) {
|
||||
Assert.equal(kid, keyword, "Found the keyword");
|
||||
break;
|
||||
let keyword = PlacesUtils.bookmarks.getKeywordForBookmark(bm);
|
||||
if (keyword && !aKeyword) {
|
||||
throw(`${aURI.spec} should not have a keyword`);
|
||||
} else if (aKeyword && keyword == aKeyword) {
|
||||
Assert.equal(keyword, aKeyword);
|
||||
}
|
||||
}
|
||||
|
||||
if (aKeyword) {
|
||||
// This API can't tell which uri the user wants, so it returns a random one.
|
||||
let re = /http:\/\/test[0-9]\.mozilla\.org/;
|
||||
let url = PlacesUtils.bookmarks.getURIForKeyword(aKeyword).spec;
|
||||
do_check_true(re.test(url));
|
||||
let uri = PlacesUtils.bookmarks.getURIForKeyword(aKeyword);
|
||||
Assert.equal(uri.spec, aURI.spec);
|
||||
// Check case insensitivity.
|
||||
url = PlacesUtils.bookmarks.getURIForKeyword(aKeyword.toUpperCase()).spec
|
||||
do_check_true(re.test(url));
|
||||
uri = PlacesUtils.bookmarks.getURIForKeyword(aKeyword.toUpperCase());
|
||||
Assert.equal(uri.spec, aURI.spec);
|
||||
}
|
||||
}
|
||||
|
||||
function check_orphans()
|
||||
{
|
||||
let stmt = DBConn().createStatement(
|
||||
`SELECT id FROM moz_keywords k WHERE NOT EXISTS (
|
||||
SELECT id FROM moz_bookmarks WHERE keyword_id = k.id
|
||||
)`
|
||||
);
|
||||
try {
|
||||
do_check_false(stmt.executeStep());
|
||||
} finally {
|
||||
stmt.finalize();
|
||||
}
|
||||
|
||||
print("Check there are no orphan database entries");
|
||||
stmt = DBConn().createStatement(
|
||||
`SELECT b.id FROM moz_bookmarks b
|
||||
LEFT JOIN moz_keywords k ON b.keyword_id = k.id
|
||||
WHERE keyword_id NOTNULL AND k.id ISNULL`
|
||||
);
|
||||
try {
|
||||
do_check_false(stmt.executeStep());
|
||||
} finally {
|
||||
stmt.finalize();
|
||||
}
|
||||
function check_orphans() {
|
||||
let db = yield PlacesUtils.promiseDBConnection();
|
||||
let rows = yield db.executeCached(
|
||||
`SELECT id FROM moz_keywords k
|
||||
WHERE NOT EXISTS (SELECT 1 FROM moz_places WHERE id = k.place_id)
|
||||
`);
|
||||
Assert.equal(rows.length, 0);
|
||||
}
|
||||
|
||||
const URIS = [
|
||||
uri("http://test1.mozilla.org/"),
|
||||
uri("http://test2.mozilla.org/"),
|
||||
];
|
||||
function expectNotifications() {
|
||||
let notifications = [];
|
||||
let observer = new Proxy(NavBookmarkObserver, {
|
||||
get(target, name) {
|
||||
if (name == "check") {
|
||||
PlacesUtils.bookmarks.removeObserver(observer);
|
||||
return expectedNotifications =>
|
||||
Assert.deepEqual(notifications, expectedNotifications);
|
||||
}
|
||||
|
||||
add_test(function test_addBookmarkWithKeyword()
|
||||
{
|
||||
check_uri_keyword(URIS[0], null);
|
||||
if (name.startsWith("onItemChanged")) {
|
||||
return (id, prop, isAnno, val, lastMod, itemType, parentId, guid, parentGuid) => {
|
||||
if (prop != "keyword")
|
||||
return;
|
||||
let args = Array.from(arguments, arg => {
|
||||
if (arg && arg instanceof Ci.nsIURI)
|
||||
return new URL(arg.spec);
|
||||
if (arg && typeof(arg) == "number" && arg >= Date.now() * 1000)
|
||||
return new Date(parseInt(arg/1000));
|
||||
return arg;
|
||||
});
|
||||
notifications.push({ name: name, arguments: args });
|
||||
}
|
||||
}
|
||||
|
||||
return target[name];
|
||||
}
|
||||
});
|
||||
PlacesUtils.bookmarks.addObserver(observer, false);
|
||||
return observer;
|
||||
}
|
||||
|
||||
add_task(function test_invalid_input() {
|
||||
Assert.throws(() => PlacesUtils.bookmarks.getURIForKeyword(null),
|
||||
/NS_ERROR_ILLEGAL_VALUE/);
|
||||
Assert.throws(() => PlacesUtils.bookmarks.getURIForKeyword(""),
|
||||
/NS_ERROR_ILLEGAL_VALUE/);
|
||||
Assert.throws(() => PlacesUtils.bookmarks.getKeywordForBookmark(null),
|
||||
/NS_ERROR_ILLEGAL_VALUE/);
|
||||
Assert.throws(() => PlacesUtils.bookmarks.getKeywordForBookmark(0),
|
||||
/NS_ERROR_ILLEGAL_VALUE/);
|
||||
Assert.throws(() => PlacesUtils.bookmarks.setKeywordForBookmark(null, "k"),
|
||||
/NS_ERROR_ILLEGAL_VALUE/);
|
||||
Assert.throws(() => PlacesUtils.bookmarks.setKeywordForBookmark(0, "k"),
|
||||
/NS_ERROR_ILLEGAL_VALUE/);
|
||||
});
|
||||
|
||||
add_task(function test_addBookmarkAndKeyword() {
|
||||
check_keyword(URI1, null);
|
||||
let fc = yield foreign_count(URI1);
|
||||
let observer = expectNotifications();
|
||||
|
||||
let itemId =
|
||||
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
|
||||
URIS[0],
|
||||
URI1,
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
"test");
|
||||
|
||||
PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "keyword");
|
||||
check_bookmark_keyword(itemId, "keyword");
|
||||
check_uri_keyword(URIS[0], "keyword");
|
||||
let bookmark = yield PlacesUtils.bookmarks.fetch({ url: URI1 });
|
||||
observer.check([ { name: "onItemChanged",
|
||||
arguments: [ itemId, "keyword", false, "keyword",
|
||||
bookmark.lastModified, bookmark.type,
|
||||
(yield PlacesUtils.promiseItemId(bookmark.parentGuid)),
|
||||
bookmark.guid, bookmark.parentGuid ] }
|
||||
]);
|
||||
yield PlacesTestUtils.promiseAsyncUpdates();
|
||||
|
||||
PlacesTestUtils.promiseAsyncUpdates().then(() => {
|
||||
check_orphans();
|
||||
run_next_test();
|
||||
});
|
||||
check_keyword(URI1, "keyword");
|
||||
Assert.equal((yield foreign_count(URI1)), fc + 2); // + 1 bookmark + 1 keyword
|
||||
|
||||
yield PlacesTestUtils.promiseAsyncUpdates();
|
||||
yield check_orphans();
|
||||
});
|
||||
|
||||
add_test(function test_addBookmarkToURIHavingKeyword()
|
||||
{
|
||||
add_task(function test_addBookmarkToURIHavingKeyword() {
|
||||
// The uri has already a keyword.
|
||||
check_keyword(URI1, "keyword");
|
||||
let fc = yield foreign_count(URI1);
|
||||
|
||||
let itemId =
|
||||
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
|
||||
URIS[0],
|
||||
URI1,
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
"test");
|
||||
// The uri has a keyword, but this specific bookmark has not.
|
||||
check_bookmark_keyword(itemId, null);
|
||||
check_uri_keyword(URIS[0], "keyword");
|
||||
check_keyword(URI1, "keyword");
|
||||
Assert.equal((yield foreign_count(URI1)), fc + 1); // + 1 bookmark
|
||||
|
||||
PlacesTestUtils.promiseAsyncUpdates().then(() => {
|
||||
check_orphans();
|
||||
run_next_test();
|
||||
});
|
||||
PlacesUtils.bookmarks.removeItem(itemId);
|
||||
yield PlacesTestUtils.promiseAsyncUpdates();
|
||||
check_orphans();
|
||||
});
|
||||
|
||||
add_test(function test_addSameKeywordToOtherURI()
|
||||
{
|
||||
add_task(function test_sameKeywordDifferentURI() {
|
||||
let fc1 = yield foreign_count(URI1);
|
||||
let fc2 = yield foreign_count(URI2);
|
||||
let observer = expectNotifications();
|
||||
|
||||
let itemId =
|
||||
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
|
||||
URIS[1],
|
||||
URI2,
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
"test2");
|
||||
check_bookmark_keyword(itemId, null);
|
||||
check_uri_keyword(URIS[1], null);
|
||||
check_keyword(URI1, "keyword");
|
||||
check_keyword(URI2, null);
|
||||
|
||||
PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "kEyWoRd");
|
||||
check_bookmark_keyword(itemId, "kEyWoRd");
|
||||
check_uri_keyword(URIS[1], "kEyWoRd");
|
||||
|
||||
// Check case insensitivity.
|
||||
check_uri_keyword(URIS[0], "kEyWoRd");
|
||||
check_bookmark_keyword(itemId, "keyword");
|
||||
check_uri_keyword(URIS[1], "keyword");
|
||||
check_uri_keyword(URIS[0], "keyword");
|
||||
let bookmark1 = yield PlacesUtils.bookmarks.fetch({ url: URI1 });
|
||||
let bookmark2 = yield PlacesUtils.bookmarks.fetch({ url: URI2 });
|
||||
observer.check([ { name: "onItemChanged",
|
||||
arguments: [ (yield PlacesUtils.promiseItemId(bookmark1.guid)),
|
||||
"keyword", false, "",
|
||||
bookmark1.lastModified, bookmark1.type,
|
||||
(yield PlacesUtils.promiseItemId(bookmark1.parentGuid)),
|
||||
bookmark1.guid, bookmark1.parentGuid ] },
|
||||
{ name: "onItemChanged",
|
||||
arguments: [ itemId, "keyword", false, "keyword",
|
||||
bookmark2.lastModified, bookmark2.type,
|
||||
(yield PlacesUtils.promiseItemId(bookmark2.parentGuid)),
|
||||
bookmark2.guid, bookmark2.parentGuid ] }
|
||||
]);
|
||||
yield PlacesTestUtils.promiseAsyncUpdates();
|
||||
|
||||
PlacesTestUtils.promiseAsyncUpdates().then(() => {
|
||||
check_orphans();
|
||||
run_next_test();
|
||||
});
|
||||
// The keyword should have been "moved" to the new URI.
|
||||
check_keyword(URI1, null);
|
||||
Assert.equal((yield foreign_count(URI1)), fc1 - 1); // - 1 keyword
|
||||
check_keyword(URI2, "keyword");
|
||||
Assert.equal((yield foreign_count(URI2)), fc2 + 2); // + 1 bookmark + 1 keyword
|
||||
|
||||
yield PlacesTestUtils.promiseAsyncUpdates();
|
||||
check_orphans();
|
||||
});
|
||||
|
||||
add_test(function test_removeBookmarkWithKeyword()
|
||||
{
|
||||
add_task(function test_sameURIDifferentKeyword() {
|
||||
let fc = yield foreign_count(URI2);
|
||||
let observer = expectNotifications();
|
||||
|
||||
let itemId =
|
||||
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
|
||||
URIS[1],
|
||||
URI2,
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
"test2");
|
||||
check_keyword(URI2, "keyword");
|
||||
|
||||
PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "keyword2");
|
||||
|
||||
let bookmarks = [];
|
||||
yield PlacesUtils.bookmarks.fetch({ url: URI2 }, bookmark => bookmarks.push(bookmark));
|
||||
observer.check([ { name: "onItemChanged",
|
||||
arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[0].guid)),
|
||||
"keyword", false, "keyword2",
|
||||
bookmarks[0].lastModified, bookmarks[0].type,
|
||||
(yield PlacesUtils.promiseItemId(bookmarks[0].parentGuid)),
|
||||
bookmarks[0].guid, bookmarks[0].parentGuid ] },
|
||||
{ name: "onItemChanged",
|
||||
arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[1].guid)),
|
||||
"keyword", false, "keyword2",
|
||||
bookmarks[1].lastModified, bookmarks[1].type,
|
||||
(yield PlacesUtils.promiseItemId(bookmarks[1].parentGuid)),
|
||||
bookmarks[1].guid, bookmarks[1].parentGuid ] }
|
||||
]);
|
||||
yield PlacesTestUtils.promiseAsyncUpdates();
|
||||
|
||||
check_keyword(URI2, "keyword2");
|
||||
Assert.equal((yield foreign_count(URI2)), fc + 2); // + 1 bookmark + 1 keyword
|
||||
|
||||
yield PlacesTestUtils.promiseAsyncUpdates();
|
||||
check_orphans();
|
||||
});
|
||||
|
||||
add_task(function test_removeBookmarkWithKeyword() {
|
||||
let fc = yield foreign_count(URI2);
|
||||
let itemId =
|
||||
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
|
||||
URI2,
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
"test");
|
||||
PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "keyword");
|
||||
check_bookmark_keyword(itemId, "keyword");
|
||||
check_uri_keyword(URIS[1], "keyword");
|
||||
|
||||
// The keyword should not be removed from other bookmarks.
|
||||
// The keyword should not be removed, since there are other bookmarks yet.
|
||||
PlacesUtils.bookmarks.removeItem(itemId);
|
||||
|
||||
check_keyword(URI2, "keyword2");
|
||||
Assert.equal((yield foreign_count(URI2)), fc); // + 1 bookmark - 1 bookmark
|
||||
|
||||
yield PlacesTestUtils.promiseAsyncUpdates();
|
||||
check_orphans();
|
||||
});
|
||||
|
||||
add_task(function test_unsetKeyword() {
|
||||
let fc = yield foreign_count(URI2);
|
||||
let observer = expectNotifications();
|
||||
|
||||
let itemId =
|
||||
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
|
||||
URI2,
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
"test");
|
||||
|
||||
// The keyword should be removed from any bookmark.
|
||||
PlacesUtils.bookmarks.setKeywordForBookmark(itemId, null);
|
||||
|
||||
let bookmarks = [];
|
||||
yield PlacesUtils.bookmarks.fetch({ url: URI2 }, bookmark => bookmarks.push(bookmark));
|
||||
do_print(bookmarks.length);
|
||||
observer.check([ { name: "onItemChanged",
|
||||
arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[0].guid)),
|
||||
"keyword", false, "",
|
||||
bookmarks[0].lastModified, bookmarks[0].type,
|
||||
(yield PlacesUtils.promiseItemId(bookmarks[0].parentGuid)),
|
||||
bookmarks[0].guid, bookmarks[0].parentGuid ] },
|
||||
{ name: "onItemChanged",
|
||||
arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[1].guid)),
|
||||
"keyword", false, "",
|
||||
bookmarks[1].lastModified, bookmarks[1].type,
|
||||
(yield PlacesUtils.promiseItemId(bookmarks[1].parentGuid)),
|
||||
bookmarks[1].guid, bookmarks[1].parentGuid ] },
|
||||
{ name: "onItemChanged",
|
||||
arguments: [ (yield PlacesUtils.promiseItemId(bookmarks[2].guid)),
|
||||
"keyword", false, "",
|
||||
bookmarks[2].lastModified, bookmarks[2].type,
|
||||
(yield PlacesUtils.promiseItemId(bookmarks[2].parentGuid)),
|
||||
bookmarks[2].guid, bookmarks[2].parentGuid ] }
|
||||
]);
|
||||
|
||||
check_keyword(URI1, null);
|
||||
check_keyword(URI2, null);
|
||||
Assert.equal((yield foreign_count(URI2)), fc - 1); // + 1 bookmark - 2 keyword
|
||||
|
||||
yield PlacesTestUtils.promiseAsyncUpdates();
|
||||
check_orphans();
|
||||
});
|
||||
|
||||
add_task(function test_addRemoveBookmark() {
|
||||
let fc = yield foreign_count(URI3);
|
||||
let observer = expectNotifications();
|
||||
|
||||
let itemId =
|
||||
PlacesUtils.bookmarks.insertBookmark(PlacesUtils.unfiledBookmarksFolderId,
|
||||
URI3,
|
||||
PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
"test3");
|
||||
|
||||
PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "keyword");
|
||||
let bookmark = yield PlacesUtils.bookmarks.fetch({ url: URI3 });
|
||||
let parentId = yield PlacesUtils.promiseItemId(bookmark.parentGuid);
|
||||
PlacesUtils.bookmarks.removeItem(itemId);
|
||||
|
||||
check_uri_keyword(URIS[1], "keyword");
|
||||
check_uri_keyword(URIS[0], "keyword");
|
||||
observer.check([ { name: "onItemChanged",
|
||||
arguments: [ itemId,
|
||||
"keyword", false, "keyword",
|
||||
bookmark.lastModified, bookmark.type,
|
||||
parentId,
|
||||
bookmark.guid, bookmark.parentGuid ] },
|
||||
{ name: "onItemChanged",
|
||||
arguments: [ itemId,
|
||||
"keyword", false, "",
|
||||
bookmark.lastModified, bookmark.type,
|
||||
parentId,
|
||||
bookmark.guid, bookmark.parentGuid ] }
|
||||
]);
|
||||
|
||||
PlacesTestUtils.promiseAsyncUpdates().then(() => {
|
||||
check_orphans();
|
||||
run_next_test();
|
||||
});
|
||||
check_keyword(URI3, null);
|
||||
Assert.equal((yield foreign_count(URI3)), fc);
|
||||
|
||||
yield PlacesTestUtils.promiseAsyncUpdates();
|
||||
check_orphans();
|
||||
});
|
||||
|
||||
add_test(function test_removeFolderWithKeywordedBookmarks()
|
||||
{
|
||||
// Keyword should be removed as well.
|
||||
PlacesUtils.bookmarks.removeFolderChildren(PlacesUtils.unfiledBookmarksFolderId);
|
||||
|
||||
check_uri_keyword(URIS[1], null);
|
||||
check_uri_keyword(URIS[0], null);
|
||||
|
||||
PlacesTestUtils.promiseAsyncUpdates().then(() => {
|
||||
check_orphans();
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
function run_test()
|
||||
{
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const CURRENT_SCHEMA_VERSION = 26;
|
||||
const CURRENT_SCHEMA_VERSION = 27;
|
||||
const FIRST_UPGRADABLE_SCHEMA_VERSION = 11;
|
||||
|
||||
const NS_APP_USER_PROFILE_50_DIR = "ProfD";
|
||||
@@ -851,3 +851,17 @@ function checkBookmarkObject(info) {
|
||||
Assert.ok(info.lastModified >= info.dateAdded, "lastModified should never be smaller than dateAdded");
|
||||
Assert.ok(typeof info.type == "number", "type should be a number");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads foreign_count value for a given url.
|
||||
*/
|
||||
function* foreign_count(url) {
|
||||
if (url instanceof Ci.nsIURI)
|
||||
url = url.spec;
|
||||
let db = yield PlacesUtils.promiseDBConnection();
|
||||
let rows = yield db.executeCached(
|
||||
`SELECT foreign_count FROM moz_places
|
||||
WHERE url = :url
|
||||
`, { url });
|
||||
return rows.length == 0 ? 0 : rows[0].getResultByName("foreign_count");
|
||||
}
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,75 @@
|
||||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
add_task(function* setup() {
|
||||
yield setupPlacesDatabase("places_v26.sqlite");
|
||||
// Setup database contents to be migrated.
|
||||
let path = OS.Path.join(OS.Constants.Path.profileDir, DB_FILENAME);
|
||||
let db = yield Sqlite.openConnection({ path });
|
||||
// Add pages.
|
||||
yield db.execute(`INSERT INTO moz_places (url, guid)
|
||||
VALUES ("http://test1.com/", "test1_______")
|
||||
, ("http://test2.com/", "test2_______")
|
||||
`);
|
||||
// Add keywords.
|
||||
yield db.execute(`INSERT INTO moz_keywords (keyword)
|
||||
VALUES ("kw1")
|
||||
, ("kw2")
|
||||
, ("kw3")
|
||||
`);
|
||||
// Add bookmarks.
|
||||
let now = Date.now() * 1000;
|
||||
let index = 0;
|
||||
yield db.execute(`INSERT INTO moz_bookmarks (type, fk, parent, position, dateAdded, lastModified, keyword_id, guid)
|
||||
VALUES (1, (SELECT id FROM moz_places WHERE guid = 'test1_______'), 3, ${index++}, ${now}, ${now},
|
||||
(SELECT id FROM moz_keywords WHERE keyword = 'kw1'), "bookmark1___")
|
||||
/* same uri, different keyword */
|
||||
, (1, (SELECT id FROM moz_places WHERE guid = 'test1_______'), 3, ${index++}, ${now}, ${now},
|
||||
(SELECT id FROM moz_keywords WHERE keyword = 'kw2'), "bookmark2___")
|
||||
/* different uri, same keyword as 1 */
|
||||
, (1, (SELECT id FROM moz_places WHERE guid = 'test2_______'), 3, ${index++}, ${now}, ${now},
|
||||
(SELECT id FROM moz_keywords WHERE keyword = 'kw1'), "bookmark3___")
|
||||
/* same uri, same keyword as 1 */
|
||||
, (1, (SELECT id FROM moz_places WHERE guid = 'test1_______'), 3, ${index++}, ${now}, ${now},
|
||||
(SELECT id FROM moz_keywords WHERE keyword = 'kw1'), "bookmark4___")
|
||||
/* same uri, same keyword as 2 */
|
||||
, (1, (SELECT id FROM moz_places WHERE guid = 'test2_______'), 3, ${index++}, ${now}, ${now},
|
||||
(SELECT id FROM moz_keywords WHERE keyword = 'kw2'), "bookmark5___")
|
||||
/* different uri, same keyword as 1 */
|
||||
, (1, (SELECT id FROM moz_places WHERE guid = 'test1_______'), 3, ${index++}, ${now}, ${now},
|
||||
(SELECT id FROM moz_keywords WHERE keyword = 'kw3'), "bookmark6___")
|
||||
`);
|
||||
// Add postData.
|
||||
yield db.execute(`INSERT INTO moz_anno_attributes (name)
|
||||
VALUES ("bookmarkProperties/POSTData")`);
|
||||
yield db.execute(`INSERT INTO moz_items_annos(anno_attribute_id, item_id, content)
|
||||
VALUES ((SELECT id FROM moz_anno_attributes where name = "bookmarkProperties/POSTData"),
|
||||
(SELECT id FROM moz_bookmarks WHERE guid = "bookmark3___"), "postData1")
|
||||
, ((SELECT id FROM moz_anno_attributes where name = "bookmarkProperties/POSTData"),
|
||||
(SELECT id FROM moz_bookmarks WHERE guid = "bookmark5___"), "postData2")`);
|
||||
yield db.close();
|
||||
});
|
||||
|
||||
add_task(function* database_is_valid() {
|
||||
Assert.equal(PlacesUtils.history.databaseStatus,
|
||||
PlacesUtils.history.DATABASE_STATUS_UPGRADED);
|
||||
|
||||
let db = yield PlacesUtils.promiseDBConnection();
|
||||
Assert.equal((yield db.getSchemaVersion()), CURRENT_SCHEMA_VERSION);
|
||||
});
|
||||
|
||||
add_task(function* test_keywords() {
|
||||
// When 2 urls have the same keyword, if one has postData it will be
|
||||
// preferred.
|
||||
let [ url1, postData1 ] = PlacesUtils.getURLAndPostDataForKeyword("kw1");
|
||||
Assert.equal(url1, "http://test2.com/");
|
||||
Assert.equal(postData1, "postData1");
|
||||
let [ url2, postData2 ] = PlacesUtils.getURLAndPostDataForKeyword("kw2");
|
||||
Assert.equal(url2, "http://test2.com/");
|
||||
Assert.equal(postData2, "postData2");
|
||||
let [ url3, postData3 ] = PlacesUtils.getURLAndPostDataForKeyword("kw3");
|
||||
Assert.equal(url3, "http://test1.com/");
|
||||
|
||||
Assert.equal((yield foreign_count("http://test1.com/")), 5); // 4 bookmark2 + 1 keywords
|
||||
Assert.equal((yield foreign_count("http://test2.com/")), 4); // 2 bookmark2 + 2 keywords
|
||||
});
|
||||
@@ -15,6 +15,7 @@ support-files =
|
||||
places_v24.sqlite
|
||||
places_v25.sqlite
|
||||
places_v26.sqlite
|
||||
places_v27.sqlite
|
||||
|
||||
[test_current_from_downgraded.js]
|
||||
[test_current_from_v6.js]
|
||||
@@ -22,3 +23,4 @@ support-files =
|
||||
[test_current_from_v19.js]
|
||||
[test_current_from_v24.js]
|
||||
[test_current_from_v25.js]
|
||||
[test_current_from_v26.js]
|
||||
|
||||
@@ -504,7 +504,7 @@ tests.push({
|
||||
|
||||
// if keywords are equal, should fall back to title
|
||||
{ isBookmark: true,
|
||||
uri: "http://example.com/b2",
|
||||
uri: "http://example.com/b1",
|
||||
parentFolder: PlacesUtils.bookmarks.toolbarFolder,
|
||||
index: PlacesUtils.bookmarks.DEFAULT_INDEX,
|
||||
title: "y8",
|
||||
|
||||
@@ -12,68 +12,60 @@
|
||||
* same keyword appear in the list.
|
||||
*/
|
||||
|
||||
add_task(function* test_keyword_searc() {
|
||||
let uri1 = NetUtil.newURI("http://abc/?search=%s");
|
||||
let uri2 = NetUtil.newURI("http://abc/?search=ThisPageIsInHistory");
|
||||
yield PlacesTestUtils.addVisits([
|
||||
{ uri: uri1, title: "Generic page title" },
|
||||
{ uri: uri2, title: "Generic page title" }
|
||||
]);
|
||||
addBookmark({ uri: uri1, title: "Keyword title", keyword: "key"});
|
||||
// Details for the keyword bookmark
|
||||
let keyBase = "http://abc/?search=";
|
||||
let keyKey = "key";
|
||||
|
||||
do_print("Plain keyword query");
|
||||
yield check_autocomplete({
|
||||
search: "key term",
|
||||
matches: [ { uri: NetUtil.newURI("http://abc/?search=term"), title: "Keyword title", style: ["keyword"] } ]
|
||||
});
|
||||
// A second keyword bookmark with the same keyword
|
||||
let otherBase = "http://xyz/?foo=";
|
||||
|
||||
do_print("Multi-word keyword query");
|
||||
yield check_autocomplete({
|
||||
search: "key multi word",
|
||||
matches: [ { uri: NetUtil.newURI("http://abc/?search=multi+word"), title: "Keyword title", style: ["keyword"] } ]
|
||||
});
|
||||
let unescaped = "ユニコード";
|
||||
let pageInHistory = "ThisPageIsInHistory";
|
||||
|
||||
do_print("Keyword query with +");
|
||||
yield check_autocomplete({
|
||||
search: "key blocking+",
|
||||
matches: [ { uri: NetUtil.newURI("http://abc/?search=blocking%2B"), title: "Keyword title", style: ["keyword"] } ]
|
||||
});
|
||||
// Define some shared uris and titles (each page needs its own uri)
|
||||
let kURIs = [
|
||||
keyBase + "%s",
|
||||
keyBase + "term",
|
||||
keyBase + "multi+word",
|
||||
keyBase + "blocking%2B",
|
||||
keyBase + unescaped,
|
||||
keyBase + pageInHistory,
|
||||
keyBase,
|
||||
otherBase + "%s",
|
||||
keyBase + "twoKey",
|
||||
otherBase + "twoKey"
|
||||
];
|
||||
let kTitles = [
|
||||
"Generic page title",
|
||||
"Keyword title",
|
||||
];
|
||||
|
||||
do_print("Unescaped term in query");
|
||||
yield check_autocomplete({
|
||||
search: "key ユニコード",
|
||||
matches: [ { uri: NetUtil.newURI("http://abc/?search=ユニコード"), title: "Keyword title", style: ["keyword"] } ]
|
||||
});
|
||||
// Add the keyword bookmark
|
||||
addPageBook(0, 0, 1, [], keyKey);
|
||||
// Add in the "fake pages" for keyword searches
|
||||
gPages[1] = [1,0];
|
||||
gPages[2] = [2,0];
|
||||
gPages[3] = [3,0];
|
||||
gPages[4] = [4,0];
|
||||
// Add a page into history
|
||||
addPageBook(5, 0);
|
||||
gPages[6] = [6,0];
|
||||
|
||||
do_print("Keyword that happens to match a page");
|
||||
yield check_autocomplete({
|
||||
search: "key ThisPageIsInHistory",
|
||||
matches: [ { uri: NetUtil.newURI("http://abc/?search=ThisPageIsInHistory"), title: "Generic page title", style: ["bookmark"] } ]
|
||||
});
|
||||
|
||||
do_print("Keyword without query (without space)");
|
||||
yield check_autocomplete({
|
||||
search: "key",
|
||||
matches: [ { uri: NetUtil.newURI("http://abc/?search="), title: "Keyword title", style: ["keyword"] } ]
|
||||
});
|
||||
|
||||
do_print("Keyword without query (with space)");
|
||||
yield check_autocomplete({
|
||||
search: "key ",
|
||||
matches: [ { uri: NetUtil.newURI("http://abc/?search="), title: "Keyword title", style: ["keyword"] } ]
|
||||
});
|
||||
|
||||
// This adds a second keyword so anything after this will match 2 keywords
|
||||
let uri3 = NetUtil.newURI("http://xyz/?foo=%s");
|
||||
yield PlacesTestUtils.addVisits([ { uri: uri3, title: "Generic page title" } ]);
|
||||
addBookmark({ uri: uri3, title: "Keyword title", keyword: "key", style: ["keyword"] });
|
||||
|
||||
do_print("Two keywords matched");
|
||||
yield check_autocomplete({
|
||||
search: "key twoKey",
|
||||
matches: [ { uri: NetUtil.newURI("http://abc/?search=twoKey"), title: "Keyword title", style: ["keyword"] },
|
||||
{ uri: NetUtil.newURI("http://xyz/?foo=twoKey"), title: "Keyword title", style: ["keyword"] } ]
|
||||
});
|
||||
|
||||
yield cleanup();
|
||||
});
|
||||
// Provide for each test: description; search terms; array of gPages indices of
|
||||
// pages that should match; optional function to be run before the test
|
||||
let gTests = [
|
||||
["0: Plain keyword query",
|
||||
keyKey + " term", [1]],
|
||||
["1: Multi-word keyword query",
|
||||
keyKey + " multi word", [2]],
|
||||
["2: Keyword query with +",
|
||||
keyKey + " blocking+", [3]],
|
||||
["3: Unescaped term in query",
|
||||
keyKey + " " + unescaped, [4]],
|
||||
["4: Keyword that happens to match a page",
|
||||
keyKey + " " + pageInHistory, [5]],
|
||||
["5: Keyword without query (without space)",
|
||||
keyKey, [6]],
|
||||
["6: Keyword without query (with space)",
|
||||
keyKey + " ", [6]],
|
||||
];
|
||||
|
||||
@@ -19,69 +19,57 @@ add_task(function* test_keyword_search() {
|
||||
{ uri: uri1, title: "Generic page title" },
|
||||
{ uri: uri2, title: "Generic page title" }
|
||||
]);
|
||||
addBookmark({ uri: uri1, title: "Keyword title", keyword: "key"});
|
||||
yield addBookmark({ uri: uri1, title: "Bookmark title", keyword: "key"});
|
||||
|
||||
do_print("Plain keyword query");
|
||||
yield check_autocomplete({
|
||||
search: "key term",
|
||||
searchParam: "enable-actions",
|
||||
matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=term", input: "key term"}), title: "Keyword title", style: [ "action", "keyword" ] } ]
|
||||
matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=term", input: "key term"}), title: "Generic page title", style: [ "action", "keyword" ] } ]
|
||||
});
|
||||
|
||||
do_print("Multi-word keyword query");
|
||||
yield check_autocomplete({
|
||||
search: "key multi word",
|
||||
searchParam: "enable-actions",
|
||||
matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=multi+word", input: "key multi word"}), title: "Keyword title", style: [ "action", "keyword" ] } ]
|
||||
matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=multi+word", input: "key multi word"}), title: "Generic page title", style: [ "action", "keyword" ] } ]
|
||||
});
|
||||
|
||||
do_print("Keyword query with +");
|
||||
yield check_autocomplete({
|
||||
search: "key blocking+",
|
||||
searchParam: "enable-actions",
|
||||
matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=blocking%2B", input: "key blocking+"}), title: "Keyword title", style: [ "action", "keyword" ] } ]
|
||||
matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=blocking%2B", input: "key blocking+"}), title: "Generic page title", style: [ "action", "keyword" ] } ]
|
||||
});
|
||||
|
||||
do_print("Unescaped term in query");
|
||||
yield check_autocomplete({
|
||||
search: "key ユニコード",
|
||||
search: "key ¿?¿?¿?¿?¿?",
|
||||
searchParam: "enable-actions",
|
||||
matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=ユニコード", input: "key ユニコード"}), title: "Keyword title", style: [ "action", "keyword" ] } ]
|
||||
matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=¿?¿?¿?¿?¿?", input: "key ¿?¿?¿?¿?¿?"}), title: "Generic page title", style: [ "action", "keyword" ] } ]
|
||||
});
|
||||
|
||||
do_print("Keyword that happens to match a page");
|
||||
yield check_autocomplete({
|
||||
search: "key ThisPageIsInHistory",
|
||||
searchParam: "enable-actions",
|
||||
matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=ThisPageIsInHistory", input: "key ThisPageIsInHistory"}), title: "Keyword title", style: [ "action", "keyword" ] } ]
|
||||
matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=ThisPageIsInHistory", input: "key ThisPageIsInHistory"}), title: "Generic page title", style: [ "action", "keyword" ] } ]
|
||||
});
|
||||
|
||||
do_print("Keyword without query (without space)");
|
||||
yield check_autocomplete({
|
||||
search: "key",
|
||||
searchParam: "enable-actions",
|
||||
matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=", input: "key"}), title: "Keyword title", style: [ "action", "keyword" ] } ]
|
||||
matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=", input: "key"}), title: "Generic page title", style: [ "action", "keyword" ] } ]
|
||||
});
|
||||
|
||||
do_print("Keyword without query (with space)");
|
||||
yield check_autocomplete({
|
||||
search: "key ",
|
||||
searchParam: "enable-actions",
|
||||
matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=", input: "key "}), title: "Keyword title", style: [ "action", "keyword" ] } ]
|
||||
});
|
||||
|
||||
// This adds a second keyword so anything after this will match 2 keywords
|
||||
let uri3 = NetUtil.newURI("http://xyz/?foo=%s");
|
||||
yield PlacesTestUtils.addVisits([ { uri: uri3, title: "Generic page title" } ]);
|
||||
addBookmark({ uri: uri3, title: "Keyword title", keyword: "key"});
|
||||
|
||||
do_print("Two keywords matched");
|
||||
yield check_autocomplete({
|
||||
search: "key twoKey",
|
||||
searchParam: "enable-actions",
|
||||
matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=twoKey", input: "key twoKey"}), title: "Keyword title", style: [ "action", "keyword" ] },
|
||||
{ uri: makeActionURI("keyword", {url: "http://xyz/?foo=twoKey", input: "key twoKey"}), title: "Keyword title", style: [ "action", "keyword" ] } ]
|
||||
matches: [ { uri: makeActionURI("keyword", {url: "http://abc/?search=", input: "key "}), title: "Generic page title", style: [ "action", "keyword" ] } ]
|
||||
});
|
||||
|
||||
yield cleanup();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,13 +1,3 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
const bmsvc = PlacesUtils.bookmarks;
|
||||
const testFolderId = PlacesUtils.bookmarksMenuFolderId;
|
||||
|
||||
// main
|
||||
function run_test() {
|
||||
var testURI = uri("http://foo.com");
|
||||
|
||||
@@ -16,11 +6,11 @@ function run_test() {
|
||||
2. Create a bookmark for the same URI, with a different keyword and different post data.
|
||||
3. Confirm that our method for getting a URI+postdata retains bookmark affinity.
|
||||
*/
|
||||
var bm1 = bmsvc.insertBookmark(testFolderId, testURI, -1, "blah");
|
||||
bmsvc.setKeywordForBookmark(bm1, "foo");
|
||||
var bm1 = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarksMenuFolderId, testURI, -1, "blah");
|
||||
PlacesUtils.bookmarks.setKeywordForBookmark(bm1, "foo");
|
||||
PlacesUtils.setPostDataForBookmark(bm1, "pdata1");
|
||||
var bm2 = bmsvc.insertBookmark(testFolderId, testURI, -1, "blah");
|
||||
bmsvc.setKeywordForBookmark(bm2, "bar");
|
||||
var bm2 = PlacesUtils.bookmarks.insertBookmark(PlacesUtils.bookmarksMenuFolderId, testURI, -1, "blah");
|
||||
PlacesUtils.bookmarks.setKeywordForBookmark(bm2, "bar");
|
||||
PlacesUtils.setPostDataForBookmark(bm2, "pdata2");
|
||||
|
||||
// check kw, pd for bookmark 1
|
||||
@@ -35,116 +25,6 @@ function run_test() {
|
||||
do_check_eq(postdata, "pdata2");
|
||||
|
||||
// cleanup
|
||||
bmsvc.removeItem(bm1);
|
||||
bmsvc.removeItem(bm2);
|
||||
|
||||
/*
|
||||
1. Create two bookmarks with the same URI and keyword.
|
||||
2. Confirm that the most recently created one is returned for that keyword.
|
||||
*/
|
||||
var bm1 = bmsvc.insertBookmark(testFolderId, testURI, -1, "blah");
|
||||
bmsvc.setKeywordForBookmark(bm1, "foo");
|
||||
PlacesUtils.setPostDataForBookmark(bm1, "pdata1");
|
||||
var bm2 = bmsvc.insertBookmark(testFolderId, testURI, -1, "blah");
|
||||
bmsvc.setKeywordForBookmark(bm2, "foo");
|
||||
PlacesUtils.setPostDataForBookmark(bm2, "pdata2");
|
||||
|
||||
var bm1da = bmsvc.getItemDateAdded(bm1);
|
||||
var bm1lm = bmsvc.getItemLastModified(bm1);
|
||||
LOG("bm1 dateAdded: " + bm1da + ", lastModified: " + bm1lm);
|
||||
var bm2da = bmsvc.getItemDateAdded(bm2);
|
||||
var bm2lm = bmsvc.getItemLastModified(bm2);
|
||||
LOG("bm2 dateAdded: " + bm2da + ", lastModified: " + bm2lm);
|
||||
do_check_true(bm1da <= bm2da);
|
||||
do_check_true(bm1lm <= bm2lm);
|
||||
|
||||
[url, postdata] = PlacesUtils.getURLAndPostDataForKeyword("foo");
|
||||
do_check_eq(testURI.spec, url);
|
||||
do_check_eq(postdata, "pdata2");
|
||||
|
||||
// cleanup
|
||||
bmsvc.removeItem(bm1);
|
||||
bmsvc.removeItem(bm2);
|
||||
|
||||
/*
|
||||
1. Create two bookmarks with the same URI and keyword.
|
||||
2. Modify the first-created bookmark.
|
||||
3. Confirm that the most recently modified one is returned for that keyword.
|
||||
*/
|
||||
var bm1 = bmsvc.insertBookmark(testFolderId, testURI, -1, "blah");
|
||||
bmsvc.setKeywordForBookmark(bm1, "foo");
|
||||
PlacesUtils.setPostDataForBookmark(bm1, "pdata1");
|
||||
var bm2 = bmsvc.insertBookmark(testFolderId, testURI, -1, "blah");
|
||||
bmsvc.setKeywordForBookmark(bm2, "foo");
|
||||
PlacesUtils.setPostDataForBookmark(bm2, "pdata2");
|
||||
|
||||
// modify the older bookmark
|
||||
bmsvc.setItemTitle(bm1, "change");
|
||||
|
||||
var bm1da = bmsvc.getItemDateAdded(bm1);
|
||||
var bm1lm = bmsvc.getItemLastModified(bm1);
|
||||
LOG("bm1 dateAdded: " + bm1da + ", lastModified: " + bm1lm);
|
||||
var bm2da = bmsvc.getItemDateAdded(bm2);
|
||||
var bm2lm = bmsvc.getItemLastModified(bm2);
|
||||
LOG("bm2 dateAdded: " + bm2da + ", lastModified: " + bm2lm);
|
||||
do_check_true(bm1da <= bm2da);
|
||||
// the last modified for bm1 should be at least as big as bm2
|
||||
// but could be equal if the test runs faster than our PRNow()
|
||||
// granularity
|
||||
do_check_true(bm1lm >= bm2lm);
|
||||
|
||||
// we need to ensure that bm1 last modified date is greater
|
||||
// that the modified date of bm2, otherwise in case of a "tie"
|
||||
// bm2 will win, as it has a bigger item id
|
||||
if (bm1lm == bm2lm)
|
||||
bmsvc.setItemLastModified(bm1, bm2lm + 1000);
|
||||
|
||||
[url, postdata] = PlacesUtils.getURLAndPostDataForKeyword("foo");
|
||||
do_check_eq(testURI.spec, url);
|
||||
do_check_eq(postdata, "pdata1");
|
||||
|
||||
// cleanup
|
||||
bmsvc.removeItem(bm1);
|
||||
bmsvc.removeItem(bm2);
|
||||
|
||||
/*
|
||||
Test that id breaks ties:
|
||||
1. Create two bookmarks with the same URI and keyword, dateAdded and lastModified.
|
||||
2. Confirm that the most recently created one is returned for that keyword.
|
||||
*/
|
||||
var testDate = Date.now() * 1000;
|
||||
var bm1 = bmsvc.insertBookmark(testFolderId, testURI, -1, "blah");
|
||||
bmsvc.setKeywordForBookmark(bm1, "foo");
|
||||
PlacesUtils.setPostDataForBookmark(bm1, "pdata1");
|
||||
bmsvc.setItemDateAdded(bm1, testDate);
|
||||
bmsvc.setItemLastModified(bm1, testDate);
|
||||
|
||||
var bm2 = bmsvc.insertBookmark(testFolderId, testURI, -1, "blah");
|
||||
bmsvc.setKeywordForBookmark(bm2, "foo");
|
||||
PlacesUtils.setPostDataForBookmark(bm2, "pdata2");
|
||||
bmsvc.setItemDateAdded(bm2, testDate);
|
||||
bmsvc.setItemLastModified(bm2, testDate);
|
||||
|
||||
var bm1da = bmsvc.getItemDateAdded(bm1, testDate);
|
||||
var bm1lm = bmsvc.getItemLastModified(bm1);
|
||||
LOG("bm1 dateAdded: " + bm1da + ", lastModified: " + bm1lm);
|
||||
var bm2da = bmsvc.getItemDateAdded(bm2);
|
||||
var bm2lm = bmsvc.getItemLastModified(bm2);
|
||||
LOG("bm2 dateAdded: " + bm2da + ", lastModified: " + bm2lm);
|
||||
|
||||
do_check_eq(bm1da, bm2da);
|
||||
do_check_eq(bm1lm, bm2lm);
|
||||
|
||||
|
||||
var ids = bmsvc.getBookmarkIdsForURI(testURI);
|
||||
do_check_eq(ids[0], bm2);
|
||||
do_check_eq(ids[1], bm1);
|
||||
|
||||
[url, postdata] = PlacesUtils.getURLAndPostDataForKeyword("foo");
|
||||
do_check_eq(testURI.spec, url);
|
||||
do_check_eq(postdata, "pdata2");
|
||||
|
||||
// cleanup
|
||||
bmsvc.removeItem(bm1);
|
||||
bmsvc.removeItem(bm2);
|
||||
PlacesUtils.bookmarks.removeItem(bm1);
|
||||
PlacesUtils.bookmarks.removeItem(bm2);
|
||||
}
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
||||
/* vim:set ts=2 sw=2 sts=2 et: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// Get bookmarks service
|
||||
try {
|
||||
var bmsvc = Cc["@mozilla.org/browser/nav-bookmarks-service;1"].
|
||||
getService(Ci.nsINavBookmarksService);
|
||||
}
|
||||
catch(ex) {
|
||||
do_throw("Could not get bookmarks service\n");
|
||||
}
|
||||
|
||||
// Get database connection
|
||||
try {
|
||||
var histsvc = Cc["@mozilla.org/browser/nav-history-service;1"].
|
||||
getService(Ci.nsINavHistoryService);
|
||||
var mDBConn = histsvc.QueryInterface(Ci.nsPIPlacesDatabase).DBConnection;
|
||||
}
|
||||
catch(ex) {
|
||||
do_throw("Could not get database connection\n");
|
||||
}
|
||||
|
||||
add_test(function test_keywordRemovedOnUniqueItemRemoval() {
|
||||
var bookmarkedURI = uri("http://foo.bar");
|
||||
var keyword = "testkeyword";
|
||||
|
||||
// TEST 1
|
||||
// 1. add a bookmark
|
||||
// 2. add a keyword to it
|
||||
// 3. remove bookmark
|
||||
// 4. check that keyword has gone
|
||||
var bookmarkId = bmsvc.insertBookmark(bmsvc.bookmarksMenuFolder,
|
||||
bookmarkedURI,
|
||||
bmsvc.DEFAULT_INDEX,
|
||||
"A bookmark");
|
||||
bmsvc.setKeywordForBookmark(bookmarkId, keyword);
|
||||
// remove bookmark
|
||||
bmsvc.removeItem(bookmarkId);
|
||||
|
||||
PlacesTestUtils.promiseAsyncUpdates().then(() => {
|
||||
// Check that keyword has been removed from the database.
|
||||
// The removal is asynchronous.
|
||||
var sql = "SELECT id FROM moz_keywords WHERE keyword = ?1";
|
||||
var stmt = mDBConn.createStatement(sql);
|
||||
stmt.bindByIndex(0, keyword);
|
||||
do_check_false(stmt.executeStep());
|
||||
stmt.finalize();
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
add_test(function test_keywordNotRemovedOnNonUniqueItemRemoval() {
|
||||
var bookmarkedURI = uri("http://foo.bar");
|
||||
var keyword = "testkeyword";
|
||||
|
||||
// TEST 2
|
||||
// 1. add 2 bookmarks
|
||||
// 2. add the same keyword to them
|
||||
// 3. remove first bookmark
|
||||
// 4. check that keyword is still there
|
||||
var bookmarkId1 = bmsvc.insertBookmark(bmsvc.bookmarksMenuFolder,
|
||||
bookmarkedURI,
|
||||
bmsvc.DEFAULT_INDEX,
|
||||
"A bookmark");
|
||||
bmsvc.setKeywordForBookmark(bookmarkId1, keyword);
|
||||
|
||||
var bookmarkId2 = bmsvc.insertBookmark(bmsvc.toolbarFolder,
|
||||
bookmarkedURI,
|
||||
bmsvc.DEFAULT_INDEX,
|
||||
keyword);
|
||||
bmsvc.setKeywordForBookmark(bookmarkId2, keyword);
|
||||
|
||||
// remove first bookmark
|
||||
bmsvc.removeItem(bookmarkId1);
|
||||
|
||||
PlacesTestUtils.promiseAsyncUpdates().then(() => {
|
||||
// check that keyword is still there
|
||||
var sql = "SELECT id FROM moz_keywords WHERE keyword = ?1";
|
||||
var stmt = mDBConn.createStatement(sql);
|
||||
stmt.bindByIndex(0, keyword);
|
||||
do_check_true(stmt.executeStep());
|
||||
stmt.finalize();
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
@@ -739,11 +739,7 @@ add_task(function* test_add_and_remove_bookmarks_with_additional_info() {
|
||||
, newValue: ANNO.value },
|
||||
{ guid: b2_info.guid
|
||||
, property: "keyword"
|
||||
, newValue: KEYWORD },
|
||||
{ guid: b2_info.guid
|
||||
, isAnnoProperty: true
|
||||
, property: PlacesUtils.POST_DATA_ANNO
|
||||
, newValue: POST_DATA } ];
|
||||
, newValue: KEYWORD } ];
|
||||
ensureItemsChanged(...b2_post_creation_changes);
|
||||
ensureTags([TAG_1, TAG_2]);
|
||||
|
||||
|
||||
@@ -0,0 +1,488 @@
|
||||
"use strict"
|
||||
|
||||
function* check_keyword(aExpectExists, aHref, aKeyword, aPostData = null) {
|
||||
// Check case-insensitivity.
|
||||
aKeyword = aKeyword.toUpperCase();
|
||||
|
||||
let entry = yield PlacesUtils.keywords.fetch(aKeyword);
|
||||
if (aExpectExists) {
|
||||
Assert.ok(!!entry, "A keyword should exist");
|
||||
Assert.equal(entry.url.href, aHref);
|
||||
Assert.equal(entry.postData, aPostData);
|
||||
} else {
|
||||
Assert.ok(!entry || entry.url.href != aHref,
|
||||
"The given keyword entry should not exist");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Polls the keywords cache waiting for the given keyword entry.
|
||||
*/
|
||||
function* promiseKeyword(keyword, expectedHref) {
|
||||
let href = null;
|
||||
do {
|
||||
yield new Promise(resolve => do_timeout(100, resolve));
|
||||
let entry = yield PlacesUtils.keywords.fetch(keyword);
|
||||
if (entry)
|
||||
href = entry.url.href;
|
||||
} while (href != expectedHref);
|
||||
}
|
||||
|
||||
function* check_no_orphans() {
|
||||
let db = yield PlacesUtils.promiseDBConnection();
|
||||
let rows = yield db.executeCached(
|
||||
`SELECT id FROM moz_keywords k
|
||||
WHERE NOT EXISTS (SELECT 1 FROM moz_places WHERE id = k.place_id)
|
||||
`);
|
||||
Assert.equal(rows.length, 0);
|
||||
}
|
||||
|
||||
function expectBookmarkNotifications() {
|
||||
let notifications = [];
|
||||
let observer = new Proxy(NavBookmarkObserver, {
|
||||
get(target, name) {
|
||||
if (name == "check") {
|
||||
PlacesUtils.bookmarks.removeObserver(observer);
|
||||
return expectedNotifications =>
|
||||
Assert.deepEqual(notifications, expectedNotifications);
|
||||
}
|
||||
|
||||
if (name.startsWith("onItemChanged")) {
|
||||
return (itemId, property) => {
|
||||
if (property != "keyword")
|
||||
return;
|
||||
let args = Array.from(arguments, arg => {
|
||||
if (arg && arg instanceof Ci.nsIURI)
|
||||
return new URL(arg.spec);
|
||||
if (arg && typeof(arg) == "number" && arg >= Date.now() * 1000)
|
||||
return new Date(parseInt(arg/1000));
|
||||
return arg;
|
||||
});
|
||||
notifications.push({ name: name, arguments: args });
|
||||
}
|
||||
}
|
||||
|
||||
if (name in target)
|
||||
return target[name];
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
PlacesUtils.bookmarks.addObserver(observer, false);
|
||||
return observer;
|
||||
}
|
||||
|
||||
add_task(function* test_invalid_input() {
|
||||
Assert.throws(() => PlacesUtils.keywords.fetch(null),
|
||||
/Invalid keyword/);
|
||||
Assert.throws(() => PlacesUtils.keywords.fetch(""),
|
||||
/Invalid keyword/);
|
||||
Assert.throws(() => PlacesUtils.keywords.fetch(5),
|
||||
/Invalid keyword/);
|
||||
|
||||
Assert.throws(() => PlacesUtils.keywords.insert(null),
|
||||
/Input should be a valid object/);
|
||||
Assert.throws(() => PlacesUtils.keywords.insert("test"),
|
||||
/Input should be a valid object/);
|
||||
Assert.throws(() => PlacesUtils.keywords.insert(undefined),
|
||||
/Input should be a valid object/);
|
||||
Assert.throws(() => PlacesUtils.keywords.insert({ }),
|
||||
/Invalid keyword/);
|
||||
Assert.throws(() => PlacesUtils.keywords.insert({ keyword: null }),
|
||||
/Invalid keyword/);
|
||||
Assert.throws(() => PlacesUtils.keywords.insert({ keyword: 5 }),
|
||||
/Invalid keyword/);
|
||||
Assert.throws(() => PlacesUtils.keywords.insert({ keyword: "" }),
|
||||
/Invalid keyword/);
|
||||
Assert.throws(() => PlacesUtils.keywords.insert({ keyword: "test", postData: 5 }),
|
||||
/Invalid POST data/);
|
||||
Assert.throws(() => PlacesUtils.keywords.insert({ keyword: "test", postData: {} }),
|
||||
/Invalid POST data/);
|
||||
Assert.throws(() => PlacesUtils.keywords.insert({ keyword: "test" }),
|
||||
/is not a valid URL/);
|
||||
Assert.throws(() => PlacesUtils.keywords.insert({ keyword: "test", url: 5 }),
|
||||
/is not a valid URL/);
|
||||
Assert.throws(() => PlacesUtils.keywords.insert({ keyword: "test", url: "" }),
|
||||
/is not a valid URL/);
|
||||
Assert.throws(() => PlacesUtils.keywords.insert({ keyword: "test", url: null }),
|
||||
/is not a valid URL/);
|
||||
Assert.throws(() => PlacesUtils.keywords.insert({ keyword: "test", url: "mozilla" }),
|
||||
/is not a valid URL/);
|
||||
|
||||
Assert.throws(() => PlacesUtils.keywords.remove(null),
|
||||
/Invalid keyword/);
|
||||
Assert.throws(() => PlacesUtils.keywords.remove(""),
|
||||
/Invalid keyword/);
|
||||
Assert.throws(() => PlacesUtils.keywords.remove(5),
|
||||
/Invalid keyword/);
|
||||
});
|
||||
|
||||
add_task(function* test_addKeyword() {
|
||||
yield check_keyword(false, "http://example.com/", "keyword");
|
||||
let fc = yield foreign_count("http://example.com/");
|
||||
let observer = expectBookmarkNotifications();
|
||||
|
||||
yield PlacesUtils.keywords.insert({ keyword: "keyword", url: "http://example.com/" });
|
||||
observer.check([]);
|
||||
|
||||
yield check_keyword(true, "http://example.com/", "keyword");
|
||||
Assert.equal((yield foreign_count("http://example.com/")), fc + 1); // +1 keyword
|
||||
|
||||
// Now remove the keyword.
|
||||
observer = expectBookmarkNotifications();
|
||||
yield PlacesUtils.keywords.remove("keyword");
|
||||
observer.check([]);
|
||||
|
||||
yield check_keyword(false, "http://example.com/", "keyword");
|
||||
Assert.equal((yield foreign_count("http://example.com/")), fc); // -1 keyword
|
||||
|
||||
// Check using URL.
|
||||
yield PlacesUtils.keywords.insert({ keyword: "keyword", url: new URL("http://example.com/") });
|
||||
yield check_keyword(true, "http://example.com/", "keyword");
|
||||
yield PlacesUtils.keywords.remove("keyword");
|
||||
yield check_keyword(false, "http://example.com/", "keyword");
|
||||
|
||||
yield check_no_orphans();
|
||||
});
|
||||
|
||||
add_task(function* test_addBookmarkAndKeyword() {
|
||||
yield check_keyword(false, "http://example.com/", "keyword");
|
||||
let fc = yield foreign_count("http://example.com/");
|
||||
let bookmark = yield PlacesUtils.bookmarks.insert({ url: "http://example.com/",
|
||||
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
|
||||
|
||||
let observer = expectBookmarkNotifications();
|
||||
yield PlacesUtils.keywords.insert({ keyword: "keyword", url: "http://example.com/" });
|
||||
|
||||
observer.check([{ name: "onItemChanged",
|
||||
arguments: [ (yield PlacesUtils.promiseItemId(bookmark.guid)),
|
||||
"keyword", false, "keyword",
|
||||
bookmark.lastModified, bookmark.type,
|
||||
(yield PlacesUtils.promiseItemId(bookmark.parentGuid)),
|
||||
bookmark.guid, bookmark.parentGuid ] } ]);
|
||||
|
||||
yield check_keyword(true, "http://example.com/", "keyword");
|
||||
Assert.equal((yield foreign_count("http://example.com/")), fc + 2); // +1 bookmark +1 keyword
|
||||
|
||||
// Now remove the keyword.
|
||||
observer = expectBookmarkNotifications();
|
||||
yield PlacesUtils.keywords.remove("keyword");
|
||||
|
||||
observer.check([{ name: "onItemChanged",
|
||||
arguments: [ (yield PlacesUtils.promiseItemId(bookmark.guid)),
|
||||
"keyword", false, "",
|
||||
bookmark.lastModified, bookmark.type,
|
||||
(yield PlacesUtils.promiseItemId(bookmark.parentGuid)),
|
||||
bookmark.guid, bookmark.parentGuid ] } ]);
|
||||
|
||||
yield check_keyword(false, "http://example.com/", "keyword");
|
||||
Assert.equal((yield foreign_count("http://example.com/")), fc + 1); // -1 keyword
|
||||
|
||||
// Add again the keyword, then remove the bookmark.
|
||||
yield PlacesUtils.keywords.insert({ keyword: "keyword", url: "http://example.com/" });
|
||||
|
||||
observer = expectBookmarkNotifications();
|
||||
yield PlacesUtils.bookmarks.remove(bookmark.guid);
|
||||
// the notification is synchronous but the removal process is async.
|
||||
// Unfortunately there's nothing explicit we can wait for.
|
||||
while ((yield foreign_count("http://example.com/")));
|
||||
// We don't get any itemChanged notification since the bookmark has been
|
||||
// removed already.
|
||||
observer.check([]);
|
||||
|
||||
yield check_keyword(false, "http://example.com/", "keyword");
|
||||
|
||||
yield check_no_orphans();
|
||||
});
|
||||
|
||||
add_task(function* test_addKeywordToURIHavingKeyword() {
|
||||
yield check_keyword(false, "http://example.com/", "keyword");
|
||||
let fc = yield foreign_count("http://example.com/");
|
||||
|
||||
let observer = expectBookmarkNotifications();
|
||||
yield PlacesUtils.keywords.insert({ keyword: "keyword", url: "http://example.com/" });
|
||||
observer.check([]);
|
||||
|
||||
yield check_keyword(true, "http://example.com/", "keyword");
|
||||
Assert.equal((yield foreign_count("http://example.com/")), fc + 1); // +1 keyword
|
||||
|
||||
yield PlacesUtils.keywords.insert({ keyword: "keyword2", url: "http://example.com/" });
|
||||
|
||||
yield check_keyword(true, "http://example.com/", "keyword");
|
||||
yield check_keyword(true, "http://example.com/", "keyword2");
|
||||
Assert.equal((yield foreign_count("http://example.com/")), fc + 2); // +1 keyword
|
||||
|
||||
// Now remove the keywords.
|
||||
observer = expectBookmarkNotifications();
|
||||
yield PlacesUtils.keywords.remove("keyword");
|
||||
yield PlacesUtils.keywords.remove("keyword2");
|
||||
observer.check([]);
|
||||
|
||||
yield check_keyword(false, "http://example.com/", "keyword");
|
||||
yield check_keyword(false, "http://example.com/", "keyword2");
|
||||
Assert.equal((yield foreign_count("http://example.com/")), fc); // -1 keyword
|
||||
|
||||
yield check_no_orphans();
|
||||
});
|
||||
|
||||
add_task(function* test_addBookmarkToURIHavingKeyword() {
|
||||
yield check_keyword(false, "http://example.com/", "keyword");
|
||||
let fc = yield foreign_count("http://example.com/");
|
||||
let observer = expectBookmarkNotifications();
|
||||
|
||||
yield PlacesUtils.keywords.insert({ keyword: "keyword", url: "http://example.com/" });
|
||||
observer.check([]);
|
||||
|
||||
yield check_keyword(true, "http://example.com/", "keyword");
|
||||
Assert.equal((yield foreign_count("http://example.com/")), fc + 1); // +1 keyword
|
||||
|
||||
observer = expectBookmarkNotifications();
|
||||
let bookmark = yield PlacesUtils.bookmarks.insert({ url: "http://example.com/",
|
||||
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
|
||||
Assert.equal((yield foreign_count("http://example.com/")), fc + 2); // +1 bookmark
|
||||
observer.check([]);
|
||||
|
||||
observer = expectBookmarkNotifications();
|
||||
yield PlacesUtils.bookmarks.remove(bookmark.guid);
|
||||
// the notification is synchronous but the removal process is async.
|
||||
// Unfortunately there's nothing explicit we can wait for.
|
||||
while ((yield foreign_count("http://example.com/")));
|
||||
// We don't get any itemChanged notification since the bookmark has been
|
||||
// removed already.
|
||||
observer.check([]);
|
||||
|
||||
yield check_keyword(false, "http://example.com/", "keyword");
|
||||
|
||||
yield check_no_orphans();
|
||||
});
|
||||
|
||||
add_task(function* test_sameKeywordDifferentURL() {
|
||||
let fc1 = yield foreign_count("http://example1.com/");
|
||||
let bookmark1 = yield PlacesUtils.bookmarks.insert({ url: "http://example1.com/",
|
||||
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
|
||||
let fc2 = yield foreign_count("http://example2.com/");
|
||||
let bookmark2 = yield PlacesUtils.bookmarks.insert({ url: "http://example2.com/",
|
||||
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
|
||||
yield PlacesUtils.keywords.insert({ keyword: "keyword", url: "http://example1.com/" });
|
||||
|
||||
yield check_keyword(true, "http://example1.com/", "keyword");
|
||||
Assert.equal((yield foreign_count("http://example1.com/")), fc1 + 2); // +1 bookmark +1 keyword
|
||||
yield check_keyword(false, "http://example2.com/", "keyword");
|
||||
Assert.equal((yield foreign_count("http://example2.com/")), fc2 + 1); // +1 bookmark
|
||||
|
||||
// Assign the same keyword to another url.
|
||||
let observer = expectBookmarkNotifications();
|
||||
yield PlacesUtils.keywords.insert({ keyword: "keyword", url: "http://example2.com/" });
|
||||
|
||||
observer.check([{ name: "onItemChanged",
|
||||
arguments: [ (yield PlacesUtils.promiseItemId(bookmark1.guid)),
|
||||
"keyword", false, "",
|
||||
bookmark1.lastModified, bookmark1.type,
|
||||
(yield PlacesUtils.promiseItemId(bookmark1.parentGuid)),
|
||||
bookmark1.guid, bookmark1.parentGuid ] },
|
||||
{ name: "onItemChanged",
|
||||
arguments: [ (yield PlacesUtils.promiseItemId(bookmark2.guid)),
|
||||
"keyword", false, "keyword",
|
||||
bookmark2.lastModified, bookmark2.type,
|
||||
(yield PlacesUtils.promiseItemId(bookmark2.parentGuid)),
|
||||
bookmark2.guid, bookmark2.parentGuid ] } ]);
|
||||
|
||||
yield check_keyword(false, "http://example1.com/", "keyword");
|
||||
Assert.equal((yield foreign_count("http://example1.com/")), fc1 + 1); // -1 keyword
|
||||
yield check_keyword(true, "http://example2.com/", "keyword");
|
||||
Assert.equal((yield foreign_count("http://example2.com/")), fc2 + 2); // +1 keyword
|
||||
|
||||
// Now remove the keyword.
|
||||
observer = expectBookmarkNotifications();
|
||||
yield PlacesUtils.keywords.remove("keyword");
|
||||
observer.check([{ name: "onItemChanged",
|
||||
arguments: [ (yield PlacesUtils.promiseItemId(bookmark2.guid)),
|
||||
"keyword", false, "",
|
||||
bookmark2.lastModified, bookmark2.type,
|
||||
(yield PlacesUtils.promiseItemId(bookmark2.parentGuid)),
|
||||
bookmark2.guid, bookmark2.parentGuid ] } ]);
|
||||
|
||||
yield check_keyword(false, "http://example1.com/", "keyword");
|
||||
yield check_keyword(false, "http://example2.com/", "keyword");
|
||||
Assert.equal((yield foreign_count("http://example1.com/")), fc1 + 1);
|
||||
Assert.equal((yield foreign_count("http://example2.com/")), fc2 + 1); // -1 keyword
|
||||
|
||||
yield PlacesUtils.bookmarks.remove(bookmark1);
|
||||
yield PlacesUtils.bookmarks.remove(bookmark2);
|
||||
Assert.equal((yield foreign_count("http://example1.com/")), fc1); // -1 bookmark
|
||||
while ((yield foreign_count("http://example2.com/"))); // -1 keyword
|
||||
|
||||
yield check_no_orphans();
|
||||
});
|
||||
|
||||
add_task(function* test_sameURIDifferentKeyword() {
|
||||
let fc = yield foreign_count("http://example.com/");
|
||||
|
||||
let observer = expectBookmarkNotifications();
|
||||
let bookmark = yield PlacesUtils.bookmarks.insert({ url: "http://example.com/",
|
||||
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
|
||||
yield PlacesUtils.keywords.insert({keyword: "keyword", url: "http://example.com/" });
|
||||
|
||||
yield check_keyword(true, "http://example.com/", "keyword");
|
||||
Assert.equal((yield foreign_count("http://example.com/")), fc + 2); // +1 bookmark +1 keyword
|
||||
|
||||
observer.check([{ name: "onItemChanged",
|
||||
arguments: [ (yield PlacesUtils.promiseItemId(bookmark.guid)),
|
||||
"keyword", false, "keyword",
|
||||
bookmark.lastModified, bookmark.type,
|
||||
(yield PlacesUtils.promiseItemId(bookmark.parentGuid)),
|
||||
bookmark.guid, bookmark.parentGuid ] } ]);
|
||||
|
||||
observer = expectBookmarkNotifications();
|
||||
yield PlacesUtils.keywords.insert({ keyword: "keyword2", url: "http://example.com/" });
|
||||
yield check_keyword(true, "http://example.com/", "keyword");
|
||||
yield check_keyword(true, "http://example.com/", "keyword2");
|
||||
Assert.equal((yield foreign_count("http://example.com/")), fc + 3); // +1 keyword
|
||||
observer.check([{ name: "onItemChanged",
|
||||
arguments: [ (yield PlacesUtils.promiseItemId(bookmark.guid)),
|
||||
"keyword", false, "keyword2",
|
||||
bookmark.lastModified, bookmark.type,
|
||||
(yield PlacesUtils.promiseItemId(bookmark.parentGuid)),
|
||||
bookmark.guid, bookmark.parentGuid ] } ]);
|
||||
|
||||
// Add a third keyword.
|
||||
yield PlacesUtils.keywords.insert({ keyword: "keyword3", url: "http://example.com/" });
|
||||
yield check_keyword(true, "http://example.com/", "keyword");
|
||||
yield check_keyword(true, "http://example.com/", "keyword2");
|
||||
yield check_keyword(true, "http://example.com/", "keyword3");
|
||||
Assert.equal((yield foreign_count("http://example.com/")), fc + 4); // +1 keyword
|
||||
|
||||
// Remove one of the keywords.
|
||||
observer = expectBookmarkNotifications();
|
||||
yield PlacesUtils.keywords.remove("keyword");
|
||||
yield check_keyword(false, "http://example.com/", "keyword");
|
||||
yield check_keyword(true, "http://example.com/", "keyword2");
|
||||
yield check_keyword(true, "http://example.com/", "keyword3");
|
||||
observer.check([{ name: "onItemChanged",
|
||||
arguments: [ (yield PlacesUtils.promiseItemId(bookmark.guid)),
|
||||
"keyword", false, "",
|
||||
bookmark.lastModified, bookmark.type,
|
||||
(yield PlacesUtils.promiseItemId(bookmark.parentGuid)),
|
||||
bookmark.guid, bookmark.parentGuid ] } ]);
|
||||
Assert.equal((yield foreign_count("http://example.com/")), fc + 3); // -1 keyword
|
||||
|
||||
// Now remove the bookmark.
|
||||
yield PlacesUtils.bookmarks.remove(bookmark);
|
||||
while ((yield foreign_count("http://example.com/")));
|
||||
yield check_keyword(false, "http://example.com/", "keyword");
|
||||
yield check_keyword(false, "http://example.com/", "keyword2");
|
||||
yield check_keyword(false, "http://example.com/", "keyword3");
|
||||
|
||||
check_no_orphans();
|
||||
});
|
||||
|
||||
add_task(function* test_deleteKeywordMultipleBookmarks() {
|
||||
let fc = yield foreign_count("http://example.com/");
|
||||
|
||||
let observer = expectBookmarkNotifications();
|
||||
let bookmark1 = yield PlacesUtils.bookmarks.insert({ url: "http://example.com/",
|
||||
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
|
||||
let bookmark2 = yield PlacesUtils.bookmarks.insert({ url: "http://example.com/",
|
||||
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
|
||||
yield PlacesUtils.keywords.insert({ keyword: "keyword", url: "http://example.com/" });
|
||||
|
||||
yield check_keyword(true, "http://example.com/", "keyword");
|
||||
Assert.equal((yield foreign_count("http://example.com/")), fc + 3); // +2 bookmark +1 keyword
|
||||
observer.check([{ name: "onItemChanged",
|
||||
arguments: [ (yield PlacesUtils.promiseItemId(bookmark2.guid)),
|
||||
"keyword", false, "keyword",
|
||||
bookmark2.lastModified, bookmark2.type,
|
||||
(yield PlacesUtils.promiseItemId(bookmark2.parentGuid)),
|
||||
bookmark2.guid, bookmark2.parentGuid ] },
|
||||
{ name: "onItemChanged",
|
||||
arguments: [ (yield PlacesUtils.promiseItemId(bookmark1.guid)),
|
||||
"keyword", false, "keyword",
|
||||
bookmark1.lastModified, bookmark1.type,
|
||||
(yield PlacesUtils.promiseItemId(bookmark1.parentGuid)),
|
||||
bookmark1.guid, bookmark1.parentGuid ] } ]);
|
||||
|
||||
observer = expectBookmarkNotifications();
|
||||
yield PlacesUtils.keywords.remove("keyword");
|
||||
yield check_keyword(false, "http://example.com/", "keyword");
|
||||
Assert.equal((yield foreign_count("http://example.com/")), fc + 2); // -1 keyword
|
||||
observer.check([{ name: "onItemChanged",
|
||||
arguments: [ (yield PlacesUtils.promiseItemId(bookmark2.guid)),
|
||||
"keyword", false, "",
|
||||
bookmark2.lastModified, bookmark2.type,
|
||||
(yield PlacesUtils.promiseItemId(bookmark2.parentGuid)),
|
||||
bookmark2.guid, bookmark2.parentGuid ] },
|
||||
{ name: "onItemChanged",
|
||||
arguments: [ (yield PlacesUtils.promiseItemId(bookmark1.guid)),
|
||||
"keyword", false, "",
|
||||
bookmark1.lastModified, bookmark1.type,
|
||||
(yield PlacesUtils.promiseItemId(bookmark1.parentGuid)),
|
||||
bookmark1.guid, bookmark1.parentGuid ] } ]);
|
||||
|
||||
// Now remove the bookmarks.
|
||||
yield PlacesUtils.bookmarks.remove(bookmark1);
|
||||
yield PlacesUtils.bookmarks.remove(bookmark2);
|
||||
Assert.equal((yield foreign_count("http://example.com/")), fc); // -2 bookmarks
|
||||
|
||||
check_no_orphans();
|
||||
});
|
||||
|
||||
add_task(function* test_multipleKeywordsSamePostData() {
|
||||
yield PlacesUtils.keywords.insert({ keyword: "keyword", url: "http://example.com/", postData: "postData1" });
|
||||
yield check_keyword(true, "http://example.com/", "keyword", "postData1");
|
||||
// Add another keyword with same postData, should fail.
|
||||
yield Assert.rejects(PlacesUtils.keywords.insert({ keyword: "keyword2", url: "http://example.com/", postData: "postData1" }),
|
||||
/constraint failed/);
|
||||
yield check_keyword(false, "http://example.com/", "keyword2", "postData1");
|
||||
|
||||
yield PlacesUtils.keywords.remove("keyword");
|
||||
|
||||
check_no_orphans();
|
||||
});
|
||||
|
||||
add_task(function* test_oldPostDataAPI() {
|
||||
let bookmark = yield PlacesUtils.bookmarks.insert({ url: "http://example.com/",
|
||||
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
|
||||
yield PlacesUtils.keywords.insert({ keyword: "keyword", url: "http://example.com/" });
|
||||
let itemId = yield PlacesUtils.promiseItemId(bookmark.guid);
|
||||
yield PlacesUtils.setPostDataForBookmark(itemId, "postData");
|
||||
yield check_keyword(true, "http://example.com/", "keyword", "postData");
|
||||
Assert.equal(PlacesUtils.getPostDataForBookmark(itemId), "postData");
|
||||
|
||||
yield PlacesUtils.keywords.remove("keyword");
|
||||
yield PlacesUtils.bookmarks.remove(bookmark);
|
||||
|
||||
check_no_orphans();
|
||||
});
|
||||
|
||||
add_task(function* test_oldKeywordsAPI() {
|
||||
let bookmark = yield PlacesUtils.bookmarks.insert({ url: "http://example.com/",
|
||||
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
|
||||
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
|
||||
yield check_keyword(false, "http://example.com/", "keyword");
|
||||
let itemId = yield PlacesUtils.promiseItemId(bookmark.guid);
|
||||
|
||||
PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "keyword");
|
||||
yield promiseKeyword("keyword", "http://example.com/");
|
||||
|
||||
// Remove the keyword.
|
||||
PlacesUtils.bookmarks.setKeywordForBookmark(itemId, "");
|
||||
yield promiseKeyword("keyword", null);
|
||||
|
||||
yield PlacesUtils.keywords.insert({ keyword: "keyword", url: "http://example.com" });
|
||||
Assert.equal(PlacesUtils.bookmarks.getKeywordForBookmark(itemId), "keyword");
|
||||
Assert.equal(PlacesUtils.bookmarks.getURIForKeyword("keyword").spec, "http://example.com/");
|
||||
yield PlacesUtils.bookmarks.remove(bookmark);
|
||||
|
||||
check_no_orphans();
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
@@ -717,44 +717,25 @@ add_test(function test_sort_folder_by_name() {
|
||||
});
|
||||
|
||||
add_test(function test_edit_postData() {
|
||||
function* promiseKeyword(keyword, href, postData) {
|
||||
while (true) {
|
||||
let entry = yield PlacesUtils.keywords.fetch(keyword);
|
||||
if (entry && entry.url.href == href && entry.postData == postData) {
|
||||
break;
|
||||
}
|
||||
const POST_DATA_ANNO = "bookmarkProperties/POSTData";
|
||||
let postData = "post-test_edit_postData";
|
||||
let testURI = NetUtil.newURI("http://test_edit_postData.com");
|
||||
let testBkmId = bmsvc.insertBookmark(root, testURI, bmsvc.DEFAULT_INDEX, "Test edit Post Data");
|
||||
PlacesUtils.bookmarks.setKeywordForBookmark(testBkmId, "kw");
|
||||
let txn = new PlacesEditBookmarkPostDataTransaction(testBkmId, postData);
|
||||
|
||||
yield new Promise(resolve => do_timeout(100, resolve));
|
||||
}
|
||||
}
|
||||
txn.doTransaction();
|
||||
let [url, post_data] = PlacesUtils.getURLAndPostDataForKeyword("kw");
|
||||
Assert.equal(url, testURI.spec);
|
||||
Assert.equal(postData, post_data);
|
||||
|
||||
Task.spawn(function* () {
|
||||
let postData = "post-test_edit_postData";
|
||||
let testURI = NetUtil.newURI("http://test_edit_postData.com");
|
||||
txn.undoTransaction();
|
||||
[url, post_data] = PlacesUtils.getURLAndPostDataForKeyword("kw");
|
||||
Assert.equal(url, testURI.spec);
|
||||
Assert.equal(null, post_data);
|
||||
|
||||
let testBkm = yield PlacesUtils.bookmarks.insert({
|
||||
parentGuid: PlacesUtils.bookmarks.menuGuid,
|
||||
url: "http://test_edit_postData.com",
|
||||
title: "Test edit Post Data"
|
||||
});
|
||||
|
||||
yield PlacesUtils.keywords.insert({
|
||||
keyword: "kw",
|
||||
url: "http://test_edit_postData.com"
|
||||
});
|
||||
|
||||
let testBkmId = yield PlacesUtils.promiseItemId(testBkm.guid);
|
||||
let txn = new PlacesEditBookmarkPostDataTransaction(testBkmId, postData);
|
||||
|
||||
txn.doTransaction();
|
||||
yield promiseKeyword("kw", testURI.spec, postData);
|
||||
|
||||
txn.undoTransaction();
|
||||
entry = yield PlacesUtils.keywords.fetch("kw");
|
||||
Assert.equal(entry.url.href, testURI.spec);
|
||||
// We don't allow anymore to set a null post data.
|
||||
//Assert.equal(null, post_data);
|
||||
}).then(run_next_test);
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
add_test(function test_tagURI_untagURI() {
|
||||
@@ -966,4 +947,4 @@ add_test(function test_create_folder_with_child_itemTxn() {
|
||||
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
i
|
||||
|
||||
@@ -544,53 +544,6 @@ tests.push({
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
tests.push({
|
||||
name: "D.5",
|
||||
desc: "Fix wrong keywords",
|
||||
|
||||
_validKeywordItemId: null,
|
||||
_invalidKeywordItemId: null,
|
||||
_validKeywordId: 1,
|
||||
_invalidKeywordId: 8888,
|
||||
_placeId: null,
|
||||
|
||||
setup: function() {
|
||||
// Insert a keyword
|
||||
let stmt = mDBConn.createStatement("INSERT INTO moz_keywords (id, keyword) VALUES(:id, :keyword)");
|
||||
stmt.params["id"] = this._validKeywordId;
|
||||
stmt.params["keyword"] = "used";
|
||||
stmt.execute();
|
||||
stmt.finalize();
|
||||
// Add a place to ensure place_id = 1 is valid
|
||||
this._placeId = addPlace();
|
||||
// Add a bookmark using the keyword
|
||||
this._validKeywordItemId = addBookmark(this._placeId, bs.TYPE_BOOKMARK, bs.unfiledBookmarksFolder, this._validKeywordId);
|
||||
// Add a bookmark using a nonexistent keyword
|
||||
this._invalidKeywordItemId = addBookmark(this._placeId, bs.TYPE_BOOKMARK, bs.unfiledBookmarksFolder, this._invalidKeywordId);
|
||||
},
|
||||
|
||||
check: function() {
|
||||
// Check that item with valid keyword is there
|
||||
let stmt = mDBConn.createStatement("SELECT id FROM moz_bookmarks WHERE id = :item_id AND keyword_id = :keyword");
|
||||
stmt.params["item_id"] = this._validKeywordItemId;
|
||||
stmt.params["keyword"] = this._validKeywordId;
|
||||
do_check_true(stmt.executeStep());
|
||||
stmt.reset();
|
||||
// Check that item with invalid keyword has been corrected
|
||||
stmt.params["item_id"] = this._invalidKeywordItemId;
|
||||
stmt.params["keyword"] = this._invalidKeywordId;
|
||||
do_check_false(stmt.executeStep());
|
||||
stmt.finalize();
|
||||
// Check that item with invalid keyword has not been removed
|
||||
stmt = mDBConn.createStatement("SELECT id FROM moz_bookmarks WHERE id = :item_id");
|
||||
stmt.params["item_id"] = this._invalidKeywordItemId;
|
||||
do_check_true(stmt.executeStep());
|
||||
stmt.finalize();
|
||||
}
|
||||
});
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
tests.push({
|
||||
name: "D.6",
|
||||
desc: "Fix wrong item types | bookmarks",
|
||||
@@ -1053,27 +1006,17 @@ tests.push({
|
||||
|
||||
setup: function() {
|
||||
// Insert 2 keywords
|
||||
let stmt = mDBConn.createStatement("INSERT INTO moz_keywords (id, keyword) VALUES(:id, :keyword)");
|
||||
let stmt = mDBConn.createStatement("INSERT INTO moz_keywords (id, keyword, place_id) VALUES(:id, :keyword, :place_id)");
|
||||
stmt.params["id"] = 1;
|
||||
stmt.params["keyword"] = "used";
|
||||
stmt.execute();
|
||||
stmt.reset();
|
||||
stmt.params["id"] = 2;
|
||||
stmt.params["keyword"] = "unused";
|
||||
stmt.params["place_id"] = 100;
|
||||
stmt.execute();
|
||||
stmt.finalize();
|
||||
// Add a place to ensure place_id = 1 is valid
|
||||
this._placeId = addPlace();
|
||||
// Insert a bookmark using the "used" keyword
|
||||
this._bookmarkId = addBookmark(this._placeId, bs.TYPE_BOOKMARK, bs.unfiledBookmarksFolder, 1);
|
||||
},
|
||||
|
||||
check: function() {
|
||||
// Check that "used" keyword is still there
|
||||
let stmt = mDBConn.createStatement("SELECT id FROM moz_keywords WHERE keyword = :keyword");
|
||||
stmt.params["keyword"] = "used";
|
||||
do_check_true(stmt.executeStep());
|
||||
stmt.reset();
|
||||
// Check that "unused" keyword has gone
|
||||
stmt.params["keyword"] = "unused";
|
||||
do_check_false(stmt.executeStep());
|
||||
|
||||
@@ -32,7 +32,7 @@ function run_test()
|
||||
add_task(function test_execute()
|
||||
{
|
||||
// Put some trash in the database.
|
||||
const URI = NetUtil.newURI("http://moz.org/");
|
||||
let uri = NetUtil.newURI("http://moz.org/");
|
||||
|
||||
let folderId = PlacesUtils.bookmarks.createFolder(PlacesUtils.unfiledBookmarksFolderId,
|
||||
"moz test",
|
||||
@@ -64,7 +64,7 @@ add_task(function test_execute()
|
||||
// Test expiration probes.
|
||||
for (let i = 0; i < 2; i++) {
|
||||
yield PlacesTestUtils.addVisits({
|
||||
uri: uri("http://" + i + ".moz.org/"),
|
||||
uri: NetUtil.newURI("http://" + i + ".moz.org/"),
|
||||
visitDate: Date.now() // [sic]
|
||||
});
|
||||
}
|
||||
|
||||
@@ -692,24 +692,14 @@ function populateActionBox() {
|
||||
}
|
||||
}
|
||||
|
||||
// Restart the browser
|
||||
function restart(safeMode) {
|
||||
// Notify all windows that an application quit has been requested.
|
||||
// Prompt user to restart the browser in safe mode
|
||||
function safeModeRestart() {
|
||||
let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
|
||||
.createInstance(Ci.nsISupportsPRBool);
|
||||
Services.obs.notifyObservers(cancelQuit, "quit-application-requested", "restart");
|
||||
|
||||
// Something aborted the quit process.
|
||||
if (cancelQuit.data) {
|
||||
return;
|
||||
}
|
||||
|
||||
let flags = Ci.nsIAppStartup.eAttemptQuit;
|
||||
|
||||
if (safeMode) {
|
||||
Services.startup.restartInSafeMode(flags);
|
||||
} else {
|
||||
Services.startup.quit(flags | Ci.nsIAppStartup.eRestart);
|
||||
if (!cancelQuit.data) {
|
||||
Services.startup.restartInSafeMode(Ci.nsIAppStartup.eAttemptQuit);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -740,12 +730,9 @@ function setupEventListeners(){
|
||||
Services.obs.notifyObservers(null, "restart-in-safe-mode", "");
|
||||
}
|
||||
else {
|
||||
restart(true);
|
||||
safeModeRestart();
|
||||
}
|
||||
});
|
||||
$("restart-button").addEventListener("click", function (event) {
|
||||
restart(false);
|
||||
});
|
||||
$("verify-place-integrity-button").addEventListener("click", function(event) {
|
||||
PlacesDBUtils.checkAndFixDatabase(function(aLog) {
|
||||
let msg = aLog.join("\n");
|
||||
|
||||
@@ -42,12 +42,6 @@
|
||||
&aboutSupport.restartInSafeMode.label;
|
||||
</button>
|
||||
</div>
|
||||
<div id="restart-box">
|
||||
<h3>&aboutSupport.restartTitle;</h3>
|
||||
<button id="restart-button">
|
||||
&aboutSupport.restartNormal.label;
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<h1>
|
||||
|
||||
@@ -108,6 +108,3 @@ variant of aboutSupport.showDir.label. -->
|
||||
|
||||
<!ENTITY aboutSupport.safeModeTitle "Try Safe Mode">
|
||||
<!ENTITY aboutSupport.restartInSafeMode.label "Restart in Safe Mode…">
|
||||
|
||||
<!ENTITY aboutSupport.restartTitle "Try Restart">
|
||||
<!ENTITY aboutSupport.restartNormal.label "Restart normally…">
|
||||
|
||||
Reference in New Issue
Block a user