Merge fx-team to m-c a=merge

This commit is contained in:
Wes Kocher 2014-11-04 19:53:20 -08:00
commit 052b085df2
70 changed files with 1014 additions and 1733 deletions

View File

@ -1804,3 +1804,5 @@ pref("print.enable_e10s_testing", false);
#else
pref("print.enable_e10s_testing", true);
#endif
pref("browser.defaultbrowser.notificationbar", false);

View File

@ -121,7 +121,7 @@ skip-if = e10s
[browser_search_favicon.js]
skip-if = os == "linux" || e10s # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
[browser_alltabslistener.js]
skip-if = os == "linux" || e10s # Linux: Intermittent failures, bug 951680; e10s: Bug ?????? - notifications don't work correctly.
skip-if = os == "linux" || e10s # Linux: Intermittent failures, bug 951680; e10s: Bug 1093594 - notifications for tabs come in in the wrong order / unexpectedly
[browser_autocomplete_a11y_label.js]
skip-if = e10s # Bug ????? - no e10s switch-to-tab support yet
[browser_backButtonFitts.js]
@ -158,7 +158,7 @@ skip-if = e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
[browser_bug423833.js]
skip-if = true # bug 428712
[browser_bug424101.js]
skip-if = e10s # Bug ?????? - test directly manipulates content
skip-if = e10s # Bug 1093155 - tries to use context menu from browser-chrome and gets in a mess when in e10s mode
[browser_bug427559.js]
skip-if = e10s # Bug ?????? - "content window is focused - Got [object ChromeWindow], expected [object XrayWrapper [object Window]]"
[browser_bug431826.js]
@ -202,7 +202,7 @@ skip-if = buildapp == 'mulet' || e10s # Bug 1093206 - need to re-enable tests re
skip-if = e10s # Bug ?????? - test doesn't wait for document to be created before it checks it
[browser_bug550565.js]
[browser_bug553455.js]
skip-if = buildapp == 'mulet' || e10s # Bug 1066070 - I don't think either popup notifications nor addon install stuff works?
skip-if = buildapp == 'mulet' || e10s # Bug 1066070 - I don't think either popup notifications nor addon install stuff works on mulet? ; for e10s, indefinite waiting halfway through the test, tracked in bug 1093586
[browser_bug555224.js]
skip-if = e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
[browser_bug555767.js]
@ -272,7 +272,7 @@ skip-if = e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
[browser_bug724239.js]
skip-if = e10s # Bug 1077738
[browser_bug734076.js]
skip-if = e10s # Bug ?????? - test directly manipulates content
skip-if = e10s # Bug 1093155 - tries to use context menu from browser-chrome and gets in a mess when in e10s mode
[browser_bug735471.js]
[browser_bug749738.js]
skip-if = e10s # Bug 921935 - focusmanager issues with e10s
@ -294,9 +294,9 @@ skip-if = e10s # Bug 1093373 - relies on browser.sessionHistory
[browser_bug902156.js]
skip-if = e10s
[browser_bug906190.js]
skip-if = buildapp == "mulet" || e10s # Bug ?????? - test directly manipulates content (strange - gets an element from a child which it tries to treat as a string, but that fails)
skip-if = buildapp == "mulet" || e10s # Bug 1093642 - test manipulates content and relies on content focus
[browser_bug970746.js]
skip-if = e10s # Bug ?????? - test directly manipulates content (directly gets elements from the content)
skip-if = e10s # Bug 1093155 - tries to use context menu from browser-chrome and gets in a mess when in e10s mode
[browser_bug1015721.js]
skip-if = os == 'win' || e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
[browser_bug1064280_changeUrlInPinnedTab.js]
@ -363,7 +363,7 @@ skip-if = e10s # Bug ?????? - test directly manipulates content ("cannot ipc non
[browser_notification_tab_switching.js]
skip-if = buildapp == 'mulet' || e10s # Bug ?????? - uncaught exception - Error: cannot ipc non-cpow object at chrome://mochitests/content/browser/browser/base/content/test/general/browser_notification_tab_switching.js:32
[browser_offlineQuotaNotification.js]
skip-if = buildapp == 'mulet' || e10s # Bug ?????? - test directly manipulates content (gBrowser.selectedBrowser.contentWindow.applicationCache.oncached = function() {...})
skip-if = buildapp == 'mulet' || e10s # Bug 1093603 - test breaks with PopupNotifications.panel.firstElementChild is null
[browser_overflowScroll.js]
[browser_pageInfo.js]
skip-if = buildapp == 'mulet' || e10s # Bug 866413 - PageInfo doesn't work in e10s
@ -377,7 +377,7 @@ skip-if = asan # Disabled because it takes a long time (see test for more inform
[browser_pinnedTabs.js]
[browser_plainTextLinks.js]
skip-if = e10s # Bug ?????? - test directly manipulates content (creates and fetches elements directly from content document)
skip-if = e10s # Bug 1093155 - tries to use context menu from browser-chrome and gets in a mess when in e10s mode
[browser_popupUI.js]
skip-if = buildapp == 'mulet' || e10s # Bug ?????? - test directly manipulates content (tries to get a popup element directly from content)
[browser_popup_blocker.js]
@ -445,7 +445,7 @@ skip-if = e10s # Bug ?????? - test needs to be updated for e10s (captures a stac
skip-if = e10s # Bug ?????? - test directly manipulates content (tries to get/set attributes directly on content docshell)
[browser_tabs_owner.js]
[browser_trackingUI.js]
skip-if = e10s
skip-if = e10s # Bug 1093155 - tries to use context menu from browser-chrome and gets in a mess when in e10s mode
support-files =
trackingPage.html
benignPage.html

View File

