Skip to content

Commit b630cda

Browse files
committed
Add a "storage.sync" tab to the extension-storage collection.
This tab lets you get and set via the "browser.storage.sync" API.
1 parent 093ee33 commit b630cda

4 files changed

Lines changed: 195 additions & 6 deletions

File tree

data/aboutsync.css

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,40 @@ html {
381381
margin: 5px 0px;
382382
}
383383

384+
.storage-sync-sections {
385+
display: flex;
386+
}
387+
388+
.storage-sync-sections > * {
389+
flex: 1 1 0;
390+
}
391+
392+
.storage-sync-ext-warning {
393+
color: lightcoral;
394+
font-weight: bold;
395+
font-size: 125%;
396+
}
397+
398+
.storage-input {
399+
height: 1.5em;
400+
flex: auto;
401+
}
402+
403+
.storage-value-input {
404+
height: 1.5em;
405+
width: 75%;
406+
}
407+
408+
.horiz-action-row {
409+
display: flex;
410+
margin-bottom: 5px;
411+
}
412+
413+
.horiz-action-row > * {
414+
margin-right: 7px;
415+
vertical-align: middle;
416+
}
417+
384418
.error-message {
385419
background: rgb(255, 128, 128);
386420
border: 1px solid red;

src/CollectionsViewer.jsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const { EngineActions } = require("./EngineActions");
88
const { ProviderState } = require("./provider");
99
const { PlacesSqlView, promiseSql } = require("./PlacesSqlView");
1010
const { BookmarkValidator } = require("./bookmarkValidator");
11+
const { WebExtStorage } = require("./WebExtStorage");
1112

1213
const validation = require("./validation");
1314

@@ -106,6 +107,12 @@ const collectionComponentBuilders = {
106107
};
107108
},
108109

