[chrome-gnome-shell/feature/extensions_sync] Implemented extensions synchronization with remote storage.
- From: Yuri Konotopov <ykonotopov src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [chrome-gnome-shell/feature/extensions_sync] Implemented extensions synchronization with remote storage.
- Date: Thu, 11 Aug 2016 10:09:37 +0000 (UTC)
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]