[chrome-gnome-shell/feature/extensions_sync] Implemented extensions synchronization with remote storage.



commit f076dbce32ad63b92df0e8cfbbd32239733ebea4
Author: Yuri Konotopov <ykonotopov gmail com>
Date:   Thu Aug 11 13:08:17 2016 +0300

    Implemented extensions synchronization with remote storage.

 extension/_locales/en/messages.json |   42 ++++-
 extension/css/options.css           |   37 ++++
 extension/extension.js              |   41 ++++-
 extension/include/constants.js      |   20 ++
 extension/include/sync.js           |  347 +++++++++++++++++++++++++++++++++++
 extension/manifest.json             |    1 +
 extension/options.html              |   25 +++-
 extension/options.js                |  158 +++++++++++++++--
 8 files changed, 653 insertions(+), 18 deletions(-)
---
diff --git a/extension/_locales/en/messages.json b/extension/_locales/en/messages.json
index 9068fc8..d73eeee 100644
--- a/extension/_locales/en/messages.json
+++ b/extension/_locales/en/messages.json
@@ -7,6 +7,22 @@
                "message": "This extension provides integration with GNOME Shell and the corresponding 
extensions repository https://extensions.gnome.org";
        },
 
+       "extension_name": {
+               "message": "Extension",
+               "description": "Used as title, eg in table header. Means 'Extension name'."
+       },
+       "extension_installed": {
+               "message": "Installed",
+               "description": "Used as title, eg in table header."
+       },
+       "extension_enabled": {
+               "message": "Enabled",
+               "description": "Used as title, eg in table header."
+       },
+       "extension_synchronized": {
+               "message": "Synchronized",
+               "description": "Used as title, eg in table header."
+       },
        "extension_status_upgrade": {
                "message": "can be upgraded"
        },
@@ -77,9 +93,6 @@
        "options_synchronize_extensions_notice": {
                "message": "If enabled, your GNOME Shell extensions list will be synchronized with your 
Google account."
        },
-       "options_synchronize_extensions_notice2": {
-               "message": "For this feature to work properly you must manage GNOME Shell extensions from 
your Chrome/Chromium browser only."
-       },
        "options_last_check": {
                "message": "Last check"
        },
@@ -90,6 +103,26 @@
        "synchronization": {
                "message": "Synchronization"
        },
+       "synchronization_storage_exists": {
+               "message": "Your remote synchronization storage already contains extensions list."
+       },
+       "synchronization_use_local": {
+               "message": "Use local extensions list, overwrite remote."
+       },
+       "synchronization_use_remote": {
+               "message": "Use remote extensions list, overwrite local."
+       },
+       "synchronization_cancel": {
+               "message": "Do not enable synchronization."
+       },
+       "synchronization_failed": {
+               "message": "Failed to synchronize extensions: $CAUSE$",
+               "placeholders": {
+                       "cause": {
+                               "content": "$1"
+                       }
+               }
+       },
        "translation_credits_title": {
                "message": "About translation"
        },
@@ -97,6 +130,9 @@
                "message": "",
                "description": "HTML markup enabled for this string."
        },
+       "unknown_error": {
+               "message": "unknown error"
+       },
 
        "hours": {
                "message": "hours"
diff --git a/extension/css/options.css b/extension/css/options.css
index a5e7583..29776d0 100644
--- a/extension/css/options.css
+++ b/extension/css/options.css
@@ -10,6 +10,12 @@ body {
        text-align: center;
 }
 
+#error {
+       display: none;
+       color: red;
+       text-align: center;
+}
+
 .buttons { text-align: center; margin-top: 1em; }
 .notice { font-size: 90%; font-weight: normal; margin-top: 0.5em; white-space: nowrap; }
 .wrapped { white-space: normal; }