110+
async extension_storage(provider, serverRecords) {
111+
return {
112+
"storage.sync": <WebExtStorage serverRecords={serverRecords}/>,
113+
}
114+
},
115+
109116
async passwords(provider, serverRecords) {
110117
return basicBuilder(new PasswordValidator(), serverRecords, true, ["guid", "id"]);
111118
},
@@ -187,7 +194,7 @@ class CollectionViewer extends React.Component {
187194
async fetchCollection() {
188195
let {response, records} = await this.props.provider.promiseCollection(this.props.info);
189196
let originalRecords = await arrayCloneWithoutJank(records);
190-
let additionalBuilder = collectionComponentBuilders[this.props.info.name];
197+
let additionalBuilder = collectionComponentBuilders[this.props.info.name.replace("-", "_")];
191198
this.setState({
192199
response,
193200
records,

src/EngineActions.jsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,12 @@ class EngineActions extends React.Component {
3939
render() {
4040
let reset;
4141
if (this.props.engine.resetClient) {
42-
reset =
43-
<div>
42+
reset =
43+
<div className="horiz-action-row">
4444
<span>Resetting an engine clears all local state, so the next Sync will
4545
act as though this was the first sync for that engine's data -
4646
all records will be downloaded, compared against local records
47-
and missing records uploaded -
47+
and missing records uploaded
4848
</span>
4949
<button onClick={event => this.reset(event)}>Reset {this.props.engine.name}</button>
5050
</div>
@@ -53,10 +53,9 @@ class EngineActions extends React.Component {
5353
let wipe;
5454
if (canWipe) {
5555
wipe =
56-
<div>
56+
<div className="horiz-action-row">
5757
<span>Wiping an engine removes data from the server. <em>It does not remove local data</em>.
5858
This device will upload all its data. Other devices will act like a <i>Reset</i>, as described above.
59-
-
6059
</span>
6160
<button onClick={event => this.wipe(event)}>Wipe {this.props.engine.name}</button>
6261
</div>

src/WebExtStorage.jsx

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import React from 'react'
2+
3+
const { ObjectInspector } = require("./common");
4+
const { extensionStorageSync } = ChromeUtils.importESModule( "resource://gre/modules/ExtensionStorageSync.sys.mjs");
5+
6+
export class WebExtStorage extends React.Component {
7+
constructor(props) {
8+
super(props);
9+
// We use 'about-sync' as an ID for this addon/scratch data. It will sync/merge etc.
10+
// Could use the addon-manager to turn ids into names?
11+
let ext_ids = new Set(["about-sync"].concat(props.serverRecords.filter(s => !s.deleted).map(s => s.extId)));
12+
this.state = {id: "about-sync", ext_ids, data: null, keys: "", error_get: "", set_error: ""};
13+
}
14+
15+
async update(state = {}) {
16+
// simple "truthy" doesn't keep an empty string.
17+
let keys_text = state.keys === undefined ? this.state.keys : state.keys;
18+
let id = state.id || this.state.id;
19+
let error_get = "";
20+
console.log("webext: update", state);
21+
let keys = null;
22+
if (keys_text) {
23+
try {
24+
keys = JSON.parse(keys_text);
25+
} catch (e) {
26+
error_get = "Invalid keys: not JSON";
27+
}
28+
}
29+
let data = null, bytesInUse = null;
30+
if (!error_get) {
31+
try {
32+
bytesInUse = await extensionStorageSync.getBytesInUse({id}, keys);
33+
data = await extensionStorageSync.get({id}, keys);
34+
} catch (e) {
35+
error_get = `Failed: ${e}`;
36+
}
37+
}
38+
this.setState({data, bytesInUse, error_get, ...state});
39+
}
40+
41+
async doSet() {
42+
let value;
43+
if (!this.state.toSet) {
44+
this.setState({ error_change: "No value to set" });
45+
return;
46+
}
47+
try {
48+
value = JSON.parse(this.state.toSet)
49+
} catch (e) {
50+
this.setState({ error_change: "Invalid value" });
51+
return;
52+
}
53+
let ext = { id: this.state.id }
54+
console.log("webext: setting", ext, value);
55+
try {
56+
await extensionStorageSync.set(ext, value);
57+
} catch (e) {
58+
console.error("webext: Failed to set extension storage", e);
59+
this.setState({error_change: `Failed to set: ${e}`});
60+
return;
61+
}
62+
await this.update();
63+
this.setState({error_change: ""});
64+
}
65+
66+
async doClear() {
67+
let ext = { id: this.state.id }
68+
console.log("webext: clearing", ext);
69+
await extensionStorageSync.clear(ext);
70+
await this.update();
71+
this.setState({error_change: ""});
72+
}
73+
74+
async componentDidMount() {
75+
await this.update();
76+
}
77+
78+
render() {
79+
// The addon <select> element children.
80+
let extensions = []; // WTF can't I work out how to use .map on a set!?
81+
for (let id of this.state.ext_ids) {
82+
extensions.push(<option value={id}>{id}</option>);
83+
}
84+
let get =
85+
<fieldset>
86+
<legend>Current Data</legend>
87+
<div className="horiz-action-row">
88+
<p>Keys to query</p>
89+
<textarea className="storage-input"
90+
value={this.state.keys}
91+
onChange={e => this.update({keys: e.target.value})}
92+
placeholder="String, array of strings, or leave empty for null"
93+
/>
94+
</div>
95+
<div>
96+
{this.state.error_get ?
97+
<p>{ this.state.error_get }</p>
98+
:
99+
<>
100+
<p>Current data ({this.state.bytesInUse} bytes)</p>
101+
<ObjectInspector data={this.state.data} expandLevel={1}/>
102+
</>
103+
}
104+
</div>
105+
</fieldset>;
106+
107+
let change =
108+
<fieldset>
109+
<legend>Change Data</legend>
110+
{this.state.id != "about-sync" &&
111+
<p className="storage-sync-ext-warning">You are messing with a real extension - you might break it!</p>
112+
}
113+
<div className="horiz-action-row">
114+
<span>Clear all data</span>
115+
<button onClick={e => this.doClear()}>Clear</button>
116+
</div>
117+
<div className="horiz-action-row">
118+
<span>Set data</span>
119+
<textarea
120+
className="storage-input"
121+
value={this.state.set_value}
122+
onChange={e => this.setState({toSet: e.target.value})}
123+
onAc
124+
placeholder="A JSON object, eg, {'foo': 'bar'}"
125+
/>
126+
<div>
127+
<button onClick={e => this.doSet()}>Set</button>
128+
</div>
129+
</div>
130+
<div>
131+
{this.state.error_change && <p className='error-message'>{this.state.error_change}</p>}
132+
</div>
133+
</fieldset>;
134+
135+
return (
136+
<>
137+
<select
138+
children={extensions}
139+
value={this.state.id}
140+
onChange={e => this.update({id: e.target.value })}
141+
/>
142+
<div className="storage-sync-sections">
143+
{get}
144+
{change}
145+
</div>
146+
</>
147+
);
148+
}
149+
}

0 commit comments

Comments
 (0)