| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 1c5e92b commit a20f2bc
6 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -52,6 +52,32 @@ Rules: | |||
| 52 | 52 | versions for readability; it is intended for humans and must not be parsed | |
| 53 | 53 | programmatically. | |
| 54 | 54 | ||
| 55 | + ### `ns:runtime` (v1) | ||
| 56 | + | ||
| 57 | + Runtime-level configuration. Keys, value domains, and scope are defined and | ||
| 58 | + validated natively; the module surface is a thin frozen wrapper. | ||
| 59 | + | ||
| 60 | + | export | description | | ||
| 61 | + |---|---| | ||
| 62 | + | `setConfig(key, value)` | Sets a runtime config key. Throws `TypeError` on an unknown key, an invalid value, or (for process-wide keys) when called from a worker isolate. | | ||
| 63 | + | `getConfig(key)` | Returns the current value of a config key. Throws `TypeError` on an unknown key. Readable from any isolate. | | ||
| 64 | + | ||
| 65 | + Config keys: | ||
| 66 | + | ||
| 67 | + | key | values | scope | default | | ||
| 68 | + |---|---|---|---| | ||
| 69 | + | `logScriptLoading` | `true` \| `false` | process-wide (main-isolate writes only; read live by every isolate) | `false`, or the `logScriptLoading` value from nativescript.config / package.json at boot | | ||
| 70 | + | `httpFetchUrlLog` | `true` \| `false` | process-wide (main-isolate writes only; read live by every isolate) | `false`, or the `httpFetchUrlLog` value from nativescript.config / package.json at boot | | ||
| 71 | + | ||
| 72 | + Remote-module security (`security.allowRemoteModules`, | ||
| 73 | + `security.remoteModuleAllowlist`) is **not** part of this surface. Those | ||
| 74 | + values are read once from nativescript.config / package.json the first time | ||
| 75 | + the HTTP loader gates a fetch, and they cannot be inspected or changed | ||
| 76 | + through `getConfig` / `setConfig`. | ||
| 77 | + | ||
| 78 | + iOS additionally registers `releasedObjectPolicy`; Android does not (it has | ||
| 79 | + no released-native-counterpart machinery). | ||
| 80 | + | ||
| 55 | 81 | ### `ns:module` (v1) | |
| 56 | 82 | ||
| 57 | 83 | The module-loader control surface consumed by development tooling | |
@@ -72,7 +98,11 @@ never present-but-throwing — so feature checks work. The module is | |||
| 72 | 98 | registered in every build; the security boundary for remote module loading | |
| 73 | 99 | sits at the network layer (`security.allowRemoteModules` in | |
| 74 | 100 | nativescript.config, enforced inside `HttpLoader`), not the module | |
| 75 | - registry. | ||
| 101 | + registry and not `ns:runtime` getConfig/setConfig. | ||
| 102 | + | ||
| 103 | + Note: `ns:module` (loader policy, structured, boot-time) is deliberately | ||
| 104 | + separate from `ns:runtime` (live key-value runtime flags, `setConfig`/ | ||
| 105 | + `getConfig`). | ||
| 76 | 106 | ||
| 77 | 107 | ## `node:` compatibility shims | |
| 78 | 108 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,67 @@ | |||
| 1 | + describe("ns:runtime", function () { | ||
| 2 | + var runtime = require("ns:runtime"); | ||
| 3 | + | ||
| 4 | + it("exposes frozen exports", function () { | ||
| 5 | + expect(Object.isFrozen(runtime)).toBe(true); | ||
| 6 | + expect(typeof runtime.setConfig).toBe("function"); | ||
| 7 | + expect(typeof runtime.getConfig).toBe("function"); | ||
| 8 | + }); | ||
| 9 | + | ||
| 10 | + it("exposes exactly the declared surface", function () { | ||
| 11 | + expect(Object.keys(runtime).sort()).toEqual(["getConfig", "setConfig"]); | ||
| 12 | + }); | ||
| 13 | + | ||
| 14 | + it("rejects unknown keys", function () { | ||
| 15 | + expect(function () { | ||
| 16 | + runtime.setConfig("noSuchKey", 1); | ||
| 17 | + }).toThrowError(TypeError, /Unknown runtime config key/); | ||
| 18 | + expect(function () { | ||
| 19 | + runtime.getConfig("noSuchKey"); | ||
| 20 | + }).toThrowError(TypeError, /Unknown runtime config key/); | ||
| 21 | + }); | ||
| 22 | + | ||
| 23 | + it("defaults logScriptLoading and httpFetchUrlLog from app config", function () { | ||
| 24 | + expect(runtime.getConfig("logScriptLoading")).toBe(false); | ||
| 25 | + expect(runtime.getConfig("httpFetchUrlLog")).toBe(false); | ||
| 26 | + }); | ||
| 27 | + | ||
| 28 | + it("round-trips logScriptLoading and httpFetchUrlLog", function () { | ||
| 29 | + runtime.setConfig("logScriptLoading", true); | ||
| 30 | + expect(runtime.getConfig("logScriptLoading")).toBe(true); | ||
| 31 | + runtime.setConfig("logScriptLoading", false); | ||
| 32 | + expect(runtime.getConfig("logScriptLoading")).toBe(false); | ||
| 33 | + | ||
| 34 | + runtime.setConfig("httpFetchUrlLog", true); | ||
| 35 | + expect(runtime.getConfig("httpFetchUrlLog")).toBe(true); | ||
| 36 | + runtime.setConfig("httpFetchUrlLog", false); | ||
| 37 | + expect(runtime.getConfig("httpFetchUrlLog")).toBe(false); | ||
| 38 | + }); | ||
| 39 | + | ||
| 40 | + it("rejects non-boolean log flag values and keeps the current one", function () { | ||
| 41 | + expect(function () { | ||
| 42 | + runtime.setConfig("logScriptLoading", "yes"); | ||
| 43 | + }).toThrowError(TypeError, /must be a boolean/); | ||
| 44 | + expect(runtime.getConfig("logScriptLoading")).toBe(false); | ||
| 45 | + expect(function () { | ||
| 46 | + runtime.setConfig("httpFetchUrlLog", 1); | ||
| 47 | + }).toThrowError(TypeError, /must be a boolean/); | ||
| 48 | + expect(runtime.getConfig("httpFetchUrlLog")).toBe(false); | ||
| 49 | + }); | ||
| 50 | + | ||
| 51 | + it("does not expose remote-module security through getConfig or setConfig", function () { | ||
| 52 | + ["security", "allowRemoteModules", "remoteModuleAllowlist"].forEach(function (key) { | ||
| 53 | + expect(function () { | ||
| 54 | + runtime.getConfig(key); | ||
| 55 | + }).toThrowError(TypeError, /Unknown runtime config key/); | ||
| 56 | + expect(function () { | ||
| 57 | + runtime.setConfig(key, true); | ||
| 58 | + }).toThrowError(TypeError, /Unknown runtime config key/); | ||
| 59 | + }); | ||
| 60 | + }); | ||
| 61 | + | ||
| 62 | + it("does not expose releasedObjectPolicy (iOS-only)", function () { | ||
| 63 | + expect(function () { | ||
| 64 | + runtime.getConfig("releasedObjectPolicy"); | ||
| 65 | + }).toThrowError(TypeError, /Unknown runtime config key/); | ||
| 66 | + }); | ||
| 67 | + }); | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -72,6 +72,7 @@ set(RUNTIME_BUILTIN_JS | |||
| 72 | 72 | ${RUNTIME_BUILTIN_JS_DIR}/message-loop-timer.js | |
| 73 | 73 | ${RUNTIME_BUILTIN_JS_DIR}/node-util.js | |
| 74 | 74 | ${RUNTIME_BUILTIN_JS_DIR}/ns-module.js | |
| 75 | + ${RUNTIME_BUILTIN_JS_DIR}/ns-runtime.js | ||
| 75 | 76 | ${RUNTIME_BUILTIN_JS_DIR}/ns-util.js | |
| 76 | 77 | ${RUNTIME_BUILTIN_JS_DIR}/performance.js | |
| 77 | 78 | ${RUNTIME_BUILTIN_JS_DIR}/primordials.js | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -8,6 +8,7 @@ | |||
| 8 | 8 | #include "ArgConverter.h" | |
| 9 | 9 | #include "BuiltinLoader.h" | |
| 10 | 10 | #include "HttpLoader.h" | |
| 11 | + #include "Runtime.h" | ||
| 11 | 12 | #include "console/Console.h" | |
| 12 | 13 | #include "robin_hood.h" | |
| 13 | 14 | ||
@@ -33,10 +34,89 @@ struct Registration { | |||
| 33 | 34 | */ | |
| 34 | 35 | constexpr Registration kRegistry[] = { | |
| 35 | 36 | {"ns:module", BuiltinId::kNsModule}, | |
| 37 | + {"ns:runtime", BuiltinId::kNsRuntime}, | ||
| 36 | 38 | {"ns:util", BuiltinId::kNsUtil}, | |
| 37 | 39 | {"node:util", BuiltinId::kNodeUtil}, | |
| 38 | 40 | }; | |
| 39 | 41 | ||
| 42 | + constexpr const char* kLogScriptLoadingKey = "logScriptLoading"; | ||
| 43 | + constexpr const char* kHttpFetchUrlLogKey = "httpFetchUrlLog"; | ||
| 44 | + | ||
| 45 | + void ThrowTypeError(Isolate* isolate, const std::string& message) { | ||
| 46 | + isolate->ThrowException(Exception::TypeError(ArgConverter::ConvertToV8String(isolate, message))); | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + bool EnsureMainIsolateWrite(Isolate* isolate, const std::string& key) { | ||
| 50 | + Runtime* runtime = Runtime::GetRuntime(isolate); | ||
| 51 | + if (runtime == nullptr || !runtime->IsMainThread()) { | ||
| 52 | + ThrowTypeError(isolate, "'" + key + | ||
| 53 | + "' is process-wide and can only be set from the main " | ||
| 54 | + "isolate"); | ||
| 55 | + return false; | ||
| 56 | + } | ||
| 57 | + return true; | ||
| 58 | + } | ||
| 59 | + | ||
| 60 | + bool ParseBooleanValue(Isolate* isolate, const FunctionCallbackInfo<Value>& info, | ||
| 61 | + const std::string& key, bool* out) { | ||
| 62 | + if (!info[1]->IsBoolean()) { | ||
| 63 | + ThrowTypeError(isolate, "'" + key + "' must be a boolean"); | ||
| 64 | + return false; | ||
| 65 | + } | ||
| 66 | + *out = info[1].As<v8::Boolean>()->Value(); | ||
| 67 | + return true; | ||
| 68 | + } | ||
| 69 | + | ||
| 70 | + void SetConfigCallback(const FunctionCallbackInfo<Value>& info) { | ||
| 71 | + Isolate* isolate = info.GetIsolate(); | ||
| 72 | + if (info.Length() < 2 || !info[0]->IsString()) { | ||
| 73 | + ThrowTypeError(isolate, "setConfig expects (key: string, value)"); | ||
| 74 | + return; | ||
| 75 | + } | ||
| 76 | + std::string key = ArgConverter::ConvertToString(info[0].As<String>()); | ||
| 77 | + if (key == kLogScriptLoadingKey) { | ||
| 78 | + if (!EnsureMainIsolateWrite(isolate, key)) { | ||
| 79 | + return; | ||
| 80 | + } | ||
| 81 | + bool value = false; | ||
| 82 | + if (!ParseBooleanValue(isolate, info, key, &value)) { | ||
| 83 | + return; | ||
| 84 | + } | ||
| 85 | + tns::SetScriptLoadingLogEnabled(value); | ||
| 86 | + return; | ||
| 87 | + } | ||
| 88 | + if (key == kHttpFetchUrlLogKey) { | ||
| 89 | + if (!EnsureMainIsolateWrite(isolate, key)) { | ||
| 90 | + return; | ||
| 91 | + } | ||
| 92 | + bool value = false; | ||
| 93 | + if (!ParseBooleanValue(isolate, info, key, &value)) { | ||
| 94 | + return; | ||
| 95 | + } | ||
| 96 | + tns::SetHttpFetchUrlLogEnabled(value); | ||
| 97 | + return; | ||
| 98 | + } | ||
| 99 | + ThrowTypeError(isolate, "Unknown runtime config key: '" + key + "'"); | ||
| 100 | + } | ||
| 101 | + | ||
| 102 | + void GetConfigCallback(const FunctionCallbackInfo<Value>& info) { | ||
| 103 | + Isolate* isolate = info.GetIsolate(); | ||
| 104 | + if (info.Length() < 1 || !info[0]->IsString()) { | ||
| 105 | + ThrowTypeError(isolate, "getConfig expects (key: string)"); | ||
| 106 | + return; | ||
| 107 | + } | ||
| 108 | + std::string key = ArgConverter::ConvertToString(info[0].As<String>()); | ||
| 109 | + if (key == kLogScriptLoadingKey) { | ||
| 110 | + info.GetReturnValue().Set(v8::Boolean::New(isolate, tns::IsScriptLoadingLogEnabled())); | ||
| 111 | + return; | ||
| 112 | + } | ||
| 113 | + if (key == kHttpFetchUrlLogKey) { | ||
| 114 | + info.GetReturnValue().Set(v8::Boolean::New(isolate, tns::IsHttpFetchUrlLogEnabled())); | ||
| 115 | + return; | ||
| 116 | + } | ||
| 117 | + ThrowTypeError(isolate, "Unknown runtime config key: '" + key + "'"); | ||
| 118 | + } | ||
| 119 | + | ||
| 40 | 120 | const Registration* Find(const std::string& specifier) { | |
| 41 | 121 | for (const Registration& registration : kRegistry) { | |
| 42 | 122 | if (specifier == registration.specifier) { | |
@@ -101,6 +181,20 @@ MaybeLocal<Object> BuildBinding(Local<Context> context, BuiltinId builtin) { | |||
| 101 | 181 | } | |
| 102 | 182 | break; | |
| 103 | 183 | } | |
| 184 | + case BuiltinId::kNsRuntime: { | ||
| 185 | + Local<v8::Function> setConfig, getConfig; | ||
| 186 | + if (!v8::Function::New(context, SetConfigCallback).ToLocal(&setConfig) || | ||
| 187 | + !v8::Function::New(context, GetConfigCallback).ToLocal(&getConfig) || | ||
| 188 | + !binding->Set(context, ArgConverter::ConvertToV8String(isolate, "setConfig"), | ||
| 189 | + setConfig) | ||
| 190 | + .FromMaybe(false) || | ||
| 191 | + !binding->Set(context, ArgConverter::ConvertToV8String(isolate, "getConfig"), | ||
| 192 | + getConfig) | ||
| 193 | + .FromMaybe(false)) { | ||
| 194 | + return MaybeLocal<Object>(); | ||
| 195 | + } | ||
| 196 | + break; | ||
| 197 | + } | ||
| 104 | 198 | case BuiltinId::kNsUtil: { | |
| 105 | 199 | // The console formatter is built once per realm; ns:util | |
| 106 | 200 | // re-exports that instance instead of creating a second one. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -46,7 +46,8 @@ module.exports = somethingTheCallSiteNeeds; | |||
| 46 | 46 | `node:util` shim: one source file per specifier, the shim owning every bit of | |
| 47 | 47 | Node compatibility. See `docs/ns-builtin-modules.md` for the cross-runtime | |
| 48 | 48 | contract. | |
| 49 | - - `ns-module.js` is the `ns:module` loader-control surface. | ||
| 49 | + - `ns-module.js` is the `ns:module` loader-control surface and `ns-runtime.js` | ||
| 50 | + is the `ns:runtime` live config surface (`setConfig`/`getConfig`). | ||
| 50 | 51 | - Destructure `binding` and `primordials` once, at the top of the file, so the | |
| 51 | 52 | file's dependencies are visible and greppable. | |
| 52 | 53 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,14 @@ | |||
| 1 | + "use strict"; | ||
| 2 | + | ||
| 3 | + // The `ns:runtime` builtin module: runtime-level configuration and (future) | ||
| 4 | + // runtime introspection. See docs/ns-builtin-modules.md for the contract and | ||
| 5 | + // the key registry — keys, their value domains, and their scope (process-wide | ||
| 6 | + // vs per-isolate) are defined and validated on the native side, so this file | ||
| 7 | + // stays a thin, frozen surface. | ||
| 8 | + | ||
| 9 | + const { setConfig, getConfig } = binding; | ||
| 10 | + const { ObjectFreeze } = primordials; | ||
| 11 | + | ||
| 12 | + exports.setConfig = setConfig; | ||
| 13 | + exports.getConfig = getConfig; | ||
| 14 | + ObjectFreeze(exports); | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments