Project

General

Profile

« Previous | Next » 

Revision 792fbe18

Added by koszko about 2 years ago

Facilitate installation of scripts from the repository

This commit includes:

  • removal of page_info_server
  • running of storage client in popup context
  • extraction of some common CSS to a separate file
  • extraction of scripts import view to a separate file
  • addition of a facility to conveniently clone complex structures from DOM (in DOM_helpers.js)
  • addition of hydrilla repo url to default settings
  • other minor changes and of course changes related to the actual installation of scripts from the repo

View differences:

html/display-panel.js
10 10
 * IMPORT browser
11 11
 * IMPORT is_chrome
12 12
 * IMPORT is_mozilla
13
 *** Using remote storage here seems inefficient, we only resort to that
14
 *** temporarily, before all storage access gets reworked.
15
 * IMPORT get_remote_storage
16
 * IMPORT get_import_frame
17
 * IMPORT query_all
13 18
 * IMPORT CONNECTION_TYPE
14 19
 * IMPORT url_item
15 20
 * IMPORT is_privileged_url
......
17 22
 * IMPORT nice_name
18 23
 * IMPORT open_in_settings
19 24
 * IMPORT for_each_possible_pattern
25
 * IMPORT by_id
26
 * IMPORT clone_template
20 27
 * IMPORTS_END
21 28
 */
22 29

  
23
function by_id(id)
24
{
25
    return document.getElementById(id);
26
}
30
let storage;
31
let tab_url;
27 32

  
28 33
const tab_query = {currentWindow: true, active: true};
29 34

  
......
55 60
	return;
56 61
    }
57 62

  
58
    const url = url_item(tab.url);
59
    page_url_heading.textContent = url;
60
    if (is_privileged_url(url)) {
63
    tab_url = url_item(tab.url);
64
    page_url_heading.textContent = tab_url;
65
    if (is_privileged_url(tab_url)) {
61 66
	show_privileged_notice_chbx.checked = true;
62 67
	return;
63 68
    }
64 69

  
65
    populate_possible_patterns_list(url);
70
    populate_possible_patterns_list(tab_url);
66 71
    show_page_state_chbx.checked = true;
67 72

  
68 73
    try_to_connect(tab.id);
69 74
}
70 75

  
71
function populate_possible_patterns_list(url)
72
{
73
    for_each_possible_pattern(url, add_pattern_to_list);
74

  
75
    const port = browser.runtime.connect({name: CONNECTION_TYPE.PAGE_INFO});
76
    port.onMessage.addListener(handle_page_info);
77
    port.postMessage(["subscribe", url]);
78
}
79

  
80 76
const possible_patterns_ul = by_id("possible_patterns");
81 77
const pattern_li_template = by_id("pattern_li_template");
82 78
pattern_li_template.removeAttribute("id");
......
121 117
    by_id(li_id).firstElementChild.nextElementSibling.textContent = text;
122 118
}
123 119

  
124
function handle_page_info(message)
120
function handle_page_change(change)
125 121
{
126
    const [type, data] = message;
122
    const li_id = ensure_pattern_exists(change.item);
123
    if (change.old_val === undefined)
124
	set_pattern_li_button_text(li_id, "Edit in settings");
125
    if (change.new_val === undefined)
126
	set_pattern_li_button_text(li_id, "Add setting");
127
}
127 128

  
128
    if (type === "change") {
129
	const li_id = ensure_pattern_exists(data.item);
130
	if (data.old_val === undefined)
131
	    set_pattern_li_button_text(li_id, "Edit in settings");
132
	if (data.new_val === undefined)
133
	    set_pattern_li_button_text(li_id, "Add setting");
134
    }