@@ -67,3 +73,34 @@ ul.tabs li:not(:last-child) {
 div.tabs-pane {
        clear: both;
 }
+
+#synchronization table {
+       width: 100%;
+       border-collapse: collapse;
+       vertical-align: middle;
+}
+
+#synchronization table th {
+       border-bottom: 1px solid #000;
+}
+
+#synchronization table tbody td.ok, #synchronization table td.fail {
+       text-align: center;
+       font-size: 150%;
+}
+
+#synchronization table tbody td.ok:after {
+       content: '\2713';
+       color: green;
+}
+
+#synchronization table td.fail:after {
+       content: '\2717';
+       color: red;
+}
+
+dialog#syncChoice button {
+       display: block;
+       width: 90%;
+       margin: 0.3em auto;
+}
diff --git a/extension/extension.js b/extension/extension.js
index 54272c5..c23cfd4 100644
--- a/extension/extension.js
+++ b/extension/extension.js
@@ -67,6 +67,7 @@ chrome.runtime.onMessageExternal.addListener(function (request, sender, sendResp
        }
 });
 
+var disabledExtensionTimeout = null;
 var lastPortMessage = {message: null, date: 0};
 var port = chrome.runtime.connectNative(NATIVE_HOST);
 /*
@@ -78,7 +79,7 @@ port.onMessage.addListener(function (message) {
                /*
                 * Skip duplicate events. This is happens eg when extension is installed.
                 */
-               if((new Date().getTime()) - lastPortMessage.date < 500 && GSC.isSignalsEqual(message, 
lastPortMessage.message))
+               if((new Date().getTime()) - lastPortMessage.date < 1000 && GSC.isSignalsEqual(message, 
lastPortMessage.message))
                {
                        lastPortMessage.date = new Date().getTime();
                        return;
@@ -97,6 +98,43 @@ port.onMessage.addListener(function (message) {
                        }
                });
 
+               /*
+                * Route message to Options page.
+                */
+               chrome.runtime.sendMessage(GS_CHROME_ID, message);
+               if(message.signal === SIGNAL_EXTENSION_CHANGED)
+               {
+                       /*
+                        * GNOME Shell sends 2 events when extension is uninstalled:
+                        * "disabled" event and then "uninstalled" event.
+                        * Let's delay any "disabled" event and drop it if
+                        * "uninstalled" event received within 1,5 secs.
+                        */
+                       if(message.parameters[EXTENSION_CHANGED_STATE] === EXTENSION_STATE.DISABLED)
+                       {
+                               disabledExtensionTimeout = setTimeout(function() {
+                                       disabledExtensionTimeout = null;
+                                       GSC.sync.onExtensionChanged(message);
+                               }, 1500);
+                       }
+                       else if(
+                               disabledExtensionTimeout &&
+                               message.parameters[EXTENSION_CHANGED_STATE] === EXTENSION_STATE.UNINSTALLED &&
+                               lastPortMessage.message.signal === SIGNAL_EXTENSION_CHANGED &&
+                               lastPortMessage.message.parameters[EXTENSION_CHANGED_UUID] === 
message.parameters[EXTENSION_CHANGED_UUID] &&
+                               lastPortMessage.message.parameters[EXTENSION_CHANGED_STATE] === 
EXTENSION_STATE.DISABLED
+                       )
+                       {
+                               clearTimeout(disabledExtensionTimeout);
+                               disabledExtensionTimeout = null;
+                               GSC.sync.onExtensionChanged(message);
+                       }
+                       else
+                       {
+                               GSC.sync.onExtensionChanged(message);
+                       }
+               }
+
                lastPortMessage = {
                        message: message,
                        date: new Date().getTime()
@@ -108,5 +146,6 @@ chrome.runtime.getPlatformInfo(function(info) {
        if (PLATFORMS_WHITELIST.indexOf(info.os) !== -1)
        {
                GSC.update.init();
+               GSC.sync.init();
        }
 });
diff --git a/extension/include/constants.js b/extension/include/constants.js
index d909a1b..0afb333 100644
--- a/extension/include/constants.js
+++ b/extension/include/constants.js
@@ -13,15 +13,21 @@ PLATFORMS_WHITELIST                 = ["linux", "openbsd"];
 
 IS_OPERA                               = navigator.userAgent.indexOf(' OPR/') >= 0;
 
+NOTIFICATION_SYNC_FAILED               = 'gs-chrome-sync-fail';
 NOTIFICATION_UPDATE_AVAILABLE          = 'gs-chrome-update';
 NOTIFICATION_UPDATE_CHECK_FAILED       = 'gs-chrome-update-fail';
 ALARM_UPDATE_CHECK                     = 'gs-chrome-update-check';
 
 MESSAGE_NEXT_UPDATE_CHANGED            = 'gs-next-update-changed';
+MESSAGE_SYNC_FROM_REMOTE               = 'gs-sync-from-remote';
 
 SIGNAL_EXTENSION_CHANGED               = 'ExtensionStatusChanged';
 SIGNAL_SHELL_APPEARED                  = 'org.gnome.Shell';
 
+EXTENSION_CHANGED_UUID                 = 0;
+EXTENSION_CHANGED_STATE                        = 1;
+EXTENSION_CHANGED_ERROR                        = 2;
+
 NATIVE_HOST                            = 'io.github.ne0sight.gs_chrome_connector';
 
 EXTENSIONS_WEBSITE                     = 'https://extensions.gnome.org/';
@@ -45,3 +51,17 @@ EXTERNAL_MESSAGES                    = [
        "version",
        "warning_versions_mismatch"
 ];
+
+// gnome-shell/js/ui/extensionSystem.js
+EXTENSION_STATE                        = {
+       ENABLED:        1,
+       DISABLED:       2,
+       ERROR:          3,
+       OUT_OF_DATE:    4,
+       DOWNLOADING:    5,
+       INITIALIZED:    6,
+
+       // Used as an error state for operations on unknown extensions,
+       // should never be in a real extensionMeta object.
+       UNINSTALLED:    99
+};
diff --git a/extension/include/sync.js b/extension/include/sync.js
new file mode 100644
index 0000000..80be629
--- /dev/null
+++ b/extension/include/sync.js
@@ -0,0 +1,347 @@
+/*
+    GNOME Shell integration for Chrome
+    Copyright (C) 2016  Yuri Konotopov <ykonotopov gmail com>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+ */
+
+/*
+ * Main object that handles extensions synchronization with remote storage.
+ */
+GSC.sync = (function($) {
+       /*
+        * Initialization rutines.
+        */
+       function init() {
+               // Opera do not supports remote storage yet.
+               if(IS_OPERA)
+               {
+                       return;
+               }
+
+               onSyncFromRemote();
+               chrome.storage.onChanged.addListener(function(changes, areaName) {
+                       if(areaName === 'sync' && changes.extensions)
+                       {
+                               onSyncFromRemote(changes.extensions.newValue);
+                       }
+               });
+
+               chrome.runtime.onMessage.addListener(
+                       function (request, sender, sendResponse) {
+                               if (sender.id && sender.id === GS_CHROME_ID && request)
+                               {
+                                       if (request === MESSAGE_SYNC_FROM_REMOTE)
+                                       {
+                                               onSyncFromRemote();
+                                       }
+                               }
+                       }
+               );
+
+               chrome.notifications.onButtonClicked.addListener(function (notificationId, buttonIndex) {
+                       if (notificationId !== NOTIFICATION_SYNC_FAILED)
+                               return;
+
+                       GSC.notifications.remove(notificationId);
+               });
+       }
+
+       /*
+        * Returns array of all local and remote extensions with structure:
+        * [
+        *      $extension_uuid: {
+        *              uuid:           extension uuid,
+        *              name:           extension name,
+        *              remoteState:    extension state in remote storage,
+        *              localState:     extension state in current GNOME Shell,
+        *              remote:         true if extensions is in remote storage,
+        *              local:          true if extension installed localy
+        *      },
+        *      ...
+        * ]
+        */
+       function getExtensions(deferred, remoteExtensions) {
+               GSC.sendNativeRequest({
+                       execute: 'listExtensions'
+               }, function(response) {
+                       if(response && response.success)
+                       {
+                               if(remoteExtensions)
+                               {
+                                       deferred.resolve(mergeExtensions(remoteExtensions, 
response.extensions));
+                               }
+                               else
+                               {
+                                       chrome.storage.sync.get({
+                                               extensions: {}
+                                       }, function(options) {
+                                               if(chrome.runtime.lastError)
+                                               {
+                                                       deferred.reject(chrome.runtime.lastError.message);
+                                               }
+                                               else
+                                               {
+                                                       var extensions = mergeExtensions(options.extensions, 
response.extensions);
+                                                       deferred.resolve(extensions);
+                                               }
+                                       });
+                               }
+                       }
+                       else
+                       {
+                               var message = response && response.message ? response.message : 
m('error_extension_response');
+                               deferred.reject(message);
+                       }
+               });
+       }
+
+       /*
+        * Returns merged list of extensions list in remote storage and
+        * locally installed extensions.
+        * 
+        * Both parameters should be in form:
+        * {
+        *      $extension_uuid: {
+        *              uuid:   ,
+        *              name:   ,
+        *              state:  
+        *      },
+        *      ...
+        * }
+        */
+       function mergeExtensions(remoteExtensions, localExtensions)
+       {
+               var extensions = {};
+
+               $.each(remoteExtensions, function(key, extension) {
+                       if(extension.uuid && extension.name && extension.state)
+                       {
+                               extensions[extension.uuid] = {
+                                       uuid:           extension.uuid,
+                                       name:           extension.name,
+                                       remoteState:    extension.state,
+                                       remote:         true,
+                                       local:          false
+                               };
+                       }
+               });
+
+               $.each(localExtensions, function(key, extension) {
+                       if(extensions[extension.uuid])
+                       {
+                               extensions[extension.uuid].name = extension.name;
+                               extensions[extension.uuid].localState = extension.state;
+                               extensions[extension.uuid].local = true;
+                       }
+                       else
+                       {
+                               extensions[extension.uuid] = {
+                                       uuid:           extension.uuid,
+                                       name:           extension.name,
+                                       remoteState:    EXTENSION_STATE.UNINSTALLED,
+                                       localState:     extension.state,
+                                       remote:         false,
+                                       local:          true
+                               };
+                       }
+               });
+
+               return extensions;
+       }
+
+       /*
+        * Synchronize local changed extensions to remote list.
+        * 
+        * @param extension - extension object:
+        * {
+        *      uuid:   extension uuid,
+        *      name:   extension name,
+        *      state:  extension state
+        * }
+        */
+       function localExtensionChanged(extension) {
+               if($.inArray(extension.state, [EXTENSION_STATE.ENABLED, EXTENSION_STATE.DISABLED, 
EXTENSION_STATE.UNINSTALLED]) !== -1)
+               {
+                       chrome.storage.sync.get({
+                               extensions: {}
+                       }, function (options) {
+                               GSC.sendNativeRequest({
+                                       execute:        'getExtensionInfo',
+                                       uuid:           extension.uuid
+                               }, function(response) {
+                                       // Extension can be uninstalled already
+                                       if(response && response.extensionInfo && 
!$.isEmptyObject(response.extensionInfo))
+                                       {
+                                               extension = response.extensionInfo;
+                                       }
+
+                                       if(extension.state === EXTENSION_STATE.UNINSTALLED && 
options.extensions[extension.uuid])
+                                       {
+                                               delete options.extensions[extension.uuid];
+                                       }
+                                       else
+                                       {
+                                               options.extensions[extension.uuid] = {
+                                                       uuid:   extension.uuid,
+                                                       name:   extension.name,
+                                                       state:  extension.state
+                                               };
+                                       }
+
+                                       chrome.storage.sync.set({
+                                               extensions: options.extensions
+                                       });
+                               });
+                       });
+               }
+       }
+
+       /*
+        * Synchronize remote changes with local GNOME Shell.
+        * 
+        * @param remoteExtensions - (optional) remote extensions list
+        */
+       function remoteExtensionsChanged(remoteExtensions) {
+               getExtensions($.Deferred().done(function(extensions) {
+                       var enableExtensions = [];
+                       $.each(extensions, function(uuid, extension) {
+                               if(extension.remote)
+                               {
+                                       if(!extension.local)
+                                       {
+                                               GSC.sendNativeRequest({
+                                                       execute: "installExtension",
+                                                       uuid: extension.uuid
+                                               }, onInstallUninstall);
+                                       }
+                                       else if (extension.remoteState !== extension.localState)
+                                       {
+                                               if(extension.remoteState === EXTENSION_STATE.ENABLED)
+                                               {
+                                                       enableExtensions.push({
+                                                               uuid: extension.uuid,
+                                                               enable: true
+                                                       });
+                                               }
+                                               else
+                                               {
+                                                       enableExtensions.push({
+                                                               uuid: extension.uuid,
+                                                               enable: false
+                                                       });
+                                               }
+                                       }
+                               }
+                               else if(extension.local)
+                               {
+                                       GSC.sendNativeRequest({
+                                               execute: "uninstallExtension",
+                                               uuid: extension.uuid
+                                       }, onInstallUninstall);
+                               }
+                       });
+
+                       if(enableExtensions.length > 0)
+                       {
+                               GSC.sendNativeRequest({
+                                       execute: "enableExtension",
+                                       extensions: enableExtensions
+                               });
+                       }
+               }).fail(function(message) {
+                       createSyncFailedNotification(message);
+               }), remoteExtensions);
+       }
+
+       /*
+        * Callback called when extension is installed or uninstalled as part
+        * of synchronization process.
+        */
+       function onInstallUninstall(response) {
+               if(response)
+               {
+                       if(!response.success)
+                       {
+                               createSyncFailedNotification(response.message);
+                       }
+               }
+               else
+               {
+                       createSyncFailedNotification();
+               }
+       }
+
+       /*
+        * Wrapper for localExtensionChanged that checks if synchronization is
+        * enabled.
+        */
+       function onExtensionChanged(request)
+       {
+               if(IS_OPERA)
+               {
+                       return;
+               }
+
+               runIfSyncEnabled(function() {
+                       localExtensionChanged({
+                               uuid:   request.parameters[EXTENSION_CHANGED_UUID],
+                               state:  request.parameters[EXTENSION_CHANGED_STATE],
+                               error:  request.parameters[EXTENSION_CHANGED_ERROR]
+                       });
+               });
+       }
+
+       /*
+        * Wrapper for remoteExtensionsChanged that checks if synchronization is
+        * enabled.
+        */
+       function onSyncFromRemote(remoteExtensions)
+       {
+               if(IS_OPERA)
+               {
+                       return;
+               }
+
+               runIfSyncEnabled(function() {
+                       remoteExtensionsChanged(remoteExtensions);
+               });
+       }
+
+       /*
+        * Runs callback function if synchronyzation is enabled.
+        * 
+        * @param callback - callback function
+        */
+       function runIfSyncEnabled(callback) {
+               chrome.storage.local.get({
+                       syncExtensions: false
+               }, function (options) {
+                       if (options.syncExtensions)
+                       {
+                               callback();
+                       }
+               });
+       }
+
+       /*
+        * Create notification when synchronization failed.
+        */
+       function createSyncFailedNotification(cause) {
+               GSC.notifications.create(NOTIFICATION_SYNC_FAILED, {
+                       message: m('synchronization_failed', cause ? cause : m('unknown_error'))
+               });
+       }
+
+       /*
+        * Public methods.
+        */
+       return {
+               init: init,
+               getExtensions: getExtensions,
+               onExtensionChanged: onExtensionChanged
+       };
+})(jQuery);
diff --git a/extension/manifest.json b/extension/manifest.json
index 4d9d237..cb19ca6 100644
--- a/extension/manifest.json
+++ b/extension/manifest.json
@@ -25,6 +25,7 @@
            "include/gsc.js",
            "include/notifications.js",
            "include/update.js",
+           "include/sync.js",
            "extension.js"
     ],
     "persistent": false
diff --git a/extension/options.html b/extension/options.html
index 1e6b900..f42cadf 100644
--- a/extension/options.html
+++ b/extension/options.html
@@ -8,6 +8,8 @@
                <script type="text/javascript" src="include/external/tabby-10.1.0.min.js"></script>
                <script type="text/javascript" src="include/i18n.js"></script>
                <script type="text/javascript" src="include/constants.js"></script>
+               <script type="text/javascript" src="include/gsc.js"></script>
+               <script type="text/javascript" src="include/sync.js"></script>
        </head>
        <body>
                <ul data-tabs='tabs' class="tabs">
@@ -18,6 +20,7 @@
                <div data-tabs-content='tabs-content'>
                        <div id="options" class="tabs-pane active" data-tabs-pane='tabs-pane'>
                                <div id="status" data-i18n="options_saved"></div>
+                               <div id="error"></div>
 
                                <fieldset>
                                        <dl>
@@ -40,8 +43,7 @@
                                        <dl>
                                                <dt>
                                                        <label for='synchronize_extensions_yes' 
data-i18n="options_synchronize_extensions"></label>:<br />
-                                                       <span class="notice wrapped" 
data-i18n="options_synchronize_extensions_notice"></span><br />
-                                                       <span class="notice wrapped" 
data-i18n="options_synchronize_extensions_notice2"></span>
+                                                       <span class="notice wrapped" 
data-i18n="options_synchronize_extensions_notice"></span>
                                                </dt>
                                                <dd>
                                                        <label for='synchronize_extensions_yes' 
