Skip to content

Instantly share code, notes, and snippets.

@modeswitch
Last active August 29, 2015 13:57
Show Gist options
  • Save modeswitch/9537777 to your computer and use it in GitHub Desktop.
Save modeswitch/9537777 to your computer and use it in GitHub Desktop.
diff --git a/services/cloudsync/CloudSync.js b/services/cloudsync/CloudSync.js
new file mode 100644
index 0000000..8bdff6b
--- /dev/null
+++ b/services/cloudsync/CloudSync.js
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
+Cu.import("resource://services-sync/util.js");
+
+const SYNC_PREFS_BRANCH = "services.cloudsync.";
+
+
+function CloudSyncService() {
+ this.wrappedJSObject = this;
+ this.ready = false;
+}
+CloudSyncService.prototype = {
+ classID: Components.ID("{21e3b97b-4e8b-42e7-b494-bb4a788e4168}"),
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+
+ ensureLoaded: function () {
+ Components.utils.import("resource://services-cloudsync/main.js");
+
+ // Side-effect of accessing the service is that it is instantiated.
+ CloudSync.Service;
+ },
+
+ whenLoaded: function() {
+ if (this.ready) {
+ return Promise.resolve();
+ }
+ let deferred = Promise.defer();
+
+ Services.obs.addObserver(function onReady() {
+ Services.obs.removeObserver(onReady, "cloudsync:service:ready");
+ deferred.resolve();
+ }, "cloudsync:service:ready", false);
+ this.ensureLoaded();
+ return deferred.promise;
+ },
+
+ observe: function (subject, topic, data) {
+ switch (topic) {
+ case "app-startup":
+ let os = Cc["@mozilla.org/observer-service;1"].
+ getService(Ci.nsIObserverService);
+ os.addObserver(this, "final-ui-startup", true);
+ break;
+
+ case "final-ui-startup":
+ // Force Weave service to load if it hasn't triggered from overlays
+ this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ this.timer.initWithCallback({
+ notify: function() {
+ this.ensureLoaded();
+ }.bind(this)
+ }, 10000, Ci.nsITimer.TYPE_ONE_SHOT);
+ break;
+ }
+ }
+};
+
+const components = [CloudSyncService];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
diff --git a/services/cloudsync/CloudSyncComponents.manifest b/services/cloudsync/CloudSyncComponents.manifest
new file mode 100644
index 0000000..76ede57
--- /dev/null
+++ b/services/cloudsync/CloudSyncComponents.manifest
@@ -0,0 +1,5 @@
+component {21e3b97b-4e8b-42e7-b494-bb4a788e4168} CloudSync.js
+contract @mozilla.org/cloudsync/service;1 {21e3b97b-4e8b-42e7-b494-bb4a788e4168}
+category app-startup CloudSyncService service,@mozilla.org/cloudsync/service;1 application={3c2e2abc-06d4-11e1-ac3b-374f68613e61} application={ec8030f7-c20a-464f-9b0e-13a3a9e97384} application={aa3c5121-dab2-40e2-81ca-7ea25febc110} application={a23983c0-fd0e-11dc-95ff-0800200c9a66} application={92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a} application={99bceaaa-e3c6-48c1-b981-ef9b46b67d60}
+
+resource services-cloudsync resource://gre/modules/services-cloudsync/
\ No newline at end of file
diff --git a/services/cloudsync/Makefile.in b/services/cloudsync/Makefile.in
new file mode 100644
index 0000000..2841eb0
--- /dev/null
+++ b/services/cloudsync/Makefile.in
@@ -0,0 +1,30 @@
+# 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/.
+
+# Definitions used by constants.js.
+cloudsync_version := 1.0.0
+cloudsync_id := {a133808b-ec8d-4cab-8767-4ef9cf8f66c3}
+
+# Preprocess files.
+CLOUDSYNC_PP := modules/constants.js
+CLOUDSYNC_PP_FLAGS := \
+ -Dcloudsync_version=$(cloudsync_version) \
+ -Dcloudsync_id='$(cloudsync_id)'
+CLOUDSYNC_PP_PATH = $(FINAL_TARGET)/modules/services-cloudsync
+PP_TARGETS += CLOUDSYNC_PP
+
+# The set of core JavaScript modules for Sync. These are copied as-is.
+cloudsync_modules := \
+ local.js \
+ main.js \
+ service.js \
+ tabs.js \
+ $(NULL)
+
+PREF_JS_EXPORTS := $(srcdir)/services-cloudsync.js
+
+# Install JS module files.
+CLOUDSYNC_MAIN_FILES := $(addprefix modules/,$(cloudsync_modules))
+CLOUDSYNC_MAIN_DEST = $(FINAL_TARGET)/modules/services-cloudsync
+INSTALL_TARGETS += CLOUDSYNC_MAIN
\ No newline at end of file
diff --git a/services/cloudsync/modules/constants.js b/services/cloudsync/modules/constants.js
new file mode 100644
index 0000000..d543d21
--- /dev/null
+++ b/services/cloudsync/modules/constants.js
@@ -0,0 +1,189 @@
+#filter substitution
+/* 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/. */
+
+// Process each item in the "constants hash" to add to "global" and give a name
+this.EXPORTED_SYMBOLS = [((this[key] = val), key) for ([key, val] in Iterator({
+
+CLOUDSYNC_VERSION: "@cloudsync_version@",
+
+// Sync Server API version that the client supports.
+CLOUDSYNC_API_VERSION: "1.0",
+USER_API_VERSION: "1.0",
+MISC_API_VERSION: "1.0",
+
+// Version of the data format this client supports. The data format describes
+// how records are packaged; this is separate from the Server API version and
+// the per-engine cleartext formats.
+STORAGE_VERSION: 5,
+PREFS_BRANCH: "services.cloudsync.",
+
+/*
+// Host "key" to access Weave Identity in the password manager
+PWDMGR_HOST: "chrome://cloudsync",
+PWDMGR_PASSWORD_REALM: "Mozilla Services Password",
+PWDMGR_PASSPHRASE_REALM: "Mozilla Services Encryption Passphrase",
+PWDMGR_KEYBUNDLE_REALM: "Mozilla Services Key Bundles",
+
+// Put in [] because those aren't allowed in a collection name.
+DEFAULT_KEYBUNDLE_NAME: "[default]",
+
+// Our extra input to SHA256-HMAC in generateEntry.
+// This includes the full crypto spec; change this when our algo changes.
+HMAC_INPUT: "Sync-AES_256_CBC-HMAC256",
+
+// Key dimensions.
+SYNC_KEY_ENCODED_LENGTH: 26,
+SYNC_KEY_DECODED_LENGTH: 16,
+SYNC_KEY_HYPHENATED_LENGTH: 31, // 26 chars, 5 hyphens.
+
+NO_SYNC_NODE_INTERVAL: 10 * 60 * 1000, // 10 minutes
+
+MAX_ERROR_COUNT_BEFORE_BACKOFF: 3,
+MAX_IGNORE_ERROR_COUNT: 5,
+
+// Backoff intervals
+MINIMUM_BACKOFF_INTERVAL: 15 * 60 * 1000, // 15 minutes
+MAXIMUM_BACKOFF_INTERVAL: 8 * 60 * 60 * 1000, // 8 hours
+
+// HMAC event handling timeout.
+// 10 minutes: a compromise between the multi-desktop sync interval
+// and the mobile sync interval.
+HMAC_EVENT_INTERVAL: 600000,
+
+// How long to wait between sync attempts if the Master Password is locked.
+MASTER_PASSWORD_LOCKED_RETRY_INTERVAL: 15 * 60 * 1000, // 15 minutes
+
+// How long to initially wait between sync attempts if the identity manager is
+// not ready. As we expect this to become ready relatively quickly, we retry
+// in (IDENTITY_NOT_READY_RETRY_INTERVAL * num_failures) seconds.
+IDENTITY_NOT_READY_RETRY_INTERVAL: 5 * 1000, // 5 seconds
+
+// Separate from the ID fetch batch size to allow tuning for mobile.
+MOBILE_BATCH_SIZE: 50,
+
+// 50 is hardcoded here because of URL length restrictions.
+// (GUIDs can be up to 64 chars long.)
+// Individual engines can set different values for their limit if their
+// identifiers are shorter.
+DEFAULT_GUID_FETCH_BATCH_SIZE: 50,
+DEFAULT_MOBILE_GUID_FETCH_BATCH_SIZE: 50,
+
+// Default batch size for applying incoming records.
+DEFAULT_STORE_BATCH_SIZE: 1,
+HISTORY_STORE_BATCH_SIZE: 50, // same as MOBILE_BATCH_SIZE
+FORMS_STORE_BATCH_SIZE: 50, // same as MOBILE_BATCH_SIZE
+PASSWORDS_STORE_BATCH_SIZE: 50, // same as MOBILE_BATCH_SIZE
+ADDONS_STORE_BATCH_SIZE: 1000000, // process all addons at once
+APPS_STORE_BATCH_SIZE: 50, // same as MOBILE_BATCH_SIZE
+
+// score thresholds for early syncs
+SINGLE_USER_THRESHOLD: 1000,
+MULTI_DEVICE_THRESHOLD: 300,
+
+// Other score increment constants
+SCORE_INCREMENT_SMALL: 1,
+SCORE_INCREMENT_MEDIUM: 10,
+
+// Instant sync score increment
+SCORE_INCREMENT_XLARGE: 300 + 1, //MULTI_DEVICE_THRESHOLD + 1
+
+// Delay before incrementing global score
+SCORE_UPDATE_DELAY: 100,
+
+// Delay for the back observer debouncer. This is chosen to be longer than any
+// observed spurious idle/back events and short enough to pre-empt user activity.
+IDLE_OBSERVER_BACK_DELAY: 100,
+
+// Number of records to upload in a single POST (multiple POSTS if exceeded)
+// FIXME: Record size limit is 256k (new cluster), so this can be quite large!
+// (Bug 569295)
+MAX_UPLOAD_RECORDS: 100,
+MAX_HISTORY_UPLOAD: 5000,
+MAX_HISTORY_DOWNLOAD: 5000,
+
+// Top-level statuses:
+STATUS_OK: "success.status_ok",
+SYNC_FAILED: "error.sync.failed",
+LOGIN_FAILED: "error.login.failed",
+SYNC_FAILED_PARTIAL: "error.sync.failed_partial",
+CLIENT_NOT_CONFIGURED: "service.client_not_configured",
+STATUS_DISABLED: "service.disabled",
+MASTER_PASSWORD_LOCKED: "service.master_password_locked",
+
+// success states
+LOGIN_SUCCEEDED: "success.login",
+SYNC_SUCCEEDED: "success.sync",
+ENGINE_SUCCEEDED: "success.engine",
+
+// login failure status codes:
+LOGIN_FAILED_NO_USERNAME: "error.login.reason.no_username",
+LOGIN_FAILED_NO_PASSWORD: "error.login.reason.no_password2",
+LOGIN_FAILED_NO_PASSPHRASE: "error.login.reason.no_recoverykey",
+LOGIN_FAILED_NETWORK_ERROR: "error.login.reason.network",
+LOGIN_FAILED_SERVER_ERROR: "error.login.reason.server",
+LOGIN_FAILED_INVALID_PASSPHRASE: "error.login.reason.recoverykey",
+LOGIN_FAILED_LOGIN_REJECTED: "error.login.reason.account",
+LOGIN_FAILED_NOT_READY: "error.login.reason.initializing",
+
+// sync failure status codes
+METARECORD_DOWNLOAD_FAIL: "error.sync.reason.metarecord_download_fail",
+VERSION_OUT_OF_DATE: "error.sync.reason.version_out_of_date",
+DESKTOP_VERSION_OUT_OF_DATE: "error.sync.reason.desktop_version_out_of_date",
+SETUP_FAILED_NO_PASSPHRASE: "error.sync.reason.setup_failed_no_passphrase",
+CREDENTIALS_CHANGED: "error.sync.reason.credentials_changed",
+ABORT_SYNC_COMMAND: "aborting sync, process commands said so",
+NO_SYNC_NODE_FOUND: "error.sync.reason.no_node_found",
+OVER_QUOTA: "error.sync.reason.over_quota",
+PROLONGED_SYNC_FAILURE: "error.sync.prolonged_failure",
+SERVER_MAINTENANCE: "error.sync.reason.serverMaintenance",
+
+RESPONSE_OVER_QUOTA: "14",
+
+// engine failure status codes
+ENGINE_UPLOAD_FAIL: "error.engine.reason.record_upload_fail",
+ENGINE_DOWNLOAD_FAIL: "error.engine.reason.record_download_fail",
+ENGINE_UNKNOWN_FAIL: "error.engine.reason.unknown_fail",
+ENGINE_APPLY_FAIL: "error.engine.reason.apply_fail",
+ENGINE_METARECORD_DOWNLOAD_FAIL: "error.engine.reason.metarecord_download_fail",
+ENGINE_METARECORD_UPLOAD_FAIL: "error.engine.reason.metarecord_upload_fail",
+
+JPAKE_ERROR_CHANNEL: "jpake.error.channel",
+JPAKE_ERROR_NETWORK: "jpake.error.network",
+JPAKE_ERROR_SERVER: "jpake.error.server",
+JPAKE_ERROR_TIMEOUT: "jpake.error.timeout",
+JPAKE_ERROR_INTERNAL: "jpake.error.internal",
+JPAKE_ERROR_INVALID: "jpake.error.invalid",
+JPAKE_ERROR_NODATA: "jpake.error.nodata",
+JPAKE_ERROR_KEYMISMATCH: "jpake.error.keymismatch",
+JPAKE_ERROR_WRONGMESSAGE: "jpake.error.wrongmessage",
+JPAKE_ERROR_USERABORT: "jpake.error.userabort",
+JPAKE_ERROR_DELAYUNSUPPORTED: "jpake.error.delayunsupported",
+
+// info types for Service.getStorageInfo
+INFO_COLLECTIONS: "collections",
+INFO_COLLECTION_USAGE: "collection_usage",
+INFO_COLLECTION_COUNTS: "collection_counts",
+INFO_QUOTA: "quota",
+
+// Ways that a sync can be disabled (messages only to be printed in debug log)
+kSyncMasterPasswordLocked: "User elected to leave Master Password locked",
+kSyncWeaveDisabled: "Weave is disabled",
+kSyncNetworkOffline: "Network is offline",
+kSyncBackoffNotMet: "Trying to sync before the server said it's okay",
+kFirstSyncChoiceNotMade: "User has not selected an action for first sync",
+
+// Application IDs
+FIREFOX_ID: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
+FENNEC_ID: "{a23983c0-fd0e-11dc-95ff-0800200c9a66}",
+SEAMONKEY_ID: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
+TEST_HARNESS_ID: "[email protected]",
+
+MIN_PP_LENGTH: 12,
+MIN_PASS_LENGTH: 8,
+
+LOG_DATE_FORMAT: "%Y-%m-%d %H:%M:%S",
+*/
+
+}))];
diff --git a/services/cloudsync/modules/local.js b/services/cloudsync/modules/local.js
new file mode 100644
index 0000000..6310e97
--- /dev/null
+++ b/services/cloudsync/modules/local.js
@@ -0,0 +1,58 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = ["Local"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://services-common/stringbundle.js");
+Cu.import("resource://services-sync/util.js");
+
+function _Local() {
+
+}
+
+_Local.prototype = {
+ get id() {
+ // Generate a random GUID id we don't have one
+ let localID = Svc.Prefs.get("client.GUID", "");
+ return localID == "" ? this.localID = Utils.makeGUID() : localID;
+ },
+
+ get name() {
+ let localName = Svc.Prefs.get("client.name", "");
+ if (localName != "")
+ return localName;
+
+ // Generate a client name if we don't have a useful one yet
+ let env = Cc["@mozilla.org/process/environment;1"]
+ .getService(Ci.nsIEnvironment);
+ let user = env.get("USER") || env.get("USERNAME") ||
+ Svc.Prefs.get("account") || Svc.Prefs.get("username");
+
+ let appName;
+ let brand = new StringBundle("chrome://branding/locale/brand.properties");
+ let brandName = brand.get("brandShortName");
+ try {
+ let syncStrings = new StringBundle("chrome://browser/locale/sync.properties");
+ appName = syncStrings.getFormattedString("sync.defaultAccountApplication", [brandName]);
+ } catch (ex) {}
+ appName = appName || brandName;
+
+ let system =
+ // 'device' is defined on unix systems
+ Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2).get("device") ||
+ // hostname of the system, usually assigned by the user or admin
+ Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2).get("host") ||
+ // fall back on ua info string
+ Cc["@mozilla.org/network/protocol;1?name=http"].getService(Ci.nsIHttpProtocolHandler).oscpu;
+
+ return this.localName = Str.sync.get("client.name2", [user, appName, system]);
+ }
+};
+
+this.Local = new _Local();
\ No newline at end of file
diff --git a/services/cloudsync/modules/main.js b/services/cloudsync/modules/main.js
new file mode 100644
index 0000000..9430d0a
--- /dev/null
+++ b/services/cloudsync/modules/main.js
@@ -0,0 +1,26 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = ['CloudSync'];
+
+this.CloudSync = {};
+let lazies = {
+ "service.js": ["Service"],
+ "local.js": ["Local"],
+ "tabs.js": ["Tabs"],
+};
+
+function lazyImport(module, dest, props) {
+ function getter(prop) function() {
+ let ns = {};
+ Components.utils.import(module, ns);
+ delete dest[prop];
+ return dest[prop] = ns[prop];
+ };
+ props.forEach(function(prop) dest.__defineGetter__(prop, getter(prop)));
+}
+
+for (let mod in lazies) {
+ lazyImport("resource://services-cloudsync/" + mod, CloudSync, lazies[mod]);
+}
\ No newline at end of file
diff --git a/services/cloudsync/modules/service.js b/services/cloudsync/modules/service.js
new file mode 100644
index 0000000..b723176
--- /dev/null
+++ b/services/cloudsync/modules/service.js
@@ -0,0 +1,40 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = ["Service"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://services-sync/util.js");
+
+function CloudSyncllService() {
+ this._notify = Utils.notify("cloudsync:service:");
+}
+
+CloudSyncllService.prototype = {
+ onStartup: function onStartup() {
+ // Send an event now that Weave service is ready. We don't do this
+ // synchronously so that observers can import this module before
+ // registering an observer.
+ Utils.nextTick(function onNextTick() {
+ // UI code uses the flag on the XPCOM service so it doesn't have
+ // to load a bunch of modules.
+ let xps = Cc["@mozilla.org/cloudsync/service;1"]
+ .getService(Ci.nsISupports)
+ .wrappedJSObject;
+ xps.ready = true;
+
+ Svc.Obs.notify("cloudsync:service:ready");
+ }.bind(this));
+ },
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+ Ci.nsISupportsWeakReference]),
+};
+
+this.Service = new CloudSyncllService();
+Service.onStartup();
\ No newline at end of file
diff --git a/services/cloudsync/modules/tabs.js b/services/cloudsync/modules/tabs.js
new file mode 100644
index 0000000..81c22d3
--- /dev/null
+++ b/services/cloudsync/modules/tabs.js
@@ -0,0 +1,214 @@
+/* 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/. */
+
+this.EXPORTED_SYMBOLS = ["Tabs"];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://services-common/observers.js");
+Cu.import("resource://services-sync/util.js");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+function _Tabs() {
+ this.cachedRemotesInfo = {};
+ this.listeners = [];
+
+ this.onTab = Utils.bind2(this, this.onTab);
+ this._unregisterListeners = Utils.bind2(this, this._unregisterListeners);
+
+ Observers.notify("cloudsync:tabs:start", null);
+}
+
+_Tabs.prototype = {
+ addEventListener: function addEventListener(type, listener) {
+ if("change" !== type ||
+ this.listeners.indexOf(listener) >= 0) {
+ return;
+ }
+
+ this.listeners.push(listener);
+ if(1 == this.listeners.length) {
+ this.resume();
+ }
+ },
+
+ removeEventListener: function removeEventListener(type, listener) {
+ var i = this.listeners.indexOf(listener);
+ if("change" !== type ||
+ i < 0) {
+ return;
+ }
+
+ this.listeners.splice(i, 1);
+ if(0 == this.listeners.length) {
+ this.suspend();
+ }
+ },
+
+ // [
+ // client: {
+ // id: client id,
+ // name: client name,
+ // isMobile: true if client is a mobile device, false otherwise
+ // },
+ // timestamp: the time that this info was generated
+ // tabs: [ array of tabs, with the following format
+ // {
+ // title: the page title of the tab
+ // lastUsed: integer in epoch time
+ // icon: url to an icon
+ // urlHistory: array of urls
+ // }
+ // ],
+ // ]
+ setRemoteTabs: function setRemoteTabs(tabsInfo) {
+ tabsInfo.forEach(function(info) {
+ let client = info.client;
+ if(this.cachedRemotesInfo[client.id]) {
+ let current = this.cachedRemotesInfo[client.id];
+ if(current.timestamp > info.timestamp) {
+ return;
+ }
+ }
+
+ this.cachedRemotesInfo[client.id] = info;
+ }.bind(this));
+
+ Observers.notify("cloudsync:tabs:set-remote-tabs", null);
+ },
+
+ getLocalTabs: function getLocalTabs(filter) {
+ filter = (undefined === filter) ? true : filter;
+ let filteredUrls = new RegExp(Svc.Prefs.get("engine.tabs.filteredUrls"), "i");
+
+ let allTabs = [];
+
+ let currentState = JSON.parse(Svc.Session.getBrowserState());
+ currentState.windows.forEach(function (window) {
+ if (window.isPrivate) {
+ return;
+ }
+ window.tabs.forEach(function (tab) {
+ // Make sure there are history entries to look at.
+ if (!tab.entries.length)
+ return;
+ // Until we store full or partial history, just grab the current entry.
+ // index is 1 based, so make sure we adjust.
+ let entry = tab.entries[tab.index - 1];
+
+ // Filter out some urls if necessary. SessionStore can return empty
+ // tabs in some cases - easiest thing is to just ignore them for now.
+ if (!entry.url || filter && filteredUrls.test(entry.url))
+ return;
+
+ // I think it's also possible that attributes[.image] might not be set
+ // so handle that as well.
+ allTabs.push({
+ title: entry.title || "",
+ urlHistory: [entry.url],
+ icon: tab.attributes && tab.attributes.image || "",
+ lastUsed: Math.floor((tab.lastAccessed || 0) / 1000)
+ });
+ });
+ });
+
+ return allTabs;
+ },
+
+ isOpenLocally: function isLocal(url) {
+ return this.getLocalTabs().some(function(tab) {
+ return tab.urlHistory[0] == url;
+ });
+ },
+
+ getAllRemotes: function getAllRemotes() {
+ return this.cachedRemotesInfo;
+ },
+
+ getRemoteById: function getRemoteById(id) {
+ return this.cachedRemotesInfo[id];
+ },
+
+ clearAllRemotes: function clearAllRemotes() {
+ this.cachedRemotesInfo = {};
+ },
+
+ clearRemoteById: function clearRemoteById(id) {
+ delete this.cachedRemotesInfo[id];
+ },
+
+ resume: function resume() {
+ Svc.Obs.add("domwindowopened", this);
+ let wins = Services.wm.getEnumerator("navigator:browser");
+ while (wins.hasMoreElements()) {
+ this._registerListenersForWindow(wins.getNext());
+ }
+ },
+
+ suspend: function suspend() {
+ Svc.Obs.remove("domwindowopened", this);
+ let wins = Services.wm.getEnumerator("navigator:browser");
+ while (wins.hasMoreElements()) {
+ this._unregisterListenersForWindow(wins.getNext());
+ }
+ },
+
+ observe: function(subject, topic, data) {
+ switch (topic) {
+ case "domwindowopened":
+ let onLoad = () => {
+ subject.removeEventListener("load", onLoad, false);
+ // Only register after the window is done loading to avoid unloads.
+ this._registerListenersForWindow(subject);
+ };
+
+ // Add tab listeners now that a window has opened.
+ subject.addEventListener("load", onLoad, false);
+ break;
+ }
+ },
+
+ _topics: ["pageshow", "TabOpen", "TabClose", "TabSelect"],
+ _registerListenersForWindow: function registerListenersFW(window) {
+ // this._log.trace("Registering tab listeners in window");
+ for each (let topic in this._topics) {
+ window.addEventListener(topic, this.onTab, false);
+ }
+ window.addEventListener("unload", this._unregisterListeners, false);
+ },
+
+ _unregisterListeners: function unregisterListeners(event) {
+ this._unregisterListenersForWindow(event.target);
+ },
+
+ _unregisterListenersForWindow: function unregisterListenersFW(window) {
+ // this._log.trace("Removing tab listeners in window");
+ window.removeEventListener("unload", this._unregisterListeners, false);
+ for each (let topic in this._topics) {
+ window.removeEventListener(topic, this.onTab, false);
+ }
+ },
+
+ onTab: function onTab(event) {
+ if (event.originalTarget.linkedBrowser) {
+ let win = event.originalTarget.linkedBrowser.contentWindow;
+ if (PrivateBrowsingUtils.isWindowPrivate(win) &&
+ !PrivateBrowsingUtils.permanentPrivateBrowsing) {
+ this._log.trace("Ignoring tab event from private browsing.");
+ return;
+ }
+ }
+
+ this.listeners.forEach(function(listener) {
+ listener.call(undefined);
+ });
+ }
+};
+
+this.Tabs = new _Tabs();
\ No newline at end of file
diff --git a/services/cloudsync/moz.build b/services/cloudsync/moz.build
new file mode 100644
index 0000000..a3a9ccf
--- /dev/null
+++ b/services/cloudsync/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXTRA_COMPONENTS += [
+ 'CloudSync.js',
+]
+
+EXTRA_PP_COMPONENTS += [
+ 'CloudSyncComponents.manifest',
+]
diff --git a/services/cloudsync/services-cloudsync.js b/services/cloudsync/services-cloudsync.js
new file mode 100644
index 0000000..7a121aa
--- /dev/null
+++ b/services/cloudsync/services-cloudsync.js
@@ -0,0 +1,80 @@
+/* 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/. */
+
+/*
+pref("services.sync.serverURL", "https://auth.services.mozilla.com/");
+pref("services.sync.userURL", "user/");
+pref("services.sync.miscURL", "misc/");
+pref("services.sync.termsURL", "https://services.mozilla.com/tos/");
+pref("services.sync.privacyURL", "https://services.mozilla.com/privacy-policy/");
+pref("services.sync.statusURL", "https://services.mozilla.com/status/");
+pref("services.sync.syncKeyHelpURL", "https://services.mozilla.com/help/synckey");
+
+pref("services.sync.lastversion", "firstrun");
+pref("services.sync.sendVersionInfo", true);
+
+pref("services.sync.scheduler.eolInterval", 604800); // 1 week
+pref("services.sync.scheduler.singleDeviceInterval", 86400); // 1 day
+pref("services.sync.scheduler.idleInterval", 3600); // 1 hour
+pref("services.sync.scheduler.activeInterval", 600); // 10 minutes
+pref("services.sync.scheduler.immediateInterval", 90); // 1.5 minutes
+pref("services.sync.scheduler.idleTime", 300); // 5 minutes
+
+pref("services.sync.errorhandler.networkFailureReportTimeout", 1209600); // 2 weeks
+
+pref("services.sync.engine.addons", true);
+pref("services.sync.engine.bookmarks", true);
+pref("services.sync.engine.history", true);
+pref("services.sync.engine.passwords", true);
+pref("services.sync.engine.prefs", true);
+pref("services.sync.engine.tabs", true);
+pref("services.sync.engine.tabs.filteredUrls", "^(about:.*|chrome://weave/.*|wyciwyg:.*|file:.*)$");
+
+pref("services.sync.jpake.serverURL", "https://setup.services.mozilla.com/");
+pref("services.sync.jpake.pollInterval", 1000);
+pref("services.sync.jpake.firstMsgMaxTries", 300); // 5 minutes
+pref("services.sync.jpake.lastMsgMaxTries", 300); // 5 minutes
+pref("services.sync.jpake.maxTries", 10);
+
+// Allow add-ons to be synced from non-trusted sources.
+pref("services.sync.addons.ignoreRepositoryChecking", false);
+
+// If true, add-on sync ignores changes to the user-enabled flag. This
+// allows people to have the same set of add-ons installed across all
+// profiles while maintaining different enabled states.
+pref("services.sync.addons.ignoreUserEnabledChanges", false);
+
+// Comma-delimited list of hostnames to trust for add-on install.
+pref("services.sync.addons.trustedSourceHostnames", "addons.mozilla.org");
+
+pref("services.sync.log.appender.console", "Warn");
+pref("services.sync.log.appender.dump", "Error");
+pref("services.sync.log.appender.file.level", "Trace");
+pref("services.sync.log.appender.file.logOnError", true);
+pref("services.sync.log.appender.file.logOnSuccess", false);
+pref("services.sync.log.appender.file.maxErrorAge", 864000); // 10 days
+pref("services.sync.log.rootLogger", "Debug");
+pref("services.sync.log.logger.addonutils", "Debug");
+pref("services.sync.log.logger.service.main", "Debug");
+pref("services.sync.log.logger.status", "Debug");
+pref("services.sync.log.logger.authenticator", "Debug");
+pref("services.sync.log.logger.network.resources", "Debug");
+pref("services.sync.log.logger.service.jpakeclient", "Debug");
+pref("services.sync.log.logger.engine.bookmarks", "Debug");
+pref("services.sync.log.logger.engine.clients", "Debug");
+pref("services.sync.log.logger.engine.forms", "Debug");
+pref("services.sync.log.logger.engine.history", "Debug");
+pref("services.sync.log.logger.engine.passwords", "Debug");
+pref("services.sync.log.logger.engine.prefs", "Debug");
+pref("services.sync.log.logger.engine.tabs", "Debug");
+pref("services.sync.log.logger.engine.addons", "Debug");
+pref("services.sync.log.logger.engine.apps", "Debug");
+pref("services.sync.log.logger.userapi", "Debug");
+pref("services.sync.log.cryptoDebug", false);
+
+pref("services.sync.tokenServerURI", "https://token.services.mozilla.com/1.0/sync/1.5");
+
+pref("services.sync.fxa.termsURL", "https://accounts.firefox.com/legal/terms");
+pref("services.sync.fxa.privacyURL", "https://accounts.firefox.com/legal/privacy");
+*/
\ No newline at end of file
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment