Skip to content

Commit 8e48875

Browse files
committed
[README] Issue #266. Update README for stack branch
1 parent 18e11cc commit 8e48875

File tree

1 file changed

+187
-10
lines changed

1 file changed

+187
-10
lines changed

README.md

+187-10
Original file line numberDiff line numberDiff line change
@@ -58,14 +58,23 @@ Thin Hook Preprocessor (experimental)
5858

5959
### Hooked Output
6060
```javascript
61-
class C {
61+
const __context_mapper__ = $hook$.$(__hook__, [
62+
'examples/example2.js,C',
63+
'_p_C;examples/example2.js,C',
64+
'examples/example2.js,C,add',
65+
'examples/example2.js,C,add,plus'
66+
]);
67+
$hook$.global(__hook__, __context_mapper__[0], 'C', 'class')[__context_mapper__[1]] = class C {
6268
add(a, b) {
6369
return __hook__((a = 1, b = 2) => {
64-
let plus = (...args) => __hook__((x, y) => x + y, this, args, 'examples/example2.js,C,add,plus');
65-
return plus(a, b);
66-
}, this, arguments, 'examples/example2.js,C,add');
70+
let plus = (...args) => __hook__((x, y) => x + y, null, args, __context_mapper__[3]);
71+
return __hook__(plus, null, [
72+
a,
73+
b
74+
], __context_mapper__[2], 0);
75+
}, null, arguments, __context_mapper__[2]);
6776
}
68-
}
77+
};
6978
```
7079