data-i18n="yes"></label> <input type='radio' id='synchronize_extensions_yes' name='synchronize_extensions' />
@@ -72,12 +74,31 @@
                                </div>
                        </div>
                        <div id="synchronization" class="tabs-pane" data-tabs-pane='tabs-pane'>
+                               <table>
+                                       <thead>
+                                               <tr>
+                                                       <th data-i18n="extension_name"></th>
+                                                       <th data-i18n="extension_installed"></th>
+                                                       <th data-i18n="extension_enabled"></th>
+                                                       <th data-i18n="extension_synchronized"></th>
+                                               </tr>
+                                       </thead>
+                                       <tbody></tbody>
+                               </table>
                        </div>
                        <div id="translation_credits" class="tabs-pane translation_credits_container" 
data-tabs-pane='tabs-pane'>
                                <div data-i18n='translation_credits' data-i18n-html='true'></div>
                        </div>
                </div>
 
+               <dialog id='syncChoice'>
+                       <h2 data-i18n='synchronization_storage_exists'></h2>
+                       <form method="dialog">
+                               <button type='submit' value='local' 
data-i18n='synchronization_use_local'></button>
+                               <button type='submit' value='remote' 
data-i18n='synchronization_use_remote'></button>
+                               <button type='submit' value='cancel' 
data-i18n='synchronization_cancel'></button>
+                       </form>
+               </dialog>
                <script type="text/javascript" src="options.js"></script>
        </body>
 </html>