129
function populate_possible_patterns_list(url)
130
{
131
    for_each_possible_pattern(url, add_pattern_to_list);
135 132

  
136
    if (type === "new_url") {
137
	for (const li_id of known_patterns.values())
138
	    set_pattern_li_button_text(li_id, "Add setting");
139
	for (const [pattern, settings] of data) {
140
	    set_pattern_li_button_text(ensure_pattern_exists(pattern),
141
				       "Edit in settings")
142
	}
133
    for (const [pattern, settings] of query_all(storage, url)) {
134
	set_pattern_li_button_text(ensure_pattern_exists(pattern),
135
				   "Edit in settings");
143 136
    }
137

  
138
    storage.add_change_listener(handle_page_change, [TYPE_PREFIX.PAGE]);
144 139
}
145 140

  
146 141
const connected_chbx = by_id("connected_chbx");
147 142
const query_pattern_but = by_id("query_pattern");
148 143

  
144
var content_script_port;
145

  
149 146
function try_to_connect(tab_id)
150 147
{
151 148
    /* This won't connect to iframes. We'll add support for them later */
152 149
    const connect_info = {name: CONNECTION_TYPE.ACTIVITY_INFO, frameId: 0};
153
    const port = browser.tabs.connect(tab_id, connect_info);
150
    content_script_port = browser.tabs.connect(tab_id, connect_info);
154 151

  
155
    const button_cb = (e) => start_querying_repos(port);
152
    const disconnect_cb = () => handle_disconnect(tab_id, start_querying_repos);
153
    content_script_port.onDisconnect.addListener(disconnect_cb);
154
    content_script_port.onMessage.addListener(handle_activity_report);
156 155

  
157
    port.onDisconnect.addListener(port => handle_disconnect(tab_id, button_cb));
158
    port.onMessage.addListener(handle_activity_report);
159

  
160
    query_pattern_but.addEventListener("click", button_cb);
156
    query_pattern_but.addEventListener("click", start_querying_repos);
161 157

  
162 158
    if (is_mozilla)
163
	setTimeout(() => monitor_connecting(port, tab_id), 1000);
159
	setTimeout(() => monitor_connecting(tab_id), 1000);
164 160
}
165 161

  
166 162
const query_started_chbx = by_id("query_started_chbx");
167 163

  
168 164
function start_querying_repos(port)
169 165
{
170
    port.postMessage("dummy (trigger repo querying)");
166
    const repo_urls = storage.get_all_names(TYPE_PREFIX.REPO);
167
    if (content_script_port)
168
	content_script_port.postMessage([TYPE_PREFIX.URL, tab_url, repo_urls]);
171 169
    query_started_chbx.checked = true;
172 170
}
173 171

  
......
176 174
function handle_disconnect(tab_id, button_cb)
177 175
{
178 176
    query_pattern_but.removeEventListener("click", button_cb);
177
    content_script_port = null;
179 178

  
180 179
    if (is_chrome && !browser.runtime.lastError)
181 180
	return;
......
188 187
    setTimeout(() => try_to_connect(tab_id), 1000);
189 188
}
190 189

  
191
function monitor_connecting(port, tab_id)
190
function monitor_connecting(tab_id)
192 191
{
193 192
    if (connected_chbx.checked)
194 193
	return;
195 194

  
196
    port.disconnect();
195
    if (content_script_port)
196
	content_script_port.disconnect();
197
    else
198
	return;
199

  
197 200
    loading_chbx.checked = !loading_chbx.checked;
198 201
    try_to_connect(tab_id);
199 202
}
......
204 207
const payload_span = by_id("payload");
205 208
const view_payload_but = by_id("view_payload");
206 209
const container_for_injected = by_id("container_for_injected");
207
const container_for_repo_responses = by_id("container_for_repo_responses");
210

  
211
const queried_items = new Map();
208 212

  
209 213
function handle_activity_report(message)
210 214
{
......
213 217
    const [type, data] = message;
214 218

  
215 219
    if (type === "settings") {
216
	let [pattern, settings, repos] = data;
220
	let [pattern, settings] = data;
217 221

  
218 222
	settings = settings || {};
219 223
	blocked_span.textContent = settings.allow ? "no" : "yes";
......
247 251
	container_for_injected.appendChild(h4);
248 252
	container_for_injected.appendChild(pre);
249 253
    }
250
    if (type === "repo_query_result") {
251
	const [repo_url, response_text] = data;
254
    if (type === "repo_query_action") {
255
	query_started_chbx.checked = true;
252 256

  
253
	const h4 = document.createElement("h4");
254
	const pre = document.createElement("pre");
255
	h4.textContent = repo_url;
256
	pre.textContent = response_text;
257
	const key = data.prefix + data.item;
258
	const results = queried_items.get(key) || {};
259
	Object.assign(results, data.results);
260
	queried_items.set(key, results);
261

  
262
	const action = data.prefix === TYPE_PREFIX.URL ?
263
	      show_query_result : record_fetched_install_dep;
264

  
265
	for (const [repo_url, result] of Object.entries(data.results))
266
	    action(data.prefix, data.item, repo_url, result);
267
    }
268
}
269

  
270
const container_for_repo_responses = by_id("container_for_repo_responses");
271

  
272
const results_lists = new Map();
273

  
274
function create_results_list(url)
275
{
276
    const list_div = document.createElement("div");
277
    const list_head = document.createElement("h4");
278
    const list = document.createElement("ul");
279

  
280
    list_head.textContent = url;
281
    list_div.appendChild(list_head);
282
    list_div.appendChild(list);
283
    container_for_repo_responses.appendChild(list_div);
284

  
285
    const list_object = {list, by_repo: new Map()};
286

  
287
    results_lists.set(url, list_object);
288

  
289
    return list_object;
290
}
291

  
292
function create_result_item(list_object, repo_url, result)
293
{
294
    const result_li = document.createElement("li");
295
    const repo_url_span = document.createElement("span");
296
    const result_item = {result_li, appended: null};
297

  
298
    repo_url_span.textContent = repo_url;
299
    result_li.appendChild(repo_url_span);
300

  
301
    list_object.list.appendChild(result_li);
302
    list_object.by_repo.set(repo_url, result_item);
303

  
304
    return result_item;
305
}
306

  
307
function set_appended(result_item, element)
308
{
309
    if (result_item.appended)
310
	result_item.appended.remove();
311
    result_item.appended = element;
312
    result_item.result_li.appendChild(element);
313
}
314

  
315
function show_message(result_item, text)
316
{
317
    const div = document.createElement("div");
318
    div.textContent = text;
319
    set_appended(result_item, div);
320
}
321

  
322
function showcb(text)
323
{
324
    return item => show_message(item, text);
325
}
326

  
327
function unroll_chbx_first_checked(entry_object)
328
{
329
    if (!entry_object.chbx.checked)
330
	return;
331

  
332
    entry_object.chbx.removeEventListener("change", entry_object.unroll_cb);
333
    delete entry_object.unroll_cb;
334

  
335
    entry_object.unroll.textContent = "preview not implemented...";
336
}
337

  
338
const show_install_chbx = by_id("show_install_view_chbx");
339

  
340
let import_frame;
341
let install_target = null;
342

  
343
function install_abort(error_state)
344
{
345
    import_frame.show_error(`Error: ${error_state}`);
346
    install_target = null;
347
}
348

  
349
/*
350
 * Translate objects from the format in which they are sent by Hydrilla to the
351
 * format in which they are stored in settings.
352
 */
257 353

  
258
	container_for_repo_responses.appendChild(h4);
259
	container_for_repo_responses.appendChild(pre);
354
function translate_script(script_object, repo_url)
355
{
356
    return {
357
	[TYPE_PREFIX.SCRIPT + script_object.name]: {
358
	    hash: script_object.sha256,
359
	    url: `${repo_url}/content/${script_object.location}`
360
	}
361
    };
362
}
363

  
364
function translate_bag(bag_object)
365
{
366
    return {
367
	[TYPE_PREFIX.BAG + bag_object.name]: bag_object.components
368
    };
369
}
370

  
371
const format_translators = {
372
    [TYPE_PREFIX.BAG]: translate_bag,
373
    [TYPE_PREFIX.SCRIPT]: translate_script
374
};
375

  
376
function install_check_ready()
377
{
378
    if (install_target.to_fetch.size > 0)
379
	return;
380

  
381
    const page_key = [TYPE_PREFIX.PAGE + install_target.pattern];
382
    const to_install = [{[page_key]: {components: install_target.payload}}];
383

  
384
    for (const key of install_target.fetched) {
385
	const old_object =
386
	      queried_items.get(key)[install_target.repo_url].response;
387
	const new_object =
388
	      format_translators[key[0]](old_object, install_target.repo_url);
389
	to_install.push(new_object);
390
    }
391

  
392
    import_frame.show_selection(to_install);
393
}
394

  
395
const possible_errors = ["connection_error", "parse_error"];
396

  
397
function fetch_install_deps(components)
398
{
399
    const needed = [...components];
400
    const processed = new Set();
401

  
402
    while (needed.length > 0) {
403
	const [prefix, item] = needed.pop();
404
	const key = prefix + item;
405
	processed.add(key);
406
	const results = queried_items.get(key);
407
	let relevant_result = null;
408

  
409
	if (results)
410
	    relevant_result = results[install_target.repo_url];
411

  
412
	if (!relevant_result) {
413
	    content_script_port.postMessage([prefix, item,
414
					     [install_target.repo_url]]);
415
	    install_target.to_fetch.add(key);
416
	    continue;
417
	}
418

  
419
	if (possible_errors.includes(relevant_result.state)) {
420
	    install_abort(relevant_result.state);
421
	    return false;
422
	}
423

  
424
	install_target.fetched.add(key);
425

  
426
	if (prefix !== TYPE_PREFIX.BAG)
427
	    continue;
428

  
429
	for (const dependency of relevant_result.response.components) {
430
	    if (processed.has(dependency.join('')))
431
		continue;
432
	    needed.push(dependency);
433
	}
260 434
    }
261 435
}
262 436

  
437
function record_fetched_install_dep(prefix, item, repo_url, result)
438
{
439
    const key = prefix + item;
440

  
441
    if (!install_target || repo_url !== install_target.repo_url ||
442
	!install_target.to_fetch.has(key))
443
	return;
444

  
445
    if (possible_errors.includes(result.state)) {
446
	install_abort(result.state);
447
	return;
448
    }
449

  
450
    if (result.state !== "completed")
451
	return;
452

  
453
    install_target.to_fetch.delete(key);
454
    install_target.fetched.add(key);
455

  
456
    if (prefix === TYPE_PREFIX.BAG &&
457
	fetch_install_deps(result.response.components) === false)
458
	return;
459

  
460
    install_check_ready();
461
}
462

  
463
function install_clicked(entry_object)
464
{
465
    show_install_chbx.checked = true;
466
    import_frame.show_loading();
467

  
468
    install_target = {
469
	repo_url: entry_object.repo_url,
470
	pattern: entry_object.match_object.pattern,
471
	payload: entry_object.match_object.payload,
472
	fetched: new Set(),
473
	to_fetch: new Set()
474
    };
475

  
476
    fetch_install_deps([install_target.payload]);
477

  
478
    install_check_ready();
479
}
480

  
481
var max_query_result_id = 0;
482

  
483
function show_query_successful_result(result_item, repo_url, result)
484
{
485
    const ul = document.createElement("ul");
486

  
487
    set_appended(result_item, ul);
488

  
489
    for (const match of result) {
490
	const entry_object = clone_template("query_match_li_template");
491

  
492
	entry_object.pattern.textContent = match.pattern;
493

  
494
	ul.appendChild(entry_object.li);
495

  
496
	if (!match.payload) {
497
	    entry_object.payload.textContent = "(none)";
498
	    for (const key of ["chbx", "br", "triangle", "unroll"])
499
		entry_object[key].remove();
500
	    continue;
501
	}
502

  
503
	entry_object.component.textContent = nice_name(...match.payload);
504

  
505
	const install_cb = () => install_clicked(entry_object);
506
	entry_object.btn.addEventListener("click", install_cb);
507

  
508
	const chbx_id = `query_result_${max_query_result_id++}`;
509
	entry_object.chbx.id = chbx_id;
510
	entry_object.lbl.setAttribute("for", chbx_id);
511

  
512
	entry_object.unroll_cb = () => unroll_chbx_first_checked(entry_object);
513
	entry_object.chbx.addEventListener("change", entry_object.unroll_cb);
514

  
515
	entry_object.component_object = match.payload;
516
	entry_object.match_object = match;
517
	entry_object.repo_url = repo_url;
518
    }
519
}
520

  
521
function show_query_result(url_prefix, url, repo_url, result)
522
{
523
    const results_list_object = results_lists.get(url) ||
524
	  create_results_list(url);
525
    const result_item = results_list_object.by_repo.get(repo_url) ||
526
	  create_result_item(results_list_object, repo_url, result);
527

  
528
    const completed_cb =
529
	  item => show_query_successful_result(item, repo_url, result.response);
530
    const possible_actions = {
531
	completed: completed_cb,
532
	started: showcb("loading..."),
533
	connection_error: showcb("Error when querying repository."),
534
	parse_error: showcb("Bad data format received.")
535
    };
536
    possible_actions[result.state](result_item, repo_url);
537
}
538

  
263 539
by_id("settings_but")
264 540
    .addEventListener("click", (e) => browser.runtime.openOptionsPage());
265 541

  
266
show_page_activity_info();
542
async function main()
543
{
544
    storage = await get_remote_storage();
545
    import_frame = await get_import_frame();
546
    import_frame.onclose = () => show_install_chbx.checked = false;
547
    show_page_activity_info();
548
}
549

  
550
main();

Also available in: Unified diff