7180
### Preprocess
@@ -191,7 +200,7 @@ Thin Hook Preprocessor (experimental)
191200
//
192201
// Configuration:
193202
// hook.parameters.hookWorker = `hook-worker.js?no-hook=true`;
194-
importScripts('../hook.min.js?no-hook=true', 'context-generator.js?no-hook=true');
203+
importScripts('../hook.min.js?no-hook=true', 'context-generator.js?no-hook=true', 'bootstrap.js?no-hook=true');
195204
onmessage = hook.hookWorkerHandler;
196205
```
197206

@@ -703,7 +712,9 @@ To achieve this, the static entry HTML has to be __Encoded__ at build time by `h
703712
? f.apply(thisArg, args)
704713
: f(...args);
705714
}
706-
</script>
715+
</script><!-- end of mandatory no-hook scripts -->
716+
<!-- comment --->
717+
<script src="..."></script>
707718
...
708719
</html>
709720
```
@@ -713,8 +724,8 @@ To achieve this, the static entry HTML has to be __Encoded__ at build time by `h
713724
```html
714725
<html>
715726
<head>
716-
<script src="../thin-hook/hook.min.js?version=1&no-hook=true&hook-name=__hook__&fallback-page=index-no-sw.html&hook-property=false&service-worker-ready=false"></script></head></html><!--
717-
<C!-- Hook Callback Function without hooking properties --C>
727+
<script src="../thin-hook/hook.min.js?version=1&no-hook=true&hook-name=__hook__&fallback-page=index-no-sw.html&hook-property=false&service-worker-ready=false"></script></head></html>
728+
<!-- Hook Callback Function without hooking properties -->
718729
<script no-hook>
719730
window.__hook__ = function __hook__(f, thisArg, args, context, newTarget) {
720731
...
@@ -724,11 +735,16 @@ To achieve this, the static entry HTML has to be __Encoded__ at build time by `h
724735
? f.apply(thisArg, args)
725736
: f(...args);
726737
}
727-
</script>
738+
</script><!--<C!-- end of mandatory no-hook scripts --C>
739+
<C!-- comment --C>
740+
<script src="..."></script>
728741
...
729742
</html>-->
730743
```
731744

745+
- `</head></html>` is inserted between the first `hook.min.js` script and the second no-hook script, which looks strange but is required for correct execution of no-hook scripts.
746+
- If `</head></html>` is inserted at the end of mandatory no-hook scripts according to the normal HTML format, the page encounters the unexpected "hook is not defined" error, whose root cause is under investigation.
747+
732748
## Supported Syntax
733749

734750
- Functions
@@ -790,6 +806,7 @@ To achieve this, the static entry HTML has to be __Encoded__ at build time by `h
790806
- `hookPrefix`: Prefix for `hook.global()._p_GlobalVariable` proxy accessors. Default: `_p_`
791807
- Note: `hook.global()` return the global object with `get/set` accessors for the prefixed name
792808
- `initialScope`: Initial scope object (`{ vname: true, ... }`) for hooked eval scripts. Default: null
809+
- `$hook$`: `$hook$ === hook`. Alias of `hook` in hooked scripts
793810
- `hook.hookHtml(html: string, hookName, url, cors, contextGenerator, contextGeneratorScripts, isDecoded, metaHooking = true, scriptOffset = 0, _hookProperty = true, asynchronous = false)`
794811
- `hook.__hook__(f: function or string, thisArg: object, args: Array, context: string, newTarget: new.target meta property)`
795812
- minimal hook callback function with property hooking
@@ -837,6 +854,13 @@ To achieve this, the static entry HTML has to be __Encoded__ at build time by `h
837854
- `cachedMethodDebug(astPath: Array)`: context as `'script.js,Class,Method'`, comparing contexts with those by "oldMethod" in console.warn() messages
838855
- `oldMethod(astPath: Array)`: context as `'script.js,Class,Method'` for compatibility
839856
- custom context generator function has to be added to this object with its unique contextGeneratorName
857+
- `hook.$(symbolToContext = __hook__, contexts)`: context symbol generator function used in hooked scripts to generate symbols corresponding to given contexts
858+
- Example call inserted at the beginning of a hooked script: `const __context_mapper__ = $hook$.$(__hook__, [ 'examples/example2.js,C', ... ]);`
859+
- `__context_mapper__`: `Array` of symbol contexts
860+
- In a hooked script, `__context_mapper__` is actually `__ + hex(sha256(topContextOfScript + code)) + __`
861+
- Note: Due to this specification, **the same script in the same URL cannot be loaded to a single document multiple times**
862+
- `__context_mapper__[N]`: the symbol context corresponding to the string context `contexts[N]`
863+
- `__hook__[__context_mapper__[N]]` is set as `contexts[N]` so that `__hook__` can convert symbol contexts to their corresponding string contexts
840864
- Hooked Native APIs: Automatically applied in `hook()` preprocessing
841865
- `hook.global(hookCallback: function = hookName, context: string, name: string, type: string)._p_name`: hooked global variable accessor when `hookGlobal` is true
842866
- `type`: one of `'var', 'function', 'let', 'const', 'class', 'get', 'set', 'delete', 'typeof'`
@@ -969,6 +993,8 @@ To achieve this, the static entry HTML has to be __Encoded__ at build time by `h
969993
- `cors=true` parameter: CORS script, e.g., `<script src="https://cross.origin.host/path/script.js?cors=true"></script>`
970994
- `hook.serviceWorkerTransformers`:
971995
- `encodeHtml(html: string)`: encode HTML for Service Worker
996+
- `<!-- end of mandatory no-hook scripts -->`: insert this exact marker as a comment so that all mandatory no-hook scripts before the marker in the HTML of the entry document can be executed even at the first load without Service Worker
997+
- Note: `no-hook-authorization` hashes are NOT effective at the first load
972998
- `decodeHtml(html: string)`: decode encoded HTML for Service Worker
973999
- `hook.hookWorkerHandler(event)`: onmessage handler for Hook Workers
9741000
- Usage: `onmessage = hook.hookWorkerHandler` in Hook Worker script
@@ -981,6 +1007,157 @@ To achieve this, the static entry HTML has to be __Encoded__ at build time by `h
9811007
- `createHash`: Synchronous SHA hash generator collections from [sha.js](https://github.com/crypto-browserify/sha.js)
9821008
- `HTMLParser`: HTML parser from [htmlparser2](https://www.npmjs.com/package/htmlparser2)
9831009

1010+
## Plugins
1011+
1012+
- Plugins are no-hook scripts for enhancements
1013+
- Currently, they are configured for the demo application under `demo/`, but fully customizable for any target applications
1014+
1015+
### `<script context-generator src="no-hook-authorization.js?no-hook=true"></script>`
1016+
1017+
- Configurations
1018+
- `hook.parameters.noHookAuthorization = { "hex sha256 digest for no-hook script": true, ... }`
1019+
- Hex sha256 digests have to be updated in the build process
1020+
- See `update-no-hook-authorization` gulp task
1021+
- Hex sha256 digest of the `no-hook-authorization.js` script itself has to be set as a parameter for `hook.min.js`
1022+
- `<script src="../../thin-hook/hook.min.js?version=496&no-hook-authorization=6a83335a7630118516213f52715a24520efc7030b3562291e92a06482894b95e&service-worker-ready=false"></script>`
1023+
- See `update-no-hook-authorization-in-html` gulp task
1024+
- `hook.parameters.sourceMap = [...]`
1025+
- `hook.parameters.hookWorker = 'hook-worker.js?no-hook=true';`
1026+
1027+
### `<script context-generator src="disable-devtools.js?no-hook=true"></script>`
1028+
1029+
- Features
1030+
- Force redirection to `about:blank` when the user tries to open Developer Tools
1031+
- Force redirection to `about:blank` when the user tries to inspect a source code of the pages
1032+
- Configurations
1033+
- `const devtoolsDisabled = true`: Use `false` and rebuild with `gulp demo` to enable Dev Tools
1034+
1035+
### `<script context-generator src="context-generator.js?no-hook=true"></script>`
1036+
1037+
- Configurations
1038+
- `hook.contextGenerators.hash`: an example custom context generator (not used for demo)
1039+
- `hook.contextGenerators.method2`: an example custom context generator (not used for demo)
1040+
- `Object.freeze(hook.contextGenerators)`
1041+
1042+
### `<script context-generator src='bootstrap.js?no-hook=true'></script>`
1043+
1044+
- Configurations
1045+
- `hook.parameters.emptyDocumentUrl`
1046+
- `hook.parameters.bootstrap`
1047+
- `hook.parameters.onloadWrapper`
1048+
- `hook.parameters.emptySvg`
1049+
- `hook.parameters.bootstrapSvgScripts`
1050+
- `hook.parameters.noHookAuthorizationParameter`: Value of `hook.min.js?no-hook-authorization` parameter used in `hook-callback.js`
1051+
- `hook.parameters.noHookAuthorizationFailed = {}`
1052+
- `hook.parameters.noHookAuthorizationPassed = {}`
1053+
1054+
### `<script context-generator no-hook>hook.parameters.* ...</script>`
1055+
1056+
- Configurations
1057+
- `hook.parameters.cors`
1058+
- `hook.parameters.opaque`
1059+
- `hook.parameters.worker` (Ineffective and unused for now)
1060+
1061+
### `<script context-generator src="cache-bundle.js?no-hook=true&authorization=..."></script>`
1062+
1063+
- Features
1064+
- Fetch `cache-bundle.json` and store the contents into `caches`
1065+
- Format: `{ "version": "version_XXX", "same origin URL path (absolute)": "text data", ..., "absolute URL": "text data", ... }`
1066+
- Supported MIME types: text-only for now
1067+
- `.js`: `application/json`
1068+
- `.html`: `text/html`
1069+
- `.json`: `application/json`
1070+
- `.svg`: `image/svg+xml`
1071+
- other extensions: `text/plain`
1072+
- Generate `cache-bundle.json` from `caches` and upload the data to saveURL (`errorReport.json`) if the entry page is invoked with `?cache-bundle=save` parameter
1073+
- The server must be `npm run upload` with `cacheBundleUploadService.js` to receive and save `cache-bundle.json`
1074+
- Parameters: `{ "type": "cache-bundle.json", "data": "stringified cache-bundle.json" }`
1075+
- Automate generation of `cache-bundle.json`
1076+
- Trigger automation by `cacheBundleGeneration.js` via `puppeteer`
1077+
- Invoked via `cache-bundle` gulp task
1078+
- Fetch a special `cache-bundle.json` at build time
1079+
- Generated by `cache-bundle-automation-json` gulp task
1080+
- Format:
1081+
- `"version": "version_123"`: version obtained via `get-version` gulp task
1082+
- `"https://thin-hook.localhost.localdomain/automation.json":`: `JSON.stringify()` with the object with the following properties
1083+
- `"state": "init"`: update state in the script to perform operations including reloading
1084+
- `"serverSecret": serverSecret`: one-time build-time-only secret for validating `cache-automation.js` script
1085+
- `"script": cacheAutomationScript`: contents of `cache-automation.js` script
1086+
- `cache-automation.js`: script for collecting caches by automatically navigating the target application
1087+
- `cache-automation.js` script is hooked with the context `https://thin-hook.localhost.localdomain/automation.json,*`
1088+
- ACL has to be defined for `cache-automation.js`
1089+
- Cache cleanup and page reload are done before `cache-automation.js` execution
1090+
- Cache bundle generation is performed after `cache-automation.js` execution
1091+
1092+
- Configurations
1093+
- `const enableCacheBundle = true`: Use `false` and rebuild with `gulp demo` to disable `cache-bundle`
1094+
- For Service Worker
1095+
- `const cacheBundleURL = new URL('cache-bundle.json', hook.parameters.baseURI);`
1096+
- `const saveURL = new URL('errorReport.json', hook.parameters.baseURI);`
1097+
- `?authorization=`: `hex(sha256(serverSecret + cache-automation.js script))`
1098+
- Set via `encode-demo-html` gulp task
1099+
- For automated generation of `cache-bundle.json`
1100+
- `cache-automation.js` must be fully customized for the target application
1101+
- ACL for `cache-automation.js` with the context `https://thin-hook.localhost.localdomain/automation.json,*`
1102+
1103+
### `<script src="hook-callback.js?no-hook=true"></script>`
1104+
1105+
- Features
1106+
- ACL for objects in HTML documents, SVG, Worker, SharedWorker
1107+
- Maintain `contextStack` with `Stack` class object
1108+
- `Stack` class object is a brancheable linked list with `push/pop` operations
1109+
- The branching feature of `Stack` is not utilized for now
1110+
- Call `hook.hookCallbackCompatibilityTest()`
1111+
- Hook global objects
1112+
- Via
1113+
- `hooked = hook[name](Symbol.for('__hook__'), [[name, { random: name === 'Node' }]], 'method')`
1114+
- `Object.defineProperty(_global, name, { value: hooked, configurable: true, enumerable: false, writable: false });`
1115+
- Target global object names
1116+
- `eval`
1117+
- `setTimeout`
1118+
- `setInterval`
1119+
- `Node`
1120+
- `Element`
1121+
- `HTMLScriptElement`
1122+
- `HTMLIFrameElement`
1123+
- `HTMLObjectElement`
1124+
- `HTMLEmbedElement`
1125+
- `HTMLAnchorElement`
1126+
- `HTMLAreaElement`
1127+
- `Document`
1128+
- `importScripts`
1129+
- Prohibit global object access via automation like puppeteer
1130+
- Return `undefined` on prohibited global object access
1131+
- Forced redirection to `about:blank` on prohibited global object access
1132+
- Configurations
1133+
- For ACL
1134+
- `__hook__`: hook callback function
1135+
- `Object.defineProperty(_global, '__hook__', { configurable: false, enumerable: false, writable: false, value: hookCallbacks.__hook__ });`
1136+
- `hookCallbacks.__hook__`: full features (acl + contextStack + object access graph)
1137+
- `hookCallbacks.__hook__acl`: acl only (acl + contextStack)
1138+
- `hookCallbacks.__hook__min`: minimal (no acl)
1139+
- `const acl`: ACL
1140+
- For global object access
1141+
- `const enableDebugging = false`: Use `true` to enable debugging by disabling forced redirection to `about:blank` on prohibited global object access
1142+
- `const wildcardWhitelist`: `Array` of `RegExp` for Chrome browser's `new Error().stack` format
1143+
- Example configurations for demo
1144+
- `new RegExp('^at (.* [(])?' + origin + '/components/'), // trust the site contents including other components`
1145+
- `new RegExp('^at ([^(]* [(])?' + 'https://cdnjs.cloudflare.com/ajax/libs/vis/4[.]18[.]1/vis[.]min[.]js'),`
1146+
- `new RegExp('^at ([^(]* [(])?' + 'https://www.gstatic.com/charts/loader[.]js'),`
1147+
- `const excludes = new Set() : { 'window.Math' }`: exclude `Math` object
1148+
- Note: `Math` object properties must be wrapped with `wrapGlobalProperty` function
1149+
1150+
#### Notes on Performance Overheads on Global Object Access
1151+
1152+
- There are significant access performance overheads on global objects due to wrapped property getter/setter functions
1153+
- To mitigate the overheads, define local alias objects for frequently used global objects
1154+
- For example, `const URL = window.URL, RegExp = window.RegExp, ...`
1155+
- Internal Details on the overheads:
1156+
- If `contextStack` is empty, the global object is accessed outside of hooked scripts and thus `new Error().stack` has to be analyzed, which is an extremely heavy operation
1157+
- If `contextStack` is not empty, the global object is accessed within a hooked script, whose access can be controlled via ACL
1158+
- `contextStack` operations are relatively lightweight without performance degradation on deep call stack
1159+
- If local alias objects are defined, the corresponding global object access is performed only once per object, whose overheads are insignificant
1160+
9841161
## TODOs
9851162

9861163
- Refine API

0 commit comments

Comments
 (0)