Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
| Comment: | Re-enabled localStorage for fossil.storage but enhanced it to sandbox the keys used by the apps on a per-repo basis, so there is no longer any (immediately visible) cross-repo polution. The underlying localStorage/sessionStorage is still shared per origin/browser profile instance, but fossil.storage clients will only see the state from their own repo. |
|---|---|
| Downloads: | Tarball | ZIP archive |
| Timelines: | family | ancestors | descendants | both | trunk |
| Files: | files | file ages | folders |
| SHA3-256: |
923affb930a27bc7c20c54a0c2ac1904 |
| User & Date: | stephan 2020-08-18 20:46:36.631 |
References
|
2020-08-18
| ||
| 20:51 | Merged in [923affb930a27b], which reinstates localStorage but sandboxes access to fossil.storage on a per-repo basis. ... (check-in: 21fbd4738c user: stephan tags: branch-2.12) | |
Context
|
2020-08-18
| ||
| 21:01 | fossil.storage.clear() is now also sandboxed - no longer nuking all state for all repos on the same origin. ... (check-in: d2d8894bb2 user: stephan tags: trunk) | |
| 20:51 | Merged in [923affb930a27b], which reinstates localStorage but sandboxes access to fossil.storage on a per-repo basis. ... (check-in: 21fbd4738c user: stephan tags: branch-2.12) | |
| 20:46 | Re-enabled localStorage for fossil.storage but enhanced it to sandbox the keys used by the apps on a per-repo basis, so there is no longer any (immediately visible) cross-repo polution. The underlying localStorage/sessionStorage is still shared per origin/browser profile instance, but fossil.storage clients will only see the state from their own repo. ... (check-in: 923affb930 user: stephan tags: trunk) | |
| 18:19 | Disabled localStorage as a backend option for the fossil.storage JS API after it was painfully discovered that multiple repos on the same hoster actually share that storage, as opposed to it being achored at the repo. That API now uses sessionStorage, if available, before falling back to transient instance-local storage. ... (check-in: 5b9a4c9059 user: stephan tags: trunk) | |
Changes
Changes to src/fossil.storage.js.
| ︙ | ︙ | |||
15 16 17 18 19 20 21 |
}catch(e){
return undefined;
}
};
/** Internal storage impl for fossil.storage. */
const $storage =
| < < < | < | | | | | | | > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | | > > | | | | 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
}catch(e){
return undefined;
}
};
/** Internal storage impl for fossil.storage. */
const $storage =
tryStorage(window.localStorage)
|| tryStorage(window.sessionStorage)
|| tryStorage({
// A basic dummy xyzStorage stand-in
$$$:{},
setItem: function(k,v){this.$$$[k]=v},
getItem: function(k){
return this.$$$.hasOwnProperty(k) ? this.$$$[k] : undefined;
},
removeItem: function(k){delete this.$$$[k]},
clear: function(){this.$$$={}}
});
/**
For the dummy storage we need to differentiate between
$storage and its real property storage for hasOwnProperty()
to work properly...
*/
const $storageHolder = $storage.hasOwnProperty('$$$') ? $storage.$$$ : $storage;
/**
A prefix which gets internally applied to all fossil.storage
property keys so that localStorage and sessionStorage across the
same browser profile instance do not "leak" across multiple repos
being hosted by the same origin server. Such polination is still
there but, with this key prefix applied, it won't be immediately
visible via the storage API.
With this in place we can justify using localStorage instead of
sessionStorage again.
One implication, it was discovered after the release of 2.12, of
using localStorage and sessionStorage, is that their scope (the
same "origin" and client application/profile) allows multiple
repos on the same origin to use the same storage. Thus a user
editing a wiki in /repoA/wikiedit could then see those edits in
/repoB/wikiedit. The data do not cross user- or browser
boundaries, though, so it "might" arguably be called a bug. Even
so, it was never intended for that to happen. Rather than lose
localStorage access altogether, storageKeyPrefix was added so
that we can sandbox that state for the various repos.
See: https://fossil-scm.org/forum/forumpost/4afc4d34de
Sidebar: it might seem odd to provide a key prefix and stick all
properties in the topmost level of the storage object. We do that
because adding a layer of object to sandbox each repo would mean
(de)serializing that whole tree on every storage property change
(and we update storage often during editing
sessions). e.g. instead of storageObject.projectName.foo we have
storageObject[storageKeyPrefix+'foo']. That's soley for
efficiency's sake (in terms of battery life and
environment-internal storage-level effort). Even so, it might
(or might not) be useful to do that someday.
*/
const storageKeyPrefix = (
$storageHolder===$storage/*localStorage or sessionStorage*/
? (
F.config.projectCode || F.config.projectName
|| F.config.shortProjectName || window.location.pathname
)+'::' : (
'' /* transient storage */
)
);
/**
A proxy for localStorage or sessionStorage or a
page-instance-local proxy, if neither one is availble.
Which exact storage implementation is uses is unspecified, and
apps must not rely on it.
*/
fossil.storage = {
storageKeyPrefix: storageKeyPrefix,
/** Sets the storage key k to value v, implicitly converting
it to a string. */
set: (k,v)=>$storage.setItem(storageKeyPrefix+k,v),
/** Sets storage key k to JSON.stringify(v). */
setJSON: (k,v)=>$storage.setItem(storageKeyPrefix+k,JSON.stringify(v)),
/** Returns the value for the given storage key, or
dflt if the key is not found in the storage. */
get: (k,dflt)=>$storageHolder.hasOwnProperty(
storageKeyPrefix+k
) ? $storage.getItem(storageKeyPrefix+k) : dflt,
/** Returns the JSON.parse()'d value of the given
storage key's value, or dflt is the key is not
found or JSON.parse() fails. */
getJSON: function f(k,dflt){
try {
const x = this.get(k,f);
return x===f ? dflt : JSON.parse(x);
}
catch(e){return dflt}
},
/** Returns true if the storage contains the given key,
else false. */
contains: (k)=>$storageHolder.hasOwnProperty(storageKeyPrefix+k),
/** Removes the given key from the storage. Returns this. */
remove: function(k){
$storage.removeItem(storageKeyPrefix+k);
return this;
},
/** Clears ALL keys from the storage. Returns this. */
clear: function(){
$storage.clear();
return this;
},
/** Returns an array of all keys currently in the storage. */
keys: ()=>Object.keys($storageHolder).filter((v)=>(v||'').startsWith(storageKeyPrefix)),
/** Returns true if this storage is transient (only available
until the page is reloaded), indicating that fileStorage
and sessionStorage are unavailable. */
isTransient: ()=>$storageHolder!==$storage,
/** Returns a symbolic name for the current storage mechanism. */
storageImplName: function(){
if($storage===window.localStorage) return 'localStorage';
|
| ︙ | ︙ |
Changes to src/style.c.
| ︙ | ︙ | |||
1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 |
** it is assumed that the caller already opened a tag.
**
** 2) Emits the static fossil.bootstrap.js using builtin_request_js().
*/
void style_emit_script_fossil_bootstrap(int addScriptTag){
static int once = 0;
if(0==once++){
/* Set up the generic/app-agnostic parts of window.fossil
** which require C-level state... */
if(addScriptTag!=0){
style_emit_script_tag(0,0);
}
CX("(function(){\n");
CX(/*MSIE NodeList.forEach polyfill, courtesy of Mozilla:
https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Polyfill
*/
"if(window.NodeList && !NodeList.prototype.forEach){"
"NodeList.prototype.forEach = Array.prototype.forEach;"
"}\n");
CX("if(!window.fossil) window.fossil={};\n"
"window.fossil.version = %!j;\n"
/* fossil.rootPath is the top-most CGI/server path,
** including a trailing slash. */
"window.fossil.rootPath = %!j+'/';\n",
get_version(), g.zTop);
/* fossil.config = {...various config-level options...} */
CX("window.fossil.config = {");
CX("/* Length of UUID hashes for display purposes. */");
CX("hashDigits: %d, hashDigitsUrl: %d,\n",
hash_digits(0), hash_digits(1));
CX("editStateMarkers: {"
"/*Symbolic markers to denote certain edit states.*/"
"isNew:'[+]', isModified:'[*]', isDeleted:'[-]'},\n");
CX("confirmerButtonTicks: 3 "
| > > > > > > > > > > | 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 1460 1461 1462 1463 1464 1465 1466 1467 1468 1469 1470 1471 1472 1473 1474 1475 1476 1477 |
** it is assumed that the caller already opened a tag.
**
** 2) Emits the static fossil.bootstrap.js using builtin_request_js().
*/
void style_emit_script_fossil_bootstrap(int addScriptTag){
static int once = 0;
if(0==once++){
char * zName;
/* Set up the generic/app-agnostic parts of window.fossil
** which require C-level state... */
if(addScriptTag!=0){
style_emit_script_tag(0,0);
}
CX("(function(){\n");
CX(/*MSIE NodeList.forEach polyfill, courtesy of Mozilla:
https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Polyfill
*/
"if(window.NodeList && !NodeList.prototype.forEach){"
"NodeList.prototype.forEach = Array.prototype.forEach;"
"}\n");
CX("if(!window.fossil) window.fossil={};\n"
"window.fossil.version = %!j;\n"
/* fossil.rootPath is the top-most CGI/server path,
** including a trailing slash. */
"window.fossil.rootPath = %!j+'/';\n",
get_version(), g.zTop);
/* fossil.config = {...various config-level options...} */
CX("window.fossil.config = {");
zName = db_get("project-name", "");
CX("projectName: %!j,\n", zName);
fossil_free(zName);
zName = db_get("short-project-name", "");
CX("shortProjectName: %!j,\n", zName);
fossil_free(zName);
zName = db_get("project-code", "");
CX("projectCode: %!j,\n", zName);
fossil_free(zName);
CX("/* Length of UUID hashes for display purposes. */");
CX("hashDigits: %d, hashDigitsUrl: %d,\n",
hash_digits(0), hash_digits(1));
CX("editStateMarkers: {"
"/*Symbolic markers to denote certain edit states.*/"
"isNew:'[+]', isModified:'[*]', isDeleted:'[-]'},\n");
CX("confirmerButtonTicks: 3 "
|
| ︙ | ︙ |