diff --git a/extension/options.js b/extension/options.js
index b2a724c..9ec3e5b 100644
--- a/extension/options.js
+++ b/extension/options.js
@@ -24,15 +24,64 @@ function save_options()
                chrome.storage.local.set({
                        syncExtensions: syncExtensions
                }, function() {
-                       // Update status to let user know options were saved.
-                       $('#status')
-                               .show()
-                               .delay(750)
-                               .hide(250);
+                       if(syncExtensions)
+                       {
+                               syncType = document.getElementById('syncChoice').returnValue;
+                               if(!syncType || syncType === 'local')
+                               {
+                                       GSC.sync.getExtensions($.Deferred().done(function (extensions) {
+                                               var localExtensions = {};
+                                               $.each(extensions, function (uuid, extension) {
+                                                       if(
+                                                               extension.local &&
+                                                               $.inArray(extension.localState,
+                                                                       [EXTENSION_STATE.ENABLED, 
EXTENSION_STATE.DISABLED]) !== -1
+                                                       )
+                                                       {
+                                                               localExtensions[extension.uuid] = {
+                                                                       uuid:   extension.uuid,
+                                                                       name:   extension.name,
+                                                                       state:  extension.localState
+                                                               };
+                                                       }
+                                               });
+
+                                               chrome.storage.sync.set({
+                                                       extensions: localExtensions
+                                               }, function() {
+                                                       showSuccessStatus();
+                                               });
+                                       }).fail(function (message) {
+                                               $('#error')
+                                                       .text(message)
+                                                       .show()
+                                                       .delay(15000)
+                                                       .hide(250);
+                                       }));
+                               }
+                               else if(syncType === 'remote')
+                               {
+                                       chrome.runtime.sendMessage(GS_CHROME_ID, MESSAGE_SYNC_FROM_REMOTE);
+                                       showSuccessStatus();
+                               }
+                       }
+                       else
+                       {
+                               showSuccessStatus();
+                       }
                });
        });
 }
 
