Fossil SCM

fossil-scm / src / builtin.c
Blame History Raw 919 lines
1
/*
2
** Copyright (c) 2014 D. Richard Hipp
3
**
4
** This program is free software; you can redistribute it and/or
5
** modify it under the terms of the Simplified BSD License (also
6
** known as the "2-Clause License" or "FreeBSD License".)
7
8
** This program is distributed in the hope that it will be useful,
9
** but without any warranty; without even the implied warranty of
10
** merchantability or fitness for a particular purpose.
11
**
12
** Author contact information:
13
** [email protected]
14
** http://www.hwaci.com/drh/
15
**
16
*******************************************************************************
17
**
18
** This file contains built-in string and BLOB resources packaged as
19
** byte arrays.
20
*/
21
#include "config.h"
22
#include "builtin.h"
23
#include <assert.h>
24
25
/*
26
** The resources provided by this file are packaged by the "mkbuiltin.c"
27
** utility program during the built process and stored in the
28
** builtin_data.h file. Include that information here:
29
*/
30
#include "builtin_data.h"
31
32
/*
33
** Return the index in the aBuiltinFiles[] array for the file
34
** whose name is zFilename. Or return -1 if the file is not
35
** found.
36
*/
37
static int builtin_file_index(const char *zFilename){
38
int lwr, upr, i, c;
39
lwr = 0;
40
upr = count(aBuiltinFiles) - 1;
41
while( upr>=lwr ){
42
i = (upr+lwr)/2;
43
c = strcmp(aBuiltinFiles[i].zName,zFilename);
44
if( c<0 ){
45
lwr = i+1;
46
}else if( c>0 ){
47
upr = i-1;
48
}else{
49
return i;
50
}
51
}
52
return -1;
53
}
54
55
/*
56
** Return a pointer to built-in content
57
**
58
** If the filename contains "-vNNNNNNNN" just before the final file
59
** suffix, where each N is a random digit, then omit that part of the
60
** filename before doing the lookup. The extra -vNNNNNNNN was added
61
** to defeat overly aggressive caching by web browsers. There must be
62
** at least 8 digits in NNNNNNNN but more than 8 are allowed.
63
*/
64
const unsigned char *builtin_file(const char *zFilename, int *piSize){
65
int i = builtin_file_index(zFilename);
66
if( i>=0 ){
67
if( piSize ) *piSize = aBuiltinFiles[i].nByte;
68
return aBuiltinFiles[i].pData;
69
}else{
70
char *zV = strstr(zFilename, "-v");
71
if( zV!=0 ){
72
for(i=0; fossil_isdigit(zV[i+2]); i++){}
73
if( i>=8 && zV[i+2]=='.' ){
74
char *zNew = mprintf("%.*s%s", (int)(zV-zFilename), zFilename, zV+i+2);
75
const unsigned char *pRes = builtin_file(zNew, piSize);
76
fossil_free(zNew);
77
return pRes;
78
}
79
}
80
if( piSize ) *piSize = 0;
81
return 0;
82
}
83
}
84
const char *builtin_text(const char *zFilename){
85
return (char*)builtin_file(zFilename, 0);
86
}
87
88
/*
89
** COMMAND: test-builtin-list
90
**
91
** If -verbose is used, it outputs a line at the end
92
** with the total item count and size.
93
**
94
** List the names and sizes of all built-in resources.
95
*/
96
void test_builtin_list(void){
97
int i, size = 0;;
98
for(i=0; i<count(aBuiltinFiles); i++){
99
const int n = aBuiltinFiles[i].nByte;
100
fossil_print("%3d. %-45s %6d\n", i+1, aBuiltinFiles[i].zName,n);
101
size += n;
102
}
103
if(find_option("verbose","v",0)!=0){
104
fossil_print("%d entries totaling %d bytes\n", i, size);
105
}
106
}
107
108
/*
109
** WEBPAGE: test-builtin-files
110
**
111
** Show all built-in text files.
112
*/
113
void test_builtin_list_page(void){
114
int i;
115
style_set_current_feature("test");
116
style_header("Built-in Text Files");
117
@ <ol>
118
for(i=0; i<count(aBuiltinFiles); i++){
119
const char *z = aBuiltinFiles[i].zName;
120
char *zUrl = href("%R/builtin?name=%T&id=%.8s&mimetype=text/plain",
121
z,fossil_exe_id());
122
@ <li>%z(zUrl)%h(z)</a>
123
}
124
@ </ol>
125
style_finish_page();
126
}
127
128
/*
129
** COMMAND: test-builtin-get
130
**
131
** Usage: %fossil test-builtin-get NAME ?OUTPUT-FILE?
132
*/
133
void test_builtin_get(void){
134
const unsigned char *pData;
135
int nByte;
136
Blob x;
137
if( g.argc!=3 && g.argc!=4 ){
138
usage("NAME ?OUTPUT-FILE?");
139
}
140
pData = builtin_file(g.argv[2], &nByte);
141
if( pData==0 ){
142
fossil_fatal("no such built-in file: [%s]", g.argv[2]);
143
}
144
blob_init(&x, (const char*)pData, nByte);
145
blob_write_to_file(&x, g.argc==4 ? g.argv[3] : "-");
146
blob_reset(&x);
147
}
148
149
/*
150
** Input zList is a list of numeric identifiers for files in
151
** aBuiltinFiles[]. Return the concatenation of all of those files
152
** using mimetype zType, or as text/javascript if zType is 0.
153
*/
154
static void builtin_deliver_multiple_js_files(
155
const char *zList, /* List of numeric identifiers */
156
const char *zType /* Override mimetype */
157
){
158
Blob *pOut;
159
if( zType==0 ) zType = "text/javascript";
160
cgi_set_content_type(zType);
161
pOut = cgi_output_blob();
162
while( zList[0] ){
163
int i = atoi(zList);
164
if( i>0 && i<=count(aBuiltinFiles) ){
165
blob_appendf(pOut, "/* %s */\n", aBuiltinFiles[i-1].zName);
166
blob_append(pOut, (const char*)aBuiltinFiles[i-1].pData,
167
aBuiltinFiles[i-1].nByte);
168
}
169
while( zList[0] && fossil_isdigit(zList[0]) ) zList++;
170
while( zList[0] && !fossil_isdigit(zList[0]) ) zList++;
171
}
172
return;
173
}
174
175
/*
176
** WEBPAGE: builtin loadavg-exempt
177
**
178
** Return one of many built-in content files. Query parameters:
179
**
180
** name=FILENAME Return the single file whose name is FILENAME.
181
** mimetype=TYPE Override the mimetype in the returned file to
182
** be TYPE. If this query parameter is omitted
183
** (the usual case) then the mimetype is inferred
184
** from the suffix on FILENAME
185
** m=IDLIST IDLIST is a comma-separated list of integers
186
** that specify multiple javascript files to be
187
** concatenated and returned all at once.
188
** id=UNIQUEID Version number of the "builtin" files. Used
189
** for cache control only.
190
**
191
** At least one of the name= or m= query parameters must be present.
192
**
193
** If the id= query parameter is present, then Fossil assumes that the
194
** result is immutable and sets a very large cache retention time (1 year).
195
*/
196
void builtin_webpage(void){
197
Blob out;
198
const char *zName = P("name");
199
const char *zContent = 0;
200
int nContent = 0;
201
const char *zId = P("id");
202
const char *zType = P("mimetype");
203
int nId;
204
if( zName ) zContent = (const char *)builtin_file(zName, &nContent);
205
if( zContent==0 ){
206
const char *zM = P("m");
207
if( zM ){
208
if( zId && (nId = (int)strlen(zId))>=8
209
&& strncmp(zId,fossil_exe_id(),nId)==0
210
){
211
g.isConst = 1;
212
}
213
etag_check(0,0);
214
builtin_deliver_multiple_js_files(zM, zType);
215
return;
216
}
217
cgi_set_status(404, "Not Found");
218
@ File "%h(zName)" not found
219
return;
220
}
221
if( zType==0 ){
222
if( sqlite3_strglob("*.js", zName)==0 ){
223
zType = "text/javascript";
224
}else{
225
zType = mimetype_from_name(zName);
226
}
227
}
228
cgi_set_content_type(zType);
229
if( zId
230
&& (nId = (int)strlen(zId))>=8
231
&& strncmp(zId,fossil_exe_id(),nId)==0
232
){
233
g.isConst = 1;
234
}
235
etag_check(0,0);
236
blob_init(&out, zContent, nContent);
237
cgi_set_content(&out);
238
}
239
240
/* Variables controlling the JS cache.
241
*/
242
static struct {
243
int aReq[30]; /* Indexes of all requested built-in JS files */
244
int nReq; /* Number of slots in aReq[] currently used */
245
int nSent; /* Number of slots in aReq[] fulfilled */
246
int eDelivery; /* Delivery mechanism */
247
} builtin;
248
249
#if INTERFACE
250
/* Various delivery mechanisms. The 0 option is the default.
251
** MAINTENANCE NOTE: Review/update the builtin_set_js_delivery_mode() and
252
** builtin_get_js_delivery_mode_name() functions if values are changed/added.
253
*/
254
#define JS_INLINE 0 /* inline, batched together at end of file */
255
#define JS_SEPARATE 1 /* Separate HTTP request for each JS file */
256
#define JS_BUNDLED 2 /* One HTTP request to load all JS files */
257
/* concatenated together into a bundle */
258
#endif /* INTERFACE */
259
260
/*
261
** The argument is a request to change the javascript delivery mode.
262
** The argument is a string which is a command-line option or CGI
263
** parameter. Try to match it against one of the delivery options
264
** and set things up accordingly. Throw an error if no match unless
265
** bSilent is true.
266
*/
267
void builtin_set_js_delivery_mode(const char *zMode, int bSilent){
268
if( zMode==0 ) return;
269
if( strcmp(zMode, "inline")==0 ){
270
builtin.eDelivery = JS_INLINE;
271
}else
272
if( strcmp(zMode, "separate")==0 ){
273
builtin.eDelivery = JS_SEPARATE;
274
}else
275
if( strcmp(zMode, "bundled")==0 ){
276
builtin.eDelivery = JS_BUNDLED;
277
}else if( !bSilent ){
278
fossil_fatal("unknown javascript delivery mode \"%s\" - should be"
279
" one of: inline separate bundled", zMode);
280
}
281
}
282
283
/*
284
** Returns the current JS delivery mode: one of JS_INLINE,
285
** JS_SEPARATE, JS_BUNDLED.
286
*/
287
int builtin_get_js_delivery_mode(void){
288
return builtin.eDelivery;
289
}
290
291
/*
292
** Returns the name of the current JS delivery mode for reuse with the --jsmode
293
** option, i.e. the other way around than builtin_set_js_delivery_mode().
294
*/
295
const char *builtin_get_js_delivery_mode_name(void){
296
switch( builtin.eDelivery ){
297
case JS_SEPARATE: {
298
return "separate";
299
}
300
case JS_BUNDLED: {
301
return "bundled";
302
}
303
case JS_INLINE:
304
/*FALLTHROUGH*/
305
default: {
306
return "inline";
307
}
308
}
309
}
310
311
/*
312
** The caller wants the Javascript file named by zFilename to be
313
** included in the generated page. Add the file to the queue of
314
** requested javascript resources, if it is not there already.
315
**
316
** The current implementation queues the file to be included in the
317
** output later. However, the caller should not depend on that
318
** behavior. In the future, this routine might decide to insert
319
** the requested javascript inline, immedaitely, or to insert
320
** a <script src=..> element to reference the javascript as a
321
** separate resource. The exact behavior might change in the future
322
** so pages that use this interface must not rely on any particular
323
** behavior.
324
**
325
** All this routine guarantees is that the named javascript file
326
** will be requested by the browser at some point. This routine
327
** does not guarantee when the javascript will be included, and it
328
** does not guarantee whether the javascript will be added inline or
329
** delivered as a separate resource.
330
*/
331
void builtin_request_js(const char *zFilename){
332
int i = builtin_file_index(zFilename);
333
int j;
334
if( i<0 ){
335
fossil_panic("unknown javascript file: \"%s\"", zFilename);
336
}
337
for(j=0; j<builtin.nReq; j++){
338
if( builtin.aReq[j]==i ) return; /* Already queued or sent */
339
}
340
if( builtin.nReq>=count(builtin.aReq) ){
341
fossil_panic("too many javascript files requested");
342
}
343
builtin.aReq[builtin.nReq++] = i;
344
}
345
346
/*
347
** Fulfill all pending requests for javascript files.
348
**
349
** The current implementation delivers all javascript in-line. However,
350
** the caller should not depend on this. Future changes to this routine
351
** might choose to deliver javascript as separate resources.
352
*/
353
void builtin_fulfill_js_requests(void){
354
if( builtin.nSent>=builtin.nReq ) return; /* nothing to do */
355
switch( builtin.eDelivery ){
356
case JS_INLINE: {
357
CX("<script nonce='%h'>\n",style_nonce());
358
do{
359
int i = builtin.aReq[builtin.nSent++];
360
CX("/* %s %.60c*/\n", aBuiltinFiles[i].zName, '*');
361
cgi_append_content((const char*)aBuiltinFiles[i].pData,
362
aBuiltinFiles[i].nByte);
363
}while( builtin.nSent<builtin.nReq );
364
CX("</script>\n");
365
break;
366
}
367
case JS_BUNDLED: {
368
if( builtin.nSent+1<builtin.nReq ){
369
Blob aList;
370
blob_init(&aList,0,0);
371
while( builtin.nSent<builtin.nReq ){
372
blob_appendf(&aList, ",%d", builtin.aReq[builtin.nSent++]+1);
373
}
374
CX("<script src='%R/builtin?m=%s&id=%.8s'></script>\n",
375
blob_str(&aList)+1, fossil_exe_id());
376
blob_reset(&aList);
377
break;
378
}
379
/* If there is only one JS file, fall through into the
380
** JS_SEPARATE case below. */
381
/*FALLTHROUGH*/
382
}
383
case JS_SEPARATE: {
384
/* Each JS file as a separate resource */
385
while( builtin.nSent<builtin.nReq ){
386
int i = builtin.aReq[builtin.nSent++];
387
CX("<script src='%R/builtin?name=%t&id=%.8s'></script>\n",
388
aBuiltinFiles[i].zName, fossil_exe_id());
389
}
390
break;
391
}
392
}
393
}
394
395
/*****************************************************************************
396
** A virtual table for accessing the information in aBuiltinFiles[].
397
*/
398
399
/* builtinVtab_vtab is a subclass of sqlite3_vtab which is
400
** underlying representation of the virtual table
401
*/
402
typedef struct builtinVtab_vtab builtinVtab_vtab;
403
struct builtinVtab_vtab {
404
sqlite3_vtab base; /* Base class - must be first */
405
/* Add new fields here, as necessary */
406
};
407
408
/* builtinVtab_cursor is a subclass of sqlite3_vtab_cursor which will
409
** serve as the underlying representation of a cursor that scans
410
** over rows of the result
411
*/
412
typedef struct builtinVtab_cursor builtinVtab_cursor;
413
struct builtinVtab_cursor {
414
sqlite3_vtab_cursor base; /* Base class - must be first */
415
/* Insert new fields here. For this builtinVtab we only keep track
416
** of the rowid */
417
sqlite3_int64 iRowid; /* The rowid */
418
};
419
420
/*
421
** The builtinVtabConnect() method is invoked to create a new
422
** builtin virtual table.
423
**
424
** Think of this routine as the constructor for builtinVtab_vtab objects.
425
**
426
** All this routine needs to do is:
427
**
428
** (1) Allocate the builtinVtab_vtab object and initialize all fields.
429
**
430
** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the
431
** result set of queries against the virtual table will look like.
432
*/
433
static int builtinVtabConnect(
434
sqlite3 *db,
435
void *pAux,
436
int argc, const char *const*argv,
437
sqlite3_vtab **ppVtab,
438
char **pzErr
439
){
440
builtinVtab_vtab *pNew;
441
int rc;
442
443
rc = sqlite3_declare_vtab(db,
444
"CREATE TABLE x(name,size,data)"
445
);
446
if( rc==SQLITE_OK ){
447
pNew = sqlite3_malloc( sizeof(*pNew) );
448
*ppVtab = (sqlite3_vtab*)pNew;
449
if( pNew==0 ) return SQLITE_NOMEM;
450
memset(pNew, 0, sizeof(*pNew));
451
}
452
return rc;
453
}
454
455
/*
456
** This method is the destructor for builtinVtab_vtab objects.
457
*/
458
static int builtinVtabDisconnect(sqlite3_vtab *pVtab){
459
builtinVtab_vtab *p = (builtinVtab_vtab*)pVtab;
460
sqlite3_free(p);
461
return SQLITE_OK;
462
}
463
464
/*
465
** Constructor for a new builtinVtab_cursor object.
466
*/
467
static int builtinVtabOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){
468
builtinVtab_cursor *pCur;
469
pCur = sqlite3_malloc( sizeof(*pCur) );
470
if( pCur==0 ) return SQLITE_NOMEM;
471
memset(pCur, 0, sizeof(*pCur));
472
*ppCursor = &pCur->base;
473
return SQLITE_OK;
474
}
475
476
/*
477
** Destructor for a builtinVtab_cursor.
478
*/
479
static int builtinVtabClose(sqlite3_vtab_cursor *cur){
480
builtinVtab_cursor *pCur = (builtinVtab_cursor*)cur;
481
sqlite3_free(pCur);
482
return SQLITE_OK;
483
}
484
485
486
/*
487
** Advance a builtinVtab_cursor to its next row of output.
488
*/
489
static int builtinVtabNext(sqlite3_vtab_cursor *cur){
490
builtinVtab_cursor *pCur = (builtinVtab_cursor*)cur;
491
pCur->iRowid++;
492
return SQLITE_OK;
493
}
494
495
/*
496
** Return values of columns for the row at which the builtinVtab_cursor
497
** is currently pointing.
498
*/
499
static int builtinVtabColumn(
500
sqlite3_vtab_cursor *cur, /* The cursor */
501
sqlite3_context *ctx, /* First argument to sqlite3_result_...() */
502
int i /* Which column to return */
503
){
504
builtinVtab_cursor *pCur = (builtinVtab_cursor*)cur;
505
const struct BuiltinFileTable *pFile = aBuiltinFiles + pCur->iRowid - 1;
506
switch( i ){
507
case 0: /* name */
508
sqlite3_result_text(ctx, pFile->zName, -1, SQLITE_STATIC);
509
break;
510
case 1: /* size */
511
sqlite3_result_int(ctx, pFile->nByte);
512
break;
513
case 2: /* data */
514
sqlite3_result_blob(ctx, pFile->pData, pFile->nByte, SQLITE_STATIC);
515
break;
516
}
517
return SQLITE_OK;
518
}
519
520
/*
521
** Return the rowid for the current row. In this implementation, the
522
** rowid is the same as the output value.
523
*/
524
static int builtinVtabRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){
525
builtinVtab_cursor *pCur = (builtinVtab_cursor*)cur;
526
*pRowid = pCur->iRowid;
527
return SQLITE_OK;
528
}
529
530
/*
531
** Return TRUE if the cursor has been moved off of the last
532
** row of output.
533
*/
534
static int builtinVtabEof(sqlite3_vtab_cursor *cur){
535
builtinVtab_cursor *pCur = (builtinVtab_cursor*)cur;
536
return pCur->iRowid>count(aBuiltinFiles);
537
}
538
539
/*
540
** This method is called to "rewind" the builtinVtab_cursor object back
541
** to the first row of output. This method is always called at least
542
** once prior to any call to builtinVtabColumn() or builtinVtabRowid() or
543
** builtinVtabEof().
544
*/
545
static int builtinVtabFilter(
546
sqlite3_vtab_cursor *pVtabCursor,
547
int idxNum, const char *idxStr,
548
int argc, sqlite3_value **argv
549
){
550
builtinVtab_cursor *pCur = (builtinVtab_cursor *)pVtabCursor;
551
pCur->iRowid = 1;
552
return SQLITE_OK;
553
}
554
555
/*
556
** SQLite will invoke this method one or more times while planning a query
557
** that uses the virtual table. This routine needs to create
558
** a query plan for each invocation and compute an estimated cost for that
559
** plan.
560
*/
561
static int builtinVtabBestIndex(
562
sqlite3_vtab *tab,
563
sqlite3_index_info *pIdxInfo
564
){
565
pIdxInfo->estimatedCost = (double)count(aBuiltinFiles);
566
pIdxInfo->estimatedRows = count(aBuiltinFiles);
567
return SQLITE_OK;
568
}
569
570
/*
571
** This following structure defines all the methods for the
572
** virtual table.
573
*/
574
static sqlite3_module builtinVtabModule = {
575
/* iVersion */ 0,
576
/* xCreate */ 0, /* The builtin vtab is eponymous and read-only */
577
/* xConnect */ builtinVtabConnect,
578
/* xBestIndex */ builtinVtabBestIndex,
579
/* xDisconnect */ builtinVtabDisconnect,
580
/* xDestroy */ 0,
581
/* xOpen */ builtinVtabOpen,
582
/* xClose */ builtinVtabClose,
583
/* xFilter */ builtinVtabFilter,
584
/* xNext */ builtinVtabNext,
585
/* xEof */ builtinVtabEof,
586
/* xColumn */ builtinVtabColumn,
587
/* xRowid */ builtinVtabRowid,
588
/* xUpdate */ 0,
589
/* xBegin */ 0,
590
/* xSync */ 0,
591
/* xCommit */ 0,
592
/* xRollback */ 0,
593
/* xFindMethod */ 0,
594
/* xRename */ 0,
595
/* xSavepoint */ 0,
596
/* xRelease */ 0,
597
/* xRollbackTo */ 0,
598
/* xShadowName */ 0,
599
/* xIntegrity */ 0
600
};
601
602
603
/*
604
** Register the builtin virtual table
605
*/
606
int builtin_vtab_register(sqlite3 *db){
607
int rc = sqlite3_create_module(db, "builtin", &builtinVtabModule, 0);
608
return rc;
609
}
610
/* End of the builtin virtual table
611
******************************************************************************/
612
613
614
/*
615
** The first time this is called, it emits code to install and
616
** bootstrap the window.fossil object, using the built-in file
617
** fossil.bootstrap.js (not to be confused with bootstrap.js).
618
**
619
** Subsequent calls are no-ops.
620
**
621
** It emits 2 parts:
622
**
623
** 1) window.fossil core object, some of which depends on C-level
624
** runtime data. That part of the script is always emitted inline. If
625
** addScriptTag is true then it is wrapped in its own SCRIPT tag, else
626
** it is assumed that the caller already opened a tag.
627
**
628
** 2) Emits the static fossil.bootstrap.js using builtin_request_js().
629
*/
630
void builtin_emit_script_fossil_bootstrap(int addScriptTag){
631
static int once = 0;
632
if(0==once++){
633
char * zName;
634
/* Set up the generic/app-agnostic parts of window.fossil
635
** which require C-level state... */
636
if(addScriptTag!=0){
637
style_script_begin(__FILE__,__LINE__);
638
}
639
CX("(function(){\n");
640
CX(/*MSIE NodeList.forEach polyfill, courtesy of Mozilla:
641
https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach#Polyfill
642
*/
643
"if(window.NodeList && !NodeList.prototype.forEach){"
644
"NodeList.prototype.forEach = Array.prototype.forEach;"
645
"}\n");
646
CX("if(!window.fossil) window.fossil={};\n"
647
"window.fossil.version = %!j;\n"
648
/* fossil.rootPath is the top-most CGI/server path,
649
** including a trailing slash. */
650
"window.fossil.rootPath = %!j+'/';\n",
651
get_version(), g.zTop);
652
/* fossil.config = {...various config-level options...} */
653
CX("window.fossil.config = {");
654
zName = db_get("project-name", "");
655
CX("projectName: %!j,\n", zName);
656
fossil_free(zName);
657
zName = db_get("short-project-name", "");
658
CX("shortProjectName: %!j,\n", zName);
659
fossil_free(zName);
660
zName = db_get("project-code", "");
661
CX("projectCode: %!j,\n", zName);
662
fossil_free(zName);
663
CX("/* Length of UUID hashes for display purposes. */");
664
CX("hashDigits: %d, hashDigitsUrl: %d,\n",
665
hash_digits(0), hash_digits(1));
666
CX("diffContextLines: %d,\n",
667
diff_context_lines(0));
668
CX("editStateMarkers: {"
669
"/*Symbolic markers to denote certain edit states.*/"
670
"isNew:'[+]', isModified:'[*]', isDeleted:'[-]'},\n");
671
CX("confirmerButtonTicks: 3 "
672
"/*default fossil.confirmer tick count.*/,\n");
673
/* Inject certain info about the current skin... */
674
CX("skin:{");
675
/* can leak a local filesystem path:
676
CX("name: %!j,", skin_in_use());*/
677
CX("isDark: %s"
678
"/*true if the current skin has the 'white-foreground' detail*/",
679
skin_detail_boolean("white-foreground") ? "true" : "false");
680
CX("}\n"/*fossil.config.skin*/);
681
CX("};\n"/* fossil.config */);
682
CX("window.fossil.user = {");
683
CX("name: %!j,", (g.zLogin&&*g.zLogin) ? g.zLogin : "guest");
684
CX("isAdmin: %s", (g.perm.Admin || g.perm.Setup) ? "true" : "false");
685
CX("};\n"/*fossil.user*/);
686
CX("if(fossil.config.skin.isDark) "
687
"document.body.classList.add('fossil-dark-style');\n");
688
/*
689
** fossil.page holds info about the current page. This is also
690
** where the current page "should" store any of its own
691
** page-specific state, and it is reserved for that purpose.
692
*/
693
CX("window.fossil.page = {"
694
"name:\"%T\""
695
"};\n", g.zPath);
696
CX("})();\n");
697
if(addScriptTag!=0){
698
style_script_end();
699
}
700
/* The remaining window.fossil bootstrap code is not dependent on
701
** C-runtime state... */
702
builtin_request_js("fossil.bootstrap.js");
703
}
704
}
705
706
/*
707
** Given the NAME part of fossil.NAME.js, this function checks whether
708
** that module has been emitted by this function before. If it has,
709
** it returns -1 with no side effects. If it has not, it queues up
710
** (via builtin_request_js()) an emit of the module via and all of its
711
** known (by this function) fossil.XYZ.js dependencies (in their
712
** dependency order) and returns 1. If it does not find the given
713
** module name it returns 0.
714
**
715
** As a special case, if passed 0 then it queues up all known modules
716
** and returns -1.
717
**
718
** The very first time this is called, it unconditionally calls
719
** builtin_emit_script_fossil_bootstrap().
720
**
721
** Any given module is only queued once, whether it is explicitly
722
** passed to the function or resolved as a dependency. Any attempts to
723
** re-queue them later are harmless no-ops.
724
*/
725
static int builtin_emit_fossil_js_once(const char * zName){
726
static int once = 0;
727
int i;
728
static struct FossilJs {
729
const char * zName; /* NAME part of fossil.NAME.js */
730
int emitted; /* True if already emitted. */
731
const char * zDeps; /* \0-delimited list of other FossilJs
732
** entries: all known deps of this one. Each
733
** REQUIRES an EXPLICIT trailing \0, including
734
** the final one! */
735
} fjs[] = {
736
/* This list ordering isn't strictly important. */
737
{"confirmer", 0, 0},
738
{"copybutton", 0, "dom\0"},
739
{"diff", 0, "dom\0fetch\0storage\0"
740
/* maintenance note: "diff" needs "storage" for storing the
741
** sbs-sync-scroll toggle. */},
742
{"dom", 0, 0},
743
{"fetch", 0, 0},
744
{"numbered-lines", 0, "popupwidget\0copybutton\0"},
745
{"pikchr", 0, "dom\0"},
746
{"popupwidget", 0, "dom\0"},
747
{"storage", 0, 0},
748
{"tabs", 0, "dom\0"}
749
};
750
const int nFjs = sizeof(fjs) / sizeof(fjs[0]);
751
if(0==once){
752
++once;
753
builtin_emit_script_fossil_bootstrap(1);
754
}
755
if(0==zName){
756
for( i = 0; i < nFjs; ++i ){
757
builtin_emit_fossil_js_once(fjs[i].zName);
758
}
759
return -1;
760
}
761
for( i = 0; i < nFjs; ++i ){
762
if(0==strcmp(zName, fjs[i].zName)){
763
if(fjs[i].emitted){
764
return -1;
765
}else{
766
char nameBuffer[50];
767
if(fjs[i].zDeps){
768
const char * zDep = fjs[i].zDeps;
769
while(*zDep!=0){
770
builtin_emit_fossil_js_once(zDep);
771
zDep += strlen(zDep)+1/*NUL delimiter*/;
772
}
773
}
774
sqlite3_snprintf(sizeof(nameBuffer)-1, nameBuffer,
775
"fossil.%s.js", fjs[i].zName);
776
builtin_request_js(nameBuffer);
777
fjs[i].emitted = 1;
778
return 1;
779
}
780
}
781
}
782
return 0;
783
}
784
785
/*
786
** COMMAND: test-js-once
787
**
788
** Tester for builtin_emit_fossil_js_once().
789
**
790
** Usage: %fossil test-js-once filename
791
*/
792
void test_js_once(void){
793
int i;
794
if(g.argc<2){
795
usage("?FILENAME...?");
796
}
797
if(2==g.argc){
798
builtin_emit_fossil_js_once(0);
799
assert(builtin.nReq>8);
800
}else{
801
for(i = 2; i < g.argc; ++i){
802
builtin_emit_fossil_js_once(g.argv[i]);
803
}
804
assert(builtin.nReq>1 && "don't forget implicit fossil.bootstrap.js");
805
}
806
for(i = 0; i < builtin.nReq; ++i){
807
fossil_print("ndx#%d = %d = %s\n", i, builtin.aReq[i],
808
aBuiltinFiles[builtin.aReq[i]].zName);
809
}
810
}
811
812
/*
813
** Convenience wrapper which calls builtin_request_js() for a series
814
** of builtin scripts named fossil.NAME.js. The first time it is
815
** called, it also calls builtin_emit_script_fossil_bootstrap() to
816
** initialize the window.fossil JS API. The first argument is the NAME
817
** part of the first API to emit. All subsequent arguments must be
818
** strings of the NAME part of additional fossil.NAME.js files,
819
** followed by a NULL argument to terminate the list.
820
**
821
** e.g. pass it ("fetch", "dom", "tabs", NULL) to load those 3 APIs (or
822
** pass it ("fetch","tabs",NULL), as "dom" is a dependency of "tabs", so
823
** it will be automatically loaded). Do not forget the trailing NULL,
824
** and do not pass 0 instead, since that isn't always equivalent to NULL
825
** in this context.
826
**
827
** If it is JS_BUNDLED then this routine queues up an emit of ALL of
828
** the JS fossil.XYZ.js APIs which are not strictly specific to a
829
** single page, and then calls builtin_fulfill_js_requests(). The idea
830
** is that we can get better bundle caching and reduced HTTP requests
831
** by including all JS, rather than creating separate bundles on a
832
** per-page basis. In this case, all arguments are ignored!
833
**
834
** This function has an internal mapping of the dependencies for each
835
** of the known fossil.XYZ.js modules and ensures that the
836
** dependencies also get queued (recursively) and that each module is
837
** queued only once.
838
**
839
** If passed a name which is not a base fossil module name then it
840
** will fail fatally!
841
**
842
** DO NOT use this for loading fossil.page.*.js: use
843
** builtin_request_js() for those.
844
**
845
** If the current JS delivery mode is *not* JS_BUNDLED then this
846
** function queues up a request for each given module and its known
847
** dependencies, but does not immediately fulfill the request, thus it
848
** can be called multiple times.
849
**
850
** If a given module is ever passed to this more than once, either in
851
** a single invocation or multiples, it is only queued for emit a
852
** single time (i.e. the 2nd and subsequent ones become
853
** no-ops). Likewise, if a module is requested but was already
854
** automatically queued to fulfill a dependency, the explicit request
855
** becomes a no-op.
856
**
857
** Bundled mode is the only mode in which this API greatly improves
858
** aggregate over-the-wire and HTTP request costs. For other modes,
859
** reducing the inclusion of fossil.XYZ APIs to their bare minimum
860
** provides the lowest aggregate costs. For debate and details, see
861
** the discussion at:
862
**
863
** https://fossil-scm.org/forum/forumpost/3fa2633f3e
864
**
865
** In practice it is normally necessary (or preferred) to call
866
** builtin_fulfill_js_requests() after calling this, before proceeding
867
** to call builtin_request_js() for page-specific JS, in order to
868
** improve cachability.
869
**
870
** Minor caveat: the purpose of emitting all of the fossil.XYZ JS APIs
871
** at once is to reduce over-the-wire transfers by enabling cross-page
872
** caching, but if there are other JS scripts pending via
873
** builtin_request_js() when this is called then they will be included
874
** in the JS request emitted by this routine, resulting in a different
875
** script URL than if they were not included. Thus, if a given page
876
** has its own scripts to install via builtin_request_js(), they
877
** should, if possible, be delayed until after this is called OR the
878
** page should call builtin_fulfill_js_requests() to flush the request
879
** queue before calling this routine.
880
**
881
** Achtung: the fossil.page.XYZ.js files are page-specific, containing
882
** the app-level logic for that specific page, and loading more than
883
** one of them in a single page will break that page. Each of those
884
** expects to "own" the page it is loaded in, and it should be loaded
885
** as late in the JS-loading process as feasible, ideally bundled (via
886
** builtin_request_js()) with any other app-/page-specific JS it may
887
** need.
888
**
889
** Example usage:
890
**
891
** builtin_fossil_js_bundle_or("dom", "fetch", NULL);
892
**
893
** In bundled mode, that will (the first time it is called) emit all
894
** builtin fossil JS APIs and "fulfill" the queue immediately. In
895
** non-bundled mode it will queue up the "dom" and "fetch" APIs to be
896
** emitted the next time builtin_fulfill_js_requests() is called.
897
*/
898
NULL_SENTINEL void builtin_fossil_js_bundle_or( const char * zApi, ... ) {
899
static int bundled = 0;
900
const char *zArg;
901
va_list vargs;
902
903
if(JS_BUNDLED == builtin_get_js_delivery_mode()){
904
if(!bundled){
905
bundled = 1;
906
builtin_emit_fossil_js_once(0);
907
builtin_fulfill_js_requests();
908
}
909
return;
910
}
911
va_start(vargs,zApi);
912
for( zArg = zApi; zArg!=NULL; (zArg = va_arg (vargs, const char *))){
913
if(0==builtin_emit_fossil_js_once(zArg)){
914
fossil_fatal("Unknown fossil JS module: %s\n", zArg);
915
}
916
}
917
va_end(vargs);
918
}
919

Keyboard Shortcuts

Open search /
Next entry (timeline) j
Previous entry (timeline) k
Open focused entry Enter
Show this help ?
Toggle theme Top nav button