added on/off switch and expiration date on instanceKeys in sync
on/off is in settings. when instanceKey date is 30 days old, it is deleted fixed bug
This commit is contained in:
parent
d7b66eca52
commit
3aa2902cde
6 changed files with 139 additions and 78 deletions
|
@ -46,6 +46,11 @@ const assignManager = {
|
||||||
return this.getByUrlKey(siteStoreKey);
|
return this.getByUrlKey(siteStoreKey);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async getSyncEnabled() {
|
||||||
|
const { syncEnabled } = await browser.storage.local.get("syncEnabled");
|
||||||
|
return !!syncEnabled;
|
||||||
|
},
|
||||||
|
|
||||||
getByUrlKey(siteStoreKey) {
|
getByUrlKey(siteStoreKey) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.area.get([siteStoreKey]).then((storageResponse) => {
|
this.area.get([siteStoreKey]).then((storageResponse) => {
|
||||||
|
@ -72,7 +77,8 @@ const assignManager = {
|
||||||
await this.area.set({
|
await this.area.set({
|
||||||
[siteStoreKey]: data
|
[siteStoreKey]: data
|
||||||
});
|
});
|
||||||
if (backup) await sync.storageArea.backup({undeleteSiteStoreKey: siteStoreKey});
|
const syncEnabled = await this.getSyncEnabled();
|
||||||
|
if (backup && syncEnabled) await sync.storageArea.backup({undeleteSiteStoreKey: siteStoreKey});
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -81,7 +87,8 @@ const assignManager = {
|
||||||
// When we remove an assignment we should clear all the exemptions
|
// When we remove an assignment we should clear all the exemptions
|
||||||
this.removeExempted(pageUrlorUrlKey);
|
this.removeExempted(pageUrlorUrlKey);
|
||||||
await this.area.remove([siteStoreKey]);
|
await this.area.remove([siteStoreKey]);
|
||||||
await sync.storageArea.backup({siteStoreKey});
|
const syncEnabled = await this.getSyncEnabled();
|
||||||
|
if (syncEnabled) await sync.storageArea.backup({siteStoreKey});
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -14,11 +14,11 @@
|
||||||
]
|
]
|
||||||
-->
|
-->
|
||||||
<script type="text/javascript" src="backgroundLogic.js"></script>
|
<script type="text/javascript" src="backgroundLogic.js"></script>
|
||||||
<script type="text/javascript" src="sync.js"></script>
|
|
||||||
<script type="text/javascript" src="assignManager.js"></script>
|
<script type="text/javascript" src="assignManager.js"></script>
|
||||||
<script type="text/javascript" src="badge.js"></script>
|
<script type="text/javascript" src="badge.js"></script>
|
||||||
<script type="text/javascript" src="identityState.js"></script>
|
<script type="text/javascript" src="identityState.js"></script>
|
||||||
<script type="text/javascript" src="messageHandler.js"></script>
|
<script type="text/javascript" src="messageHandler.js"></script>
|
||||||
<script type="text/javascript" src="test.js"></script>
|
<script type="text/javascript" src="test.js"></script>
|
||||||
|
<script type="text/javascript" src="sync.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -10,6 +10,9 @@ const messageHandler = {
|
||||||
let response;
|
let response;
|
||||||
|
|
||||||
switch (m.method) {
|
switch (m.method) {
|
||||||
|
case "resetSync":
|
||||||
|
response = sync.resetSync();
|
||||||
|
break;
|
||||||
case "resetBookmarksContext":
|
case "resetBookmarksContext":
|
||||||
response = assignManager.resetBookmarksMenuItem();
|
response = assignManager.resetBookmarksMenuItem();
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -12,14 +12,40 @@ const sync = {
|
||||||
return await this.area.set(options);
|
return await this.area.set(options);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async deleteIdentity(deletedIdentityUUID) {
|
||||||
|
const deletedIdentityList =
|
||||||
|
await sync.storageArea.getDeletedIdentityList();
|
||||||
|
if (
|
||||||
|
deletedIdentityList.find(element => element === deletedIdentityUUID)
|
||||||
|
) return;
|
||||||
|
deletedIdentityList.push(deletedIdentityUUID);
|
||||||
|
await sync.storageArea.set({ deletedIdentityList });
|
||||||
|
await sync.storageArea.area.remove( "identity@@_" + deletedIdentityUUID);
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteSite(siteStoreKey) {
|
||||||
|
const deletedSiteList =
|
||||||
|
await sync.storageArea.getDeletedSiteList();
|
||||||
|
if (deletedSiteList.find(element => element === siteStoreKey)) return;
|
||||||
|
deletedSiteList.push(siteStoreKey);
|
||||||
|
await sync.storageArea.set({ deletedSiteList });
|
||||||
|
await sync.storageArea.area.remove(siteStoreKey);
|
||||||
|
},
|
||||||
|
|
||||||
async getDeletedIdentityList() {
|
async getDeletedIdentityList() {
|
||||||
const storedArray = await this.getStoredItem("deletedIdentityList");
|
const storedArray = await this.getStoredItem("deletedIdentityList");
|
||||||
return (storedArray) ? storedArray : [];
|
return (storedArray) ? storedArray : [];
|
||||||
},
|
},
|
||||||
|
|
||||||
async getIdentities() {
|
async getIdentities() {
|
||||||
const storedArray = await this.getStoredItem("identities");
|
const allSyncStorage = await this.get();
|
||||||
return (storedArray) ? storedArray : [];
|
const identities = [];
|
||||||
|
for (const storageKey of Object.keys(allSyncStorage)) {
|
||||||
|
if (storageKey.includes("identity@@_")) {
|
||||||
|
identities.push(allSyncStorage[storageKey]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return identities;
|
||||||
},
|
},
|
||||||
|
|
||||||
async getDeletedSiteList() {
|
async getDeletedSiteList() {
|
||||||
|
@ -27,14 +53,15 @@ const sync = {
|
||||||
return (storedArray) ? storedArray : [];
|
return (storedArray) ? storedArray : [];
|
||||||
},
|
},
|
||||||
|
|
||||||
async getCookieStoreIDMap() {
|
|
||||||
const storedArray = await this.getStoredItem("cookieStoreIDmap");
|
|
||||||
return (storedArray) ? storedArray : {};
|
|
||||||
},
|
|
||||||
|
|
||||||
async getAssignedSites() {
|
async getAssignedSites() {
|
||||||
const storedArray = await this.getStoredItem("assignedSites");
|
const allSyncStorage = await this.get();
|
||||||
return (storedArray) ? storedArray : {};
|
const sites = {};
|
||||||
|
for (const storageKey of Object.keys(allSyncStorage)) {
|
||||||
|
if (storageKey.includes("siteContainerMap@@_")) {
|
||||||
|
sites[storageKey] = allSyncStorage[storageKey];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sites;
|
||||||
},
|
},
|
||||||
|
|
||||||
async getStoredItem(objectKey) {
|
async getStoredItem(objectKey) {
|
||||||
|
@ -54,6 +81,23 @@ const sync = {
|
||||||
return instanceList;
|
return instanceList;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getInstanceKey() {
|
||||||
|
return browser.runtime.getURL("")
|
||||||
|
.replace(/moz-extension:\/\//, "MACinstance:")
|
||||||
|
.replace(/\//, "");
|
||||||
|
},
|
||||||
|
async removeInstance(installUUID) {
|
||||||
|
console.log("removing", installUUID);
|
||||||
|
await this.area.remove(installUUID);
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
async removeThisInstanceFromSync() {
|
||||||
|
const installUUID = this.getInstanceKey();
|
||||||
|
await this.removeInstance(installUUID);
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
async hasSyncStorage(){
|
async hasSyncStorage(){
|
||||||
const inSync = await this.get();
|
const inSync = await this.get();
|
||||||
return !(Object.entries(inSync).length === 0);
|
return !(Object.entries(inSync).length === 0);
|
||||||
|
@ -64,24 +108,16 @@ const sync = {
|
||||||
await sync.checkForListenersMaybeRemove();
|
await sync.checkForListenersMaybeRemove();
|
||||||
|
|
||||||
const identities = await updateSyncIdentities();
|
const identities = await updateSyncIdentities();
|
||||||
await updateCookieStoreIdMap();
|
|
||||||
const siteAssignments = await updateSyncSiteAssignments();
|
const siteAssignments = await updateSyncSiteAssignments();
|
||||||
await updateInstanceInfo(identities, siteAssignments);
|
await updateInstanceInfo(identities, siteAssignments);
|
||||||
if (options && options.uuid)
|
if (options && options.uuid)
|
||||||
await updateDeletedIdentityList(options.uuid);
|
await this.deleteIdentity(options.uuid);
|
||||||
if (options && options.undeleteUUID)
|
if (options && options.undeleteUUID)
|
||||||
await removeFromDeletedIdentityList(options.undeleteUUID);
|
await removeFromDeletedIdentityList(options.undeleteUUID);
|
||||||
if (options && options.siteStoreKey)
|
if (options && options.siteStoreKey)
|
||||||
await addToDeletedSitesList(options.siteStoreKey);
|
await this.deleteSite(options.siteStoreKey);
|
||||||
if (options && options.undeleteSiteStoreKey)
|
if (options && options.undeleteSiteStoreKey)
|
||||||
await removeFromDeletedSitesList(options.undeleteSiteStoreKey);
|
await removeFromDeletedSitesList(options.undeleteSiteStoreKey);
|
||||||
// if (SYNC_DEBUG) {
|
|
||||||
// const storage = await sync.storageArea.get();
|
|
||||||
// console.log("inSync: ", storage);
|
|
||||||
// const localStorage = await browser.storage.local.get();
|
|
||||||
// console.log("inLocal:", localStorage);
|
|
||||||
// console.log("idents: ", await browser.contextualIdentities.query({}));
|
|
||||||
// }
|
|
||||||
console.log("Backed up!");
|
console.log("Backed up!");
|
||||||
await sync.checkForListenersMaybeAdd();
|
await sync.checkForListenersMaybeAdd();
|
||||||
|
|
||||||
|
@ -92,28 +128,29 @@ const sync = {
|
||||||
delete identity.colorCode;
|
delete identity.colorCode;
|
||||||
delete identity.iconUrl;
|
delete identity.iconUrl;
|
||||||
identity.macAddonUUID = await identityState.lookupMACaddonUUID(identity.cookieStoreId);
|
identity.macAddonUUID = await identityState.lookupMACaddonUUID(identity.cookieStoreId);
|
||||||
|
if(identity.macAddonUUID) {
|
||||||
|
const storageKey = "identity@@_" + identity.macAddonUUID;
|
||||||
|
await sync.storageArea.set({ [storageKey]: identity });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
await sync.storageArea.set({ identities });
|
//await sync.storageArea.set({ identities });
|
||||||
return identities;
|
return identities;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateCookieStoreIdMap() {
|
|
||||||
const cookieStoreIDmap =
|
|
||||||
await identityState.getCookieStoreIDuuidMap();
|
|
||||||
await sync.storageArea.set({ cookieStoreIDmap });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updateSyncSiteAssignments() {
|
async function updateSyncSiteAssignments() {
|
||||||
const assignedSites =
|
const assignedSites =
|
||||||
await assignManager.storageArea.getAssignedSites();
|
await assignManager.storageArea.getAssignedSites();
|
||||||
await sync.storageArea.set({ assignedSites });
|
for (const siteKey of Object.keys(assignedSites)) {
|
||||||
|
await sync.storageArea.set({ [siteKey]: assignedSites[siteKey] });
|
||||||
|
}
|
||||||
return assignedSites;
|
return assignedSites;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateInstanceInfo(identitiesInput, siteAssignmentsInput) {
|
async function updateInstanceInfo(identitiesInput, siteAssignmentsInput) {
|
||||||
const installUUID = browser.runtime.getURL("")
|
const date = new Date();
|
||||||
.replace(/moz-extension:\/\//, "MACinstance:")
|
const timestamp = date.getTime();
|
||||||
.replace(/\//, "");
|
const installUUID = sync.storageArea.getInstanceKey();
|
||||||
|
console.log("adding", installUUID);
|
||||||
const identities = [];
|
const identities = [];
|
||||||
const siteAssignments = [];
|
const siteAssignments = [];
|
||||||
for (const identity of identitiesInput) {
|
for (const identity of identitiesInput) {
|
||||||
|
@ -122,17 +159,7 @@ const sync = {
|
||||||
for (const siteAssignmentKey of Object.keys(siteAssignmentsInput)) {
|
for (const siteAssignmentKey of Object.keys(siteAssignmentsInput)) {
|
||||||
siteAssignments.push(siteAssignmentKey);
|
siteAssignments.push(siteAssignmentKey);
|
||||||
}
|
}
|
||||||
await sync.storageArea.set({ [installUUID]: { identities, siteAssignments } });
|
await sync.storageArea.set({ [installUUID]: { timestamp, identities, siteAssignments } });
|
||||||
}
|
|
||||||
|
|
||||||
async function updateDeletedIdentityList(deletedIdentityUUID) {
|
|
||||||
const deletedIdentityList =
|
|
||||||
await sync.storageArea.getDeletedIdentityList();
|
|
||||||
if (
|
|
||||||
deletedIdentityList.find(element => element === deletedIdentityUUID)
|
|
||||||
) return;
|
|
||||||
deletedIdentityList.push(deletedIdentityUUID);
|
|
||||||
await sync.storageArea.set({ deletedIdentityList });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeFromDeletedIdentityList(identityUUID) {
|
async function removeFromDeletedIdentityList(identityUUID) {
|
||||||
|
@ -143,14 +170,6 @@ const sync = {
|
||||||
await sync.storageArea.set({ deletedIdentityList: newDeletedIdentityList });
|
await sync.storageArea.set({ deletedIdentityList: newDeletedIdentityList });
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addToDeletedSitesList(siteStoreKey) {
|
|
||||||
const deletedSiteList =
|
|
||||||
await sync.storageArea.getDeletedSiteList();
|
|
||||||
if (deletedSiteList.find(element => element === siteStoreKey)) return;
|
|
||||||
deletedSiteList.push(siteStoreKey);
|
|
||||||
await sync.storageArea.set({ deletedSiteList });
|
|
||||||
}
|
|
||||||
|
|
||||||
async function removeFromDeletedSitesList(siteStoreKey) {
|
async function removeFromDeletedSitesList(siteStoreKey) {
|
||||||
const deletedSiteList =
|
const deletedSiteList =
|
||||||
await sync.storageArea.getDeletedSiteList();
|
await sync.storageArea.getDeletedSiteList();
|
||||||
|
@ -170,28 +189,20 @@ const sync = {
|
||||||
await identityState.lookupMACaddonUUID(identity.cookieStoreId);
|
await identityState.lookupMACaddonUUID(identity.cookieStoreId);
|
||||||
await identityState.storageArea.remove(identity.cookieStoreId);
|
await identityState.storageArea.remove(identity.cookieStoreId);
|
||||||
sync.storageArea.backup({uuid: deletedUUID});
|
sync.storageArea.backup({uuid: deletedUUID});
|
||||||
},
|
|
||||||
|
|
||||||
async dataIsReliable() {
|
|
||||||
const cookieStoreIDmap = await this.getCookieStoreIDMap();
|
|
||||||
const identities = await this.getIdentities();
|
|
||||||
for (const cookieStoreId of Object.keys(cookieStoreIDmap)) {
|
|
||||||
const match = identities.find(identity =>
|
|
||||||
identity.cookieStoreId === cookieStoreId
|
|
||||||
);
|
|
||||||
// if one has no match, this is bad data.
|
|
||||||
if (!match) return false;
|
|
||||||
}
|
|
||||||
return !(Object.entries(cookieStoreIDmap).length === 0);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
init() {
|
async init() {
|
||||||
// Add listener to sync storage and containers.
|
const syncEnabled = await assignManager.storageArea.getSyncEnabled();
|
||||||
// Works for all installs that have any sync storage.
|
if (syncEnabled) {
|
||||||
// Waits for sync storage change before kicking off the restore/backup
|
// Add listener to sync storage and containers.
|
||||||
// initial sync must be kicked off by user.
|
// Works for all installs that have any sync storage.
|
||||||
this.checkForListenersMaybeAdd();
|
// Waits for sync storage change before kicking off the restore/backup
|
||||||
|
// initial sync must be kicked off by user.
|
||||||
|
this.checkForListenersMaybeAdd();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.checkForListenersMaybeRemove();
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -252,8 +263,7 @@ const sync = {
|
||||||
await assignManager.storageArea.upgradeData();
|
await assignManager.storageArea.upgradeData();
|
||||||
|
|
||||||
const hasSyncStorage = await sync.storageArea.hasSyncStorage();
|
const hasSyncStorage = await sync.storageArea.hasSyncStorage();
|
||||||
const dataIsReliable = await sync.storageArea.dataIsReliable();
|
if (hasSyncStorage) await restore();
|
||||||
if (hasSyncStorage && dataIsReliable) await restore();
|
|
||||||
|
|
||||||
await sync.storageArea.backup();
|
await sync.storageArea.backup();
|
||||||
await removeOldDeletedItems();
|
await removeOldDeletedItems();
|
||||||
|
@ -283,6 +293,16 @@ const sync = {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async resetSync() {
|
||||||
|
const syncEnabled = await assignManager.storageArea.getSyncEnabled();
|
||||||
|
if (syncEnabled) {
|
||||||
|
this.errorHandledRunSync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.checkForListenersMaybeRemove();
|
||||||
|
await this.storageArea.removeThisInstanceFromSync();
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
sync.init();
|
sync.init();
|
||||||
|
@ -323,11 +343,8 @@ async function reconcileIdentities(){
|
||||||
const localIdentities = await browser.contextualIdentities.query({});
|
const localIdentities = await browser.contextualIdentities.query({});
|
||||||
const syncIdentities =
|
const syncIdentities =
|
||||||
await sync.storageArea.getIdentities();
|
await sync.storageArea.getIdentities();
|
||||||
const cookieStoreIDmap =
|
|
||||||
await sync.storageArea.getCookieStoreIDMap();
|
|
||||||
// now compare all containers for matching names.
|
// now compare all containers for matching names.
|
||||||
for (const syncIdentity of syncIdentities) {
|
for (const syncIdentity of syncIdentities) {
|
||||||
syncIdentity.macAddonUUID = cookieStoreIDmap[syncIdentity.cookieStoreId];
|
|
||||||
if (syncIdentity.macAddonUUID){
|
if (syncIdentity.macAddonUUID){
|
||||||
const localMatch = localIdentities.find(
|
const localMatch = localIdentities.find(
|
||||||
localIdentity => localIdentity.name === syncIdentity.name
|
localIdentity => localIdentity.name === syncIdentity.name
|
||||||
|
@ -483,10 +500,22 @@ async function reconcileSiteAssignments() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MILISECONDS_IN_THIRTY_DAYS = 2592000000;
|
||||||
|
|
||||||
async function removeOldDeletedItems() {
|
async function removeOldDeletedItems() {
|
||||||
const instanceList = await sync.storageArea.getAllInstanceInfo();
|
const instanceList = await sync.storageArea.getAllInstanceInfo();
|
||||||
const deletedSiteList = await sync.storageArea.getDeletedSiteList();
|
const deletedSiteList = await sync.storageArea.getDeletedSiteList();
|
||||||
const deletedIdentityList = await sync.storageArea.getDeletedIdentityList();
|
const deletedIdentityList = await sync.storageArea.getDeletedIdentityList();
|
||||||
|
|
||||||
|
for (const instanceKey of Object.keys(instanceList)) {
|
||||||
|
const date = new Date();
|
||||||
|
const currentTimestamp = date.getTime();
|
||||||
|
if (instanceList[instanceKey].timestamp < currentTimestamp - MILISECONDS_IN_THIRTY_DAYS) {
|
||||||
|
delete instanceList[instanceKey];
|
||||||
|
sync.storageArea.removeInstance(instanceKey);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
for (const siteStoreKey of deletedSiteList) {
|
for (const siteStoreKey of deletedSiteList) {
|
||||||
let hasMatch = false;
|
let hasMatch = false;
|
||||||
for (const instance of Object.values(instanceList)) {
|
for (const instance of Object.values(instanceList)) {
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
async function requestPermissions() {
|
async function requestPermissions() {
|
||||||
const checkbox = document.querySelector("#bookmarksPermissions");
|
const checkbox = document.querySelector("#bookmarksPermissions");
|
||||||
if (checkbox.checked) {
|
if (checkbox.checked) {
|
||||||
|
@ -13,13 +12,31 @@ async function requestPermissions() {
|
||||||
browser.runtime.sendMessage({ method: "resetBookmarksContext" });
|
browser.runtime.sendMessage({ method: "resetBookmarksContext" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function enableDisableSync() {
|
||||||
|
const checkbox = document.querySelector("#syncCheck");
|
||||||
|
if (checkbox.checked) {
|
||||||
|
await browser.storage.local.set({syncEnabled: true});
|
||||||
|
} else {
|
||||||
|
await browser.storage.local.set({syncEnabled: false});
|
||||||
|
}
|
||||||
|
browser.runtime.sendMessage({ method: "resetSync" });
|
||||||
|
}
|
||||||
|
|
||||||
async function restoreOptions() {
|
async function restoreOptions() {
|
||||||
const hasPermission = await browser.permissions.contains({permissions: ["bookmarks"]});
|
const hasPermission = await browser.permissions.contains({permissions: ["bookmarks"]});
|
||||||
|
const { syncEnabled } = await browser.storage.local.get("syncEnabled");
|
||||||
|
console.log(syncEnabled);
|
||||||
if (hasPermission) {
|
if (hasPermission) {
|
||||||
document.querySelector("#bookmarksPermissions").checked = true;
|
document.querySelector("#bookmarksPermissions").checked = true;
|
||||||
}
|
}
|
||||||
|
if (syncEnabled) {
|
||||||
|
document.querySelector("#syncCheck").checked = true;
|
||||||
|
} else {
|
||||||
|
document.querySelector("#syncCheck").checked = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", restoreOptions);
|
document.addEventListener("DOMContentLoaded", restoreOptions);
|
||||||
document.querySelector("#bookmarksPermissions").addEventListener( "change", requestPermissions);
|
document.querySelector("#bookmarksPermissions").addEventListener( "change", requestPermissions);
|
||||||
|
document.querySelector("#syncCheck").addEventListener( "change", enableDisableSync);
|
||||||
|
|
|
@ -12,6 +12,11 @@
|
||||||
Enable Bookmark Menus
|
Enable Bookmark Menus
|
||||||
</label>
|
</label>
|
||||||
<p>This setting allows you to open a bookmark or folder of bookmarks in a container.</p>
|
<p>This setting allows you to open a bookmark or folder of bookmarks in a container.</p>
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" id="syncCheck">
|
||||||
|
Enable Sync
|
||||||
|
</label>
|
||||||
|
<p>This setting allows you to sync your containers and site assignments across devices.</p>
|
||||||
</form>
|
</form>
|
||||||
<script src="js/options.js"></script>
|
<script src="js/options.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
Loading…
Add table
Reference in a new issue