+function showSuccessStatus()
+{
+       // Update status to let user know options were saved.
+       $('#status')
+               .show()
+               .delay(750)
+               .hide(250);
+}
+
 function restore_options()
 {
        tabby.init();
@@ -53,6 +102,7 @@ function restore_options()
 
        if(!IS_OPERA)
        {
+               updateSynchronizationStatus();
                chrome.storage.local.get(DEFAULT_LOCAL_OPTIONS, function (items) {
                        setSyncExtensions(items.syncExtensions);
                });
@@ -147,6 +197,61 @@ function handleWebrequestPermission()
        }
 }
 
+function updateSynchronizationStatus()
+{
+       GSC.sync.getExtensions($.Deferred().done(function (extensions) {
+               var keys = Object.keys(extensions).sort(function (a, b) {
+                       var nameA = extensions[a].name.toLowerCase();
+                       var nameB = extensions[b].name.toLowerCase();
+
+                       if (nameA < nameB)
+                       {
+                               return -1;
+                       }
+
+                       if (nameA > nameB)
+                       {
+                               return 1;
+                       }
+
+                       return 0;
+               });
+
+               $('#synchronization table tbody').empty();
+               $.each(keys, function (key, uuid) {
+                       var extension = extensions[uuid];
+
+                       $('#synchronization table tbody').append(
+                               $('<tr />')
+                                       .append(
+                                               $('<td />').text(extension.name)
+                                       )
+                                       .append(
+                                               $('<td />').addClass(
+                                                       extension.local ? 'ok' : 'fail'
+                                               )
+                                       )
+                                       .append(
+                                               $('<td />').addClass(
+                                                       extension.localState == EXTENSION_STATE.ENABLED ? 
'ok' : 'fail'
+                                               )
+                                       )
+                                       .append(
+                                               $('<td />').addClass(
+                                                       extension.remote ? 'ok' : 'fail'
+                                               )
+                                       )
+                       );
+               });
+       }).fail(function (message) {
+               $('#error')
+                       .text(message)
+                       .show()
+                       .delay(15000)
+                       .hide(250);
+       }));
+}
+
 i18n();
 
 document.addEventListener('DOMContentLoaded', restore_options);
@@ -155,29 +260,58 @@ $.each(document.getElementsByName('show_network_errors'), function(index, contro
        control.addEventListener('change', handleWebrequestPermission);
 });
 
+document.getElementById('syncChoice').addEventListener('close', function() {
+       if(document.getElementById('syncChoice').returnValue === 'cancel')
+       {
+               $('#synchronize_extensions_no').prop('checked', 'checked');
+       }
+});
+$('input[type="radio"][name="synchronize_extensions"]').change(function() {
+       if($('#synchronize_extensions_yes').is(':checked'))
+       {
+               chrome.storage.sync.get({
+                       extensions: {}
+               }, function (options) {
+                       if(!$.isEmptyObject(options.extensions))
+                       {
+                               document.getElementById('syncChoice').showModal();
+                       }
+               });
+       }
+});
+
 if($('#translation_credits div').is(':empty'))
 {
        $('.translation_credits_container').remove();
 }
 
 chrome.storage.onChanged.addListener(function (changes, areaName) {
-       if (areaName === 'local' && changes.lastUpdateCheck)
+       if (areaName === 'local')
        {
-               if (changes.lastUpdateCheck.newValue)
+               if(changes.lastUpdateCheck && changes.lastUpdateCheck.newValue)
                {
                        $('#last_update_check').text(changes.lastUpdateCheck.newValue);
                }
        }
+
+       if (areaName === 'sync' && changes.extensions)
+       {
+               updateSynchronizationStatus();
+       }
 });
 
 chrome.runtime.onMessage.addListener(
        function (request, sender, sendResponse) {
-               if(
-                       sender.id && sender.id === GS_CHROME_ID &&
-                       request && request === MESSAGE_NEXT_UPDATE_CHANGED
-               )
+               if(sender.id && sender.id === GS_CHROME_ID && request)
                {
-                       retrieveNextUpdateTime();
+                       if(request === MESSAGE_NEXT_UPDATE_CHANGED)
+                       {
+                               retrieveNextUpdateTime();
+                       }
+                       else if(request.signal && request.signal === SIGNAL_EXTENSION_CHANGED)
+                       {
+                               updateSynchronizationStatus();
+                       }
                }
        }
 );


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]