@ -18,7 +18,7 @@ loop.conversation = (function(mozL10n) {
var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
var CallIdentifierView = loop.conversationViews.CallIdentifierView;
var EmptyRoomView = loop.roomViews.EmptyRoomView;
var DesktopRoomView = loop.roomViews.DesktopRoomView;
var IncomingCallView = React.createClass({displayName: 'IncomingCallView',
mixins: [sharedMixins.DropdownMenuMixin],
@ -576,7 +576,7 @@ loop.conversation = (function(mozL10n) {
));
}
case "room": {
return (EmptyRoomView({
return (DesktopRoomView({
mozLoop: navigator.mozLoop,
localRoomStore: this.props.localRoomStore}
));

View File

@ -18,7 +18,7 @@ loop.conversation = (function(mozL10n) {
var OutgoingConversationView = loop.conversationViews.OutgoingConversationView;
var CallIdentifierView = loop.conversationViews.CallIdentifierView;
var EmptyRoomView = loop.roomViews.EmptyRoomView;
var DesktopRoomView = loop.roomViews.DesktopRoomView;
var IncomingCallView = React.createClass({
mixins: [sharedMixins.DropdownMenuMixin],
@ -576,7 +576,7 @@ loop.conversation = (function(mozL10n) {
/>);
}
case "room": {
return (<EmptyRoomView
return (<DesktopRoomView
mozLoop={navigator.mozLoop}
localRoomStore={this.props.localRoomStore}
/>);

View File

@ -10,24 +10,8 @@ var loop = loop || {};
loop.roomViews = (function(mozL10n) {
"use strict";
/**
* Root object, by default set to window.
* @type {DOMWindow|Object}
*/
var rootObject = window;
/**
* Sets a new root object. This is useful for testing native DOM events so we
* can fake them.
*
* @param {Object}
*/
function setRootObject(obj) {
rootObject = obj;
}
var EmptyRoomView = React.createClass({displayName: 'EmptyRoomView',
mixins: [Backbone.Events],
var DesktopRoomView = React.createClass({displayName: 'DesktopRoomView',
mixins: [Backbone.Events, loop.shared.mixins.DocumentTitleMixin],
propTypes: {
mozLoop:
@ -45,28 +29,6 @@ loop.roomViews = (function(mozL10n) {
this._onLocalRoomStoreChanged);
},
componentDidMount: function() {
// XXXremoveMe (just the conditional itself) in patch 2 for bug 1074686,
// once the addCallback stuff lands
if (this.props.mozLoop.rooms && this.props.mozLoop.rooms.addCallback) {
this.props.mozLoop.rooms.addCallback(
this.state.localRoomId,
"RoomCreationError", this.onCreationError);
}
},
/**
* Attached to the "RoomCreationError" with mozLoop.rooms.addCallback,
* which is fired mozLoop.rooms.createRoom from the panel encounters an
* error while attempting to create the room for this view.
*
* @param {Error} err - JS Error object with info about the problem
*/
onCreationError: function(err) {
// XXX put up a user friendly error instead of this
rootObject.console.error("EmptyRoomView creation error: ", err);
},
/**
* Handles a "change" event on the localRoomStore, and updates this.state
* to match the store.
@ -79,20 +41,11 @@ loop.roomViews = (function(mozL10n) {
componentWillUnmount: function() {
this.stopListening(this.props.localRoomStore);
// XXXremoveMe (just the conditional itself) in patch 2 for bug 1074686,
// once the addCallback stuff lands
if (this.props.mozLoop.rooms && this.props.mozLoop.rooms.removeCallback) {
this.props.mozLoop.rooms.removeCallback(
this.state.localRoomId,
"RoomCreationError", this.onCreationError);
}
},
render: function() {
// XXX switch this to use the document title mixin once bug 1081079 lands
if (this.state.serverData && this.state.serverData.roomName) {
rootObject.document.title = this.state.serverData.roomName;
this.setTitle(this.state.serverData.roomName);
}
return (
@ -102,8 +55,7 @@ loop.roomViews = (function(mozL10n) {
});
return {
setRootObject: setRootObject,
EmptyRoomView: EmptyRoomView
DesktopRoomView: DesktopRoomView
};
})(document.mozL10n || navigator.mozL10n);;

View File

@ -10,24 +10,8 @@ var loop = loop || {};
loop.roomViews = (function(mozL10n) {
"use strict";
/**
* Root object, by default set to window.
* @type {DOMWindow|Object}
*/
var rootObject = window;
/**
* Sets a new root object. This is useful for testing native DOM events so we
* can fake them.
*
* @param {Object}
*/
function setRootObject(obj) {
rootObject = obj;
}
var EmptyRoomView = React.createClass({
mixins: [Backbone.Events],
var DesktopRoomView = React.createClass({
mixins: [Backbone.Events, loop.shared.mixins.DocumentTitleMixin],
propTypes: {
mozLoop:
@ -45,28 +29,6 @@ loop.roomViews = (function(mozL10n) {
this._onLocalRoomStoreChanged);
},
componentDidMount: function() {
// XXXremoveMe (just the conditional itself) in patch 2 for bug 1074686,
// once the addCallback stuff lands
if (this.props.mozLoop.rooms && this.props.mozLoop.rooms.addCallback) {
this.props.mozLoop.rooms.addCallback(
this.state.localRoomId,
"RoomCreationError", this.onCreationError);
}
},
/**
* Attached to the "RoomCreationError" with mozLoop.rooms.addCallback,
* which is fired mozLoop.rooms.createRoom from the panel encounters an
* error while attempting to create the room for this view.
*
* @param {Error} err - JS Error object with info about the problem
*/
onCreationError: function(err) {
// XXX put up a user friendly error instead of this
rootObject.console.error("EmptyRoomView creation error: ", err);
},
/**
* Handles a "change" event on the localRoomStore, and updates this.state
* to match the store.
@ -79,20 +41,11 @@ loop.roomViews = (function(mozL10n) {
componentWillUnmount: function() {
this.stopListening(this.props.localRoomStore);
// XXXremoveMe (just the conditional itself) in patch 2 for bug 1074686,
// once the addCallback stuff lands
if (this.props.mozLoop.rooms && this.props.mozLoop.rooms.removeCallback) {
this.props.mozLoop.rooms.removeCallback(
this.state.localRoomId,
"RoomCreationError", this.onCreationError);
}
},
render: function() {
// XXX switch this to use the document title mixin once bug 1081079 lands
if (this.state.serverData && this.state.serverData.roomName) {
rootObject.document.title = this.state.serverData.roomName;
this.setTitle(this.state.serverData.roomName);
}
return (
@ -102,8 +55,7 @@ loop.roomViews = (function(mozL10n) {
});
return {
setRootObject: setRootObject,
EmptyRoomView: EmptyRoomView
DesktopRoomView: DesktopRoomView
};
})(document.mozL10n || navigator.mozL10n);;

View File

@ -167,7 +167,7 @@ body {
.room-list {
max-height: 335px; /* XXX better computation needed */
overflow: scroll;
overflow: auto;
}
.room-list > .room-entry {

View File

@ -57,6 +57,17 @@ loop.shared.mixins = (function() {
}
};
/**
* Document title mixin.
*
* @type {Object}
*/
var DocumentTitleMixin = {
setTitle: function(newTitle) {
rootObject.document.title = newTitle;
}
};
/**
* Dropdown menu mixin.
* @type {Object}
@ -184,6 +195,7 @@ loop.shared.mixins = (function() {
DropdownMenuMixin: DropdownMenuMixin,
DocumentVisibilityMixin: DocumentVisibilityMixin,
DocumentLocationMixin: DocumentLocationMixin,
DocumentTitleMixin: DocumentTitleMixin,
UrlHashChangeMixin: UrlHashChangeMixin
};
})();

View File

@ -201,13 +201,13 @@ describe("loop.conversation", function() {
loop.conversation.IncomingConversationView);
});
it("should display the EmptyRoomView for rooms", function() {
it("should display the RoomView for rooms", function() {
conversationAppStore.setStoreState({windowType: "room"});
ccView = mountTestComponent();
TestUtils.findRenderedComponentWithType(ccView,
loop.roomViews.EmptyRoomView);
loop.roomViews.DesktopRoomView);
});
it("should display the GenericFailureView for failures", function() {

View File

@ -18,7 +18,7 @@ describe("loop.roomViews", function () {
removeCallback: fakeRemoveCallback } };
fakeWindow = { document: {} };
loop.roomViews.setRootObject(fakeWindow);
loop.shared.mixins.setRootObject(fakeWindow);
store = new loop.store.LocalRoomStore({
dispatcher: { register: function() {} },
@ -29,54 +29,18 @@ describe("loop.roomViews", function () {
afterEach(function() {
sinon.sandbox.restore();
loop.roomViews.setRootObject(window);
loop.shared.mixins.setRootObject(window);
});
describe("EmptyRoomView", function() {
describe("DesktopRoomView", function() {
function mountTestComponent() {
return TestUtils.renderIntoDocument(
new loop.roomViews.EmptyRoomView({
new loop.roomViews.DesktopRoomView({
mozLoop: fakeMozLoop,
localRoomStore: store
}));
}
describe("#componentDidMount", function() {
it("should add #onCreationError using mozLoop.rooms.addCallback",
function() {
var testComponent = mountTestComponent();
sinon.assert.calledOnce(fakeMozLoop.rooms.addCallback);
sinon.assert.calledWithExactly(fakeMozLoop.rooms.addCallback,
fakeRoomId, "RoomCreationError", testComponent.onCreationError);
});
});
describe("#componentWillUnmount", function () {
it("should remove #onCreationError using mozLoop.rooms.addCallback",
function () {
var testComponent = mountTestComponent();
testComponent.componentWillUnmount();
sinon.assert.calledOnce(fakeMozLoop.rooms.removeCallback);
sinon.assert.calledWithExactly(fakeMozLoop.rooms.removeCallback,
fakeRoomId, "RoomCreationError", testComponent.onCreationError);
});
});
describe("#onCreationError", function() {
it("should log an error using console.error", function() {
fakeWindow.console = { error: sandbox.stub() };
var testComponent = mountTestComponent();
testComponent.onCreationError(new Error("fake error"));
sinon.assert.calledOnce(fakeWindow.console.error);
});
});
describe("#render", function() {
it("should set document.title to store.serverData.roomName",
function() {
@ -87,7 +51,7 @@ describe("loop.roomViews", function () {
mountTestComponent();
expect(fakeWindow.document.title).to.equal(fakeRoomName);
})
});
});
});

View File

@ -21,7 +21,7 @@ describe("loop.shared.mixins", function() {
sandbox.restore();
});
describe("loop.webapp.UrlHashChangeMixin", function() {
describe("loop.shared.mixins.UrlHashChangeMixin", function() {
function createTestComponent(onUrlHashChange) {
var TestComp = React.createClass({
mixins: [loop.shared.mixins.UrlHashChangeMixin],
@ -61,7 +61,7 @@ describe("loop.shared.mixins", function() {
});
});
describe("loop.webapp.DocumentLocationMixin", function() {
describe("loop.shared.mixins.DocumentLocationMixin", function() {
var reloadStub, TestComp;
beforeEach(function() {
@ -90,7 +90,33 @@ describe("loop.shared.mixins", function() {
});
});
describe("loop.panel.DocumentVisibilityMixin", function() {
describe("loop.shared.mixins.DocumentTitleMixin", function() {
var TestComp, rootObject;
beforeEach(function() {
rootObject = {
document: {}
};
sharedMixins.setRootObject(rootObject);
TestComp = React.createClass({
mixins: [loop.shared.mixins.DocumentTitleMixin],
render: function() {
return React.DOM.div();
}
});
});
it("should set window.document.title", function() {
var comp = TestUtils.renderIntoDocument(TestComp());
comp.setTitle("It's a Fake!");
expect(rootObject.document.title).eql("It's a Fake!");
});
});
describe("loop.shared.mixins.DocumentVisibilityMixin", function() {
var comp, TestComp, onDocumentVisibleStub, onDocumentHiddenStub;
beforeEach(function() {

View File

@ -502,9 +502,6 @@ BrowserGlue.prototype = {
#endif
os.addObserver(this, "browser-search-engine-modified", false);
os.addObserver(this, "browser-search-service", false);
#ifdef NIGHTLY_BUILD
Services.prefs.addObserver(POLARIS_ENABLED, this, false);
#endif
},
// cleanup (called on application shutdown)
@ -601,6 +598,10 @@ BrowserGlue.prototype = {
LoginManagerParent.init();
#ifdef NIGHTLY_BUILD
Services.prefs.addObserver(POLARIS_ENABLED, this, false);
#endif
Services.obs.notifyObservers(null, "browser-ui-startup-complete", "");
},
@ -2285,28 +2286,23 @@ let DefaultBrowserCheck = {
} catch (ex) {
Cu.reportError(ex);
}
this.closePrompt();
},
_createPopup: function(win, bundle) {
_createPopup: function(win, notNowStrings, neverStrings) {
let doc = win.document;
let popup = doc.createElement("menupopup");
popup.id = this.OPTIONPOPUP;
let notNowItem = doc.createElement("menuitem");
notNowItem.id = "defaultBrowserNotNow";
let label = bundle.getString("setDefaultBrowserNotNow.label");
notNowItem.setAttribute("label", label);
let accesskey = bundle.getString("setDefaultBrowserNotNow.accesskey");
notNowItem.setAttribute("accesskey", accesskey);
notNowItem.setAttribute("label", notNowStrings.label);
notNowItem.setAttribute("accesskey", notNowStrings.accesskey);
popup.appendChild(notNowItem);
let neverItem = doc.createElement("menuitem");
neverItem.id = "defaultBrowserNever";
label = bundle.getString("setDefaultBrowserNever.label");
neverItem.setAttribute("label", label);
accesskey = bundle.getString("setDefaultBrowserNever.accesskey");
neverItem.setAttribute("accesskey", accesskey);
neverItem.setAttribute("label", neverStrings.label);
neverItem.setAttribute("accesskey", neverStrings.accesskey);
popup.appendChild(neverItem);
popup.addEventListener("command", this);
@ -2332,40 +2328,72 @@ let DefaultBrowserCheck = {
let promptMessage = shellBundle.getFormattedString("setDefaultBrowserMessage2",
[brandShortName]);
let confirmMessage = shellBundle.getFormattedString("setDefaultBrowserConfirm.label",
[brandShortName]);
let confirmKey = shellBundle.getString("setDefaultBrowserConfirm.accesskey");
let yesButton = shellBundle.getFormattedString("setDefaultBrowserConfirm.label",
[brandShortName]);
let optionsMessage = shellBundle.getString("setDefaultBrowserOptions.label");
let optionsKey = shellBundle.getString("setDefaultBrowserOptions.accesskey");
let notNowButton = shellBundle.getString("setDefaultBrowserNotNow.label");
let notNowButtonKey = shellBundle.getString("setDefaultBrowserNotNow.accesskey");
let selectedBrowser = win.gBrowser.selectedBrowser;
let notificationBox = win.document.getElementById("high-priority-global-notificationbox");
let neverLabel = shellBundle.getString("setDefaultBrowserNever.label");
let neverKey = shellBundle.getString("setDefaultBrowserNever.accesskey");
this._createPopup(win, shellBundle);
let useNotificationBar = Services.prefs.getBoolPref("browser.defaultbrowser.notificationbar");
if (useNotificationBar) {
let optionsMessage = shellBundle.getString("setDefaultBrowserOptions.label");
let optionsKey = shellBundle.getString("setDefaultBrowserOptions.accesskey");
let buttons = [
{
label: confirmMessage,
accessKey: confirmKey,
callback: this.setAsDefault.bind(this)
},
{
label: optionsMessage,
accessKey: optionsKey,
popup: this.OPTIONPOPUP
let yesButtonKey = shellBundle.getString("setDefaultBrowserConfirm.accesskey");
let notificationBox = win.document.getElementById("high-priority-global-notificationbox");
this._createPopup(win, {
label: notNowButton,
accesskey: notNowButtonKey
}, {
label: neverLabel,
accesskey: neverKey
});
let buttons = [
{
label: yesButton,
accessKey: yesButtonKey,
callback: () => {
this.setAsDefault();
this.closePrompt();
}
},
{
label: optionsMessage,
accessKey: optionsKey,
popup: this.OPTIONPOPUP
}
];
let iconPixels = win.devicePixelRatio > 1 ? "32" : "16";
let iconURL = "chrome://branding/content/icon" + iconPixels + ".png";
const priority = notificationBox.PRIORITY_WARNING_HIGH;
let callback = this._onNotificationEvent.bind(this);
this._notification = notificationBox.appendNotification(promptMessage, "default-browser",
iconURL, priority, buttons,
callback);
} else {
// Modal prompt
let promptTitle = shellBundle.getString("setDefaultBrowserTitle");
let ps = Services.prompt;
let dontAsk = { value: false };
let buttonFlags = (ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_0) +
(ps.BUTTON_TITLE_IS_STRING * ps.BUTTON_POS_1) +
ps.BUTTON_POS_0_DEFAULT;
let rv = ps.confirmEx(win, promptTitle, promptMessage, buttonFlags,
yesButton, notNowButton, null, neverLabel, dontAsk);
if (rv == 0) {
this.setAsDefault();
} else if (dontAsk.value) {
ShellService.shouldCheckDefaultBrowser = false;
}
];
let iconPixels = win.devicePixelRatio > 1 ? "32" : "16";
let iconURL = "chrome://branding/content/icon" + iconPixels + ".png";
const priority = notificationBox.PRIORITY_WARNING_HIGH;
let callback = this._onNotificationEvent.bind(this);
this._notification = notificationBox.appendNotification(promptMessage, "default-browser",
iconURL, priority, buttons,
callback);
this._notification.persistence = -1;
}
},
_onNotificationEvent: function(eventType) {

View File

@ -80,7 +80,7 @@
preference="privacy.trackingprotection.enabled"
accesskey="&trackingProtection.accesskey;"
label="&trackingProtection.label;" />
<image id="trackingProtectionImage" src="chrome://browser/skin/bad-content-blocked-16.png"/>
<image id="trackingProtectionImage"/>
</hbox>
<label id="trackingProtectionLearnMore"
class="text-link"

View File

@ -268,7 +268,7 @@ Toolbox.prototype = {
let domReady = () => {
this.isReady = true;
this._listFrames();
let framesPromise = this._listFrames();
this.closeButton = this.doc.getElementById("toolbox-close");
this.closeButton.addEventListener("command", this.destroy, true);
@ -309,7 +309,8 @@ Toolbox.prototype = {
promise.all([
splitConsolePromise,
buttonsPromise
buttonsPromise,
framesPromise
]).then(() => {
this.emit("ready");
deferred.resolve();
@ -1229,13 +1230,13 @@ Toolbox.prototype = {
if (!this._target.form || !this._target.form.actor) {
// We are not targetting a regular TabActor
// it can be either an addon or browser toolbox actor
return;
return promise.resolve();
}
let packet = {
to: this._target.form.actor,
type: "listFrames"
};
this._target.client.request(packet, resp => {
return this._target.client.request(packet, resp => {
this._updateFrames(null, { frames: resp.frames });
});
},

View File

@ -70,9 +70,9 @@
list-style-image: url("chrome://browser/skin/devedition/search.svg#search-icon-mac-inverted");
}
/* Don't use default colors when in full screen */
#main-window:not([customizing]) #navigator-toolbox[inFullscreen] > #TabsToolbar:not(:-moz-lwtheme) {
-moz-appearance: none;
/* Don't use the default background for tabs toolbar */
#TabsToolbar {
-moz-appearance: none !important;
}
/* Tab styling - make sure to use an inverted icon for the selected tab

View File

@ -295,6 +295,18 @@ description > html|a {
-moz-margin-start: 33px;
}
#trackingProtectionImage {
width: 16px;
height: 16px;
list-style-image: url(chrome://browser/skin/bad-content-blocked-16.png);
}
@media (min-resolution: 2dppx) {
#trackingProtectionImage {
list-style-image: url(chrome://browser/skin/bad-content-blocked-16@2x.png);
}
}
/**
* Sub-dialog
*/

View File

@ -3479,7 +3479,7 @@ nsDOMWindowUtils::LoadSheet(nsIURI *aSheetURI, uint32_t aSheetType)
}
NS_IMETHODIMP
nsDOMWindowUtils::LoadSheetFromURIString(const nsACString& aSheetURI, uint32_t aSheetType)
nsDOMWindowUtils::LoadSheetUsingURIString(const nsACString& aSheetURI, uint32_t aSheetType)
{
MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
@ -3531,6 +3531,18 @@ nsDOMWindowUtils::RemoveSheet(nsIURI *aSheetURI, uint32_t aSheetType)
return NS_OK;
}
NS_IMETHODIMP
nsDOMWindowUtils::RemoveSheetUsingURIString(const nsACString& aSheetURI, uint32_t aSheetType)
{
MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), aSheetURI);
NS_ENSURE_SUCCESS(rv, rv);
return RemoveSheet(uri, aSheetType);
}
NS_IMETHODIMP
nsDOMWindowUtils::GetIsHandlingUserInput(bool* aHandlingUserInput)
{

View File

@ -51,7 +51,7 @@ interface nsITranslationNodeList;
interface nsIJSRAIIHelper;
interface nsIContentPermissionRequest;
[scriptable, uuid(f7e4d5da-4dd0-455a-b448-d0224c17fd10)]
[scriptable, uuid(e293355b-ae7f-4ef7-9237-452bcf3e9e6b)]
interface nsIDOMWindowUtils : nsISupports {
/**
@ -1601,7 +1601,7 @@ interface nsIDOMWindowUtils : nsISupports {
/**
* Same as the above method but allows passing the URI as a string.
*/
void loadSheetFromURIString(in ACString sheetURI, in unsigned long type);
void loadSheetUsingURIString(in ACString sheetURI, in unsigned long type);
/**
* Adds a style sheet to the list of additional style sheets of the document.
@ -1618,6 +1618,11 @@ interface nsIDOMWindowUtils : nsISupports {
*/
void removeSheet(in nsIURI sheetURI, in unsigned long type);
/**
* Same as the above method but allows passing the URI as a string.
*/
void removeSheetUsingURIString(in ACString sheetURI, in unsigned long type);
/**
* Returns true if a user input is being handled.
*

View File

@ -1326,10 +1326,14 @@ public class BrowserApp extends GeckoApp
}
private void updateSideBarState() {
if (NewTabletUI.isEnabled(this)) {
return;
}
if (mMainLayoutAnimator != null)
mMainLayoutAnimator.stop();
boolean isSideBar = !NewTabletUI.isEnabled(this) && (HardwareUtils.isTablet() && getOrientation() == Configuration.ORIENTATION_LANDSCAPE);
boolean isSideBar = (HardwareUtils.isTablet() && getOrientation() == Configuration.ORIENTATION_LANDSCAPE);
final int sidebarWidth = getResources().getDimensionPixelSize(R.dimen.tabs_sidebar_width);
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) mTabsPanel.getLayoutParams();
@ -1729,6 +1733,10 @@ public class BrowserApp extends GeckoApp
if (!areTabsShown()) {
mTabsPanel.setVisibility(View.INVISIBLE);
mTabsPanel.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
} else {
// Cancel editing mode to return to page content when the TabsPanel closes. We cancel
// it here because there are graphical glitches if it's canceled while it's visible.
mBrowserToolbar.cancelEdit();
}
mTabsPanel.finishTabsAnimation();

View File

@ -7,12 +7,15 @@ package org.mozilla.gecko;
import android.content.Context;
import android.content.SharedPreferences;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.preferences.GeckoPreferences;
import org.mozilla.gecko.util.HardwareUtils;
public class NewTabletUI {
// This value should be in sync with preferences_display.xml.
private static final boolean DEFAULT = false;
// This value should be in sync with preferences_display.xml. On non-release
// builds, the preference UI will be hidden and the (unused) default
// preference UI value will still be 'true'.
private static final boolean DEFAULT = !AppConstants.RELEASE_BUILD;
private static Boolean sNewTabletUI;

View File

@ -4,6 +4,7 @@
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<org.mozilla.gecko.tabs.TabsLayoutItemView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:gecko="http://schemas.android.com/apk/res-auto"
style="@style/TabsItem"
android:focusable="true"
android:id="@+id/info"
@ -21,21 +22,27 @@
android:paddingRight="@dimen/new_tablet_tab_highlight_stroke_width"
android:paddingBottom="@dimen/new_tablet_tab_highlight_stroke_width">
<TextView android:id="@+id/title"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1.0"
style="@style/TabLayoutItemTextAppearance"
android:textSize="14sp"
android:textColor="@color/new_tablet_tab_item_title"
android:singleLine="true"
android:duplicateParentState="true"/>
<org.mozilla.gecko.widget.FadedTextView android:id="@+id/title"
android:layout_width="0dip"
android:layout_height="wrap_content"
android:layout_weight="1.0"
style="@style/TabLayoutItemTextAppearance"
android:textSize="14sp"
android:textColor="@color/new_tablet_tab_item_title"
android:singleLine="true"
android:duplicateParentState="true"
gecko:fadeWidth="15dp"
android:paddingRight="5dp"/>
<!-- Use of baselineAlignBottom only supported from API 11+ - if this needs to work on lower API versions
we'll need to override getBaseLine() and return image height, but we assume this won't happen -->
<ImageButton android:id="@+id/close"
style="@style/TabsItemClose"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:scaleType="center"
android:baselineAlignBottom="true"
android:background="@android:color/transparent"
android:contentDescription="@string/close_tab"
android:src="@drawable/new_tablet_tab_item_close_button"

View File

@ -8,4 +8,5 @@
<item type="layout" name="tabs_layout_item_view">@layout/tabs_item_row</item>
<item type="layout" name="new_tablet_browser_toolbar">@null</item>
<item type="layout" name="new_tablet_tab_strip">@null</item>
<item type="layout" name="new_tablet_tabs_item_cell">@null</item>
</resources>

View File

@ -27,7 +27,7 @@
<CheckBoxPreference android:key="android.not_a_preference.new_tablet_ui"
android:title="@string/new_tablet_pref"
android:defaultValue="false" />
android:defaultValue="true" />
<PreferenceCategory android:title="@string/pref_category_advanced">

View File

@ -82,9 +82,7 @@ public class TabStrip extends ThemedLinearLayout {
switch (msg) {
case RESTORED:
case ADDED:
// Refresh the list to make sure the new tab is
// added in the right position.
tabStripView.refreshTabs();
tabStripView.addTab(tab);
break;
case CLOSED:

View File

@ -11,11 +11,16 @@ import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.view.ViewTreeObserver.OnPreDrawListener;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorSet;
import com.nineoldandroids.animation.ObjectAnimator;
import java.util.ArrayList;
import java.util.List;
@ -27,6 +32,10 @@ import org.mozilla.gecko.widget.TwoWayView;
public class TabStripView extends TwoWayView {
private static final String LOGTAG = "GeckoTabStrip";
private static final int ANIM_TIME_MS = 200;
private static final AccelerateDecelerateInterpolator ANIM_INTERPOLATOR =
new AccelerateDecelerateInterpolator();
private final TabStripAdapter adapter;
private final Drawable divider;
@ -71,14 +80,110 @@ public class TabStripView extends TwoWayView {
setItemChecked(selected, true);
}
private void updateSelectedPosition() {
private void updateSelectedPosition(boolean ensureVisible) {
final int selected = getPositionForSelectedTab();
if (selected != -1) {
updateSelectedStyle(selected);
ensurePositionIsVisible(selected);
if (ensureVisible) {
ensurePositionIsVisible(selected);
}
}
}
private void animateRemoveTab(Tab removedTab) {
final int removedPosition = adapter.getPositionForTab(removedTab);
final View removedView = getViewForTab(removedTab);
// The removed position might not have a matching child view
// when it's not within the visible range of positions in the strip.
if (removedView == null) {
return;
}
// We don't animate the removed child view (it just disappears)
// but we still need its size of animate all affected children
// within the visible viewport.
final int removedSize = removedView.getWidth() + getItemMargin();
getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
final int firstPosition = getFirstVisiblePosition();
final List<Animator> childAnimators = new ArrayList<Animator>();
final int childCount = getChildCount();
for (int i = removedPosition - firstPosition; i < childCount; i++) {
final View child = getChildAt(i);
// TODO: optimize with Valueresolver
final ObjectAnimator animator =
ObjectAnimator.ofFloat(child, "translationX", removedSize, 0);
childAnimators.add(animator);
}
final AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(childAnimators);
animatorSet.setDuration(ANIM_TIME_MS);
animatorSet.setInterpolator(ANIM_INTERPOLATOR);
animatorSet.start();
return true;
}
});
}
private void animateNewTab(Tab newTab) {
final int newPosition = adapter.getPositionForTab(newTab);
if (newPosition < 0) {
return;
}
getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
final int firstPosition = getFirstVisiblePosition();
final View newChild = getChildAt(newPosition - firstPosition);
if (newChild == null) {
return true;
}
final List<Animator> childAnimators = new ArrayList<Animator>();
childAnimators.add(
ObjectAnimator.ofFloat(newChild, "translationY", newChild.getHeight(), 0));
// This will momentaneously add a gap on the right side
// because TwoWayView doesn't provide APIs to control
// view recycling programatically to handle these transitory
// states in the container during animations.
final int tabSize = newChild.getWidth();
final int newIndex = newPosition - firstPosition;
final int childCount = getChildCount();
for (int i = newIndex + 1; i < childCount; i++) {
final View child = getChildAt(i);
childAnimators.add(
ObjectAnimator.ofFloat(child, "translationX", -tabSize, 0));
}
final AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(childAnimators);
animatorSet.setDuration(ANIM_TIME_MS);
animatorSet.setInterpolator(ANIM_INTERPOLATOR);
animatorSet.start();
return true;
}
});
}
private void ensurePositionIsVisible(final int position) {
getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
@Override
@ -111,16 +216,24 @@ public class TabStripView extends TwoWayView {
}
adapter.refresh(tabs);
updateSelectedPosition();
updateSelectedPosition(true);
}
void clearTabs() {
adapter.clear();
}
void addTab(Tab tab) {
// Refresh the list to make sure the new tab is
// added in the right position.
refreshTabs();
animateNewTab(tab);
}
void removeTab(Tab tab) {
animateRemoveTab(tab);
adapter.removeTab(tab);
updateSelectedPosition();
updateSelectedPosition(false);
}
void selectTab(Tab tab) {
@ -128,7 +241,7 @@ public class TabStripView extends TwoWayView {
isPrivate = tab.isPrivate();
refreshTabs();
} else {
updateSelectedPosition();
updateSelectedPosition(true);
}
}

View File

@ -9,13 +9,15 @@ import org.mozilla.gecko.Tab;
import org.mozilla.gecko.widget.TabThumbnailWrapper;
import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewTreeObserver;
import android.widget.Checkable;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import android.widget.LinearLayout;
import android.widget.TextView;
@ -85,6 +87,22 @@ public class TabsLayoutItemView extends LinearLayout
mThumbnail = (ImageView) findViewById(R.id.thumbnail);
mCloseButton = (ImageButton) findViewById(R.id.close);
mThumbnailWrapper = (TabThumbnailWrapper) findViewById(R.id.wrapper);
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
@Override
public boolean onPreDraw() {
getViewTreeObserver().removeOnPreDrawListener(this);
final Rect r = new Rect();
mCloseButton.getHitRect(r);
r.left -= 25;
r.bottom += 25;
setTouchDelegate(new TouchDelegate(r, mCloseButton));
return true;
}
});
}
protected void assignValues(Tab tab) {

View File

@ -315,6 +315,10 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout
tabsButton.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View v) {
// Clear focus so a back press with the tabs
// panel open does not go to the editing field.
urlEditLayout.clearFocus();
toggleTabs();
}
});
@ -341,6 +345,8 @@ public abstract class BrowserToolbar extends ThemedRelativeLayout
menuButton.setOnClickListener(new Button.OnClickListener() {
@Override
public void onClick(View view) {
// Drop the soft keyboard.
urlEditLayout.clearFocus();
activity.openOptionsMenu();
}
});

View File

@ -99,6 +99,20 @@ let Bookmarks = Object.freeze({
*/
DEFAULT_INDEX: -1,
/**
* Special GUIDs associated with bookmark roots.
* It's guaranteed that the roots will always have these guids.
*/
rootGuid: "root________",
menuGuid: "menu________",
toolbarGuid: "toolbar_____",
unfiledGuid: "unfiled_____",
// With bug 424160, tags will stop being bookmarks, thus this root will
// be removed. Do not rely on this, rather use the tagging service API.
tagsGuid: "tags________",
/**
* Inserts a bookmark-item into the bookmarks tree.
*
@ -437,9 +451,9 @@ let Bookmarks = Object.freeze({
let rows = yield db.executeCached(
`WITH RECURSIVE
descendants(did) AS (
SELECT id FROM moz_bookmarks
WHERE parent IN (SELECT folder_id FROM moz_bookmarks_roots
WHERE root_name IN ("toolbar", "menu", "unfiled"))
SELECT b.id FROM moz_bookmarks b
JOIN moz_bookmarks p ON b.parent = p.id
WHERE p.guid IN ( :toolbarGuid, :menuGuid, :unfiledGuid )
UNION ALL
SELECT id FROM moz_bookmarks
JOIN descendants ON parent = did
@ -452,21 +466,23 @@ let Bookmarks = Object.freeze({
JOIN moz_bookmarks p ON p.id = b.parent
LEFT JOIN moz_places h ON b.fk = h.id
WHERE b.id IN descendants
`);
`, { menuGuid: this.menuGuid, toolbarGuid: this.toolbarGuid,
unfiledGuid: this.unfiledGuid });
let items = rowsToItemsArray(rows);
yield db.executeCached(
`WITH RECURSIVE
descendants(did) AS (
SELECT id FROM moz_bookmarks
WHERE parent IN (SELECT folder_id FROM moz_bookmarks_roots
WHERE root_name IN ("toolbar", "menu", "unfiled"))
SELECT b.id FROM moz_bookmarks b
JOIN moz_bookmarks p ON b.parent = p.id
WHERE p.guid IN ( :toolbarGuid, :menuGuid, :unfiledGuid )
UNION ALL
SELECT id FROM moz_bookmarks
JOIN descendants ON parent = did
)
DELETE FROM moz_bookmarks WHERE id IN descendants
`);
`, { menuGuid: this.menuGuid, toolbarGuid: this.toolbarGuid,
unfiledGuid: this.unfiledGuid });
// Clenup orphans.
yield removeOrphanAnnotations(db);
@ -477,9 +493,11 @@ let Bookmarks = Object.freeze({
// Update roots' lastModified.
yield db.executeCached(
`UPDATE moz_bookmarks SET lastModified = :time
WHERE id IN (SELECT folder_id FROM moz_bookmarks_roots
WHERE root_name IN ("places", "toolbar", "menu", "unfiled"));
`, { time: toPRTime(new Date()) });
WHERE id IN (SELECT id FROM moz_bookmarks
WHERE guid IN ( :rootGuid, :toolbarGuid, :menuGuid, :unfiledGuid ))
`, { time: toPRTime(new Date()), rootGuid: this.rootGuid,
menuGuid: this.menuGuid, toolbarGuid: this.toolbarGuid,
unfiledGuid: this.unfiledGuid });
let urls = [for (item of items) if (item.url) item.url];
updateFrecency(db, urls).then(null, Cu.reportError);
@ -506,7 +524,7 @@ let Bookmarks = Object.freeze({
}
}
}
});
}.bind(this));
}),
/**

View File

@ -257,7 +257,7 @@ NS_IMPL_ISUPPORTS(
nsresult
CreateRoot(nsCOMPtr<mozIStorageConnection>& aDBConn,
const nsCString& aRootName,
const nsCString& aRootName, const nsCString& aGuid,
const nsXPIDLString& titleString)
{
MOZ_ASSERT(NS_IsMainThread());
@ -277,7 +277,7 @@ CreateRoot(nsCOMPtr<mozIStorageConnection>& aDBConn,
"INSERT INTO moz_bookmarks "
"(type, position, title, dateAdded, lastModified, guid, parent) "
"VALUES (:item_type, :item_position, :item_title,"
":date_added, :last_modified, GENERATE_GUID(),"
":date_added, :last_modified, :guid,"
"IFNULL((SELECT id FROM moz_bookmarks WHERE parent = 0), 0))"
), getter_AddRefs(stmt));
if (NS_FAILED(rv)) return rv;
@ -294,6 +294,8 @@ CreateRoot(nsCOMPtr<mozIStorageConnection>& aDBConn,
if (NS_FAILED(rv)) return rv;
rv = stmt->BindInt64ByName(NS_LITERAL_CSTRING("last_modified"), timestamp);
if (NS_FAILED(rv)) return rv;
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), aGuid);
if (NS_FAILED(rv)) return rv;
rv = stmt->Execute();
if (NS_FAILED(rv)) return rv;
@ -301,18 +303,15 @@ CreateRoot(nsCOMPtr<mozIStorageConnection>& aDBConn,
nsCOMPtr<mozIStorageStatement> newRootStmt;
rv = aDBConn->CreateStatement(NS_LITERAL_CSTRING(
"INSERT INTO moz_bookmarks_roots (root_name, folder_id) "
"VALUES (:root_name, "
"(SELECT id from moz_bookmarks WHERE "
" position = :item_position AND "
" parent = IFNULL((SELECT MIN(folder_id) FROM moz_bookmarks_roots), 0)))"
"VALUES (:root_name, (SELECT id from moz_bookmarks WHERE guid = :guid))"
), getter_AddRefs(newRootStmt));
if (NS_FAILED(rv)) return rv;
rv = newRootStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("root_name"),
aRootName);
if (NS_FAILED(rv)) return rv;
rv = newRootStmt->BindInt32ByName(NS_LITERAL_CSTRING("item_position"),
itemPosition);
rv = newRootStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"),
aGuid);
if (NS_FAILED(rv)) return rv;
rv = newRootStmt->Execute();
if (NS_FAILED(rv)) return rv;
@ -638,43 +637,12 @@ Database::InitSchema(bool* aDatabaseMigrated)
if (currentSchemaVersion < DATABASE_SCHEMA_VERSION) {
*aDatabaseMigrated = true;
if (currentSchemaVersion < 6) {
// These are early Firefox 3.0 alpha versions that are not supported
if (currentSchemaVersion < 11) {
// These are versions older than Firefox 4 that are not supported
// anymore. In this case it's safer to just replace the database.
return NS_ERROR_FILE_CORRUPTED;
}
// Firefox 3.0 uses schema version 6.
if (currentSchemaVersion < 7) {
rv = MigrateV7Up();
NS_ENSURE_SUCCESS(rv, rv);
}
if (currentSchemaVersion < 8) {
rv = MigrateV8Up();
NS_ENSURE_SUCCESS(rv, rv);
}
// Firefox 3.5 uses schema version 8.
if (currentSchemaVersion < 9) {
rv = MigrateV9Up();
NS_ENSURE_SUCCESS(rv, rv);
}
if (currentSchemaVersion < 10) {
rv = MigrateV10Up();
NS_ENSURE_SUCCESS(rv, rv);
}
// Firefox 3.6 uses schema version 10.
if (currentSchemaVersion < 11) {
rv = MigrateV11Up();
NS_ENSURE_SUCCESS(rv, rv);
}
// Firefox 4 uses schema version 11.
// Firefox 8 uses schema version 12.
@ -750,8 +718,16 @@ Database::InitSchema(bool* aDatabaseMigrated)
rv = MigrateV24Up();
NS_ENSURE_SUCCESS(rv, rv);
}
// Firefox 34 uses schema version 24.
if (currentSchemaVersion < 25) {
rv = MigrateV25Up();
NS_ENSURE_SUCCESS(rv, rv);
}
// Firefox 36 uses schema version 25.
// Schema Upgrades must add migration code here.
rv = UpdateBookmarkRootTitles();
@ -875,42 +851,43 @@ Database::CreateBookmarkRoots()
nsXPIDLString rootTitle;
// The first root's title is an empty string.
rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("places"), rootTitle);
rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("places"),
NS_LITERAL_CSTRING("root________"), rootTitle);
if (NS_FAILED(rv)) return rv;
// Fetch the internationalized folder name from the string bundle.
rv = bundle->GetStringFromName(MOZ_UTF16("BookmarksMenuFolderTitle"),
getter_Copies(rootTitle));
if (NS_FAILED(rv)) return rv;
rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("menu"), rootTitle);
rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("menu"),
NS_LITERAL_CSTRING("menu________"), rootTitle);
if (NS_FAILED(rv)) return rv;
rv = bundle->GetStringFromName(MOZ_UTF16("BookmarksToolbarFolderTitle"),
getter_Copies(rootTitle));
if (NS_FAILED(rv)) return rv;
rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("toolbar"), rootTitle);
rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("toolbar"),
NS_LITERAL_CSTRING("toolbar_____"), rootTitle);
if (NS_FAILED(rv)) return rv;
rv = bundle->GetStringFromName(MOZ_UTF16("TagsFolderTitle"),
getter_Copies(rootTitle));
if (NS_FAILED(rv)) return rv;
rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("tags"), rootTitle);
rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("tags"),
NS_LITERAL_CSTRING("tags________"), rootTitle);
if (NS_FAILED(rv)) return rv;
rv = bundle->GetStringFromName(MOZ_UTF16("UnsortedBookmarksFolderTitle"),
getter_Copies(rootTitle));
if (NS_FAILED(rv)) return rv;
rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("unfiled"), rootTitle);
rv = CreateRoot(mMainConn, NS_LITERAL_CSTRING("unfiled"),
NS_LITERAL_CSTRING("unfiled_____"), rootTitle);
if (NS_FAILED(rv)) return rv;
#if DEBUG
nsCOMPtr<mozIStorageStatement> stmt;
rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT "
"(SELECT COUNT(*) FROM moz_bookmarks), "
"(SELECT COUNT(*) FROM moz_bookmarks_roots), "
"(SELECT SUM(position) FROM moz_bookmarks WHERE "
"id IN (SELECT folder_id FROM moz_bookmarks_roots))"
"SELECT count(*), sum(position) FROM moz_bookmarks"
), getter_AddRefs(stmt));
if (NS_FAILED(rv)) return rv;
@ -918,16 +895,9 @@ Database::CreateBookmarkRoots()
rv = stmt->ExecuteStep(&hasResult);
if (NS_FAILED(rv)) return rv;
MOZ_ASSERT(hasResult);
int32_t bookmarkCount = 0;
rv = stmt->GetInt32(0, &bookmarkCount);
if (NS_FAILED(rv)) return rv;
int32_t rootCount = 0;
rv = stmt->GetInt32(1, &rootCount);
if (NS_FAILED(rv)) return rv;
int32_t positionSum = 0;
rv = stmt->GetInt32(2, &positionSum);
if (NS_FAILED(rv)) return rv;
MOZ_ASSERT(bookmarkCount == 5 && rootCount == 5 && positionSum == 6);
int32_t bookmarkCount = stmt->AsInt32(0);
int32_t positionSum = stmt->AsInt32(1);
MOZ_ASSERT(bookmarkCount == 5 && positionSum == 6);
#endif
return NS_OK;
@ -999,8 +969,7 @@ Database::UpdateBookmarkRootTitles()
nsCOMPtr<mozIStorageAsyncStatement> stmt;
rv = mMainConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
"UPDATE moz_bookmarks SET title = :new_title WHERE id = "
"(SELECT folder_id FROM moz_bookmarks_roots WHERE root_name = :root_name)"
"UPDATE moz_bookmarks SET title = :new_title WHERE guid = :guid"
), getter_AddRefs(stmt));
if (NS_FAILED(rv)) return rv;
@ -1008,13 +977,18 @@ Database::UpdateBookmarkRootTitles()
rv = stmt->NewBindingParamsArray(getter_AddRefs(paramsArray));
if (NS_FAILED(rv)) return rv;
const char *rootNames[] = { "menu", "toolbar", "tags", "unfiled" };
const char *titleStringIDs[] = {
"BookmarksMenuFolderTitle", "BookmarksToolbarFolderTitle",
"TagsFolderTitle", "UnsortedBookmarksFolderTitle"
};
const char *rootGuids[] = { "menu________"
, "toolbar_____"
, "tags________"
, "unfiled_____"
};
const char *titleStringIDs[] = { "BookmarksMenuFolderTitle"
, "BookmarksToolbarFolderTitle"
, "TagsFolderTitle"
, "UnsortedBookmarksFolderTitle"
};
for (uint32_t i = 0; i < ArrayLength(rootNames); ++i) {
for (uint32_t i = 0; i < ArrayLength(rootGuids); ++i) {
nsXPIDLString title;
rv = bundle->GetStringFromName(NS_ConvertASCIItoUTF16(titleStringIDs[i]).get(),
getter_Copies(title));
@ -1023,8 +997,8 @@ Database::UpdateBookmarkRootTitles()
nsCOMPtr<mozIStorageBindingParams> params;
rv = paramsArray->NewBindingParams(getter_AddRefs(params));
if (NS_FAILED(rv)) return rv;
rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("root_name"),
nsDependentCString(rootNames[i]));
rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"),
nsDependentCString(rootGuids[i]));
if (NS_FAILED(rv)) return rv;
rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("new_title"),
NS_ConvertUTF16toUTF8(title));
@ -1042,528 +1016,6 @@ Database::UpdateBookmarkRootTitles()
return NS_OK;
}
nsresult
Database::CheckAndUpdateGUIDs()
{
MOZ_ASSERT(NS_IsMainThread());
// First, import any bookmark guids already set by Sync.
nsCOMPtr<mozIStorageStatement> updateStmt;
nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"UPDATE moz_bookmarks "
"SET guid = :guid "
"WHERE id = :item_id "
), getter_AddRefs(updateStmt));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStorageStatement> stmt;
rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT item_id, content "
"FROM moz_items_annos "
"JOIN moz_anno_attributes "
"WHERE name = :anno_name "
), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"),
SYNCGUID_ANNO);
NS_ENSURE_SUCCESS(rv, rv);
bool hasResult;
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
int64_t itemId;
rv = stmt->GetInt64(0, &itemId);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString guid;
rv = stmt->GetUTF8String(1, guid);
NS_ENSURE_SUCCESS(rv, rv);
// If we have an invalid guid, we don't need to do any more work.
if (!IsValidGUID(guid)) {
continue;
}
mozStorageStatementScoper updateScoper(updateStmt);
rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("item_id"), itemId);
NS_ENSURE_SUCCESS(rv, rv);
rv = updateStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), guid);
NS_ENSURE_SUCCESS(rv, rv);
rv = updateStmt->Execute();
if (rv == NS_ERROR_STORAGE_CONSTRAINT) {
// We just tried to insert a duplicate guid. Ignore this error, and we
// will generate a new one next.
continue;
}
NS_ENSURE_SUCCESS(rv, rv);
}
// Now, remove all the bookmark guid annotations that we just imported.
rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_items_annos "
"WHERE anno_attribute_id = ( "
"SELECT id "
"FROM moz_anno_attributes "
"WHERE name = :anno_name "
") "
), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"),
SYNCGUID_ANNO);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// Next, generate guids for any bookmark that does not already have one.
rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"UPDATE moz_bookmarks "
"SET guid = GENERATE_GUID() "
"WHERE guid IS NULL "
), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// Now, import any history guids already set by Sync.
rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"UPDATE moz_places "
"SET guid = :guid "
"WHERE id = :place_id "
), getter_AddRefs(updateStmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT place_id, content "
"FROM moz_annos "
"JOIN moz_anno_attributes "
"WHERE name = :anno_name "
), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"),
SYNCGUID_ANNO);
NS_ENSURE_SUCCESS(rv, rv);
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
int64_t placeId;
rv = stmt->GetInt64(0, &placeId);
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString guid;
rv = stmt->GetUTF8String(1, guid);
NS_ENSURE_SUCCESS(rv, rv);
// If we have an invalid guid, we don't need to do any more work.
if (!IsValidGUID(guid)) {
continue;
}
mozStorageStatementScoper updateScoper(updateStmt);
rv = updateStmt->BindInt64ByName(NS_LITERAL_CSTRING("place_id"), placeId);
NS_ENSURE_SUCCESS(rv, rv);
rv = updateStmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"), guid);
NS_ENSURE_SUCCESS(rv, rv);
rv = updateStmt->Execute();
if (rv == NS_ERROR_STORAGE_CONSTRAINT) {
// We just tried to insert a duplicate guid. Ignore this error, and we
// will generate a new one next.
continue;
}
NS_ENSURE_SUCCESS(rv, rv);
}
// Now, remove all the place guid annotations that we just imported.
rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"DELETE FROM moz_annos "
"WHERE anno_attribute_id = ( "
"SELECT id "
"FROM moz_anno_attributes "
"WHERE name = :anno_name "
") "
), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("anno_name"),
SYNCGUID_ANNO);
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// Finally, generate guids for any places that do not already have one.
rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"UPDATE moz_places "
"SET guid = GENERATE_GUID() "
"WHERE guid IS NULL "
), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
Database::MigrateV7Up()
{
MOZ_ASSERT(NS_IsMainThread());
// Some old v6 databases come from alpha versions that missed indices.
// Just bail out and replace the database in such a case.
bool URLUniqueIndexExists = false;
nsresult rv = mMainConn->IndexExists(NS_LITERAL_CSTRING(
"moz_places_url_uniqueindex"
), &URLUniqueIndexExists);
NS_ENSURE_SUCCESS(rv, rv);
if (!URLUniqueIndexExists) {
return NS_ERROR_FILE_CORRUPTED;
}
// We need an index on lastModified to catch quickly last modified bookmark
// title for tag container's children. This will be useful for Sync, too.
bool lastModIndexExists = false;
rv = mMainConn->IndexExists(
NS_LITERAL_CSTRING("moz_bookmarks_itemlastmodifiedindex"),
&lastModIndexExists);
NS_ENSURE_SUCCESS(rv, rv);
if (!lastModIndexExists) {
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_PLACELASTMODIFIED);
NS_ENSURE_SUCCESS(rv, rv);
}
// We need to do a one-time change of the moz_historyvisits.pageindex
// to speed up finding last visit date when joinin with moz_places.
// See bug 392399 for more details.
bool pageIndexExists = false;
rv = mMainConn->IndexExists(
NS_LITERAL_CSTRING("moz_historyvisits_pageindex"), &pageIndexExists);
NS_ENSURE_SUCCESS(rv, rv);
if (pageIndexExists) {
// drop old index
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP INDEX IF EXISTS moz_historyvisits_pageindex"));
NS_ENSURE_SUCCESS(rv, rv);
// create the new multi-column index
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_HISTORYVISITS_PLACEDATE);
NS_ENSURE_SUCCESS(rv, rv);
}
// for existing profiles, we may not have a frecency column
nsCOMPtr<mozIStorageStatement> hasFrecencyStatement;
rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT frecency FROM moz_places"),
getter_AddRefs(hasFrecencyStatement));
if (NS_FAILED(rv)) {
// Add frecency column to moz_places, default to -1 so that all the
// frecencies are invalid
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_places ADD frecency INTEGER DEFAULT -1 NOT NULL"));
NS_ENSURE_SUCCESS(rv, rv);
// create index for the frecency column
// XXX multi column index with typed, and visit_count?
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_FRECENCY);
NS_ENSURE_SUCCESS(rv, rv);
// Invalidate all frecencies, since they need recalculation.
nsCOMPtr<mozIStorageAsyncStatement> stmt = GetAsyncStatement(
"UPDATE moz_places SET frecency = ( "
"CASE "
"WHEN url BETWEEN 'place:' AND 'place;' "
"THEN 0 "
"ELSE -1 "
"END "
") "
);
NS_ENSURE_STATE(stmt);
nsCOMPtr<mozIStoragePendingStatement> ps;
(void)stmt->ExecuteAsync(nullptr, getter_AddRefs(ps));
}
// Temporary migration code for bug 396300
nsCOMPtr<mozIStorageStatement> moveUnfiledBookmarks;
rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"UPDATE moz_bookmarks "
"SET parent = ("
"SELECT folder_id "
"FROM moz_bookmarks_roots "
"WHERE root_name = :root_name "
") "
"WHERE type = :item_type "
"AND parent = ("
"SELECT folder_id "
"FROM moz_bookmarks_roots "
"WHERE root_name = :parent_name "
")"),
getter_AddRefs(moveUnfiledBookmarks));
NS_ENSURE_SUCCESS(rv, rv);
rv = moveUnfiledBookmarks->BindUTF8StringByName(
NS_LITERAL_CSTRING("root_name"), NS_LITERAL_CSTRING("unfiled")
);
NS_ENSURE_SUCCESS(rv, rv);
rv = moveUnfiledBookmarks->BindInt32ByName(
NS_LITERAL_CSTRING("item_type"), nsINavBookmarksService::TYPE_BOOKMARK
);
NS_ENSURE_SUCCESS(rv, rv);
rv = moveUnfiledBookmarks->BindUTF8StringByName(
NS_LITERAL_CSTRING("parent_name"), NS_LITERAL_CSTRING("places")
);
NS_ENSURE_SUCCESS(rv, rv);
rv = moveUnfiledBookmarks->Execute();
NS_ENSURE_SUCCESS(rv, rv);
// Create a statement to test for trigger creation
nsCOMPtr<mozIStorageStatement> triggerDetection;
rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT name "
"FROM sqlite_master "
"WHERE type = 'trigger' "
"AND name = :trigger_name"),
getter_AddRefs(triggerDetection));
NS_ENSURE_SUCCESS(rv, rv);
// Check for existence
bool triggerExists;
rv = triggerDetection->BindUTF8StringByName(
NS_LITERAL_CSTRING("trigger_name"),
NS_LITERAL_CSTRING("moz_historyvisits_afterinsert_v1_trigger")
);
NS_ENSURE_SUCCESS(rv, rv);
rv = triggerDetection->ExecuteStep(&triggerExists);
NS_ENSURE_SUCCESS(rv, rv);
rv = triggerDetection->Reset();
NS_ENSURE_SUCCESS(rv, rv);
// We need to create two triggers on moz_historyvists to maintain the
// accuracy of moz_places.visit_count. For this to work, we must ensure that
// all moz_places.visit_count values are correct.
// See bug 416313 for details.
if (!triggerExists) {
// First, we do a one-time reset of all the moz_places.visit_count values.
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"UPDATE moz_places SET visit_count = "
"(SELECT count(*) FROM moz_historyvisits "
"WHERE place_id = moz_places.id "
"AND visit_type NOT IN ") +
nsPrintfCString("(0,%d,%d,%d) ",
nsINavHistoryService::TRANSITION_EMBED,
nsINavHistoryService::TRANSITION_FRAMED_LINK,
nsINavHistoryService::TRANSITION_DOWNLOAD) +
NS_LITERAL_CSTRING(")"));
NS_ENSURE_SUCCESS(rv, rv);
// We used to create two triggers here, but we no longer need that with
// schema version eight and greater. We've removed their creation here as
// a result.
}
// Check for existence
rv = triggerDetection->BindUTF8StringByName(
NS_LITERAL_CSTRING("trigger_name"),
NS_LITERAL_CSTRING("moz_bookmarks_beforedelete_v1_trigger")
);
NS_ENSURE_SUCCESS(rv, rv);
rv = triggerDetection->ExecuteStep(&triggerExists);
NS_ENSURE_SUCCESS(rv, rv);
rv = triggerDetection->Reset();
NS_ENSURE_SUCCESS(rv, rv);
// We need to create one trigger on moz_bookmarks to remove unused keywords.
// See bug 421180 for details.
if (!triggerExists) {
// First, remove any existing dangling keywords
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DELETE FROM moz_keywords "
"WHERE id IN ("
"SELECT k.id "
"FROM moz_keywords k "
"LEFT OUTER JOIN moz_bookmarks b "
"ON b.keyword_id = k.id "
"WHERE b.id IS NULL"
")"));
NS_ENSURE_SUCCESS(rv, rv);
}
// Add the moz_inputhistory table, if missing.
bool tableExists = false;
rv = mMainConn->TableExists(NS_LITERAL_CSTRING("moz_inputhistory"),
&tableExists);
NS_ENSURE_SUCCESS(rv, rv);
if (!tableExists) {
rv = mMainConn->ExecuteSimpleSQL(CREATE_MOZ_INPUTHISTORY);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
Database::MigrateV8Up()
{
MOZ_ASSERT(NS_IsMainThread());
nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TRIGGER IF EXISTS moz_historyvisits_afterinsert_v1_trigger"));
NS_ENSURE_SUCCESS(rv, rv);
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP TRIGGER IF EXISTS moz_historyvisits_afterdelete_v1_trigger"));
NS_ENSURE_SUCCESS(rv, rv);
// bug #381795 - remove unused indexes
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP INDEX IF EXISTS moz_places_titleindex"));
NS_ENSURE_SUCCESS(rv, rv);
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP INDEX IF EXISTS moz_annos_item_idindex"));
NS_ENSURE_SUCCESS(rv, rv);
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP INDEX IF EXISTS moz_annos_place_idindex"));
NS_ENSURE_SUCCESS(rv, rv);
// Do a one-time re-creation of the moz_annos indexes (bug 415201)
bool oldIndexExists = false;
rv = mMainConn->IndexExists(NS_LITERAL_CSTRING("moz_annos_attributesindex"), &oldIndexExists);
NS_ENSURE_SUCCESS(rv, rv);
if (oldIndexExists) {
// drop old uri annos index
rv = mMainConn->ExecuteSimpleSQL(
NS_LITERAL_CSTRING("DROP INDEX moz_annos_attributesindex"));
NS_ENSURE_SUCCESS(rv, rv);
// create new uri annos index
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ANNOS_PLACEATTRIBUTE);
NS_ENSURE_SUCCESS(rv, rv);
// drop old item annos index
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"DROP INDEX IF EXISTS moz_items_annos_attributesindex"));
NS_ENSURE_SUCCESS(rv, rv);
// create new item annos index
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_ITEMSANNOS_PLACEATTRIBUTE);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
Database::MigrateV9Up()
{
MOZ_ASSERT(NS_IsMainThread());
// Added in Bug 488966. The last_visit_date column caches the last
// visit date, this enhances SELECT performances when we
// need to sort visits by visit date.
// The cached value is synced by triggers on every added or removed visit.
// See nsPlacesTriggers.h for details on the triggers.
bool oldIndexExists = false;
nsresult rv = mMainConn->IndexExists(
NS_LITERAL_CSTRING("moz_places_lastvisitdateindex"), &oldIndexExists);
NS_ENSURE_SUCCESS(rv, rv);
if (!oldIndexExists) {
// Add last_visit_date column to moz_places.
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_places ADD last_visit_date INTEGER"));
NS_ENSURE_SUCCESS(rv, rv);
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_LASTVISITDATE);
NS_ENSURE_SUCCESS(rv, rv);
// Now let's sync the column contents with real visit dates.
// This query can be really slow due to disk access, since it will basically
// dupe the table contents in the journal file, and then write them down
// in the database.
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"UPDATE moz_places SET last_visit_date = "
"(SELECT MAX(visit_date) "
"FROM moz_historyvisits "
"WHERE place_id = moz_places.id)"));
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
nsresult
Database::MigrateV10Up()
{
MOZ_ASSERT(NS_IsMainThread());
// LastModified is set to the same value as dateAdded on item creation.
// This way we can use lastModified index to sort.
nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"UPDATE moz_bookmarks SET lastModified = dateAdded "
"WHERE lastModified IS NULL"));
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
Database::MigrateV11Up()
{
MOZ_ASSERT(NS_IsMainThread());
// Temp tables are going away.
// For triggers correctness, every time we pass through this migration
// step, we must ensure correctness of visit_count values.
nsresult rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"UPDATE moz_places SET visit_count = "
"(SELECT count(*) FROM moz_historyvisits "
"WHERE place_id = moz_places.id "
"AND visit_type NOT IN ") +
nsPrintfCString("(0,%d,%d,%d) ",
nsINavHistoryService::TRANSITION_EMBED,
nsINavHistoryService::TRANSITION_FRAMED_LINK,
nsINavHistoryService::TRANSITION_DOWNLOAD) +
NS_LITERAL_CSTRING(")")
);
NS_ENSURE_SUCCESS(rv, rv);
// For existing profiles, we may not have a moz_bookmarks.guid column
nsCOMPtr<mozIStorageStatement> hasGuidStatement;
rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT guid FROM moz_bookmarks"),
getter_AddRefs(hasGuidStatement));
if (NS_FAILED(rv)) {
// moz_bookmarks grew a guid column. Add the column, but do not populate it
// with anything just yet. We will do that soon.
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_bookmarks "
"ADD COLUMN guid TEXT"
));
NS_ENSURE_SUCCESS(rv, rv);
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_BOOKMARKS_GUID);
NS_ENSURE_SUCCESS(rv, rv);
// moz_places grew a guid column. Add the column, but do not populate it
// with anything just yet. We will do that soon.
rv = mMainConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
"ALTER TABLE moz_places "
"ADD COLUMN guid TEXT"
));
NS_ENSURE_SUCCESS(rv, rv);
rv = mMainConn->ExecuteSimpleSQL(CREATE_IDX_MOZ_PLACES_GUID);
NS_ENSURE_SUCCESS(rv, rv);
}
// We need to update our guids before we do any real database work.
rv = CheckAndUpdateGUIDs();
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult
Database::MigrateV13Up()
{
@ -1961,6 +1413,60 @@ Database::MigrateV24Up()
return NS_OK;
}
nsresult
Database::MigrateV25Up()
{
MOZ_ASSERT(NS_IsMainThread());
// Change bookmark roots GUIDs to constant values.
// If moz_bookmarks_roots doesn't exist anymore, it's because we finally have
// been able to remove it. In such a case, we already assigned constant GUIDs
// to the roots and we can skip this migration.
{
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"SELECT root_name FROM moz_bookmarks_roots"
), getter_AddRefs(stmt));
if (NS_FAILED(rv)) {
return NS_OK;
}
}
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = mMainConn->CreateStatement(NS_LITERAL_CSTRING(
"UPDATE moz_bookmarks SET guid = :guid "
"WHERE id = (SELECT folder_id FROM moz_bookmarks_roots WHERE root_name = :name) "
), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
const char *rootNames[] = { "places", "menu", "toolbar", "tags", "unfiled" };
const char *rootGuids[] = { "root________"
, "menu________"
, "toolbar_____"
, "tags________"
, "unfiled_____"
};
for (uint32_t i = 0; i < ArrayLength(rootNames); ++i) {
// Since this is using the synchronous API, we cannot use
// a BindingParamsArray.
mozStorageStatementScoper scoper(stmt);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("name"),
nsDependentCString(rootNames[i]));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("guid"),
nsDependentCString(rootGuids[i]));
NS_ENSURE_SUCCESS(rv, rv);
rv = stmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
void
Database::Shutdown()
{

View File

@ -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 24
#define DATABASE_SCHEMA_VERSION 25
// Fired after Places inited.
#define TOPIC_PLACES_INIT_COMPLETE "places-init-complete"
@ -260,11 +260,6 @@ protected:
/**
* Helpers used by schema upgrades.
*/
nsresult MigrateV7Up();
nsresult MigrateV8Up();
nsresult MigrateV9Up();
nsresult MigrateV10Up();
nsresult MigrateV11Up();
nsresult MigrateV13Up();
nsresult MigrateV14Up();
nsresult MigrateV15Up();
@ -277,9 +272,9 @@ protected:
nsresult MigrateV22Up();
nsresult MigrateV23Up();
nsresult MigrateV24Up();
nsresult MigrateV25Up();
nsresult UpdateBookmarkRootTitles();
nsresult CheckAndUpdateGUIDs();
private:
~Database();

View File

@ -502,9 +502,8 @@ this.PlacesBackups = {
* * children: array of child items in a folder
*/
getBookmarksTree: Task.async(function* () {
let rootGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.placesRootId);
let startTime = Date.now();
let root = yield PlacesUtils.promiseBookmarksTree(rootGuid, {
let root = yield PlacesUtils.promiseBookmarksTree(PlacesUtils.bookmarks.rootGuid, {
excludeItemsCallback: aItem => {
return aItem.annos &&
aItem.annos.find(a => a.name == PlacesUtils.EXCLUDE_FROM_BACKUP_ANNO);

View File

@ -334,7 +334,7 @@ this.PlacesDBUtils = {
)`);
cleanupStatements.push(deleteOrphanAnnos);
// MOZ_BOOKMARKS_ROOTS
// Bookmarks roots
// C.1 fix missing Places root
// Bug 477739 shows a case where the root could be wrongly removed
// due to an endianness issue. We try to fix broken roots here.
@ -346,17 +346,21 @@ this.PlacesDBUtils = {
let createPlacesRoot = DBConn.createAsyncStatement(
`INSERT INTO moz_bookmarks (id, type, fk, parent, position, title,
guid)
VALUES (:places_root, 2, NULL, 0, 0, :title, GENERATE_GUID())`);
VALUES (:places_root, 2, NULL, 0, 0, :title, :guid)`);
createPlacesRoot.params["places_root"] = PlacesUtils.placesRootId;
createPlacesRoot.params["title"] = "";
createPlacesRoot.params["guid"] = PlacesUtils.bookmarks.rootGuid;
cleanupStatements.push(createPlacesRoot);
// Now ensure that other roots are children of Places root.
let fixPlacesRootChildren = DBConn.createAsyncStatement(
`UPDATE moz_bookmarks SET parent = :places_root WHERE id IN
(SELECT folder_id FROM moz_bookmarks_roots
WHERE folder_id <> :places_root)`);
`UPDATE moz_bookmarks SET parent = :places_root WHERE guid IN
( :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid )`);
fixPlacesRootChildren.params["places_root"] = PlacesUtils.placesRootId;
fixPlacesRootChildren.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
fixPlacesRootChildren.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
fixPlacesRootChildren.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
fixPlacesRootChildren.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
cleanupStatements.push(fixPlacesRootChildren);
}
selectPlacesRoot.finalize();
@ -400,20 +404,25 @@ this.PlacesDBUtils = {
// D.1 remove items without a valid place
// if fk IS NULL we fix them in D.7
let deleteNoPlaceItems = DBConn.createAsyncStatement(
`DELETE FROM moz_bookmarks WHERE id NOT IN (
SELECT folder_id FROM moz_bookmarks_roots /* skip roots */
`DELETE FROM moz_bookmarks WHERE guid NOT IN (
:rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid /* skip roots */
) AND id IN (
SELECT b.id FROM moz_bookmarks b
WHERE fk NOT NULL AND b.type = :bookmark_type
AND NOT EXISTS (SELECT url FROM moz_places WHERE id = b.fk LIMIT 1)
)`);
deleteNoPlaceItems.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK;
deleteNoPlaceItems.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
deleteNoPlaceItems.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
deleteNoPlaceItems.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
deleteNoPlaceItems.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
deleteNoPlaceItems.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
cleanupStatements.push(deleteNoPlaceItems);
// D.2 remove items that are not uri bookmarks from tag containers
let deleteBogusTagChildren = DBConn.createAsyncStatement(
`DELETE FROM moz_bookmarks WHERE id NOT IN (
SELECT folder_id FROM moz_bookmarks_roots /* skip roots */
`DELETE FROM moz_bookmarks WHERE guid NOT IN (
:rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid /* skip roots */
) AND id IN (
SELECT b.id FROM moz_bookmarks b
WHERE b.parent IN
@ -422,12 +431,17 @@ this.PlacesDBUtils = {
)`);
deleteBogusTagChildren.params["tags_folder"] = PlacesUtils.tagsFolderId;
deleteBogusTagChildren.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK;
deleteBogusTagChildren.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
deleteBogusTagChildren.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
deleteBogusTagChildren.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
deleteBogusTagChildren.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
deleteBogusTagChildren.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
cleanupStatements.push(deleteBogusTagChildren);
// D.3 remove empty tags
let deleteEmptyTags = DBConn.createAsyncStatement(
`DELETE FROM moz_bookmarks WHERE id NOT IN (
SELECT folder_id FROM moz_bookmarks_roots /* skip roots */
`DELETE FROM moz_bookmarks WHERE guid NOT IN (
:rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid /* skip roots */
) AND id IN (
SELECT b.id FROM moz_bookmarks b
WHERE b.id IN
@ -436,31 +450,45 @@ this.PlacesDBUtils = {
(SELECT id from moz_bookmarks WHERE parent = b.id LIMIT 1)
)`);
deleteEmptyTags.params["tags_folder"] = PlacesUtils.tagsFolderId;
deleteEmptyTags.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
deleteEmptyTags.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
deleteEmptyTags.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
deleteEmptyTags.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
deleteEmptyTags.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
cleanupStatements.push(deleteEmptyTags);
// D.4 move orphan items to unsorted folder
let fixOrphanItems = DBConn.createAsyncStatement(
`UPDATE moz_bookmarks SET parent = :unsorted_folder WHERE id NOT IN (
SELECT folder_id FROM moz_bookmarks_roots /* skip roots */
`UPDATE moz_bookmarks SET parent = :unsorted_folder WHERE guid NOT IN (
:rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid /* skip roots */
) AND id IN (
SELECT b.id FROM moz_bookmarks b
WHERE b.parent <> 0 /* exclude Places root */
AND NOT EXISTS
WHERE NOT EXISTS
(SELECT id FROM moz_bookmarks WHERE id = b.parent LIMIT 1)
)`);
fixOrphanItems.params["unsorted_folder"] = PlacesUtils.unfiledBookmarksFolderId;
fixOrphanItems.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
fixOrphanItems.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
fixOrphanItems.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
fixOrphanItems.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
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 id NOT IN (
SELECT folder_id FROM moz_bookmarks_roots /* skip roots */
`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
@ -468,8 +496,8 @@ this.PlacesDBUtils = {
// If they have a valid fk convert them to bookmarks. Later in D.9 we
// will move eventual children to unsorted bookmarks.
let fixBookmarksAsFolders = DBConn.createAsyncStatement(
`UPDATE moz_bookmarks SET type = :bookmark_type WHERE id NOT IN (
SELECT folder_id FROM moz_bookmarks_roots /* skip roots */
`UPDATE moz_bookmarks SET type = :bookmark_type WHERE guid NOT IN (
:rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid /* skip roots */
) AND id IN (
SELECT id FROM moz_bookmarks b
WHERE type IN (:folder_type, :separator_type)
@ -478,14 +506,19 @@ this.PlacesDBUtils = {
fixBookmarksAsFolders.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK;
fixBookmarksAsFolders.params["folder_type"] = PlacesUtils.bookmarks.TYPE_FOLDER;
fixBookmarksAsFolders.params["separator_type"] = PlacesUtils.bookmarks.TYPE_SEPARATOR;
fixBookmarksAsFolders.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
fixBookmarksAsFolders.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
fixBookmarksAsFolders.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
fixBookmarksAsFolders.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
fixBookmarksAsFolders.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
cleanupStatements.push(fixBookmarksAsFolders);
// D.7 fix wrong item types
// Bookmarks should have an fk, if they don't have any, convert them to
// folders.
let fixFoldersAsBookmarks = DBConn.createAsyncStatement(
`UPDATE moz_bookmarks SET type = :folder_type WHERE id NOT IN (
SELECT folder_id FROM moz_bookmarks_roots /* skip roots */
`UPDATE moz_bookmarks SET type = :folder_type WHERE guid NOT IN (
:rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid /* skip roots */
) AND id IN (
SELECT id FROM moz_bookmarks b
WHERE type = :bookmark_type
@ -493,14 +526,19 @@ this.PlacesDBUtils = {
)`);
fixFoldersAsBookmarks.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK;
fixFoldersAsBookmarks.params["folder_type"] = PlacesUtils.bookmarks.TYPE_FOLDER;
fixFoldersAsBookmarks.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
fixFoldersAsBookmarks.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
fixFoldersAsBookmarks.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
fixFoldersAsBookmarks.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
fixFoldersAsBookmarks.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
cleanupStatements.push(fixFoldersAsBookmarks);
// D.9 fix wrong parents
// Items cannot have separators or other bookmarks
// as parent, if they have bad parent move them to unsorted bookmarks.
let fixInvalidParents = DBConn.createAsyncStatement(
`UPDATE moz_bookmarks SET parent = :unsorted_folder WHERE id NOT IN (
SELECT folder_id FROM moz_bookmarks_roots /* skip roots */
`UPDATE moz_bookmarks SET parent = :unsorted_folder WHERE guid NOT IN (
:rootGuid, :menuGuid, :toolbarGuid, :unfiledGuid, :tagsGuid /* skip roots */
) AND id IN (
SELECT id FROM moz_bookmarks b
WHERE EXISTS
@ -511,6 +549,11 @@ this.PlacesDBUtils = {
fixInvalidParents.params["unsorted_folder"] = PlacesUtils.unfiledBookmarksFolderId;
fixInvalidParents.params["bookmark_type"] = PlacesUtils.bookmarks.TYPE_BOOKMARK;
fixInvalidParents.params["separator_type"] = PlacesUtils.bookmarks.TYPE_SEPARATOR;
fixInvalidParents.params["rootGuid"] = PlacesUtils.bookmarks.rootGuid;
fixInvalidParents.params["menuGuid"] = PlacesUtils.bookmarks.menuGuid;
fixInvalidParents.params["toolbarGuid"] = PlacesUtils.bookmarks.toolbarGuid;
fixInvalidParents.params["unfiledGuid"] = PlacesUtils.bookmarks.unfiledGuid;
fixInvalidParents.params["tagsGuid"] = PlacesUtils.bookmarks.tagsGuid;
cleanupStatements.push(fixInvalidParents);
// D.10 recalculate positions

View File

@ -1334,11 +1334,10 @@ PT.Tag.prototype = {
if (yield promiseIsBookmarked(currentURI)) {
// Tagging is only allowed for bookmarked URIs (but see 424160).
let unfiledGuid =
yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let createTxn = TransactionsHistory.getRawTransaction(
PT.NewBookmark({ uri: currentURI
, tags: aTags, parentGuid: unfiledGuid }));
, tags: aTags
, parentGuid: PlacesUtils.bookmarks.unfiledGuid }));
yield createTxn.execute();
onUndo.unshift(createTxn.undo.bind(createTxn));
onRedo.push(createTxn.redo.bind(createTxn));

View File

@ -1692,7 +1692,7 @@ this.PlacesUtils = {
if (!aItemGuid)
aItemGuid = yield this.promiseItemGuid(PlacesUtils.placesRootId);
aItemGuid = this.bookmarks.rootGuid;
let hasExcludeItemsCallback =
aOptions.hasOwnProperty("excludeItemsCallback");

View File

@ -277,34 +277,35 @@ nsNavBookmarks::ReadRoots()
{
nsCOMPtr<mozIStorageStatement> stmt;
nsresult rv = mDB->MainConn()->CreateStatement(NS_LITERAL_CSTRING(
"SELECT root_name, folder_id FROM moz_bookmarks_roots"
"SELECT guid, id FROM moz_bookmarks WHERE guid IN ( "
"'root________', 'menu________', 'toolbar_____', "
"'tags________', 'unfiled_____' )"
), getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
bool hasResult;
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
nsAutoCString rootName;
rv = stmt->GetUTF8String(0, rootName);
nsAutoCString guid;
rv = stmt->GetUTF8String(0, guid);
NS_ENSURE_SUCCESS(rv, rv);
int64_t rootId;
rv = stmt->GetInt64(1, &rootId);
int64_t id;
rv = stmt->GetInt64(1, &id);
NS_ENSURE_SUCCESS(rv, rv);
NS_ABORT_IF_FALSE(rootId != 0, "Root id is 0, that is an invalid value.");
if (rootName.EqualsLiteral("places")) {
mRoot = rootId;
if (guid.EqualsLiteral("root________")) {
mRoot = id;
}
else if (rootName.EqualsLiteral("menu")) {
mMenuRoot = rootId;
else if (guid.EqualsLiteral("menu________")) {
mMenuRoot = id;
}
else if (rootName.EqualsLiteral("toolbar")) {
mToolbarRoot = rootId;
else if (guid.EqualsLiteral("toolbar_____")) {
mToolbarRoot = id;
}
else if (rootName.EqualsLiteral("tags")) {
mTagsRoot = rootId;
else if (guid.EqualsLiteral("tags________")) {
mTagsRoot = id;
}
else if (rootName.EqualsLiteral("unfiled")) {
mUnfiledRoot = rootId;
else if (guid.EqualsLiteral("unfiled_____")) {
mUnfiledRoot = id;
}
}

View File

@ -8,11 +8,10 @@ add_task(function* test_eraseEverything() {
let frecencyForMozilla = frecencyForUrl("http://example.com/");
Assert.ok(frecencyForExample > 0);
Assert.ok(frecencyForMozilla > 0);
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let unfiledFolder = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let unfiledFolder = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
checkBookmarkObject(unfiledFolder);
let unfiledBookmark = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let unfiledBookmark = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
keyword: "kw1" });
@ -25,11 +24,10 @@ add_task(function* test_eraseEverything() {
PlacesUtils.annotations.setItemAnnotation((yield PlacesUtils.promiseItemId(unfiledBookmarkInFolder.guid)),
"testanno1", "testvalue1", 0, 0);
let menuGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.bookmarksMenuFolderId);
let menuFolder = yield PlacesUtils.bookmarks.insert({ parentGuid: menuGuid,
let menuFolder = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.menuGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
checkBookmarkObject(menuFolder);
let menuBookmark = yield PlacesUtils.bookmarks.insert({ parentGuid: menuGuid,
let menuBookmark = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.menuGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
keyword: "kw2" });
@ -42,11 +40,10 @@ add_task(function* test_eraseEverything() {
PlacesUtils.annotations.setItemAnnotation((yield PlacesUtils.promiseItemId(menuBookmarkInFolder.guid)),
"testanno1", "testvalue1", 0, 0);
let toolbarGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.toolbarFolderId);
let toolbarFolder = yield PlacesUtils.bookmarks.insert({ parentGuid: toolbarGuid,
let toolbarFolder = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
checkBookmarkObject(toolbarFolder);
let toolbarBookmark = yield PlacesUtils.bookmarks.insert({ parentGuid: toolbarGuid,
let toolbarBookmark = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.toolbarGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
keyword: "kw3" });
@ -82,16 +79,11 @@ add_task(function* test_eraseEverything_roots() {
yield PlacesUtils.bookmarks.eraseEverything();
// Ensure the roots have not been removed.
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
Assert.ok(yield PlacesUtils.bookmarks.fetch(unfiledGuid));
let toolbarGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.toolbarFolderId);
Assert.ok(yield PlacesUtils.bookmarks.fetch(toolbarGuid));
let menuGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.bookmarksMenuFolderId);
Assert.ok(yield PlacesUtils.bookmarks.fetch(menuGuid));
let tagsGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.tagsFolderId);
Assert.ok(yield PlacesUtils.bookmarks.fetch(tagsGuid));
let rootGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.placesRootId);
Assert.ok(yield PlacesUtils.bookmarks.fetch(rootGuid));
Assert.ok(yield PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.unfiledGuid));
Assert.ok(yield PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.toolbarGuid));
Assert.ok(yield PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.menuGuid));
Assert.ok(yield PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.tagsGuid));
Assert.ok(yield PlacesUtils.bookmarks.fetch(PlacesUtils.bookmarks.rootGuid));
});
function run_test() {

View File

@ -100,8 +100,7 @@ add_task(function* fetch_nonexistent_guid() {
});
add_task(function* fetch_bookmark() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
title: "a bookmark" });
@ -115,7 +114,7 @@ add_task(function* fetch_bookmark() {
Assert.deepEqual(gAccumulator.results[0], bm1);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.parentGuid, unfiledGuid);
Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm2.index, 0);
Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
@ -127,8 +126,7 @@ add_task(function* fetch_bookmark() {
});
add_task(function* fetch_bookmar_empty_title() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
title: "" });
@ -146,8 +144,7 @@ add_task(function* fetch_bookmar_empty_title() {
});
add_task(function* fetch_folder() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "a folder" });
checkBookmarkObject(bm1);
@ -156,7 +153,7 @@ add_task(function* fetch_folder() {
checkBookmarkObject(bm2);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.parentGuid, unfiledGuid);
Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm2.index, 0);
Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_FOLDER);
@ -168,8 +165,7 @@ add_task(function* fetch_folder() {
});
add_task(function* fetch_folder_empty_title() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "" });
checkBookmarkObject(bm1);
@ -185,9 +181,7 @@ add_task(function* fetch_folder_empty_title() {
});
add_task(function* fetch_separator() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_SEPARATOR });
checkBookmarkObject(bm1);
@ -195,7 +189,7 @@ add_task(function* fetch_separator() {
checkBookmarkObject(bm2);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.parentGuid, unfiledGuid);
Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm2.index, 0);
Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_SEPARATOR);
@ -215,8 +209,7 @@ add_task(function* fetch_byposition_nonexisting_parentGuid() {
});
add_task(function* fetch_byposition_nonexisting_index() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.fetch({ parentGuid: unfiledGuid,
let bm = yield PlacesUtils.bookmarks.fetch({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
index: 100 },
gAccumulator.callback);
Assert.equal(bm, null);
@ -224,8 +217,7 @@ add_task(function* fetch_byposition_nonexisting_index() {
});
add_task(function* fetch_byposition() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
title: "a bookmark" });
@ -240,7 +232,7 @@ add_task(function* fetch_byposition() {
Assert.deepEqual(gAccumulator.results[0], bm1);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.parentGuid, unfiledGuid);
Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm2.index, 0);
Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
@ -257,8 +249,7 @@ add_task(function* fetch_byurl_nonexisting() {
});
add_task(function* fetch_byurl() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://byurl.com/",
title: "a bookmark" });
@ -272,14 +263,14 @@ add_task(function* fetch_byurl() {
Assert.deepEqual(gAccumulator.results[0], bm1);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.parentGuid, unfiledGuid);
Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
Assert.equal(bm2.url.href, "http://byurl.com/");
Assert.equal(bm2.title, "a bookmark");
Assert.ok(!("keyword" in bm2));
let bm3 = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm3 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://byurl.com/",
title: "a bookmark" });
@ -312,8 +303,7 @@ add_task(function* fetch_bykeyword_nonexisting() {
});
add_task(function* fetch_bykeyword() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://bykeyword1.com/",
keyword: "bykeyword" });
@ -327,13 +317,13 @@ add_task(function* fetch_bykeyword() {
Assert.deepEqual(gAccumulator.results[0], bm1);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.parentGuid, unfiledGuid);
Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
Assert.equal(bm2.url.href, "http://bykeyword1.com/");
Assert.equal(bm2.keyword, "bykeyword");
let bm3 = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm3 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://bykeyword2.com/",
keyword: "bykeyword" });

View File

@ -115,12 +115,11 @@ add_task(function* long_title_trim() {
for (let i = 0; i < 4096; i++) {
longtitle += "a";
}
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: longtitle });
checkBookmarkObject(bm);
Assert.equal(bm.parentGuid, unfiledGuid);
Assert.equal(bm.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm.index, 0);
Assert.equal(bm.dateAdded, bm.lastModified);
Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_FOLDER);
@ -130,12 +129,11 @@ add_task(function* long_title_trim() {
});
add_task(function* create_separator() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
index: PlacesUtils.bookmarks.DEFAULT_INDEX });
checkBookmarkObject(bm);
Assert.equal(bm.parentGuid, unfiledGuid);
Assert.equal(bm.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm.index, 1);
Assert.equal(bm.dateAdded, bm.lastModified);
Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_SEPARATOR);
@ -143,9 +141,8 @@ add_task(function* create_separator() {
});
add_task(function* create_separator_w_title_fail() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
try {
yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
title: "a separator" });
Assert.ok(false, "Trying to set title for a separator should reject");
@ -162,14 +159,13 @@ add_task(function* create_separator_invalid_parent_fail() {
});
add_task(function* create_separator_given_guid() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
index: PlacesUtils.bookmarks.DEFAULT_INDEX,
guid: "123456789012" });
checkBookmarkObject(bm);
Assert.equal(bm.guid, "123456789012");
Assert.equal(bm.parentGuid, unfiledGuid);
Assert.equal(bm.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm.index, 2);
Assert.equal(bm.dateAdded, bm.lastModified);
Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_SEPARATOR);
@ -184,12 +180,11 @@ add_task(function* create_item_given_guid_no_type_fail() {
});
add_task(function* create_separator_big_index() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
index: 9999 });
checkBookmarkObject(bm);
Assert.equal(bm.parentGuid, unfiledGuid);
Assert.equal(bm.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm.index, 3);
Assert.equal(bm.dateAdded, bm.lastModified);
Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_SEPARATOR);
@ -199,8 +194,7 @@ add_task(function* create_separator_big_index() {
add_task(function* create_separator_given_dateAdded() {
let time = new Date();
let past = new Date(time - 86400000);
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
dateAdded: past });
checkBookmarkObject(bm);
@ -209,11 +203,10 @@ add_task(function* create_separator_given_dateAdded() {
});
add_task(function* create_folder() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
checkBookmarkObject(bm);
Assert.equal(bm.parentGuid, unfiledGuid);
Assert.equal(bm.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm.dateAdded, bm.lastModified);
Assert.equal(bm.type, PlacesUtils.bookmarks.TYPE_FOLDER);
Assert.ok(!("title" in bm), "title should not be set");
@ -231,8 +224,7 @@ add_task(function* create_folder() {
});
add_task(function* create_bookmark() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
let parentGuid = bm.guid;
@ -278,8 +270,7 @@ add_task(function* create_bookmark() {
});
add_task(function* create_bookmark_frecency() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
title: "a bookmark" });

View File

@ -2,10 +2,9 @@
* http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(function* insert_separator_notification() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let observer = expectNotifications();
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
parentGuid: unfiledGuid});
parentGuid: PlacesUtils.bookmarks.unfiledGuid});
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
observer.check([ { name: "onItemAdded",
@ -16,10 +15,9 @@ add_task(function* insert_separator_notification() {
});
add_task(function* insert_folder_notification() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let observer = expectNotifications();
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: unfiledGuid,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
title: "a folder" });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
@ -31,10 +29,9 @@ add_task(function* insert_folder_notification() {
});
add_task(function* insert_folder_notitle_notification() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let observer = expectNotifications();
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: unfiledGuid });
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
observer.check([ { name: "onItemAdded",
@ -45,10 +42,9 @@ add_task(function* insert_folder_notitle_notification() {
});
add_task(function* insert_bookmark_notification() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let observer = expectNotifications();
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: unfiledGuid,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: new URL("http://example.com/"),
title: "a bookmark" });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
@ -61,10 +57,9 @@ add_task(function* insert_bookmark_notification() {
});
add_task(function* insert_bookmark_notitle_notification() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let observer = expectNotifications();
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: unfiledGuid,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: new URL("http://example.com/") });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
@ -76,10 +71,9 @@ add_task(function* insert_bookmark_notitle_notification() {
});
add_task(function* insert_bookmark_keyword_notification() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let observer = expectNotifications();
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: unfiledGuid,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: new URL("http://example.com/"),
keyword: "kw" });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
@ -96,16 +90,14 @@ add_task(function* insert_bookmark_keyword_notification() {
});
add_task(function* insert_bookmark_tag_notification() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: unfiledGuid,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: new URL("http://tag.example.com/") });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
let tagsGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.tagsFolderId);
let tagFolder = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: tagsGuid,
parentGuid: PlacesUtils.bookmarks.tagsGuid,
title: "tag" });
let observer = expectNotifications();
let tag = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
@ -130,9 +122,8 @@ add_task(function* insert_bookmark_tag_notification() {
});
add_task(function* update_bookmark_lastModified() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: unfiledGuid,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: new URL("http://lastmod.example.com/") });
let observer = expectNotifications();
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
@ -148,9 +139,8 @@ add_task(function* update_bookmark_lastModified() {
});
add_task(function* update_bookmark_title() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: unfiledGuid,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: new URL("http://title.example.com/") });
let observer = expectNotifications();
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
@ -166,9 +156,8 @@ add_task(function* update_bookmark_title() {
});
add_task(function* update_bookmark_uri() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: unfiledGuid,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: new URL("http://url.example.com/") });
let observer = expectNotifications();
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
@ -184,9 +173,8 @@ add_task(function* update_bookmark_uri() {
});
add_task(function* update_bookmark_keyword() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: unfiledGuid,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: new URL("http://keyword.example.com/") });
let observer = expectNotifications();
bm = yield PlacesUtils.bookmarks.update({ guid: bm.guid,
@ -202,9 +190,8 @@ add_task(function* update_bookmark_keyword() {
});
add_task(function* remove_bookmark() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: unfiledGuid,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: new URL("http://remove.example.com/") });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
@ -220,9 +207,8 @@ add_task(function* remove_bookmark() {
});
add_task(function* remove_folder() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: unfiledGuid });
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
@ -235,16 +221,14 @@ add_task(function* remove_folder() {
});
add_task(function* remove_bookmark_tag_notification() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: unfiledGuid,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: new URL("http://untag.example.com/") });
let itemId = yield PlacesUtils.promiseItemId(bm.guid);
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
let tagsGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.tagsFolderId);
let tagFolder = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: tagsGuid,
parentGuid: PlacesUtils.bookmarks.tagsGuid,
title: "tag" });
let tag = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: tagFolder.guid,
@ -269,9 +253,8 @@ add_task(function* eraseEverything_notification() {
// Let's start from a clean situation.
yield PlacesUtils.bookmarks.eraseEverything();
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let folder1 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: unfiledGuid });
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
let folder1Id = yield PlacesUtils.promiseItemId(folder1.guid);
let folder1ParentId = yield PlacesUtils.promiseItemId(folder1.parentGuid);
@ -282,20 +265,18 @@ add_task(function* eraseEverything_notification() {
let parentId = yield PlacesUtils.promiseItemId(bm.parentGuid);
let folder2 = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: unfiledGuid });
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
let folder2Id = yield PlacesUtils.promiseItemId(folder2.guid);
let folder2ParentId = yield PlacesUtils.promiseItemId(folder2.parentGuid);
let toolbarGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.toolbarFolderId);
let toolbarBm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: toolbarGuid,
parentGuid: PlacesUtils.bookmarks.toolbarGuid,
url: new URL("http://example.com/") });
let toolbarBmId = yield PlacesUtils.promiseItemId(toolbarBm.guid);
let toolbarBmParentId = yield PlacesUtils.promiseItemId(toolbarBm.parentGuid);
let menuGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.bookmarksMenuFolderId);
let menuBm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: menuGuid,
parentGuid: PlacesUtils.bookmarks.menuGuid,
url: new URL("http://example.com/") });
let menuBmId = yield PlacesUtils.promiseItemId(menuBm.guid);
let menuBmParentId = yield PlacesUtils.promiseItemId(menuBm.parentGuid);

View File

@ -44,17 +44,15 @@ add_task(function* remove_nonexistent_guid() {
});
add_task(function* remove_roots_fail() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
try {
yield PlacesUtils.bookmarks.remove(unfiledGuid);
yield PlacesUtils.bookmarks.remove(PlacesUtils.bookmarks.unfiledGuid);
Assert.ok(false, "Should have thrown");
} catch (ex) {
Assert.ok(/It's not possible to remove Places root folders/.test(ex));
}
let placesRootGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.placesRootId);
try {
yield PlacesUtils.bookmarks.remove(placesRootGuid);
yield PlacesUtils.bookmarks.remove(PlacesUtils.bookmarks.rootGuid);
Assert.ok(false, "Should have thrown");
} catch (ex) {
Assert.ok(/It's not possible to remove Places root folders/.test(ex));
@ -62,8 +60,7 @@ add_task(function* remove_roots_fail() {
});
add_task(function* remove_bookmark() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
title: "a bookmark" });
@ -73,7 +70,7 @@ add_task(function* remove_bookmark() {
checkBookmarkObject(bm2);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.parentGuid, unfiledGuid);
Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm2.index, 0);
Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_BOOKMARK);
@ -84,8 +81,7 @@ add_task(function* remove_bookmark() {
add_task(function* remove_bookmark_orphans() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
title: "a bookmark",
@ -111,8 +107,7 @@ add_task(function* remove_bookmark_orphans() {
});
add_task(function* remove_bookmark_empty_title() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
title: "" });
@ -128,8 +123,7 @@ add_task(function* remove_bookmark_empty_title() {
});
add_task(function* remove_folder() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "a folder" });
checkBookmarkObject(bm1);
@ -138,7 +132,7 @@ add_task(function* remove_folder() {
checkBookmarkObject(bm2);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.parentGuid, unfiledGuid);
Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm2.index, 0);
Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_FOLDER);
@ -148,8 +142,7 @@ add_task(function* remove_folder() {
});
add_task(function* remove_folder_empty_title() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "" });
checkBookmarkObject(bm1);
@ -163,8 +156,7 @@ add_task(function* remove_folder_empty_title() {
});
add_task(function* remove_separator() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm1 = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_SEPARATOR });
checkBookmarkObject(bm1);
@ -172,7 +164,7 @@ add_task(function* remove_separator() {
checkBookmarkObject(bm2);
Assert.deepEqual(bm1, bm2);
Assert.equal(bm2.parentGuid, unfiledGuid);
Assert.equal(bm2.parentGuid, PlacesUtils.bookmarks.unfiledGuid);
Assert.equal(bm2.index, 0);
Assert.deepEqual(bm2.dateAdded, bm2.lastModified);
Assert.equal(bm2.type, PlacesUtils.bookmarks.TYPE_SEPARATOR);

View File

@ -97,9 +97,8 @@ add_task(function* nonexisting_bookmark_throws() {
});
add_task(function* invalid_properties_for_existing_bookmark() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
parentGuid: unfiledGuid,
parentGuid: PlacesUtils.bookmarks.unfiledGuid,
url: "http://example.com/" });
try {
@ -145,7 +144,7 @@ add_task(function* invalid_properties_for_existing_bookmark() {
}
let folder = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_FOLDER,
parentGuid: unfiledGuid });
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
try {
yield PlacesUtils.bookmarks.update({ guid: folder.guid,
url: "http://example.com/" });
@ -162,7 +161,7 @@ add_task(function* invalid_properties_for_existing_bookmark() {
}
let separator = yield PlacesUtils.bookmarks.insert({ type: PlacesUtils.bookmarks.TYPE_SEPARATOR,
parentGuid: unfiledGuid });
parentGuid: PlacesUtils.bookmarks.unfiledGuid });
try {
yield PlacesUtils.bookmarks.update({ guid: separator.guid,
url: "http://example.com/" });
@ -191,8 +190,7 @@ add_task(function* long_title_trim() {
for (let i = 0; i < 4096; i++) {
longtitle += "a";
}
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "title" });
checkBookmarkObject(bm);
@ -207,9 +205,8 @@ add_task(function* long_title_trim() {
});
add_task(function* update_lastModified() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let yesterday = new Date(Date.now() - 86400000);
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
title: "title",
dateAdded: yesterday });
@ -242,8 +239,7 @@ add_task(function* update_lastModified() {
});
add_task(function* update_keyword() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
title: "title",
@ -277,8 +273,7 @@ add_task(function* update_keyword() {
});
add_task(function* update_url() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_BOOKMARK,
url: "http://example.com/",
title: "title",
@ -303,8 +298,7 @@ add_task(function* update_url() {
});
add_task(function* update_index() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let parent = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let parent = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER }) ;
let f1 = yield PlacesUtils.bookmarks.insert({ parentGuid: parent.guid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
@ -344,8 +338,7 @@ add_task(function* update_index() {
});
add_task(function* update_move_folder_into_descendant_throws() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let parent = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let parent = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER }) ;
let descendant = yield PlacesUtils.bookmarks.insert({ parentGuid: parent.guid,
type: PlacesUtils.bookmarks.TYPE_FOLDER });
@ -370,8 +363,7 @@ add_task(function* update_move_folder_into_descendant_throws() {
});
add_task(function* update_move() {
let unfiledGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.unfiledBookmarksFolderId);
let parent = yield PlacesUtils.bookmarks.insert({ parentGuid: unfiledGuid,
let parent = yield PlacesUtils.bookmarks.insert({ parentGuid: PlacesUtils.bookmarks.unfiledGuid,
type: PlacesUtils.bookmarks.TYPE_FOLDER }) ;
let bm = yield PlacesUtils.bookmarks.insert({ parentGuid: parent.guid,
url: "http://example.com/",

View File

@ -3,7 +3,8 @@
* 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 = 24;
const CURRENT_SCHEMA_VERSION = 25;
const FIRST_UPGRADABLE_SCHEMA_VERSION = 11;
const NS_APP_USER_PROFILE_50_DIR = "ProfD";
const NS_APP_PROFILE_DIR_STARTUP = "ProfDS";
@ -43,6 +44,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "PlacesTransactions",
"resource://gre/modules/PlacesTransactions.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
"resource://gre/modules/Sqlite.jsm");
// This imports various other objects in addition to PlacesUtils.
Cu.import("resource://gre/modules/PlacesUtils.jsm");

View File

@ -1,10 +1,9 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const Ci = Components.interfaces;
const Cc = Components.classes;
const Cr = Components.results;
const Cu = Components.utils;
"use strict"
const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
Cu.import("resource://gre/modules/Services.jsm");
@ -16,25 +15,31 @@ let (commonFile = do_get_file("../head_common.js", false)) {
// Put any other stuff relative to this test folder below.
const kDBName = "places.sqlite";
const DB_FILENAME = "places.sqlite";
/**
* Sets the database to use for the given test. This should be the very first
* thing we do otherwise, this database will not be used!
* thing in the test, otherwise this database will not be used!
*
* @param aFileName
* The filename of the database to use. This database must exist in
* toolkit/components/places/tests/migration!
* @return {Promise}
*/
function setPlacesDatabase(aFileName)
{
let file = do_get_file(aFileName);
let setupPlacesDatabase = Task.async(function* (aFileName) {
let currentDir = yield OS.File.getCurrentDirectory();
let src = OS.Path.join(currentDir, aFileName);
Assert.ok((yield OS.File.exists(src)), "Database file found");
// Ensure that our database doesn't already exist.
let (dbFile = gProfD.clone()) {
dbFile.append(kDBName);
do_check_false(dbFile.exists());
}
let dest = OS.Path.join(OS.Constants.Path.profileDir, DB_FILENAME);
Assert.ok(!(yield OS.File.exists(dest)), "Database file should not exist yet");
file.copyToFollowingLinks(gProfD, kDBName);
yield OS.File.copy(src, dest);
});
// This works provided all tests in this folder use add_task.
function run_test() {
run_next_test();
}

View File

@ -0,0 +1,19 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(function* setup() {
yield setupPlacesDatabase(`places_v${CURRENT_SCHEMA_VERSION}.sqlite`);
// Downgrade the schema version to the first supported one.
let path = OS.Path.join(OS.Constants.Path.profileDir, DB_FILENAME);
let db = yield Sqlite.openConnection({ path: path });
yield db.setSchemaVersion(FIRST_UPGRADABLE_SCHEMA_VERSION);
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);
});

View File

@ -1,368 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* This file tests migration invariants from schema version 10 to the current
* schema version.
*/
////////////////////////////////////////////////////////////////////////////////
//// Constants
const kGuidAnnotationName = "sync/guid";
const kExpectedAnnotations = 5;
const kExpectedValidGuids = 2;
////////////////////////////////////////////////////////////////////////////////
//// Globals
// Set in test_initial_state to the value in the database.
var gItemGuid = [];
var gItemId = [];
var gPlaceGuid = [];
var gPlaceId = [];
////////////////////////////////////////////////////////////////////////////////
//// Helpers
/**
* Determines if a guid is valid or not.
*
* @return true if it is a valid guid, false otherwise.
*/
function isValidGuid(aGuid)
{
return /^[a-zA-Z0-9\-_]{12}$/.test(aGuid);
}
////////////////////////////////////////////////////////////////////////////////
//// Test Functions
function test_initial_state()
{
// Mostly sanity checks our starting DB to make sure it's setup as we expect
// it to be.
let dbFile = gProfD.clone();
dbFile.append(kDBName);
let db = Services.storage.openUnsharedDatabase(dbFile);
let stmt = db.createStatement("PRAGMA journal_mode");
do_check_true(stmt.executeStep());
// WAL journal mode should not be set on this database.
do_check_neq(stmt.getString(0).toLowerCase(), "wal");
stmt.finalize();
do_check_false(db.indexExists("moz_bookmarks_guid_uniqueindex"));
do_check_false(db.indexExists("moz_places_guid_uniqueindex"));
// There should be five item annotations for a bookmark guid.
stmt = db.createStatement(
`SELECT content AS guid, item_id
FROM moz_items_annos
WHERE anno_attribute_id = (
SELECT id
FROM moz_anno_attributes
WHERE name = :attr_name
)`
);
stmt.params.attr_name = kGuidAnnotationName;
while (stmt.executeStep()) {
gItemGuid.push(stmt.row.guid);
gItemId.push(stmt.row.item_id)
}
do_check_eq(gItemGuid.length, gItemId.length);
do_check_eq(gItemGuid.length, kExpectedAnnotations);
stmt.finalize();
// There should be five item annotations for a place guid.
stmt = db.createStatement(
`SELECT content AS guid, place_id
FROM moz_annos
WHERE anno_attribute_id = (
SELECT id
FROM moz_anno_attributes
WHERE name = :attr_name
)`
);
stmt.params.attr_name = kGuidAnnotationName;
while (stmt.executeStep()) {
gPlaceGuid.push(stmt.row.guid);
gPlaceId.push(stmt.row.place_id)
}
do_check_eq(gPlaceGuid.length, gPlaceId.length);
do_check_eq(gPlaceGuid.length, kExpectedAnnotations);
stmt.finalize();
// Check our schema version to make sure it is actually at 10.
do_check_eq(db.schemaVersion, 10);
db.close();
run_next_test();
}
function test_moz_bookmarks_guid_exists()
{
// This will throw if the column does not exist
let stmt = DBConn().createStatement(
`SELECT guid
FROM moz_bookmarks`
);
stmt.finalize();
run_next_test();
}
function test_bookmark_guids_non_null()
{
// First, sanity check that we have a non-zero amount of bookmarks.
let stmt = DBConn().createStatement(
`SELECT COUNT(1)
FROM moz_bookmarks`
);
do_check_true(stmt.executeStep());
do_check_neq(stmt.getInt32(0), 0);
stmt.finalize();
// Now, make sure we have no NULL guid entry.
stmt = DBConn().createStatement(
`SELECT guid
FROM moz_bookmarks
WHERE guid IS NULL`
);
do_check_false(stmt.executeStep());
stmt.finalize();
run_next_test();
}
function test_bookmark_guid_annotation_imported()
{
// Make sure we have the imported guid; not a newly generated one.
let stmt = DBConn().createStatement(
`SELECT id
FROM moz_bookmarks
WHERE guid = :guid
AND id = :item_id`
);
let validGuids = 0;
let seenGuids = [];
for (let i = 0; i < gItemGuid.length; i++) {
let guid = gItemGuid[i];
stmt.params.guid = guid;
stmt.params.item_id = gItemId[i];
// Check that it is a valid guid that we expect, and that it is not a
// duplicate (which would violate the unique constraint).
let valid = isValidGuid(guid) && seenGuids.indexOf(guid) == -1;
seenGuids.push(guid);
if (valid) {
validGuids++;
do_check_true(stmt.executeStep());
}
else {
do_check_false(stmt.executeStep());
}
stmt.reset();
}
do_check_eq(validGuids, kExpectedValidGuids);
stmt.finalize();
run_next_test();
}
function test_bookmark_guid_annotation_removed()
{
let stmt = DBConn().createStatement(
`SELECT COUNT(1)
FROM moz_items_annos
WHERE anno_attribute_id = (
SELECT id
FROM moz_anno_attributes
WHERE name = :attr_name
)`
);
stmt.params.attr_name = kGuidAnnotationName;
do_check_true(stmt.executeStep());
do_check_eq(stmt.getInt32(0), 0);
stmt.finalize();
run_next_test();
}
function test_moz_places_guid_exists()
{
// This will throw if the column does not exist
let stmt = DBConn().createStatement(
`SELECT guid
FROM moz_places`
);
stmt.finalize();
run_next_test();
}
function test_place_guids_non_null()
{
// First, sanity check that we have a non-zero amount of places.
let stmt = DBConn().createStatement(
`SELECT COUNT(1)
FROM moz_places`
);
do_check_true(stmt.executeStep());
do_check_neq(stmt.getInt32(0), 0);
stmt.finalize();
// Now, make sure we have no NULL guid entry.
stmt = DBConn().createStatement(
`SELECT guid
FROM moz_places
WHERE guid IS NULL`
);
do_check_false(stmt.executeStep());
stmt.finalize();
run_next_test();
}
function test_place_guid_annotation_imported()
{
// Make sure we have the imported guid; not a newly generated one.
let stmt = DBConn().createStatement(
`SELECT id
FROM moz_places
WHERE guid = :guid
AND id = :item_id`
);
let validGuids = 0;
let seenGuids = [];
for (let i = 0; i < gPlaceGuid.length; i++) {
let guid = gPlaceGuid[i];
stmt.params.guid = guid;
stmt.params.item_id = gPlaceId[i];
// Check that it is a valid guid that we expect, and that it is not a
// duplicate (which would violate the unique constraint).
let valid = isValidGuid(guid) && seenGuids.indexOf(guid) == -1;
seenGuids.push(guid);
if (valid) {
validGuids++;
do_check_true(stmt.executeStep());
}
else {
do_check_false(stmt.executeStep());
}
stmt.reset();
}
do_check_eq(validGuids, kExpectedValidGuids);
stmt.finalize();
run_next_test();
}
function test_place_guid_annotation_removed()
{
let stmt = DBConn().createStatement(
`SELECT COUNT(1)
FROM moz_annos
WHERE anno_attribute_id = (
SELECT id
FROM moz_anno_attributes
WHERE name = :attr_name
)`
);
stmt.params.attr_name = kGuidAnnotationName;
do_check_true(stmt.executeStep());
do_check_eq(stmt.getInt32(0), 0);
stmt.finalize();
run_next_test();
}
function test_moz_hosts()
{
// This will throw if the column does not exist
let stmt = DBConn().createStatement(
`SELECT host, frecency, typed, prefix
FROM moz_hosts`
);
stmt.finalize();
// moz_hosts is populated asynchronously, so query asynchronously to serialize
// to that.
// check the number of entries in moz_hosts equals the number of
// unique rev_host in moz_places
stmt = DBConn().createAsyncStatement(
`SELECT (SELECT COUNT(host) FROM moz_hosts),
(SELECT COUNT(DISTINCT rev_host)
FROM moz_places
WHERE LENGTH(rev_host) > 1)`);
try {
stmt.executeAsync({
handleResult: function (aResult) {
this._hasResults = true;
let row = aResult.getNextRow();
let mozHostsCount = row.getResultByIndex(0);
let mozPlacesCount = row.getResultByIndex(1);
do_check_true(mozPlacesCount > 0);
do_check_eq(mozPlacesCount, mozHostsCount);
},
handleError: function () {},
handleCompletion: function (aReason) {
do_check_eq(aReason, Ci.mozIStorageStatementCallback.REASON_FINISHED);
do_check_true(this._hasResults);
run_next_test();
}
});
}
finally {
stmt.finalize();
}
}
function test_final_state()
{
// We open a new database mostly so that we can check that the settings were
// actually saved.
let dbFile = gProfD.clone();
dbFile.append(kDBName);
let db = Services.storage.openUnsharedDatabase(dbFile);
let (stmt = db.createStatement("PRAGMA journal_mode")) {
do_check_true(stmt.executeStep());
// WAL journal mode should be set on this database.
do_check_eq(stmt.getString(0).toLowerCase(), "wal");
stmt.finalize();
}
do_check_true(db.indexExists("moz_bookmarks_guid_uniqueindex"));
do_check_true(db.indexExists("moz_places_guid_uniqueindex"));
do_check_true(db.indexExists("moz_favicons_guid_uniqueindex"));
do_check_eq(db.schemaVersion, CURRENT_SCHEMA_VERSION);
db.close();
run_next_test();
}
////////////////////////////////////////////////////////////////////////////////
//// Test Runner
[
test_initial_state,
test_moz_bookmarks_guid_exists,
test_bookmark_guids_non_null,
test_bookmark_guid_annotation_imported,
test_bookmark_guid_annotation_removed,
test_moz_places_guid_exists,
test_place_guids_non_null,
test_place_guid_annotation_imported,
test_place_guid_annotation_removed,
test_moz_hosts,
test_final_state,
].forEach(add_test);
function run_test()
{
setPlacesDatabase("places_v10.sqlite");
run_next_test();
}

View File

@ -1,132 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* This file tests migration invariants from a database with schema version 14
* that was then downgraded to a database with a schema version 10. Places
* should then migrate this database to one with the current schema version.
*/
////////////////////////////////////////////////////////////////////////////////
//// Test Functions
function test_initial_state()
{
// Mostly sanity checks our starting DB to make sure it's setup as we expect
// it to be.
let dbFile = gProfD.clone();
dbFile.append(kDBName);
let db = Services.storage.openUnsharedDatabase(dbFile);
let stmt = db.createStatement("PRAGMA journal_mode");
do_check_true(stmt.executeStep());
// WAL journal mode should have been unset this database when it was migrated
// down to v10.
do_check_neq(stmt.getString(0).toLowerCase(), "wal");
stmt.finalize();
do_check_true(db.indexExists("moz_bookmarks_guid_uniqueindex"));
do_check_true(db.indexExists("moz_places_guid_uniqueindex"));
// There should be a non-zero amount of bookmarks without a guid.
stmt = db.createStatement(
`SELECT COUNT(1)
FROM moz_bookmarks
WHERE guid IS NULL`
);
do_check_true(stmt.executeStep());
do_check_neq(stmt.getInt32(0), 0);
stmt.finalize();
// There should be a non-zero amount of places without a guid.
stmt = db.createStatement(
`SELECT COUNT(1)
FROM moz_places
WHERE guid IS NULL`
);
do_check_true(stmt.executeStep());
do_check_neq(stmt.getInt32(0), 0);
stmt.finalize();
// Check our schema version to make sure it is actually at 10.
do_check_eq(db.schemaVersion, 10);
db.close();
run_next_test();
}
function test_bookmark_guids_non_null()
{
// First, sanity check that we have a non-zero amount of bookmarks. If
// migration failed, we would have zero.
let stmt = DBConn().createStatement(
`SELECT COUNT(1)
FROM moz_bookmarks`
);
do_check_true(stmt.executeStep());
do_check_neq(stmt.getInt32(0), 0);
stmt.finalize();
// Now, make sure we have no NULL guid entries.
stmt = DBConn().createStatement(
`SELECT guid
FROM moz_bookmarks
WHERE guid IS NULL`
);
do_check_false(stmt.executeStep());
stmt.finalize();
run_next_test();
}
function test_place_guids_non_null()
{
// First, sanity check that we have a non-zero amount of places. If migration
// failed, we would have zero.
let stmt = DBConn().createStatement(
`SELECT COUNT(1)
FROM moz_places`
);
do_check_true(stmt.executeStep());
do_check_neq(stmt.getInt32(0), 0);
stmt.finalize();
// Now, make sure we have no NULL guid entry.
stmt = DBConn().createStatement(
`SELECT guid
FROM moz_places
WHERE guid IS NULL`
);
do_check_false(stmt.executeStep());
stmt.finalize();
run_next_test();
}
function test_final_state()
{
// We open a new database mostly so that we can check that the settings were
// actually saved.
let dbFile = gProfD.clone();
dbFile.append(kDBName);
let db = Services.storage.openUnsharedDatabase(dbFile);
do_check_eq(db.schemaVersion, CURRENT_SCHEMA_VERSION);
db.close();
run_next_test();
}
////////////////////////////////////////////////////////////////////////////////
//// Test Runner
[
test_initial_state,
test_bookmark_guids_non_null,
test_place_guids_non_null,
test_final_state,
].forEach(add_test);
function run_test()
{
setPlacesDatabase("places_v10_from_v14.sqlite");
run_next_test();
}

View File

@ -0,0 +1,48 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(function* setup() {
yield setupPlacesDatabase("places_v16.sqlite");
});
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_moz_hosts() {
let db = yield PlacesUtils.promiseDBConnection();
// This will throw if the column does not exist.
yield db.execute("SELECT host, frecency, typed, prefix FROM moz_hosts");
// moz_hosts is populated asynchronously, so we need to wait.
yield promiseAsyncUpdates();
// check the number of entries in moz_hosts equals the number of
// unique rev_host in moz_places
let rows = yield db.execute(
`SELECT (SELECT COUNT(host) FROM moz_hosts),
(SELECT COUNT(DISTINCT rev_host)
FROM moz_places
WHERE LENGTH(rev_host) > 1)
`);
Assert.equal(rows.length, 1);
let mozHostsCount = rows[0].getResultByIndex(0);
let mozPlacesCount = rows[0].getResultByIndex(1);
Assert.ok(mozPlacesCount > 0, "There is some url in the database");
Assert.equal(mozPlacesCount, mozHostsCount, "moz_hosts has the expected number of entries");
});
add_task(function* test_journal() {
let db = yield PlacesUtils.promiseDBConnection();
let rows = yield db.execute("PRAGMA journal_mode");
Assert.equal(rows.length, 1);
// WAL journal mode should be set on this database.
Assert.equal(rows[0].getResultByIndex(0), "wal");
});

View File

@ -1,62 +1,42 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* This file tests migration invariants from schema version 19 to the current
* schema version.
*/
const ANNO_LEGACYGUID = "placesInternal/GUID";
////////////////////////////////////////////////////////////////////////////////
//// Globals
const kGuidAnnotationName = "placesInternal/GUID";
function getTotalGuidAnnotationsCount(aStorageConnection) {
stmt = aStorageConnection.createStatement(
let getTotalGuidAnnotationsCount = Task.async(function* (db) {
let rows = yield db.execute(
`SELECT count(*)
FROM moz_items_annos a
JOIN moz_anno_attributes b ON a.anno_attribute_id = b.id
WHERE b.name = :attr_name`
);
try {
stmt.params.attr_name = kGuidAnnotationName;
do_check_true(stmt.executeStep());
return stmt.getInt32(0);
} finally {
stmt.finalize();
}
}
////////////////////////////////////////////////////////////////////////////////
//// Tests
function run_test()
{
setPlacesDatabase("places_v19.sqlite");
run_next_test();
}
add_test(function test_initial_state()
{
let dbFile = gProfD.clone();
dbFile.append(kDBName);
let db = Services.storage.openUnsharedDatabase(dbFile);
// There should be an obsolete bookmark GUID annotation.
do_check_eq(getTotalGuidAnnotationsCount(db), 1);
// Check our schema version to make sure it is actually at 19.
do_check_eq(db.schemaVersion, 19);
db.close();
run_next_test();
WHERE b.name = :attr_name
`, { attr_name: ANNO_LEGACYGUID });
return rows[0].getResultByIndex(0);
});
add_test(function test_bookmark_guid_annotation_removed()
{
// There should be no obsolete bookmark GUID annotation anymore.
do_check_eq(getTotalGuidAnnotationsCount(DBConn()), 0);
run_next_test();
add_task(function* setup() {
yield setupPlacesDatabase("places_v19.sqlite");
});
add_task(function* initial_state() {
let path = OS.Path.join(OS.Constants.Path.profileDir, DB_FILENAME);
let db = yield Sqlite.openConnection({ path: path });
Assert.equal((yield getTotalGuidAnnotationsCount(db)), 1,
"There should be 1 obsolete guid annotation");
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_bookmark_guid_annotation_removed()
{
let db = yield PlacesUtils.promiseDBConnection();
Assert.equal((yield getTotalGuidAnnotationsCount(db)), 0,
"There should be no more obsolete GUID annotations.");
});

View File

@ -0,0 +1,35 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
add_task(function* setup() {
yield setupPlacesDatabase("places_v24.sqlite");
});
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_bookmark_guid_annotation_removed()
{
yield PlacesUtils.bookmarks.eraseEverything();
let db = yield PlacesUtils.promiseDBConnection();
let m = new Map([
[PlacesUtils.placesRootId, PlacesUtils.bookmarks.rootGuid],
[PlacesUtils.bookmarksMenuFolderId, PlacesUtils.bookmarks.menuGuid],
[PlacesUtils.toolbarFolderId, PlacesUtils.bookmarks.toolbarGuid],
[PlacesUtils.unfiledBookmarksFolderId, PlacesUtils.bookmarks.unfiledGuid],
[PlacesUtils.tagsFolderId, PlacesUtils.bookmarks.tagsGuid]
]);
let rows = yield db.execute(`SELECT id, guid FROM moz_bookmarks`);
for (let row of rows) {
let id = row.getResultByName("id");
let guid = row.getResultByName("guid");
Assert.equal(m.get(id), guid, "The root folder has the correct GUID");
}
});

View File

@ -0,0 +1,38 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* This file tests migration from a preliminary schema version 6 that
* lacks frecency column and moz_inputhistory table.
*/
add_task(function* setup() {
yield setupPlacesDatabase("places_v6.sqlite");
});
add_task(function* corrupt_database_not_exists() {
let corruptPath = OS.Path.join(OS.Constants.Path.profileDir,
"places.sqlite.corrupt");
Assert.ok(!(yield OS.File.exists(corruptPath)), "Corrupt file should not exist");
});
add_task(function* database_is_valid() {
Assert.equal(PlacesUtils.history.databaseStatus,
PlacesUtils.history.DATABASE_STATUS_CORRUPT);
let db = yield PlacesUtils.promiseDBConnection();
Assert.equal((yield db.getSchemaVersion()), CURRENT_SCHEMA_VERSION);
});
add_task(function* check_columns() {
// Check the database has been replaced, these would throw otherwise.
let db = yield PlacesUtils.promiseDBConnection();
yield db.execute("SELECT frecency from moz_places");
yield db.execute("SELECT 1 from moz_inputhistory");
});
add_task(function* corrupt_database_exists() {
let corruptPath = OS.Path.join(OS.Constants.Path.profileDir,
"places.sqlite.corrupt");
Assert.ok((yield OS.File.exists(corruptPath)), "Corrupt file should exist");
});

View File

@ -1,34 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* This file tests migration replaces the database if schema version < 6.
*/
add_test(function corrupt_database_not_exists() {
let dbFile = gProfD.clone();
dbFile.append("places.sqlite.corrupt");
do_check_false(dbFile.exists());
run_next_test();
});
add_test(function database_is_valid() {
do_check_eq(PlacesUtils.history.databaseStatus,
PlacesUtils.history.DATABASE_STATUS_CORRUPT);
do_check_eq(DBConn().schemaVersion, CURRENT_SCHEMA_VERSION);
run_next_test();
});
add_test(function corrupt_database_exists() {
let dbFile = gProfD.clone();
dbFile.append("places.sqlite.corrupt");
do_check_true(dbFile.exists());
run_next_test();
});
function run_test()
{
setPlacesDatabase("places_alpha.sqlite");
run_next_test();
}

View File

@ -1,31 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* This file tests migration from a preliminary schema version 6 that
* lacks frecency column and moz_inputhistory table.
*/
add_test(function database_is_valid() {
do_check_eq(PlacesUtils.history.databaseStatus,
PlacesUtils.history.DATABASE_STATUS_UPGRADED);
// This throws if frecency column does not exist.
stmt = DBConn().createStatement("SELECT frecency from moz_places");
stmt.finalize();
// Check moz_inputhistory is in place.
do_check_true(DBConn().tableExists("moz_inputhistory"));
run_next_test();
});
add_test(function corrupt_database_not_exists() {
let dbFile = gProfD.clone();
dbFile.append("places.sqlite.corrupt");
do_check_false(dbFile.exists());
run_next_test();
});
function run_test()
{
setPlacesDatabase("places_v6_no_frecency.sqlite");
run_next_test();
}

View File

@ -1,34 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* This file tests migration from a preliminary schema version 6 that
* lacks important indices. The database should be replaced.
*/
add_test(function corrupt_database_not_exists() {
let dbFile = gProfD.clone();
dbFile.append("places.sqlite.corrupt");
do_check_false(dbFile.exists());
run_next_test();
});
add_test(function database_is_valid() {
do_check_eq(PlacesUtils.history.databaseStatus,
PlacesUtils.history.DATABASE_STATUS_CORRUPT);
do_check_eq(DBConn().schemaVersion, CURRENT_SCHEMA_VERSION);
run_next_test();
});
add_test(function corrupt_database_exists() {
let dbFile = gProfD.clone();
dbFile.append("places.sqlite.corrupt");
do_check_true(dbFile.exists());
run_next_test();
});
function run_test()
{
setPlacesDatabase("places_v6_no_indices.sqlite");
run_next_test();
}

View File

@ -1,18 +1,22 @@
[DEFAULT]
head = head_migration.js
tail =
skip-if = toolkit == 'android' || toolkit == 'gonk'
support-files =
places_alpha.sqlite
places_v10.sqlite
places_v10_from_v14.sqlite
places_v19.sqlite
places_v6_no_frecency.sqlite
places_v6_no_indices.sqlite
[test_current_from_v10.js]
[test_current_from_v10_migrated_from_v14.js]
support-files =
places_v6.sqlite
places_v10.sqlite
places_v11.sqlite
places_v16.sqlite
places_v17.sqlite
places_v19.sqlite
places_v21.sqlite
places_v22.sqlite
places_v23.sqlite
places_v24.sqlite
places_v25.sqlite
[test_current_from_downgraded.js]
[test_current_from_v6.js]
[test_current_from_v16.js]
[test_current_from_v19.js]
[test_database_from_alpha.js]
[test_database_from_v6_no_frecency.js]
[test_database_from_v6_no_indices.js]
[test_current_from_v24.js]

View File

@ -210,17 +210,13 @@ function* test_promiseBookmarksTreeAgainstResult(aItemGuid = "",
add_task(function* () {
// Add some bookmarks to cover various use cases.
let toolbarGuid =
yield PlacesUtils.promiseItemGuid(PlacesUtils.toolbarFolderId);
let menuGuid =
yield PlacesUtils.promiseItemGuid(PlacesUtils.bookmarksMenuFolderId);
yield new_bookmark({ parentGuid: toolbarGuid });
yield new_folder({ parentGuid: menuGuid
yield new_bookmark({ parentGuid: PlacesUtils.bookmarks.toolbarGuid });
yield new_folder({ parentGuid: PlacesUtils.bookmarks.menuGuid
, annotations: [{ name: "TestAnnoA", value: "TestVal"
, name: "TestAnnoB", value: 0 }]});
yield PlacesTransactions.transact(
PlacesTransactions.NewSeparator({ parentGuid: menuGuid }));
let folderGuid = yield new_folder({ parentGuid: menuGuid });
PlacesTransactions.NewSeparator({ parentGuid: PlacesUtils.bookmarks.menuGuid }));
let folderGuid = yield new_folder({ parentGuid: PlacesUtils.bookmarks.menuGuid });
yield new_bookmark({ title: null
, parentGuid: folderGuid
, keyword: "test_keyword"
@ -234,8 +230,7 @@ add_task(function* () {
yield test_promiseBookmarksTreeAgainstResult();
// Do specify it
let rootGuid = yield PlacesUtils.promiseItemGuid(PlacesUtils.placesRootId);
yield test_promiseBookmarksTreeAgainstResult(rootGuid);
yield test_promiseBookmarksTreeAgainstResult(PlacesUtils.bookmarks.rootGuid);
// Exclude the bookmarks menu.
// The calllback should be four times - once for the toolbar, once for
@ -246,13 +241,13 @@ add_task(function* () {
// passed in.
let guidsPassedToExcludeCallback = new Set();
let placesRootWithoutTheMenu =
yield test_promiseBookmarksTreeAgainstResult(rootGuid, {
yield test_promiseBookmarksTreeAgainstResult(PlacesUtils.bookmarks.rootGuid, {
excludeItemsCallback: aItem => {
guidsPassedToExcludeCallback.add(aItem.guid);
return aItem.root == "bookmarksMenuFolder";
},
includeItemIds: true
}, [menuGuid]);
}, [PlacesUtils.bookmarks.menuGuid]);
do_check_eq(guidsPassedToExcludeCallback.size, 4);
do_check_eq(placesRootWithoutTheMenu.children.length, 2);
});

View File

@ -0,0 +1,54 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict"
this.EXPORTED_SYMBOLS = ["PromiseUtils"];
Components.utils.import("resource://gre/modules/Timer.jsm");
this.PromiseUtils = {
/*
* A simple timeout mechanism.
*
* Example:
* resolveOrTimeout(myModule.shutdown(), 1000, new Error("The module took too long to shutdown"));
*
* @param {Promise} promise The Promise that should resolve/reject quickly.
* @param {number} delay A delay after which to stop waiting for `promise`, in milliseconds.
* @param {function} rejection If `promise` hasn't resolved/rejected after `delay`,
* a value used to construct the rejection.
*
* @return {Promise} A promise that behaves as `promise`, if `promise` is
* resolved/rejected within `delay` ms, or rejects with `rejection()` otherwise.
*/
resolveOrTimeout : function(promise, delay, rejection) {
// throw a TypeError if <promise> is not a Promise object
if (!(promise instanceof Promise)) {
throw new TypeError("first argument <promise> must be a Promise object");
}
// throw a TypeError if <delay> is not a number
if (typeof delay != "number" || delay < 0) {
throw new TypeError("second argument <delay> must be a positive number");
}
// throws a TypeError if <rejection> is not a function
if (rejection && typeof rejection != "function") {
throw new TypeError("third optional argument <rejection> must be a function");
}
return new Promise((resolve, reject) => {
promise.then(resolve, reject);
let id = setTimeout(() => {
try {
rejection ? reject(rejection()) : reject(new Error("Promise Timeout"));
} catch(ex) {
reject(ex);
}
clearTimeout(id);
}, delay);
});
}
}

View File

@ -36,6 +36,7 @@ EXTRA_JS_MODULES += [
'PrivateBrowsingUtils.jsm',
'Promise-backend.js',
'Promise.jsm',
'PromiseUtils.jsm',
'PropertyListUtils.jsm',
'RemoteController.jsm',
'RemoteFinder.jsm',

View File

@ -0,0 +1,78 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
Components.utils.import("resource://gre/modules/PromiseUtils.jsm");
Components.utils.import("resource://gre/modules/Timer.jsm");
// Tests for PromiseUtils.jsm
function run_test() {
run_next_test();
}
/* Tests for the case when arguments to resolveOrTimeout
* are not of correct type */
add_task(function* test_wrong_arguments() {
let p = new Promise((resolve, reject) => {});
// for the first argument
Assert.throws(() => PromiseUtils.resolveOrTimeout("string", 200), /first argument <promise> must be a Promise object/,
"TypeError thrown because first argument is not a Promsie object");
// for second argument
Assert.throws(() => PromiseUtils.resolveOrTimeout(p, "string"), /second argument <delay> must be a positive number/,
"TypeError thrown because second argument is not a positive number");
// for the third argument
Assert.throws(() => PromiseUtils.resolveOrTimeout(p, 200, "string"), /third optional argument <rejection> must be a function/,
"TypeError thrown because thrird argument is not a function");
});
/* Tests for the case when the optional third argument is not provided
* In that case the returned promise rejects with a default Error */
add_task(function* test_optional_third_argument() {
let p = new Promise((resolve, reject) => {});
yield Assert.rejects(PromiseUtils.resolveOrTimeout(p, 200), /Promise Timeout/, "Promise rejects with a default Error");
});
/* Test for the case when the passed promise resolves quickly
* In that case the returned promise also resolves with the same value */
add_task(function* test_resolve_quickly() {
let p = new Promise((resolve, reject) => setTimeout(() => resolve("Promise is resolved"), 20));
let result = yield PromiseUtils.resolveOrTimeout(p, 200);
Assert.equal(result, "Promise is resolved", "Promise resolves quickly");
});
/* Test for the case when the passed promise rejects quickly
* In that case the returned promise also rejects with the same value */
add_task(function* test_reject_quickly() {
let p = new Promise((resolve, reject) => setTimeout(() => reject("Promise is rejected"), 20));
yield Assert.rejects(PromiseUtils.resolveOrTimeout(p, 200), /Promise is rejected/, "Promise rejects quickly");
});
/* Tests for the case when the passed promise doesn't settle
* and rejection returns string/object/undefined */
add_task(function* test_rejection_function() {
let p = new Promise((resolve, reject) => {});
// for rejection returning string
yield Assert.rejects(PromiseUtils.resolveOrTimeout(p, 200, () => {
return "Rejection returned a string";
}), /Rejection returned a string/, "Rejection returned a string");
// for rejection returning object
yield Assert.rejects(PromiseUtils.resolveOrTimeout(p, 200, () => {
return {Name:"Promise"};
}), Object, "Rejection returned an object");
// for rejection returning undefined
yield Assert.rejects(PromiseUtils.resolveOrTimeout(p, 200, () => {
return;
}), undefined, "Rejection returned undefined");
});
/* Tests for the case when the passed promise doesn't settles
* and rejection throws an error */
add_task(function* test_rejection_throw_error() {
let p = new Promise((resolve, reject) => {});
yield Assert.rejects(PromiseUtils.resolveOrTimeout(p, 200, () => {
throw new Error("Rejection threw an Error");
}), /Rejection threw an Error/, "Rejection threw an error");
});

View File

@ -21,6 +21,7 @@ skip-if = os == 'android'
[test_PermissionsUtils.js]
[test_Preferences.js]
[test_Promise.js]
[test_PromiseUtils.js]
[test_propertyListsUtils.js]
[test_readCertPrefs.js]
[test_Services.js]