diff options
Diffstat (limited to 'services/sync/tps')
12 files changed, 241 insertions, 582 deletions
diff --git a/services/sync/tps/extensions/mozmill/resource/driver/controller.js b/services/sync/tps/extensions/mozmill/resource/driver/controller.js index 8d66a41ae..1a4e6f3b6 100644 --- a/services/sync/tps/extensions/mozmill/resource/driver/controller.js +++ b/services/sync/tps/extensions/mozmill/resource/driver/controller.js @@ -870,7 +870,7 @@ MozMillController.prototype.mouseMove = function (doc, start, dest) { /** * Drag an element to the specified offset on another element, firing mouse and - * drag events. Adapted from EventUtils.js synthesizeDrop() + * drag events. Adapted from ChromeUtils.js synthesizeDrop() * * @deprecated Use the MozMillElement object * diff --git a/services/sync/tps/extensions/mozmill/resource/driver/mozelement.js b/services/sync/tps/extensions/mozmill/resource/driver/mozelement.js index 850c86523..ae55cb0ce 100644 --- a/services/sync/tps/extensions/mozmill/resource/driver/mozelement.js +++ b/services/sync/tps/extensions/mozmill/resource/driver/mozelement.js @@ -131,7 +131,7 @@ MozMillElement.prototype.__defineGetter__("element", function () { /** * Drag an element to the specified offset on another element, firing mouse and - * drag events. Adapted from EventUtils.js synthesizeDrop() + * drag events. Adapted from ChromeUtils.js synthesizeDrop() * * By default it will drag the source element over the destination's element * center with a "move" dropEffect. @@ -218,7 +218,7 @@ MozMillElement.prototype.dragToElement = function(aElement, aOffsetX, aOffsetY, EventUtils.synthesizeMouse(destNode, destCoords.x, destCoords.y, { type: "mousemove" }, destWindow); - var event = destWindow.document.createEvent("DragEvent"); + var event = destWindow.document.createEvent("DragEvents"); event.initDragEvent("dragenter", true, true, destWindow, 0, 0, 0, 0, 0, false, false, false, false, 0, null, dataTransfer); event.initDragEvent("dragover", true, true, destWindow, 0, 0, 0, 0, 0, diff --git a/services/sync/tps/extensions/mozmill/resource/stdlib/securable-module.js b/services/sync/tps/extensions/mozmill/resource/stdlib/securable-module.js index 2648afd27..bfa7ef5a9 100644 --- a/services/sync/tps/extensions/mozmill/resource/stdlib/securable-module.js +++ b/services/sync/tps/extensions/mozmill/resource/stdlib/securable-module.js @@ -40,8 +40,6 @@ const Cu = Components.utils; const Cr = Components.results; - Cu.import("resource://gre/modules/NetUtil.jsm"); - var exports = {}; var ios = Cc['@mozilla.org/network/io-service;1'] @@ -315,26 +313,17 @@ else baseURI = ios.newURI(base, null, null); var newURI = ios.newURI(path, null, baseURI); - var channel = NetUtil.newChannel({ - uri: newURI, - loadUsingSystemPrincipal: true - }); + var channel = ios.newChannelFromURI(newURI); try { - channel.open2().close(); - } catch (e) { - if (e.result != Cr.NS_ERROR_FILE_NOT_FOUND) { - throw e; - } + channel.open().close(); + } catch (e if e.result == Cr.NS_ERROR_FILE_NOT_FOUND) { return null; } return newURI.spec; }, getFile: function getFile(path) { - var channel = NetUtil.newChannel({ - uri: path, - loadUsingSystemPrincipal: true - }); - var iStream = channel.open2(); + var channel = ios.newChannel(path, null, null); + var iStream = channel.open(); var ciStream = Cc["@mozilla.org/intl/converter-input-stream;1"]. createInstance(Ci.nsIConverterInputStream); var bufLen = 0x8000; diff --git a/services/sync/tps/extensions/mozmill/resource/stdlib/utils.js b/services/sync/tps/extensions/mozmill/resource/stdlib/utils.js index 73e13e11f..f27bbaaf7 100644 --- a/services/sync/tps/extensions/mozmill/resource/stdlib/utils.js +++ b/services/sync/tps/extensions/mozmill/resource/stdlib/utils.js @@ -19,7 +19,7 @@ Cu.import("resource://gre/modules/NetUtil.jsm"); Cu.import("resource://gre/modules/Services.jsm"); const applicationIdMap = { - '{ec8030f7-c20a-464f-9b0e-13a3a9e97384}': 'Firefox' + '{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}': 'Firefox' } const applicationName = applicationIdMap[Services.appinfo.ID] || Services.appinfo.name; diff --git a/services/sync/tps/extensions/tps/install.rdf b/services/sync/tps/extensions/tps/install.rdf index 3dcdc5e44..cc9491b07 100644 --- a/services/sync/tps/extensions/tps/install.rdf +++ b/services/sync/tps/extensions/tps/install.rdf @@ -12,7 +12,7 @@ <em:targetApplication> <!-- Firefox --> <Description> - <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> + <em:id>{8de7fcbb-c55c-4fbe-bfc5-fc555c87dbc4}</em:id> <em:minVersion>24.0.*</em:minVersion> <em:maxVersion>31.0.*</em:maxVersion> </Description> diff --git a/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm b/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm index 86d0ed113..f5daa14be 100644 --- a/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm +++ b/services/sync/tps/extensions/tps/resource/auth/fxaccounts.jsm @@ -12,7 +12,6 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import("resource://gre/modules/FxAccounts.jsm"); Cu.import("resource://gre/modules/FxAccountsClient.jsm"); -Cu.import("resource://gre/modules/FxAccountsConfig.jsm"); Cu.import("resource://services-common/async.js"); Cu.import("resource://services-sync/main.js"); Cu.import("resource://tps/logger.jsm"); @@ -68,10 +67,7 @@ var Authentication = { Logger.AssertTrue(account["username"], "Username has been found"); Logger.AssertTrue(account["password"], "Password has been found"); - Logger.logInfo("Login user: " + account["username"]); - - // Required here since we don't go through the real login page - Async.promiseSpinningly(FxAccountsConfig.ensureConfigured()); + Logger.logInfo("Login user: " + account["username"] + '\n'); let client = new FxAccountsClient(); client.signIn(account["username"], account["password"], true).then(credentials => { @@ -96,26 +92,5 @@ var Authentication = { } catch (error) { throw new Error("signIn() failed with: " + error.message); } - }, - - /** - * Sign out of Firefox Accounts. It also clears out the device ID, if we find one. - */ - signOut() { - if (Authentication.isLoggedIn) { - let user = Authentication.getSignedInUser(); - if (!user) { - throw new Error("Failed to get signed in user!"); - } - let fxc = new FxAccountsClient(); - let { sessionToken, deviceId } = user; - if (deviceId) { - Logger.logInfo("Destroying device " + deviceId); - Async.promiseSpinningly(fxc.signOutAndDestroyDevice(sessionToken, deviceId, { service: "sync" })); - } else { - Logger.logError("No device found."); - Async.promiseSpinningly(fxc.signOut(sessionToken, { service: "sync" })); - } - } } }; diff --git a/services/sync/tps/extensions/tps/resource/auth/sync.jsm b/services/sync/tps/extensions/tps/resource/auth/sync.jsm index 35ffeb269..676b17a91 100644 --- a/services/sync/tps/extensions/tps/resource/auth/sync.jsm +++ b/services/sync/tps/extensions/tps/resource/auth/sync.jsm @@ -80,9 +80,5 @@ var Authentication = { } return true; - }, - - signOut() { - Weave.Service.logout(); } }; diff --git a/services/sync/tps/extensions/tps/resource/modules/addons.jsm b/services/sync/tps/extensions/tps/resource/modules/addons.jsm index 1570b42b1..8dae75ede 100644 --- a/services/sync/tps/extensions/tps/resource/modules/addons.jsm +++ b/services/sync/tps/extensions/tps/resource/modules/addons.jsm @@ -9,7 +9,7 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import("resource://gre/modules/AddonManager.jsm"); Cu.import("resource://gre/modules/addons/AddonRepository.jsm"); -Cu.import("resource://gre/modules/NetUtil.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://services-common/async.js"); Cu.import("resource://services-sync/addonutils.js"); Cu.import("resource://services-sync/util.js"); @@ -20,11 +20,15 @@ const STATE_ENABLED = 1; const STATE_DISABLED = 2; function GetFileAsText(file) { - let channel = NetUtil.newChannel({ - uri: file, - loadUsingSystemPrincipal: true - }); - let inputStream = channel.open2(); + let channel = Services.io.newChannel2(file, + null, + null, + null, // aLoadingNode + Services.scriptSecurityManager.getSystemPrincipal(), + null, // aTriggeringPrincipal + Ci.nsILoadInfo.SEC_NORMAL, + Ci.nsIContentPolicy.TYPE_OTHER); + let inputStream = channel.open(); if (channel instanceof Ci.nsIHttpChannel && channel.responseStatus != 200) { return ""; diff --git a/services/sync/tps/extensions/tps/resource/modules/bookmarks.jsm b/services/sync/tps/extensions/tps/resource/modules/bookmarks.jsm index 857c0c1e8..6a288bbec 100644 --- a/services/sync/tps/extensions/tps/resource/modules/bookmarks.jsm +++ b/services/sync/tps/extensions/tps/resource/modules/bookmarks.jsm @@ -13,7 +13,6 @@ var EXPORTED_SYMBOLS = ["PlacesItem", "Bookmark", "Separator", "Livemark", const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import("resource://gre/modules/PlacesBackups.jsm"); -Cu.import("resource://gre/modules/PlacesSyncUtils.jsm"); Cu.import("resource://gre/modules/PlacesUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://services-common/async.js"); @@ -110,11 +109,6 @@ PlacesItem.prototype = { return string; }, - GetSyncId() { - let guid = Async.promiseSpinningly(PlacesUtils.promiseItemGuid(this.props.item_id)); - return PlacesSyncUtils.bookmarks.guidToSyncId(guid); - }, - /** * GetPlacesNodeId * @@ -438,19 +432,8 @@ Bookmark.prototype = { * @return nothing */ SetKeyword: function(keyword) { - if (keyword != null) { - // Mirror logic from PlacesSyncUtils's updateBookmarkMetadata - let entry = Async.promiseSpinningly(PlacesUtils.keywords.fetch({ - url: this.props.uri, - })); - if (entry) { - Async.promiseSpinningly(PlacesUtils.keywords.remove(entry)); - } - Async.promiseSpinningly(PlacesUtils.keywords.insert({ - keyword: keyword, - url: this.props.uri - })); - } + if (keyword != null) + PlacesUtils.bookmarks.setKeywordForBookmark(this.props.item_id, keyword); }, /** @@ -559,11 +542,11 @@ Bookmark.prototype = { Update: function() { Logger.AssertTrue(this.props.item_id != -1 && this.props.item_id != null, "Invalid item_id during Remove"); + this.SetKeyword(this.updateProps.keyword); this.SetDescription(this.updateProps.description); this.SetLoadInSidebar(this.updateProps.loadInSidebar); this.SetTitle(this.updateProps.title); this.SetUri(this.updateProps.uri); - this.SetKeyword(this.updateProps.keyword); this.SetTags(this.updateProps.tags); this.SetLocation(this.updateProps.location); this.SetPosition(this.updateProps.position); @@ -595,8 +578,7 @@ Bookmark.prototype = { if (!this.CheckDescription(this.props.description)) return -1; if (this.props.keyword != null) { - let { keyword } = Async.promiseSpinningly( - PlacesSyncUtils.bookmarks.fetch(this.GetSyncId())); + let keyword = PlacesUtils.bookmarks.getKeywordForBookmark(this.props.item_id); if (keyword != this.props.keyword) { Logger.logPotentialError("Incorrect keyword - expected: " + this.props.keyword + ", actual: " + keyword + diff --git a/services/sync/tps/extensions/tps/resource/modules/forms.jsm b/services/sync/tps/extensions/tps/resource/modules/forms.jsm index deb1a28a5..ece2e14f7 100644 --- a/services/sync/tps/extensions/tps/resource/modules/forms.jsm +++ b/services/sync/tps/extensions/tps/resource/modules/forms.jsm @@ -13,45 +13,74 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; Cu.import("resource://tps/logger.jsm"); -Cu.import("resource://gre/modules/FormHistory.jsm"); -Cu.import("resource://gre/modules/Log.jsm"); +let formService = Cc["@mozilla.org/satchel/form-history;1"] + .getService(Ci.nsIFormHistory2); /** * FormDB * - * Helper object containing methods to interact with the FormHistory module. + * Helper object containing methods to interact with the moz_formhistory + * SQLite table. */ -var FormDB = { - _update(data) { - return new Promise((resolve, reject) => { - let handlers = { - handleError(error) { - Logger.logError("Error occurred updating form history: " + Log.exceptionStr(error)); - reject(error); - }, - handleCompletion(reason) { - resolve(); - } - } - FormHistory.update(data, handlers); - }); +let FormDB = { + /** + * makeGUID + * + * Generates a brand-new globally unique identifier (GUID). Borrowed + * from Weave's utils.js. + * + * @return the new guid + */ + makeGUID: function makeGUID() { + // 70 characters that are not-escaped URL-friendly + const code = + "!()*-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"; + + let guid = ""; + let num = 0; + let val; + + // Generate ten 70-value characters for a 70^10 (~61.29-bit) GUID + for (let i = 0; i < 10; i++) { + // Refresh the number source after using it a few times + if (i == 0 || i == 5) + num = Math.random(); + + // Figure out which code to use for the next GUID character + num *= 70; + val = Math.floor(num); + guid += code[val]; + num -= val; + } + + return guid; }, /** * insertValue * - * Adds the specified value for the specified fieldname into form history. + * Inserts the specified value for the specified fieldname into the + * moz_formhistory table. * * @param fieldname The form fieldname to insert * @param value The form value to insert * @param us The time, in microseconds, to use for the lastUsed * and firstUsed columns - * @return Promise<undefined> + * @return nothing */ - insertValue(fieldname, value, us) { - let data = { op: "add", fieldname, value, timesUsed: 1, - firstUsed: us, lastUsed: us } - return this._update(data); + insertValue: function (fieldname, value, us) { + let query = this.createStatement( + "INSERT INTO moz_formhistory " + + "(fieldname, value, timesUsed, firstUsed, lastUsed, guid) VALUES " + + "(:fieldname, :value, :timesUsed, :firstUsed, :lastUsed, :guid)"); + query.params.fieldname = fieldname; + query.params.value = value; + query.params.timesUsed = 1; + query.params.firstUsed = us; + query.params.lastUsed = us; + query.params.guid = this.makeGUID(); + query.execute(); + query.reset(); }, /** @@ -61,10 +90,15 @@ var FormDB = { * * @param id The id of the row to update * @param newvalue The new value to set - * @return Promise<undefined> + * @return nothing */ - updateValue(id, newvalue) { - return this._update({ op: "update", guid: id, value: newvalue }); + updateValue: function (id, newvalue) { + let query = this.createStatement( + "UPDATE moz_formhistory SET value = :value WHERE id = :id"); + query.params.id = id; + query.params.value = newvalue; + query.execute(); + query.reset(); }, /** @@ -75,44 +109,52 @@ var FormDB = { * * @param fieldname The fieldname of the row to query * @param value The value of the row to query - * @return Promise<null if no row is found with the specified fieldname and value, - * or an object containing the row's guid, lastUsed, and firstUsed - * values> + * @return null if no row is found with the specified fieldname and value, + * or an object containing the row's id, lastUsed, and firstUsed + * values */ - getDataForValue(fieldname, value) { - return new Promise((resolve, reject) => { - let result = null; - let handlers = { - handleResult(oneResult) { - if (result != null) { - reject("more than 1 result for this query"); - return; - } - result = oneResult; - }, - handleError(error) { - Logger.logError("Error occurred updating form history: " + Log.exceptionStr(error)); - reject(error); - }, - handleCompletion(reason) { - resolve(result); - } - } - FormHistory.search(["guid", "lastUsed", "firstUsed"], { fieldname }, handlers); - }); + getDataForValue: function (fieldname, value) { + let query = this.createStatement( + "SELECT id, lastUsed, firstUsed FROM moz_formhistory WHERE " + + "fieldname = :fieldname AND value = :value"); + query.params.fieldname = fieldname; + query.params.value = value; + if (!query.executeStep()) + return null; + + return { + id: query.row.id, + lastUsed: query.row.lastUsed, + firstUsed: query.row.firstUsed + }; }, /** - * remove + * createStatement * - * Removes the specified GUID from the database. + * Creates a statement from a SQL string. This function is borrowed + * from Weave's forms.js. * - * @param guid The guid of the item to delete - * @return Promise<> + * @param query The SQL query string + * @return the mozIStorageStatement created from the specified SQL */ - remove(guid) { - return this._update({ op: "remove", guid }); - }, + createStatement: function createStatement(query) { + try { + // Just return the statement right away if it's okay + return formService.DBConnection.createStatement(query); + } + catch(ex) { + // Assume guid column must not exist yet, so add it with an index + formService.DBConnection.executeSimpleSQL( + "ALTER TABLE moz_formhistory ADD COLUMN guid TEXT"); + formService.DBConnection.executeSimpleSQL( + "CREATE INDEX IF NOT EXISTS moz_formhistory_guid_index " + + "ON moz_formhistory (guid)"); + } + + // Try creating the query now that the column exists + return formService.DBConnection.createStatement(query); + } }; /** @@ -162,18 +204,18 @@ FormData.prototype = { Logger.AssertTrue(this.fieldname != null && this.value != null, "Must specify both fieldname and value"); - return FormDB.getDataForValue(this.fieldname, this.value).then(formdata => { - if (!formdata) { - // this item doesn't exist yet in the db, so we need to insert it - return FormDB.insertValue(this.fieldname, this.value, - this.hours_to_us(this.date)); - } else { - /* Right now, we ignore this case. If bug 552531 is ever fixed, - we might need to add code here to update the firstUsed or - lastUsed fields, as appropriate. - */ - } - }); + let formdata = FormDB.getDataForValue(this.fieldname, this.value); + if (!formdata) { + // this item doesn't exist yet in the db, so we need to insert it + FormDB.insertValue(this.fieldname, this.value, + this.hours_to_us(this.date)); + } + else { + /* Right now, we ignore this case. If bug 552531 is ever fixed, + we might need to add code here to update the firstUsed or + lastUsed fields, as appropriate. + */ + } }, /** @@ -185,22 +227,21 @@ FormData.prototype = { * @return true if this entry exists in the database, otherwise false */ Find: function() { - return FormDB.getDataForValue(this.fieldname, this.value).then(formdata => { - let status = formdata != null; - if (status) { - /* - //form history dates currently not synced! bug 552531 - let us = this.hours_to_us(this.date); - status = Logger.AssertTrue( - us >= formdata.firstUsed && us <= formdata.lastUsed, - "No match for with that date value"); - - if (status) - */ - this.id = formdata.guid; - } - return status; - }); + let formdata = FormDB.getDataForValue(this.fieldname, this.value); + let status = formdata != null; + if (status) { + /* + //form history dates currently not synced! bug 552531 + let us = this.hours_to_us(this.date); + status = Logger.AssertTrue( + us >= formdata.firstUsed && us <= formdata.lastUsed, + "No match for with that date value"); + + if (status) + */ + this.id = formdata.id; + } + return status; }, /** @@ -214,6 +255,7 @@ FormData.prototype = { Remove: function() { /* Right now Weave doesn't handle this correctly, see bug 568363. */ - return FormDB.remove(this.id); + formService.removeEntry(this.fieldname, this.value); + return true; }, }; diff --git a/services/sync/tps/extensions/tps/resource/modules/history.jsm b/services/sync/tps/extensions/tps/resource/modules/history.jsm index 78deb42ab..3e750a5f0 100644 --- a/services/sync/tps/extensions/tps/resource/modules/history.jsm +++ b/services/sync/tps/extensions/tps/resource/modules/history.jsm @@ -70,8 +70,8 @@ var HistoryEntry = { "WHERE place_id = (" + "SELECT id " + "FROM moz_places " + - "WHERE url_hash = hash(:url) AND url = :url) " + - "ORDER BY date DESC LIMIT 20"); + "WHERE url = :url) " + + "ORDER BY date DESC LIMIT 10"); this.__defineGetter__("_visitStm", () => stm); return stm; }, @@ -189,16 +189,9 @@ var HistoryEntry = { PlacesUtils.history.removePagesFromHost(item.host, false); } else if ("begin" in item && "end" in item) { - let cb = Async.makeSpinningCallback(); - let msSinceEpoch = parseInt(usSinceEpoch / 1000); - let filter = { - beginDate: new Date(msSinceEpoch + (item.begin * 60 * 60 * 1000)), - endDate: new Date(msSinceEpoch + (item.end * 60 * 60 * 1000)) - }; - PlacesUtils.history.removeVisitsByFilter(filter) - .catch(ex => Logger.AssertTrue(false, "An error occurred while deleting history: " + ex)) - .then(result => {cb(null, result)}, err => {cb(err)}); - Async.waitForSyncCallback(cb); + PlacesUtils.history.removeVisitsByTimeframe( + usSinceEpoch + (item.begin * 60 * 60 * 1000 * 1000), + usSinceEpoch + (item.end * 60 * 60 * 1000 * 1000)); } else { Logger.AssertTrue(false, "invalid entry in delete history"); diff --git a/services/sync/tps/extensions/tps/resource/tps.jsm b/services/sync/tps/extensions/tps/resource/tps.jsm index f4cc0214a..ca3e4d578 100644 --- a/services/sync/tps/extensions/tps/resource/tps.jsm +++ b/services/sync/tps/extensions/tps/resource/tps.jsm @@ -14,21 +14,13 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components; var module = this; // Global modules -Cu.import("resource://gre/modules/Log.jsm"); Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/AppConstants.jsm"); -Cu.import("resource://gre/modules/PlacesUtils.jsm"); -Cu.import("resource://gre/modules/FileUtils.jsm"); Cu.import("resource://services-common/async.js"); Cu.import("resource://services-sync/constants.js"); Cu.import("resource://services-sync/main.js"); Cu.import("resource://services-sync/util.js"); -Cu.import("resource://services-sync/telemetry.js"); -Cu.import("resource://services-sync/bookmark_validator.js"); -Cu.import("resource://services-sync/engines/passwords.js"); -Cu.import("resource://services-sync/engines/forms.js"); -Cu.import("resource://services-sync/engines/addons.js"); + // TPS modules Cu.import("resource://tps/logger.jsm"); @@ -50,11 +42,6 @@ var prefs = Cc["@mozilla.org/preferences-service;1"] var mozmillInit = {}; Cu.import('resource://mozmill/driver/mozmill.js', mozmillInit); -XPCOMUtils.defineLazyGetter(this, "fileProtocolHandler", () => { - let fileHandler = Services.io.getProtocolHandler("file"); - return fileHandler.QueryInterface(Ci.nsIFileProtocolHandler); -}); - // Options for wiping data during a sync const SYNC_RESET_CLIENT = "resetClient"; const SYNC_WIPE_CLIENT = "wipeClient"; @@ -90,7 +77,7 @@ const ACTIONS = [ const OBSERVER_TOPICS = ["fxaccounts:onlogin", "fxaccounts:onlogout", "private-browsing", - "profile-before-change", + "quit-application-requested", "sessionstore-windows-restored", "weave:engine:start-tracking", "weave:engine:stop-tracking", @@ -107,14 +94,13 @@ var TPS = { _currentPhase: -1, _enabledEngines: null, _errors: 0, + _finalPhase: false, _isTracking: false, _operations_pending: 0, _phaseFinished: false, _phaselist: {}, _setupComplete: false, _syncActive: false, - _syncCount: 0, - _syncsReportedViaTelemetry: 0, _syncErrors: 0, _syncWipeAction: null, _tabsAdded: 0, @@ -122,18 +108,12 @@ var TPS = { _test: null, _triggeredSync: false, _usSinceEpoch: 0, - _requestedQuit: false, - shouldValidateAddons: false, - shouldValidateBookmarks: false, - shouldValidatePasswords: false, - shouldValidateForms: false, _init: function TPS__init() { // Check if Firefox Accounts is enabled let service = Cc["@mozilla.org/weave/service;1"] .getService(Components.interfaces.nsISupports) .wrappedJSObject; - this.fxaccounts_enabled = service.fxAccountsEnabled; this.delayAutoSync(); @@ -141,27 +121,13 @@ var TPS = { Services.obs.addObserver(this, aTopic, true); }, this); - // Configure some logging prefs for Sync itself. - Weave.Svc.Prefs.set("log.appender.dump", "Debug"); // Import the appropriate authentication module - if (this.fxaccounts_enabled) { - Cu.import("resource://tps/auth/fxaccounts.jsm", module); - } - else { - Cu.import("resource://tps/auth/sync.jsm", module); - } + Cu.import("resource://tps/auth/sync.jsm", module); }, - DumpError(msg, exc = null) { + DumpError: function TPS__DumpError(msg) { this._errors++; - let errInfo; - if (exc) { - errInfo = Log.exceptionStr(exc); // includes details and stack-trace. - } else { - // always write a stack even if no error passed. - errInfo = Log.stackTrace(new Error()); - } - Logger.logError(`[phase ${this._currentPhase}] ${msg} - ${errInfo}`); + Logger.logError("[phase" + this._currentPhase + "] " + msg); this.quit(); }, @@ -177,7 +143,14 @@ var TPS = { Logger.logInfo("private browsing " + data); break; - case "profile-before-change": + case "quit-application-requested": + // Ensure that we eventually wipe the data on the server + if (this._errors || !this._phaseFinished || this._finalPhase) { + try { + this.WipeServer(); + } catch (ex) {} + } + OBSERVER_TOPICS.forEach(function(topic) { Services.obs.removeObserver(this, topic); }, this); @@ -253,7 +226,7 @@ var TPS = { } } catch (e) { - this.DumpError("Observer failed", e); + this.DumpError("Exception caught: " + Utils.exceptionStr(e)); return; } }, @@ -286,7 +259,6 @@ var TPS = { }, quit: function TPS__quit() { - this._requestedQuit = true; this.goQuitApplication(); }, @@ -371,25 +343,23 @@ var TPS = { }, HandleForms: function (data, action) { - this.shouldValidateForms = true; for (let datum of data) { Logger.logInfo("executing action " + action.toUpperCase() + " on form entry " + JSON.stringify(datum)); let formdata = new FormData(datum, this._usSinceEpoch); switch(action) { case ACTION_ADD: - Async.promiseSpinningly(formdata.Create()); + formdata.Create(); break; case ACTION_DELETE: - Async.promiseSpinningly(formdata.Remove()); + formdata.Remove(); break; case ACTION_VERIFY: - Logger.AssertTrue(Async.promiseSpinningly(formdata.Find()), - "form data not found"); + Logger.AssertTrue(formdata.Find(), "form data not found"); break; case ACTION_VERIFY_NOT: - Logger.AssertTrue(!Async.promiseSpinningly(formdata.Find()), - "form data found, but it shouldn't be present"); + Logger.AssertTrue(!formdata.Find(), + "form data found, but it shouldn't be present"); break; default: Logger.AssertTrue(false, "invalid action: " + action); @@ -433,32 +403,31 @@ var TPS = { }, HandlePasswords: function (passwords, action) { - this.shouldValidatePasswords = true; try { for (let password of passwords) { let password_id = -1; Logger.logInfo("executing action " + action.toUpperCase() + " on password " + JSON.stringify(password)); - let passwordOb = new Password(password); + var password = new Password(password); switch (action) { case ACTION_ADD: - Logger.AssertTrue(passwordOb.Create() > -1, "error adding password"); + Logger.AssertTrue(password.Create() > -1, "error adding password"); break; case ACTION_VERIFY: - Logger.AssertTrue(passwordOb.Find() != -1, "password not found"); + Logger.AssertTrue(password.Find() != -1, "password not found"); break; case ACTION_VERIFY_NOT: - Logger.AssertTrue(passwordOb.Find() == -1, + Logger.AssertTrue(password.Find() == -1, "password found, but it shouldn't exist"); break; case ACTION_DELETE: - Logger.AssertTrue(passwordOb.Find() != -1, "password not found"); - passwordOb.Remove(); + Logger.AssertTrue(password.Find() != -1, "password not found"); + password.Remove(); break; case ACTION_MODIFY: - if (passwordOb.updateProps != null) { - Logger.AssertTrue(passwordOb.Find() != -1, "password not found"); - passwordOb.Update(); + if (password.updateProps != null) { + Logger.AssertTrue(password.Find() != -1, "password not found"); + password.Update(); } break; default: @@ -475,7 +444,6 @@ var TPS = { }, HandleAddons: function (addons, action, state) { - this.shouldValidateAddons = true; for (let entry of addons) { Logger.logInfo("executing action " + action.toUpperCase() + " on addon " + JSON.stringify(entry)); @@ -505,10 +473,9 @@ var TPS = { }, HandleBookmarks: function (bookmarks, action) { - this.shouldValidateBookmarks = true; try { let items = []; - for (let folder in bookmarks) { + for (folder in bookmarks) { let last_item_pos = -1; for (let bookmark of bookmarks[folder]) { Logger.clearPotentialError(); @@ -597,163 +564,10 @@ var TPS = { Logger.logInfo("mozmill setTest: " + obj.name); }, - Cleanup() { - try { - this.WipeServer(); - } catch (ex) { - Logger.logError("Failed to wipe server: " + Log.exceptionStr(ex)); - } - try { - if (Authentication.isLoggedIn) { - // signout and wait for Sync to completely reset itself. - Logger.logInfo("signing out"); - let waiter = this.createEventWaiter("weave:service:start-over:finish"); - Authentication.signOut(); - waiter(); - Logger.logInfo("signout complete"); - } - } catch (e) { - Logger.logError("Failed to sign out: " + Log.exceptionStr(e)); - } - }, - - /** - * Use Sync's bookmark validation code to see if we've corrupted the tree. - */ - ValidateBookmarks() { - - let getServerBookmarkState = () => { - let bookmarkEngine = Weave.Service.engineManager.get('bookmarks'); - let collection = bookmarkEngine.itemSource(); - let collectionKey = bookmarkEngine.service.collectionKeys.keyForCollection(bookmarkEngine.name); - collection.full = true; - let items = []; - collection.recordHandler = function(item) { - item.decrypt(collectionKey); - items.push(item.cleartext); - }; - collection.get(); - return items; - }; - let serverRecordDumpStr; - try { - Logger.logInfo("About to perform bookmark validation"); - let clientTree = Async.promiseSpinningly(PlacesUtils.promiseBookmarksTree("", { - includeItemIds: true - })); - let serverRecords = getServerBookmarkState(); - // We can't wait until catch to stringify this, since at that point it will have cycles. - serverRecordDumpStr = JSON.stringify(serverRecords); - - let validator = new BookmarkValidator(); - let {problemData} = validator.compareServerWithClient(serverRecords, clientTree); - - for (let {name, count} of problemData.getSummary()) { - // Exclude mobile showing up on the server hackily so that we don't - // report it every time, see bug 1273234 and 1274394 for more information. - if (name === "serverUnexpected" && problemData.serverUnexpected.indexOf("mobile") >= 0) { - --count; - } - if (count) { - // Log this out before we assert. This is useful in the context of TPS logs, since we - // can see the IDs in the test files. - Logger.logInfo(`Validation problem: "${name}": ${JSON.stringify(problemData[name])}`); - } - Logger.AssertEqual(count, 0, `Bookmark validation error of type ${name}`); - } - } catch (e) { - // Dump the client records (should always be doable) - DumpBookmarks(); - // Dump the server records if gotten them already. - if (serverRecordDumpStr) { - Logger.logInfo("Server bookmark records:\n" + serverRecordDumpStr + "\n"); - } - this.DumpError("Bookmark validation failed", e); - } - Logger.logInfo("Bookmark validation finished"); - }, - - ValidateCollection(engineName, ValidatorType) { - let serverRecordDumpStr; - let clientRecordDumpStr; - try { - Logger.logInfo(`About to perform validation for "${engineName}"`); - let engine = Weave.Service.engineManager.get(engineName); - let validator = new ValidatorType(engine); - let serverRecords = validator.getServerItems(engine); - let clientRecords = Async.promiseSpinningly(validator.getClientItems()); - try { - // This substantially improves the logs for addons while not making a - // substantial difference for the other two - clientRecordDumpStr = JSON.stringify(clientRecords.map(r => { - let res = validator.normalizeClientItem(r); - delete res.original; // Try and prevent cyclic references - return res; - })); - } catch (e) { - // ignore the error, the dump string is just here to make debugging easier. - clientRecordDumpStr = "<Cyclic value>"; - } - try { - serverRecordDumpStr = JSON.stringify(serverRecords); - } catch (e) { - // as above - serverRecordDumpStr = "<Cyclic value>"; - } - let { problemData } = validator.compareClientWithServer(clientRecords, serverRecords); - for (let { name, count } of problemData.getSummary()) { - if (count) { - Logger.logInfo(`Validation problem: "${name}": ${JSON.stringify(problemData[name])}`); - } - Logger.AssertEqual(count, 0, `Validation error for "${engineName}" of type "${name}"`); - } - } catch (e) { - // Dump the client records if possible - if (clientRecordDumpStr) { - Logger.logInfo(`Client state for ${engineName}:\n${clientRecordDumpStr}\n`); - } - // Dump the server records if gotten them already. - if (serverRecordDumpStr) { - Logger.logInfo(`Server state for ${engineName}:\n${serverRecordDumpStr}\n`); - } - this.DumpError(`Validation failed for ${engineName}`, e); - } - Logger.logInfo(`Validation finished for ${engineName}`); - }, - - ValidatePasswords() { - return this.ValidateCollection("passwords", PasswordValidator); - }, - - ValidateForms() { - return this.ValidateCollection("forms", FormValidator); - }, - - ValidateAddons() { - return this.ValidateCollection("addons", AddonValidator); - }, - RunNextTestAction: function() { try { if (this._currentAction >= - this._phaselist[this._currentPhase].length) { - // Run necessary validations and then finish up - if (this.shouldValidateBookmarks) { - this.ValidateBookmarks(); - } - if (this.shouldValidatePasswords) { - this.ValidatePasswords(); - } - if (this.shouldValidateForms) { - this.ValidateForms(); - } - if (this.shouldValidateAddons) { - this.ValidateAddons(); - } - // Force this early so that we run the validation and detect missing pings - // *before* we start shutting down, since if we do it after, the python - // code won't notice the failure. - SyncTelemetry.shutdown(); + this._phaselist["phase" + this._currentPhase].length) { // we're all done Logger.logInfo("test phase " + this._currentPhase + ": " + (this._errors ? "FAIL" : "PASS")); @@ -761,7 +575,7 @@ var TPS = { this.quit(); return; } - this.seconds_since_epoch = prefs.getIntPref("tps.seconds_since_epoch", 0); + if (this.seconds_since_epoch) this._usSinceEpoch = this.seconds_since_epoch * 1000 * 1000; else { @@ -769,7 +583,7 @@ var TPS = { return; } - let phase = this._phaselist[this._currentPhase]; + let phase = this._phaselist["phase" + this._currentPhase]; let action = phase[this._currentAction]; Logger.logInfo("starting action: " + action[0].name); action[0].apply(this, action.slice(1)); @@ -781,64 +595,12 @@ var TPS = { this._currentAction++; } catch(e) { - if (Async.isShutdownException(e)) { - if (this._requestedQuit) { - Logger.logInfo("Sync aborted due to requested shutdown"); - } else { - this.DumpError("Sync aborted due to shutdown, but we didn't request it"); - } - } else { - this.DumpError("RunNextTestAction failed", e); - } + this.DumpError("Exception caught: " + Utils.exceptionStr(e)); return; } this.RunNextTestAction(); }, - _getFileRelativeToSourceRoot(testFileURL, relativePath) { - let file = fileProtocolHandler.getFileFromURLSpec(testFileURL); - let root = file // <root>/services/sync/tests/tps/test_foo.js - .parent // <root>/services/sync/tests/tps - .parent // <root>/services/sync/tests - .parent // <root>/services/sync - .parent // <root>/services - .parent // <root> - ; - root.appendRelativePath(relativePath); - return root; - }, - - // Attempt to load the sync_ping_schema.json and initialize `this.pingValidator` - // based on the source of the tps file. Assumes that it's at "../unit/sync_ping_schema.json" - // relative to the directory the tps test file (testFile) is contained in. - _tryLoadPingSchema(testFile) { - try { - let schemaFile = this._getFileRelativeToSourceRoot(testFile, - "services/sync/tests/unit/sync_ping_schema.json"); - - let stream = Cc["@mozilla.org/network/file-input-stream;1"] - .createInstance(Ci.nsIFileInputStream); - - let jsonReader = Cc["@mozilla.org/dom/json;1"] - .createInstance(Components.interfaces.nsIJSON); - - stream.init(schemaFile, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0); - let schema = jsonReader.decodeFromStream(stream, stream.available()); - Logger.logInfo("Successfully loaded schema") - - // Importing resource://testing-common/* isn't possible from within TPS, - // so we load Ajv manually. - let ajvFile = this._getFileRelativeToSourceRoot(testFile, "testing/modules/ajv-4.1.1.js"); - let ajvURL = fileProtocolHandler.getURLSpecFromFile(ajvFile); - let ns = {}; - Cu.import(ajvURL, ns); - let ajv = new ns.Ajv({ async: "co*" }); - this.pingValidator = ajv.compile(schema); - } catch (e) { - this.DumpError(`Failed to load ping schema and AJV relative to "${testFile}".`, e); - } - }, - /** * Runs a single test phase. * @@ -870,11 +632,8 @@ var TPS = { Logger.init(logpath); Logger.logInfo("Sync version: " + WEAVE_VERSION); - Logger.logInfo("Firefox buildid: " + Services.appinfo.appBuildID); - Logger.logInfo("Firefox version: " + Services.appinfo.version); - Logger.logInfo("Firefox source revision: " + (AppConstants.SOURCE_REVISION_URL || "unknown")); - Logger.logInfo("Firefox platform: " + AppConstants.platform); - Logger.logInfo('Firefox Accounts enabled: ' + this.fxaccounts_enabled); + Logger.logInfo(Services.appinfo.name + " buildid: " + Services.appinfo.appBuildID); + Logger.logInfo(Services.appinfo.name + " version: " + Services.appinfo.version); // do some sync housekeeping if (Weave.Service.isLoggedIn) { @@ -887,15 +646,12 @@ var TPS = { this.waitForEvent("weave:service:ready"); } - // We only want to do this if we modified the bookmarks this phase. - this.shouldValidateBookmarks = false; - // Always give Sync an extra tick to initialize. If we waited for the // service:ready event, this is required to ensure all handlers have // executed. Utils.nextTick(this._executeTestPhase.bind(this, file, phase, settings)); } catch(e) { - this.DumpError("RunTestPhase failed", e); + this.DumpError("Exception caught: " + Utils.exceptionStr(e)); return; } }, @@ -907,28 +663,17 @@ var TPS = { */ _executeTestPhase: function _executeTestPhase(file, phase, settings) { try { - this.config = JSON.parse(prefs.getCharPref('tps.config')); // parse the test file Services.scriptloader.loadSubScript(file, this); this._currentPhase = phase; - if (this._currentPhase.startsWith("cleanup-")) { - let profileToClean = Cc["@mozilla.org/toolkit/profile-service;1"] - .getService(Ci.nsIToolkitProfileService) - .selectedProfile.name; - this.phases[this._currentPhase] = profileToClean; - this.Phase(this._currentPhase, [[this.Cleanup]]); - } else { - // Don't bother doing this for cleanup phases. - this._tryLoadPingSchema(file); - } - let this_phase = this._phaselist[this._currentPhase]; + let this_phase = this._phaselist["phase" + this._currentPhase]; if (this_phase == undefined) { this.DumpError("invalid phase " + this._currentPhase); return; } - if (this.phases[this._currentPhase] == undefined) { + if (this.phases["phase" + this._currentPhase] == undefined) { this.DumpError("no profile defined for phase " + this._currentPhase); return; } @@ -948,66 +693,49 @@ var TPS = { } } } - Logger.logInfo("Starting phase " + this._currentPhase); - Logger.logInfo("setting client.name to " + this.phases[this._currentPhase]); - Weave.Svc.Prefs.set("client.name", this.phases[this._currentPhase]); + Logger.logInfo("Starting phase " + parseInt(phase, 10) + "/" + + Object.keys(this._phaselist).length); + + Logger.logInfo("setting client.name to " + this.phases["phase" + this._currentPhase]); + Weave.Svc.Prefs.set("client.name", this.phases["phase" + this._currentPhase]); + + // TODO Phases should be defined in a data type that has strong + // ordering, not by lexical sorting. + let currentPhase = parseInt(this._currentPhase, 10); + + // Login at the beginning of the test. + if (currentPhase <= 1) { + this_phase.unshift([this.Login]); + } + + // Wipe the server at the end of the final test phase. + if (currentPhase >= Object.keys(this.phases).length) { + this._finalPhase = true; + } + + // If a custom server was specified, set it now + if (this.config["serverURL"]) { + Weave.Service.serverURL = this.config.serverURL; + prefs.setCharPref('tps.serverURL', this.config.serverURL); + } - this._interceptSyncTelemetry(); + // Store account details as prefs so they're accessible to the Mozmill + // framework. + prefs.setCharPref('tps.account.username', this.config.sync_account.username); + prefs.setCharPref('tps.account.password', this.config.sync_account.password); + prefs.setCharPref('tps.account.passphrase', this.config.sync_account.passphrase); // start processing the test actions this._currentAction = 0; } catch(e) { - this.DumpError("_executeTestPhase failed", e); + this.DumpError("Exception caught: " + Utils.exceptionStr(e)); return; } }, /** - * Override sync telemetry functions so that we can detect errors generating - * the sync ping, and count how many pings we report. - */ - _interceptSyncTelemetry() { - let originalObserve = SyncTelemetry.observe; - let self = this; - SyncTelemetry.observe = function() { - try { - originalObserve.apply(this, arguments); - } catch (e) { - self.DumpError("Error when generating sync telemetry", e); - } - }; - SyncTelemetry.submit = record => { - Logger.logInfo("Intercepted sync telemetry submission: " + JSON.stringify(record)); - this._syncsReportedViaTelemetry += record.syncs.length + (record.discarded || 0); - if (record.discarded) { - if (record.syncs.length != SyncTelemetry.maxPayloadCount) { - this.DumpError("Syncs discarded from ping before maximum payload count reached"); - } - } - // If this is the shutdown ping, check and see that the telemetry saw all the syncs. - if (record.why === "shutdown") { - // If we happen to sync outside of tps manually causing it, its not an - // error in the telemetry, so we only complain if we didn't see all of them. - if (this._syncsReportedViaTelemetry < this._syncCount) { - this.DumpError(`Telemetry missed syncs: Saw ${this._syncsReportedViaTelemetry}, should have >= ${this._syncCount}.`); - } - } - if (!record.syncs.length) { - // Note: we're overwriting submit, so this is called even for pings that - // may have no data (which wouldn't be submitted to telemetry and would - // fail validation). - return; - } - if (!this.pingValidator(record)) { - // Note that we already logged the record. - this.DumpError("Sync ping validation failed with errors: " + JSON.stringify(this.pingValidator.errors)); - } - }; - }, - - /** * Register a single phase with the test harness. * * This is called when loading individual test files. @@ -1018,10 +746,6 @@ var TPS = { * Array of functions/actions to perform. */ Phase: function Test__Phase(phasename, fnlist) { - if (Object.keys(this._phaselist).length === 0) { - // This is the first phase, add that we need to login. - fnlist.unshift([this.Login]); - } this._phaselist[phasename] = fnlist; }, @@ -1067,56 +791,28 @@ var TPS = { }, /** - * Return an object that when called, will block until the named event - * is observed. This is similar to waitForEvent, although is typically safer - * if you need to do some other work that may make the event fire. - * - * eg: - * doSomething(); // causes the event to be fired. - * waitForEvent("something"); - * is risky as the call to doSomething may trigger the event before the - * waitForEvent call is made. Contrast with: - * - * let waiter = createEventWaiter("something"); // does *not* block. - * doSomething(); // causes the event to be fired. - * waiter(); // will return as soon as the event fires, even if it fires - * // before this function is called. - * - * @param aEventName - * String event to wait for. - */ - createEventWaiter(aEventName) { - Logger.logInfo("Setting up wait for " + aEventName + "..."); - let cb = Async.makeSpinningCallback(); - Svc.Obs.add(aEventName, cb); - return function() { - try { - cb.wait(); - } finally { - Svc.Obs.remove(aEventName, cb); - Logger.logInfo(aEventName + " observed!"); - } - } - }, - - - /** * Synchronously wait for the named event to be observed. * * When the event is observed, the function will wait an extra tick before * returning. * - * Note that in general, you should probably use createEventWaiter unless you - * are 100% sure that the event being waited on can only be sent after this - * call adds the listener. - * * @param aEventName * String event to wait for. */ waitForEvent: function waitForEvent(aEventName) { - this.createEventWaiter(aEventName)(); + Logger.logInfo("Waiting for " + aEventName + "..."); + let cb = Async.makeSpinningCallback(); + Svc.Obs.add(aEventName, cb); + cb.wait(); + Svc.Obs.remove(aEventName, cb); + Logger.logInfo(aEventName + " observed!"); + + cb = Async.makeSpinningCallback(); + Utils.nextTick(cb); + cb.wait(); }, + /** * Waits for Sync to logged in before returning */ @@ -1153,18 +849,11 @@ var TPS = { } Logger.logInfo("Setting client credentials and login."); - let account = this.fxaccounts_enabled ? this.config.fx_account - : this.config.sync_account; + let account = this.config.sync_account; Authentication.signIn(account); this.waitForSetupComplete(); Logger.AssertEqual(Weave.Status.service, Weave.STATUS_OK, "Weave status OK"); this.waitForTracking(); - // If fxaccounts is enabled we get an initial sync at login time - let - // that complete. - if (this.fxaccounts_enabled) { - this._triggeredSync = true; - this.waitForSyncFinished(); - } }, /** @@ -1189,12 +878,10 @@ var TPS = { } this.Login(false); - ++this._syncCount; this._triggeredSync = true; this.StartAsyncOperation(); Weave.Service.sync(); - Logger.logInfo("Sync is complete"); }, WipeServer: function TPS__WipeServer() { @@ -1230,9 +917,6 @@ var Addons = { verifyNot: function Addons__verifyNot(addons) { TPS.HandleAddons(addons, ACTION_VERIFY_NOT); }, - skipValidation() { - TPS.shouldValidateAddons = false; - } }; var Bookmarks = { @@ -1250,9 +934,6 @@ var Bookmarks = { }, verifyNot: function Bookmarks__verifyNot(bookmarks) { TPS.HandleBookmarks(bookmarks, ACTION_VERIFY_NOT); - }, - skipValidation() { - TPS.shouldValidateBookmarks = false; } }; @@ -1301,9 +982,6 @@ var Passwords = { }, verifyNot: function Passwords__verifyNot(passwords) { this.HandlePasswords(passwords, ACTION_VERIFY_NOT); - }, - skipValidation() { - TPS.shouldValidatePasswords = false; } }; @@ -1337,4 +1015,4 @@ var Windows = { }; // Initialize TPS -TPS._init(); +TPS._init();
\ No newline at end of file |